Compare maps stored in a list in java - java

Is possible to compare many maps stored in an arrayList?
I mean:
public static void main(String[] args) {
List<Map<String, Object>> mapList = new ArrayList<Map<String, Object>>();
Map<String, Object> entry1 = new HashMap<>();
Map<String, Object> entry2 = new HashMap<>();
Map<String, Object> entry3 = new HashMap<>();
Map<String, Object> entry4 = new HashMap<>();
entry1.put("entity1", "19820271");
entry2.put("entity1", "19820271");
entry3.put("entity2", "19820272");
entry4.put("entity2", "19820272");
mapList.add(entry1);
mapList.add(entry2);
mapList.add(entry3);
mapList.add(entry4);
groupMaps(mapList);
}
private static List<Map<String, Object>> groupMaps(List<Map<String, Object>> mapList) {
List<Map<String, Object>> resultante = new ArrayList<>();
for (int i = 0; i < mapList.size(); i++) {
Map<String, Object> map1= mapList.get(0);
for (Map.Entry<String, Object> entry : mapList.get(i).entrySet()) {
if (entry.getValue().equals(map1.get("entity"))) {
resultante.add(mapList.get(i));
}
}
}
return mapList;
}
In the "groupMaps" method I need to compare each map so I can group by match, for example:
map1 matches map2
map3 matches map7
map4 matches map5
and so on...
Is there any way to do it?

The most performant way to do that is to create a histogram of frequencies. I.e. a map that will use your maps from the source list as keys and values will represent the number of occurrences of each map in the list.
That is how it could be implemented using Stream API.
private static List<Map<String, Object>> groupMaps(List<Map<String, Object>> mapList) {
return mapList.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.filter(entry -> entry.getValue() > 1)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
The same result can be achieved using an iterative approach. The main logic remains the same:
create a map of frequencies;
then extract duplicates based on it.
private static List<Map<String, Object>> groupMaps(List<Map<String, Object>> mapList) {
Map<Map<String, Object>, Integer> histogram = getHist(mapList);
List<Map<String, Object>> duplicates = new ArrayList<>();
for (Map.Entry<Map<String, Object>, Integer> entry: histogram.entrySet()) {
if (entry.getValue() > 1) {
duplicates.add(entry.getKey());
}
}
return duplicates;
}
private static Map<Map<String, Object>, Integer> getHist(List<Map<String, Object>> mapList) {
Map<Map<String, Object>, Integer> histogram = new HashMap<>();
for (Map<String, Object> next: mapList) {
histogram.merge(next, 1, Integer::sum);
}
return histogram;
}
Output for your example (both versions)
[{entity2=19820272}, {entity1=19820271}]
Note, if you are working on this problem just for exercise it's OK, but you should be aware that using Object as a generic type is as good as not using generics at all. If your map is intended to store string values then it has to be Map<String, String>.

Related

Streams for iterating through Map of List of Map in Java

I have a map that contains Integer as key and (List of Map of String as key and boolean as the value) as value. Map<Int, List<Map<String, Boolean>>>, I want to populate a set that has Int as key of the outer map based on condition.
MyService.java
public Set<Integer> getValue(String input){
Map<String, Boolean> in1 = new HashMap<>();
in1.put("test_1", true);
Map<String, Boolean> in2 = new HashMap<>();
in2.put("test_2", false);
Map<String, Boolean> in3 = new HashMap<>();
in2.put("test_3", false);
Map<String, Boolean> in4 = new HashMap<>();
in2.put("test_4", true);
List<Map<String, Boolean>> l1 = new ArrayList<>();
l1.add(in1);
l1.add(in2);
List<Map<String, Boolean>> l2 = new ArrayList<>();
l2.add(in3);
l2.add(in4);
Map<Integer, List<Map<String,Boolean>>> map = new HashMap();
map.put(123, l1);
map.put(345, l2);
Set<Integer> result = new HashSet<>();
for(Map.Entry<Integer, List<Map<String, Boolean>>> entry : map.entrySet()){
for(Map<String, Boolean> m: entry.getValue() ){
if(m.containsKey(input) && m.get(input) == true){
result.add(entry.getKey());
}
}
}
return result;
}
So, basically I want to iterate from first the exterior map to get the internal map and then iterate the internal map to check if the input is present and add it to a set. How can I do this using java 8 streams?
I tried with for loop, but I will like to replace it using Java streams.
This produced the same results as your code in a test that passed "test_1", etc. into it.
map.entrySet().stream()
.filter(entry -> entry.getValue().stream()
.anyMatch(m -> m.getOrDefault(input, false)))
.map(Map.Entry::getKey)
.collect(Collectors.toSet());

Java 8 how to sort list of list of map

I have code like this
List<List<Map<String, String>>> outerList = new ArrayList<>();
List<Map<String, String>> innerList = new ArrayList<>();
outerList.add(innerList)
How to sort outerList using java8 based on the map values. I do not want the inner list to be sorted.
Example:
List<Map<String, String>> innerList1 = new ArrayList<>();
Map<String, String> map1= new HashMap<String, String>();
map1.put("sort", "2")
innerList1.add(map1);
List<Map<String, String>> innerList2 = new ArrayList<>();
Map<String, String> map2 = new HashMap<String, String>();
map2.put("sort", "1")
innerList2.add(map2);
outerList.add(innerList1);
outerList.add(innerList2);
after sorting the innerList2 should be first in the list and innerlist1 should be second
Since the sort value is 2 and 1;
Assuming sorting based on 1st element of outerList and "sort" key of map
below lambda expression will work for you.
List<List<Map<String, String>>> collect = outerList.stream().sorted((e1, e2) -> e1.get(0).get("sort").compareTo(e2.get(0).get("sort"))).collect(Collectors.toList());
Without Streams:
outerList.sort((e1, e2) -> e1.get(0).get("sort").compareTo(e2.get(0).get("sort")));
With comparator:
outerList.sort(new Comparator<List<Map<String,String>>>() {
#Override
public int compare(List<Map<String, String>> e1, List<Map<String, String>> e2) {
<your logic here>
return <int value>;
});

How to convert List<Map<String,Object>> to Map<String, String>?

I have a value like below,
[{S.No=1, Column2=Data2, Column3=Data3}]
This is of type List<Map<String, Object>>. How to convert this to a Map of type <String, String>, with values split with '='for a key value pair
I think this should work for you
List<Map<String, Object>> list = new ArrayList<>();
Map<String, String> newMap = new HashMap<String, String>();
for(Map<String, Object> element: list) {
for(Entry<String, Object> entry : element.entrySet()) {
newMap.put(entry.getKey(), (String) entry.getValue());
}
}

Java8 - How to convert a nested maps to list of nested maps gathered by value of inner map's key

I have a nested maps object like below
{12345={{"status":"200","outcome":"Success","message":"Account created"}}
{23121={{"status":"400","outcome":"Exception","message":"Invalid State value"}}
{43563={{"status":"200","outcome":"Success","message":"Account updated"}}
{72493={{"status":"400","outcome":"Exception","message":"Bad Request"}}
I need to transform this into Map<String, List<Map<String, Map<String, String>>> where the key of outer map is value of the status ( 200 or 400 ) and the value is the list of original maps that have status = 200 or 400 or other valid values from the inner map.
So, the new structure would look like
{{200={[{12345={"status":"200","outcome":"Success","message":"Account created"}},
{43563={"status":"200","outcome":"Success","message":"Account updated"}}
]
},
{400={[{23121={"status":"400","outcome":"Exception","message":"Invalid State value"}},
{72493={"status":"400","outcome":"Exception","message":"Bad Request"}}
]
}
}
Ultimately, I need to generate a report based on the different stati.
This is what I have started with, but am stuck.
I want to loop through outer map, get the inner map, get the value of status key and add the map to a list based on status code value.
This is how I am doing it using loops
private static Map<String, List<Map<String, Map<String, String>>>> covertToReport(Map<String, Map<String, String>> originalMap) {
Map<String, List<Map<String, Map<String, String>>>> statusBasedListOfMaps = new TreeMap<>();
//loop through the map
//for each key, get the inner map
//get the status value for each inner map
List<Map<String, Map<String, String>>> accountsMapsList;
for (Entry<String, Map<String, String>> entry : originalMap.entrySet()) {
String accNum = entry.getKey();
Map<String, String> childMap = entry.getValue();
String stausVal = childMap.get("status");
accountsMapsList = statusBasedListOfMaps.get(stausVal) == null ? new ArrayList<>() : statusBasedListOfMaps.get(stausVal);
accountsMapsList.add((Map<String, Map<String, String>>) entry);
statusBasedListOfMaps.put(stausVal, accountsMapsList);
}
return statusBasedListOfMaps;
}
Of course, the below code doesn't compile, but that is what I am trying to get.
private static void covertToReport(Map<String, Map<String, String>> originalMap) {
Map<String, List<Map<String, Map<String, String>>>> statusBasedListOfMaps;
statusBasedListOfMaps = originalMap.entrySet()
.stream()
.filter(e -> e.getValue()
.values()
.stream()
.map(innerMap -> Collectors.toList())
.collect(Collectors.toMap(Map.Entry::getKey, Collectors.toList(e)));
Is this possible?
You can just use Collectors.groupingBy() with Collectors.mapping():
private static Map<String, List<Map<String, Map<String, String>>>> convertToReport(Map<String, Map<String, String>> originalMap) {
return originalMap.entrySet().stream()
.collect(Collectors.groupingBy(e -> e.getValue().get("status"),
Collectors.mapping(Map::ofEntries, Collectors.toList())));
}
You group by status and then map the associated entry to an own map using Map.ofEntries(). If you are using Java you can use this instead of Map::ofEntries:
e -> new HashMap<>() {{ put(e.getKey(), e.getValue()); }}
The result will be this:
200=[
{12345={status=200, message=Account created, outcome=Success}},
{43563={status=200, message=Account created, outcome=Success}}
],
400=[
{72493={status=400, message=Invalid State value, outcome=Exception}},
{23121={status=400, message=Invalid State value, outcome=Exception}}
]
Your function return a Map<String, List<Map<String, Map<String, String>>>>, but your structure look like Map<String, Map<String, Map<String, String>>>
If what you really like is a Map<String, Map<String, Map<String, String>>> here is the code:
Map<String, Map<String, Map<String, String>>> result= map.entrySet().stream().collect(Collectors.groupingBy(entry -> entry.getValue().get("status"), Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));

Java 8 List<Map<String, Object>> to List<Map<String, Object>> group by key and count by value

I have the following list of maps
List<Map<String, Object>> listBeforeGroup = new ArrayList<Map<String, Object>>();
Map<String, Object> m1 = new HashMap<String, Object>();
m1.put("company", "LG");
m1.put("billType", "A");
m1.put("billPeriod", "09-2018");
Map<String, Object> m2 = new HashMap<String, Object>();
m2.put("company", "LG");
m2.put("billType", "A");
m2.put("billPeriod", "09-2018");
Map<String, Object> m3 = new HashMap<String, Object>();
m3.put("company", "LG");
m3.put("billType", "A");
m3.put("billPeriod", "09-2018");
Map<String, Object> m4 = new HashMap<String, Object>();
m4.put("company", "LG");
m4.put("billType", "B");
m4.put("billPeriod", "01-2019");
Map<String, Object> m5 = new HashMap<String, Object>();
m5.put("company", "LG");
m5.put("billType", "B");
m5.put("billPeriod", "01-2019");
Map<String, Object> m6 = new HashMap<String, Object>();
m6.put("company", "Samsung");
m6.put("billType", "A");
m6.put("billPeriod", "10-2018");
Map<String, Object> m7 = new HashMap<String, Object>();
m7.put("company", "Samsung");
m7.put("billType", "A");
m7.put("billPeriod", "10-2018");
Map<String, Object> m8 = new HashMap<String, Object>();
m8.put("company", "Samsung");
m8.put("billType", "B");
m8.put("billPeriod", "11-2018");
listBeforeGroup.add(m1);listBeforeGroup.add(m2);
listBeforeGroup.add(m3);listBeforeGroup.add(m4);
listBeforeGroup.add(m5);listBeforeGroup.add(m6);
How do I get this output?
//Desired Output - List<Map<String, Object>>
//{company=LG, billType=A, billPeriod=09-2018, count=3}
//{company=LG, billType=B, billPeriod=01-2019, count=2}
//{company=Samsung, billType=A, billPeriod=10-2018, count=2}
//{company=Samsung, billType=B, billPeriod=11-2018, count=1}
I tried this, using java 8 streams, but I can't get the desired output
List<Map<String, Object>> listAfterGroup = listBeforeGroup.stream().map(m -> m.entrySet().stream().collect(Collectors.toMap(p -> p.getKey(), p - > p.getValue()))).collect(Collectors.toList());
And tried this, btw this solution gives a map but I don't want this
Map<Object, Long> listAfterGroup = listBeforeGroup.stream().flatMap(m -> m.entrySet().stream()).collect(Collectors.groupingBy(Map.Entry::getKey,Collectors.counting()));
I want to group the maps by the key "billPeriod" for example, and count items by the values, then generate a new list of maps.
You can create a class Company and then subsequent operations become much simpler.
class Company {
String company;
String billType;
String billPeriod;
public Company(String company, String billType, String billPeriod) {
this.company = company;
this.billType = billType;
this.billPeriod = billPeriod;
}
// getters, setters, toString, etc
}
Initialize the list :
List<Company> list = new ArrayList<>();
list.add(new Company("LG", "A", "09-2018"));
list.add(new Company("LG", "A", "09-2018"));
list.add(new Company("LG", "A", "09-2018"));
list.add(new Company("LG", "B", "01-2019"));
list.add(new Company("LG", "B", "01-2019"));
list.add(new Company("Samsung", "A", "10-2018"));
list.add(new Company("Samsung", "A", "10-2018"));
list.add(new Company("Samsung", "B", "11-2018"));
Now for an example, you can group by company name :
Map<String, Long> map = list.stream().collect(
Collectors.groupingBy(Company::getCompany,
Collectors.mapping((Company c) -> c, Collectors.counting())));
Now it becomes much easier to perform other operations as you desire. Also, here instead of creating 8 maps you end up dealing with just 1 list.
It's really difficult to grouping and counting a map because your map data will be changed after you increase your counter value. Therefore, you must save the original data of the map, and save your counter value to the another map. Join 2 maps after your counting process is complete.
Map<Map<String, Object>, Long> counterData = listBeforeGroup.stream().collect(Collectors.groupingBy(m -> m, Collectors.counting()));
List<Map<String, Object>> listAfterGroup = new ArrayList<>();
for (Map<String, Object> m : counterData.keySet()) {
Map<String, Object> newMap = new HashMap<>(m);
newMap.put("count", counterData.get(m));
listAfterGroup.add(newMap);
}
Update Java 8 approach, not easy to debug
List<Map<String, Object>> listAfterGroup = listBeforeGroup.stream().collect(Collectors.groupingBy(m -> m, Collectors.counting())).entrySet().stream().map(e -> {
Map<String, Object> newMap = e.getKey();
newMap.put("count", e.getValue());
return newMap;
}).collect(Collectors.toList());
My approach with java 8:
Function<Map<String, Object>, String> createHashKey = map ->
map.values().stream()
.map(val -> String.valueOf(val))
.collect(Collectors.joining());
BinaryOperator<Map<String, Object>> mergeDuplicate = (map1, map2) -> {
int incrementedCount = (int)map1.get("count") + 1;
map1.put("count", incrementedCount);
return map1;
};
List<Map<String, Object>> listAfterGroup = listBeforeGroup.stream()
.collect(Collectors.toMap(createHashKey, el -> {
el.put("count", 1);
return el;
},mergeDuplicate))
.values().stream()
.collect(toList());
Maybe not the most succinct solution but I think quite readable and easy to follow the logic of the code.

Categories

Resources