Group by multiple fields in stream java 8 - java

I have a class:
public class PublicationDTO {
final String publicationName;
final String publicationID;
final String locale;
final Integer views;
final Integer shares;
}
A need to get sum of views and shares. For test i created a list:
PublicationDTO publicationDTO1 = new PublicationDTO("Name1", "name1", "CA", 5, 6);
PublicationDTO publicationDTO2 = new PublicationDTO("Name2", "name2", "US", 6, 3);
PublicationDTO publicationDTO3 = new PublicationDTO("Name1", "name1", "CA", 10, 1);
PublicationDTO publicationDTO4 = new PublicationDTO("Name2", "name2", "CA", 2, 3);
List<PublicationDTO> publicationDTOS = List.of(publicationDTO1, publicationDTO2, publicationDTO3, publicationDTO4);
I want to group objects in list by publicationName, publicationId and locale and get result list like:
List.of(new PublicationDTO("Name1", "name1", "CA", 15, 7),
new PublicationDTO("Name2", "name2", "CA", 2, 3),
new PublicationDTO("Name2", "name2", "US", 6, 3));
I found a solution like:
List<PublicationDTO> collect = publicationDTOS.stream()
.collect(groupingBy(PublicationDTO::getPublicationID))
.values().stream()
.map(dtos -> dtos.stream()
.reduce((f1, f2) -> new PublicationDTO(f1.publicationName, f1.publicationID, f1.locale, f1.views + f2.views, f1.shares + f2.shares)))
.map(Optional::get)
.collect(toList());
but the result not grouped by locale and I'm not sure if it works by publicationId. Please let me know how to properly use collectors in such case?

You are only grouping by getPublicationID:
.collect(groupingBy(PublicationDTO::getPublicationID))
Since those fields you are interested in for grouping are all strings you could concatenate them and use that as a grouping classifier:
.collect(Collectors.groupingBy(p -> p.getPublicationName() + p.getPublicationID() + p.getLocale()))

Related

Test Get method using MockMvc and Mockito

I'm working on writing test case using MockMvc and Mokito.
Assume I have a list of order:
{
{
"order_id": 1,
"user_id": 1,
"price": 11.0,
"order_status": "Pending",
},
{
"order_id": 2,
"user_id": 2,
"price": 12.0,
"order_status": "Cancelled",
},
{
"order_id": 3,
"user_id": 1,
"price": 13.0,
"order_status": "Delivered",
},
}
The column order_id is unique since I set it as primary key. But one user can have multiple orders so the column user_id can appear multiple times.
When I tried the endpoint /api/order/getOrders/user/{user_id} in Postman (For example, I set user_id = 1). The response is exactly same what I expected:
{
{
"order_id": 1,
"user_id": 1,
"price": 11.0,
"order_status": "Pending",
},
{
"order_id": 3,
"user_id": 1,
"price": 13.0,
"order_status": "Delivered",
},
}
However, when I tried to write the unit test case using MockMvc and Mockito. It always return all the orders. Here is my code:
List<Order> orderList = new ArrayList<>();
Order order1 = new Order(1, 1, 11.0, "Pending");
Order order2 = new Order(2, 2, 12.0, "Cancelled");
Order order3 = new Order(3, 1, 13.0, "Shipped");
orderList.add(order1);
orderList.add(order2);
orderList.add(order3);
Mockito.when(orderService.getOrdersUserId(1)).thenReturn(orderList);
mockMvc.perform(MockMvcRequestBuilders.get("/api/order/getOrders/user/{user_id}", 1).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$", hasSize(2)));
The test is failed because the response include all 3 items. I believe that I was wrong by using thenReturn(orderList) but I have no idea to fix it.
Can someone let me know how can I fix it, please?
The order list that you return has all three orders in it so it's always going to return all of them. If you are mocking the behaviour then just add orders 1 and 3
List<Order> orderList = new ArrayList<>();
Order order1 = new Order(1, 1, 11.0, "Pending");
Order order2 = new Order(2, 2, 12.0, "Cancelled");
Order order3 = new Order(3, 1, 13.0, "Shipped");
orderList.add(order1);
orderList.add(order3);

Find the missing elements of a list not in the values of a hashmap

I want to get a list of hashmap that grouped by year and find the customer_id that doesn't contain a group based on a list of customer_id.
This is an example of the dataset.
List tagList = new ArrayList<>();
# Customer
HashMap<String, Object> customerMap = new HashMap<>();
## feeding data example
customerMap.put("date", "2018");
customerMap.put("name", "John");
customerMap.put("custemer_no", "1a");
tagList.add(customerMap);
customer_id_list = ['1a', '2b', '3c']
customer_list = [
{
"date": "2019",
"name": "John",
"customer_id": "1a"
},
{
"date": "2019",
"name": "David",
"customer_id": "2b"
},
{
"date": "2020",
"name": "John",
"customer_id": "1a"
},
{
"date": "2020",
"name": "Alex",
"customer_id": "3c"
},
{
"date": "2021",
"name": "John",
"customer_id": "1a"
}
]
This is a sample output that I want.
missing_customer_list = [
{
"date": "2019",
"name": "Alex",
"customer_id": "3c"
},
{
"date": "2020",
"name": "David",
"customer_id": "2b"
},
{
"date": "2021",
"name": "David",
"customer_id": "2b"
},
{
"date": "2021",
"name": "Alex",
"customer_id": "3c"
}
]
Do you have any ideas how I can get this sample output using stream comprehension?
If I cannot filter the list directly using stream comprehension, using a for loop is fine too.
I found the way how to group by year for now, but don't know how to handle the rest of the filtering..
Please help me! thanks in advance
List<Customer> result = customer_list.stream()
.collect(Collectors.groupingBy(Customer::getDate))
UPDATE
Referring to the current answers,
I ve got stuck at the point where I couldn't create the new instance of customer.
So, Im planning to use for-loops to find the missing_customer_id during iteratation of customer_id_list and filtered_customer_list.
Once I get the missing_customer_id in a list, I will try to re-create the Customers manually and add them into a new list to print
There are many different ways to do this. These solutions can become inefficient for large lists. Because, calling .contains() on a List is a linear-time operation, meaning doing so n times is quadratic.
List<String> customerIdList = Arrays.asList("a", "b", "c");
List<Customer> customerList = new ArrayList<>();
customerList.add(new Customer("2018", "a"));
customerList.add(new Customer("2018", "b"));
customerList.add(new Customer("2019", "b"));
customerList.add(new Customer("2019", "c"));
customerList.add(new Customer("2020", "a"));
customerList.add(new Customer("2020", "c"));
customerList.add(new Customer("2021", "a"));
Map<String, List<Customer>> collect = customerList.stream()
.collect(Collectors.groupingBy(Customer::getDate))
.entrySet().stream().map(notExistCustomerByYear(customerIdList))
.flatMap(Collection::stream)
.collect(Collectors.groupingBy(Customer::getDate));
The method that accomplishes what I really want is as follows;
private Function<Map.Entry<String, List<Customer>>, List<Customer>> notExistCustomerByYear(List<String> customerIdList) {
return e -> {
List<String> customerIds = e.getValue().stream().map(Customer::getCustomerId).collect(Collectors.toList());
List<String> notExistCustomerIdsInYear = customerIdList.stream().filter(id -> !customerIds.contains(id)).collect(Collectors.toList());
return notExistCustomerIdsInYear.stream().map(id -> new Customer(e.getKey(), id)).collect(Collectors.toList());
};
}
What you describe is not filtering. According to your question and comments, you want a completely new list with combinations of year and customer-id which were not present in the original list.
First, you can group customer-ids per year to have a better representation of your original list.
Map<Integer, List<String>> customerIdPerYear = customerList.stream()
.collect(Collectors.groupingBy(
Customer::getYear,
Collectors.mapping(
Customer::getCustomerId,
Collectors.toList())));
System.out.println(customerIdPerYear);
// output: {2018=[a, b], 2019=[b, c], 2020=[a, c], 2021=[a]}
In a second step, you create a new list per year with the customer-ids not found in the original list. Finally, you can create new Customer objects and return a flattened list with flatMap.
List<Customer> missingCustomersPerYear = customerIdPerYear.entrySet().stream()
.flatMap(e -> customerIdList.stream()
.filter(id -> !e.getValue().contains(id))
.map(id -> new Customer(e.getKey(), id)))
.collect(Collectors.toList());
System.out.println(missingCustomersPerYear);
// output: [(2018, c), (2019, a), (2020, b), (2021, b), (2021, c)]
To be complete, here is the Customer class used for the above examples:
class Customer {
private int year;
private String customerId;
public Customer(final int year, final String customerId) {
this.year = year;
this.customerId = customerId;
}
public int getYear() {
return year;
}
public String getCustomerId() {
return customerId;
}
#Override
public String toString() {
return "(" + year + ", " + customerId + ")";
}
}

How to convert list of map to array of hash

I am using Java for my application. Here is the my input data.
[{
"id": 1,
"firstname": "one",
"lastname": "1"
},
{
"id": 2,
"firstname": "two",
"lastname": "2"
},
{
"id": 3,
"firstname": "three",
"lastname": "3"
}
]
I want to convert the above input to like below output. How can I achieve the below output in an efficient manner?
{
["id", "firstname", "lastname"], [1, "one", "1"], [2, "two", "2"], [3, "three", "3"]
}
Update:
I have tried the below. But it resulted as below
Expected:
result => {[lastname, id, firstname]=[[1, 1, one], [2, 2, two], [3, 3, three]]}
Actual:
result => {[lastname, id, firstname], [1, 1, one], [2, 2, two], [3, 3, three]}
Code:
Map<String, Object> one = Map.of("id", 1, "firstname", "one", "lastname", "1");
Map<String, Object> two = Map.of("id", 2, "firstname", "two", "lastname", "2");
Map<String, Object> three = Map.of("id", 3, "firstname", "three", "lastname", "3");
ArrayList<Map<String,Object>> list = new ArrayList<>();
list.add(one);
list.add(two);
list.add(three);
MultiValueMap<Object, Object> result = new LinkedMultiValueMap<>();
Set<String> strings = list.get(0).keySet();
ArrayList<Object> objects = new ArrayList<>();
for(Map<String,Object> map: list) {
objects.add(map.values());
}
result.put(strings, objects);
The input JSON may be read into a list of maps data sharing the same key sets and this list is to be converted into a list of object arrays, with the first element of this list being the keys of a map.
So, at first the array of field names should be created, converted to Stream, and then merged with Stream<Object[]> retrieved from the values of each map in data list:
// using Jackson JSON to read the input
ObjectMapper mapper = new ObjectMapper();
String input = "[{\"id\":1, \"firstname\":\"First\", \"lastname\": \"Last\"}]";
List<Map<String, Object>> data = mapper.readValue(input, new TypeReference<>() {});
List<Object[]> output = Stream.concat(
Stream.<Object[]>of(data.get(0).keySet().toArray()),
data.stream().map(m -> m.values().toArray())
)
.collect(Collectors.toList());
System.out.printf("Result: {%n\t%s%n}%n",
output.stream().map(Arrays::toString).collect(Collectors.joining(", ")));
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(output));
Output:
Result: {
[id, firstname, lastname], [1, First, Last]
}
[ [ "id", "firstname", "lastname" ], [ 1, "First", "Last" ] ]
Or similarly the result could be a list of raw collections based on maps' keySet() and values, which could be created like this:
List<Collection> result = Stream.concat(
Stream.of(data.get(0).keySet()),
data.stream().map(Map::values)
).collect(Collectors.toList());

transform narrow table into wide in java

I have a txt file containing wide table of strings. I want to transform it to wide list of lists based on the ID column. The data is look like this
Long format has 3 columns: country, key, value
- M*N rows.
e.g.
'USA', 'President', 'Obama'
...
'USA', 'Currency', 'Dollar'
Wide format has N=16 columns: county, key1, ..., keyN
- M rows
example:
country, President, ... , Currency
'USA', 'Obama', ... , 'Dollar'
I want to know the equivalent of
SELECT country,
MAX( IF( key='President', value, NULL ) ) AS President,
MAX( IF( key='Currency', value, NULL ) ) AS Currency,
...
FROM table
GROUP BY country;
in java!
I think you can make it a little bit easier with some Collectors.groupingBy() but a simpler version would be this:
List<String[]> list = new ArrayList<>();
list.add(new String[] { "USA", "President", "Obama" });
list.add(new String[] { "USA", "Currency", "Dollar" });
list.add(new String[] { "Germany", "President", "Steinmeier" });
list.add(new String[] { "Germany", "Currency", "Euro" });
list.add(new String[] { "United Kingdom", "President", "Queen Elisabeth" });
list.add(new String[] { "United Kingdom", "Currency", "Pound" });
Map<String, Map<String, String>> map = new HashMap<>();
list.forEach(s -> {
map.putIfAbsent(s[0], new HashMap<>());
map.get(s[0]).put(s[1], s[2]);
});
List<String[]> wideList = map.entrySet().stream()
.map(m -> new String[] { m.getKey(), m.getValue().get("President"), m.getValue().get("Currency") })//
.collect(Collectors.toList());
System.out.println("country, President, Currency");
wideList.forEach(s -> System.out.println(s[0] + ", " + s[1] + ", " + s[2]));

Java 8: Group Object array to Map to return as JSON

I have an Object 2D array (Object [][]) that is returned from querying a database. I now want to map it to Objects which can be returned in API call after grouping.
Here is my 2D Object array.
Object [][] dbResult =
{
{1, "a", 9, "Content1", "format1", false, true},
{1, "a", 9, "Content1", "format2", true, false},
{2, "b", 8, "Content2", "format3", true, false},
{2, "b", 8, "Content2", "format4", false, false},
{3, "c", 7, "Content3", "format5", true, true},
{4, "d", 8, "Content2", "format6", false, true},
{4, "d", 6, "Content3", "format7", true, true},
{4, "d", 5, "Content4", "format8", false, false},
{5, "e", 4, "Content5", "format9", false, true},
{6, "f", 3, "Content6", "format10", true, false}
};
Here is the legend/key for the index.
[ID, Name, AnotherID, AnotherName, Format, Boolean, Boolean]
I want to return
List<IdName> idsNames;
Where each of the classes should be mapped like this.
class IdName {
String id;
String name;
List<Another> anotherNameList;
}
class Another {
String anotherId;
String anotherName;
List<Format> formatList;
}
class Format {
String format;
Boolean isEnabled;
Boolean isManaged;
}
I tried using Java 8's groupingBy but I couldn't get to the state that I want.
Sample expected result:
[
{
"id": 1,
"name": "a",
"another": [
{
"anotherId": 9,
"anotherName": "Content1",
"format": [
{
"format": "format1",
"isEnabled": true,
"isManaged": false
},
{
"format": "format2",
"isEnabled": true,
"isManaged": false
}
]
}
]
}
]
Looks like you should use Collectors.collectingAndThen.
First create the extractors (Assuming your classes have constructors & getters):
// The cast types are just an example. You can Cast/convert the array values to any type you want
IdName extractIdName(Object[] row) {
return new IdName((String) row[0], (String) row[1], null);
}
Another extractAnother(Object[] row) {
return new Another((String) row[2], (String) row[3], null);
}
Format extractFormat(Object[] row) {
return new Format((String) row[4], (boolean) row[5], (boolean) row[6]);
}
Then you will need the merge functions:
List<Another> setFormats(Map<Another, List<Format>> map) {
return map.entrySet()
.stream()
.map(e -> {
e.getKey().setFormatList(e.getValue());
return e.getKey();
})
.collect(toList());
}
List<IdName> setAnothers(Map<IdName, List<Another>> map) {
return map.entrySet()
.stream()
.map(entry -> {
entry.getKey().setAnotherNameList(entry.getValue());
return entry.getKey();
})
.collect(toList());
}
Finally this will do the trick:
// Converting Object[][] to List<IdName>
List<IdName> list =
Arrays.stream(dbResult)
.collect(
collectingAndThen(
groupingBy(this::extractIdName,
collectingAndThen(
groupingBy(this::extractAnother,
mapping(this::extractFormat, toList())),
this::setFormats
)),
this::setAnothers));
It can be done in several steps. Let all values be String for simplicity.
Also you are supposed to have constructors and equals/hashcode methods implemented.
Map<IdName, Map<Another, List<String[]>>> map = Arrays.stream(dbResult)
.collect(
groupingBy(s -> new IdName(s[0], s[1], null),
groupingBy(s -> new Another(s[2], s[3], null))));
Then we can create Format objects and put everything together.
for (Map.Entry<IdName, Map<Another, List<String[]>>> entry : map.entrySet()) {
IdName idName = entry.getKey(); // main object
Set<Another> anothers = entry.getValue().keySet();
for (Another another : anothers) { // create list<Format> for each Another
List<Format> formatList = entry.getValue().get(another).stream()
.map(format -> new Format(format[4], format[5], format[6]))
.collect(Collectors.toList());
another.setFormatList(formatList);
}
idName.setAnotherNameList(anothers);
}
Now we can get all assembled objects
Set<IdName> idNames = map.keySet();

Categories

Resources