JAVA List GroupBy multiple fields and Sum - java

I have list having fields Name, Currency and Amount.
I want to group it by Name, Currency and Sum of Amount.
List<myPojo> rows= ................
public class myPojo
{
private String name;
private String currency;
private BigDecimal amount;
......................
}
I want result/output in list

You can use Stream API.
Use Collectors.toMap and use AbstractMap.SimpleEntry as key of map. Then define merge function for multiple values of the same key.
List<myPojo> res = new ArrayList<>(rows.stream()
.collect(Collectors.toMap(
e -> new AbstractMap.SimpleEntry<>(e.getName(), e.getCurrency()),
Function.identity(),
(a, b) -> new myPojo(a.getName(), a.getCurrency(), a.getAmount().add(b.getAmount()))))
.values());
Demo:
List<myPojo> list = new ArrayList<>();
list.add(new myPojo("A", "USD", new BigDecimal(1.0)));
list.add(new myPojo("A", "USD", new BigDecimal(2.0)));
list.add(new myPojo("A", "USD", new BigDecimal(3.0)));
list.add(new myPojo("B", "USD", new BigDecimal(1.0)));
list.add(new myPojo("B", "USD", new BigDecimal(2.0)));
list.add(new myPojo("B", "USD", new BigDecimal(3.0)));
list.add(new myPojo("A", "US", new BigDecimal(1.0)));
list.add(new myPojo("A", "US", new BigDecimal(2.0)));
list.add(new myPojo("A", "US", new BigDecimal(3.0)));
List<myPojo> res = new ArrayList<>(list.stream()
.collect(Collectors.toMap(
e -> new AbstractMap.SimpleEntry<>(e.getName(), e.getCurrency()),
Function.identity(),
(a, b) -> new myPojo(a.getName(), a.getCurrency(), a.getAmount().add(b.getAmount()))))
.values());
System.out.println(res.toString());
Output:
[myPojo [name=B, currency=USD, amount=6],
myPojo [name=A, currency=USD, amount=6],
myPojo [name=A, currency=US, amount=6]]
Note: Try to capitalize the name of the class like MyPojo for better convention

You could use groupingBy to generate a Map<String,Map<String,BigDecimal>> corresponding to Map<Name,Map<Currency,sum of Amount>>
List<myPojo> rows = List.of(new myPojo("ABCD", "USD", new BigDecimal(20)),
new myPojo("XYZ", "GBP", new BigDecimal(60)),
new myPojo("XYZ", "THB", new BigDecimal(35)),
new myPojo("ABCD", "INR", new BigDecimal(90)),
new myPojo("ABCD", "USD", new BigDecimal(80)),
new myPojo("XYZ", "THB", new BigDecimal(45)));
Map<String,Map<String,BigDecimal>> map =
rows.stream()
.collect(Collectors.groupingBy(myPojo::getName,
Collectors.groupingBy(myPojo::getCurrency,
Collectors.reducing(BigDecimal.ZERO, myPojo::getAmount, BigDecimal::add))));
System.out.println(map);
// output: {XYZ={GBP=60, THB=80}, ABCD={USD=100, INR=90}}

Related

Using streams to extract specific entries of a List of Maps in to a new Map

Given a org.bson.Document
{
"doneDate":"",
"todoEstimates":"",
"forecastDate":"",
"cardType":{
"projectData":[
{
"color":"#ffcd03",
"boardId":"30022"
},
{
"color":"#ffcd03",
"boardId":"1559427"
}
],
"cardFields":[
{
"fieldName":"id",
"fieldLabel":"Unique ID",
"fieldType":"Integer",
"itemType":"Long",
"isRequired":"NO",
"isReadOnly":"Yes",
"isDisabled":"NO",
"inputMethod":"System Generated",
"defaultValue":null,
"isUserType":"No"
},
{
"fieldName":"name",
"fieldLabel":"Title",
"fieldType":"Single-Line Text",
"itemType":"String",
"isRequired":"Yes",
"isReadOnly":"NO",
"isDisabled":"NO",
"inputMethod":"Manual Entry",
"defaultValue":null,
"isUserType":"No"
}
]
}
How do I extract the values of fieldName and fieldLabel via streams into the following?
{
"id": "Unique ID",
"name:" "Title",
...
}
I tried the following but I get stuck at the part where I get value of the cardFields list.
document.entrySet().stream().filter(e -> e.getKey().equals("cardType"))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
.entrySet().stream().filter(e -> e.getKey().equals("cardFields"))
.map(e -> (Map)e.getValue()).toList();
Here is a working solution with streams:
Map<String, Object> fields = ((List<Map<String, Object>>) ((Map<String, Object>) document.entrySet()
.stream()
.filter(entry -> entry.getKey().equals("cardType"))
.findFirst()
.orElseThrow(() -> new RuntimeException("card type not found"))
.getValue())
.entrySet()
.stream()
.filter(entry -> entry.getKey().equals("cardFields"))
.findFirst()
.orElseThrow(() -> new RuntimeException("card fields not found"))
.getValue())
.stream()
.collect(Collectors.toMap(el -> el.get("fieldName").toString(), element -> element.get("fieldLabel")));
Document result = new Document(fields);
System.out.println(result.toJson());
That's probably the worst code i have written - absolutely unreadable and you can't debug it. I would suggest that you do not use stream for this particular task, it isn't the right tool for it. So here is another working solution using Map.get(key):
Map<String, Object> cardType = (Map<String, Object>) document.get("cardType");
List<Map<String, Object>> cardFields = (List<Map<String, Object>>) cardType.get("cardFields");
Document result = new Document();
cardFields.forEach(cardField -> result.put((String) cardField.get("fieldName"), cardField.get("fieldLabel")));
System.out.println(result.toJson());
This is shorter, readable, you can debug it if needed and probably it's more performant. I'd say it's much better overall.
You may be able to parse your document like this:
Document cardType = document.get("cardType", Document.class);
final Class<? extends List> listOfMaps = new ArrayList<Map<String, String>>().getClass();
List<Map<String, String>> fields = cardType.get("cardFields", listOfMaps);
fields.stream().map(f -> {
System.out.println(f.get("fieldName") + ": " + f.get("fieldLabel"));
// here you can construct your new object
}).collect(Collectors.toList());
If you don't mind casting a lot, you could try following:
List cardFields = (List) ((Map) document.get("cardType")).get("cardFields");
Map<String, String> map = (Map) cardFields.stream()
.collect(Collectors.toMap(cf -> ((Document) cf).getString("fieldName"),
cv -> ((Document) cv).getString("fieldLabel")));
System.out.println(map);
or you can emit omit the casting with the following:
List<Document> carFields = document.get("cardType", Document.class)
.getList("cardFields", Document.class);
Map<String, String> map = carFields.stream()
.collect(Collectors.toMap(k -> k.getString("fieldName"),
v -> v.getString("fieldLabel")));
System.out.println(map);
Here is the complete example running with java 17:
import org.bson.Document;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Bson {
private String json=
"""
{
"doneDate": "",
"todoEstimates": "",
"forecastDate": "",
"cardType": {
"projectData": [
{
"color": "#ffcd03",
"boardId": "30022"
},
{
"color": "#ffcd03",
"boardId": "1559427"
}
],
"cardFields": [
{
"fieldName": "id",
"fieldLabel": "Unique ID",
"fieldType": "Integer",
"itemType": "Long",
"isRequired": "NO",
"isReadOnly": "Yes",
"isDisabled": "NO",
"inputMethod": "System Generated",
"defaultValue": null,
"isUserType": "No"
},
{
"fieldName": "name",
"fieldLabel": "Title",
"fieldType": "Single-Line Text",
"itemType": "String",
"isRequired": "Yes",
"isReadOnly": "NO",
"isDisabled": "NO",
"inputMethod": "Manual Entry",
"defaultValue": null,
"isUserType": "No"
}
]
}
}
""";
public static void main(String[] args) {
Bson bson = new Bson();
Document document = Document.parse(bson.json);
List cardType = (List) ((Map) document.get("cardType")).get("cardFields");
Map<String, String> map = (Map) cardType.stream()
.collect(Collectors.toMap(cf -> ((Document) cf).getString("fieldName"),
cv -> ((Document) cv).getString("fieldLabel")));
System.out.println(map);
List<Document> carFields = document.get("cardType", Document.class).getList("cardFields", Document.class);
Map<String, String> map1 = carFields.stream()
.collect(Collectors.toMap(k -> k.getString("fieldName"), v -> v.getString("fieldLabel")));
System.out.println(map1);
}
}

Generate object using Java streams

I have a question regarding Java streams. I built below maps.
One map includes country name as key and list of cities as value.
Another includes Continent name as key and list of countries as value.
List<String> inCities = new ArrayList<String>(Arrays.asList("Delhi", "Mumbai", "Hyderabad", "Banglore", "Chennai"));
List<String> jpCities = new ArrayList<String>(Arrays.asList("Tokyo", "Osaka", "Kyoto"));
List<String> usCities = new ArrayList<String>(Arrays.asList("Dallas", "Chicago", "NewYork"));
List<String> ukCities = new ArrayList<String>(Arrays.asList("London", "Cardiff", "Oxford"));
List<String> frCities = new ArrayList<String>(Arrays.asList("Paris", "Marseille", "Lyon"));
Map<String, List<String>> countryWiseCities = new HashMap<String, List<String>>();
countryWiseCities.put("India", inCities);
countryWiseCities.put("Japan", jpCities);
countryWiseCities.put("USA", usCities);
countryWiseCities.put("UK", ukCities);
countryWiseCities.put("France", frCities);
List<String> asiaCountries = new ArrayList<String>(Arrays.asList("India", "Japan"));
List<String> northAmericaCountries = new ArrayList<String>(Arrays.asList("USA", "Canada"));
List<String> europeCountries = new ArrayList<String>(Arrays.asList("UK", "France"));
Map<String, List<String>> continentWiseCountries = new HashMap<String, List<String>>();
continentWiseCountries.put("Asia", asiaCountries);
continentWiseCountries.put("NorthAmerica", northAmericaCountries);
continentWiseCountries.put("Europe", europeCountries);
Existing maps.
{"India":["Delhi", "Mumbai", "Hyderabad", "Banglore", "Chennai"], "Japan":["Tokyo", "Osaka", "Kyoto"], "USA":["Dallas", "Chicago", "NewYork"], "Canada":["Ontario", "Toronto", "vancouver"],
"UK":["London", "Cardiff", "Oxford"], "France":["Paris", "Marseille", "Lyon"]}
{"Asia":["India", "Japan"], "NorthAmerica":["USA", "Canada"], "Europe":["UK", "France"]}
I am looking for a way to build below map using java streams. What is the simplest way to achieve below output using Java streams?.
Expected output:
{"ASIA":["Delhi", "Mumbai", "Hyderabad", "Banglore", "Chennai", "Tokyo", "Osaka", "Kyoto"],
"NorthAmerica":["Dallas", "Chicago", "NewYork", "Ontario", "Toronto", "vancouver"],
"Europe":["London", "Cardiff", "Oxford", "Paris", "Marseille", "Lyon"]
Here key is continent name.
Value is list cities in countries in a particular continent.
Below is the code which I tried.
Map<String, List<Map.Entry<String, List<String>>>> collect1 = countryWiseCities.entrySet().stream()
.collect(Collectors.groupingBy(Map.Entry::getKey));
Here's one thing you can do:
Map<String, List<String>> continentCities = continentWiseCountries.entrySet()
.stream()
.map(continent -> new AbstractMap.SimpleEntry<>(
continent.getKey(),
continent.getValue()
.stream()
.flatMap(country ->
countryWiseCities.getOrDefault(country, Collections.emptyList())
.stream())
.collect(Collectors.toList())))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
All it's doing is using an intermediate stream to join from continents to cities (through countries), then collecting cities and continent pairs.
Note that getOrDefault(country, Collections.emptyList()) is needed because you have countries in your continent list that do not exist in your country-list mapping.
The output of the above code is something like this:
{
"Asia" : [ "Delhi", "Mumbai", "Hyderabad", "Banglore", "Chennai", "Tokyo", "Osaka", "Kyoto" ],
"Europe" : [ "London", "Cardiff", "Oxford", "Paris", "Marseille", "Lyon" ],
"NorthAmerica" : [ "Dallas", "Chicago", "NewYork" ]
}
Here a solution without the intermediate stream, just using the flatMap in the value-function of the toMap-collector:
Map<String, List<String>> continentCityMap = continentWiseCountries.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey,
e -> e.getValue().stream().flatMap(
country -> countryWiseCities.getOrDefault(country, Collections.emptyList()).stream())
.collect(Collectors.toList())));

Creating a List of Maps from a List using java stream

I have a List of Students that I want to convert to a List of Maps with each map containing specifc student data.
The Student Object:
class Student {
String name;
String age;
String getName() {
return name;
}
}
I have a list of Students that I want to convert to a list of maps that should look like the following:
[
{ name: "Mike",
age: "14"
},
{ name: "Jack",
age: "10"
},
{ name: "John",
age: "16"
},
{ name: "Paul",
age: "12"
}
]
Is there a way to convert List<Student> into List<Map<String, String>> ? The keys of each map should be name & age.
Java-9 solution using Map.of:
myList.stream()
.map(s -> Map.of("name", s.getName(), "age", s.getAge()))
.collect(Collectors.toList());
Did you mean :
List<Student> listStudent = new ArrayList<>();
List<Map<String, String>> result = listStudent.stream()
.map(student -> {
return new HashMap<String, String>() {
{
put("age", student.getAge());
put("name", student.getName());
}
};
}).collect(Collectors.toList());
For example if you have :
List<Student> listStudent = new ArrayList<>(
Arrays.asList(
new Student("Mike", "14"),
new Student("Jack", "10"),
new Student("John", "16"),
new Student("Paul", "12")
));
The result should look like this :
[{name=Mike, age=14}, {name=Jack, age=10}, {name=John, age=16}, {name=Paul, age=12}]

java 8 List<Object[]> to Map<String, Map<String, BigDecimal>>

The list contains Arrays of (String,String,BigDecimal).
I want to convert the List to Map<String, Map<String, BigDecimal>>
grouped by the first String using java 8 Stream class.
List<Object[]> list = ss.createCriteria(PayrollDeduction.class)
.createAlias("payroll", "pay")
.add(Restrictions.le("pay.paymentDate", atDate))
.createAlias("payroll.employee", "employee")
.add(Restrictions.in("employee.id", Arrays.asList(empId)))
.setProjection(Projections.projectionList()
.add(Projections.property("gouvAgencyCode"))
.add(Projections.property("code"))
.add(Projections.sum("amount"))
.add(Projections.groupProperty("gouvAgencyCode"))
.add(Projections.groupProperty("code")))
.setReadOnly(true)
.list();
list.stream().collect(Collectors.groupingBy(r->r[0], Collectors.mapping(mapper, downstream)));
Do it like this:
List<Object[]> list = Arrays.asList(
new Object[] { "A", "X", new BigDecimal("1") },
new Object[] { "A", "Y", new BigDecimal("2.0") },
new Object[] { "B", "X", new BigDecimal("3.00") },
new Object[] { "C", "Z", new BigDecimal("4.000") }
);
Map<String, Map<String, BigDecimal>> map = list.stream()
.collect(Collectors.groupingBy(r -> (String) r[0],
Collectors.toMap(r -> (String) r[1],
r -> (BigDecimal) r[2])));
System.out.println(map);
Output
{A={X=1, Y=2.0}, B={X=3.00}, C={Z=4.000}}
To me, it looks like a XY Problem:
You seem to be using Hibernate;
You want to transform the results from your projection into some other format.
Hibernate's own ResultTransformer could have solved your problem in a cleaner way.

Is there a direct way to convert Map<String, List<String>> into a JSON String (using javax.json library)

Lets assume I have the following Map
Map<String, List<String>> map = new HashMap<>();
List<String> list = new ArrayList<>();
list.add("First in list");
list.add("Second in list");
map.put("First in map", list);
So how can I convert it in a direct way to a JSON String using javax.json library?
I just tried the following
Map<String, List<String>> map = new HashMap<>();
List<String> list = new ArrayList<>();
list.add("First in list");
list.add("Second in list");
List<String> list1 = new ArrayList<>();
list1.add("First in secondlist");
list1.add("Second in secondlist");
map.put("First in map", list);
map.put("Second in map", list1);
final JsonArrayBuilder outerJsonArray = Json.createArrayBuilder();
map.forEach((key, innerList) -> {
JsonArrayBuilder innerJsonArray = Json.createArrayBuilder();
innerList.forEach(item -> innerJsonArray.add(item));
outerJsonArray.add(Json.createObjectBuilder().add(key, innerJsonArray));
});
JsonArray usersJson = outerJsonArray.build();
System.out.println(usersJson.toString());
And the resulting JSON array is (missing the insertion order)
[
{
"Second in map": [
"First in secondlist",
"Second in secondlist"
]
},
{
"First in map": [
"First in list",
"Second in list"
]
}
]

Categories