Merge Map<String, List<String> Java 8 Stream - java

I would like to merge two Map with JAVA 8 Stream:
Map<String, List<String>> mapGlobal = new HashMap<String, List<String>>();
Map<String, List<String>> mapAdded = new HashMap<String, List<String>>();
I try to use this implementation:
mapGlobal = Stream.of(mapGlobal, mapAdded)
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue,
Collectors.toList())
));
However, this implementation only create a result like:
Map<String, List<Object>>
If one key is not contained in the mapGlobal, it would be added as a new key with the corresponding List of String. If the key is duplicated in mapGlobal and mapAdded, both list of values will be merge as: A = {1, 3, 5, 7} and B = {1, 2, 4, 6} then A ∪ B = {1, 2, 3, 4, 5, 6, 7}.

You can do this by iterating over all the entries in mapAdded and merging them into mapGlobal.
The following iterates over the entries of mapAdded by calling forEach(action) where the action consumes the key and value of each entry. For each entry, we call merge(key, value, remappingFunction) on mapGlobal: this will either create the entry under the key k and value v if the key didn't exist or it will invoke the given remapping function if they already existed. This function takes the 2 lists to merge, which in this case, are first added to a TreeSet to ensure both unique and sorted elements and converted back into a list:
mapAdded.forEach((k, v) -> mapGlobal.merge(k, v, (v1, v2) -> {
Set<String> set = new TreeSet<>(v1);
set.addAll(v2);
return new ArrayList<>(set);
}));
If you want to run that potentially in parallel, you can create a Stream pipeline by getting the entrySet() and calling parallelStream() on it. But then, you need to make sure to use a map that supports concurrency for mapGlobal, like a ConcurrentHashMap.
ConcurrentMap<String, List<String>> mapGlobal = new ConcurrentHashMap<>();
// ...
mapAdded.entrySet().parallelStream().forEach(e -> mapGlobal.merge(e.getKey(), e.getValue(), (v1, v2) -> {
Set<String> set = new TreeSet<>(v1);
set.addAll(v2);
return new ArrayList<>(set);
}));

Using foreach over Map can be used to merge given arraylist.
public Map<String, ArrayList<String>> merge(Map<String, ArrayList<String>> map1, Map<String, ArrayList<String>> map2) {
Map<String, ArrayList<String>> map = new HashMap<>();
map.putAll(map1);
map2.forEach((key , value) -> {
//Get the value for key in map.
ArrayList<String> list = map.get(key);
if (list == null) {
map.put(key,value);
}
else {
//Merge two list together
ArrayList<String> mergedValue = new ArrayList<>(value);
mergedValue.addAll(list);
map.put(key , mergedValue);
}
});
return map;
}

The original implementation doesn't create result like Map<String, List<Object>>, but Map<String, List<List<String>>>. You need additional Stream pipeline on it to produce Map<String, List<String>>.

Map<String, List<String>> result = new HashMap<String, List<String>>();
Map<String, List<String>> map1 = new HashMap<String, List<String>>();
Map<String, List<String>> map2 = new HashMap<String, List<String>>();
for(Map.Entry<String, List<String>> entry: map1.entrySet()) {
result.put(entry.getKey(), new ArrayList<>(entry.getValue());
}
for(Map.Entry<String, List<String>> entry: map2.entrySet()) {
if(result.contains(entry.getKey())){
result.get(entry.getKey()).addAll(entry.getValue());
} else {
result.put(entry.getKey(), new ArrayList<>(entry.getValue());
}
}
This solution creates independent result map without any reference to map1 and map2 lists.

Using StreamEx
Map<String, List<String>> mergedMap =
EntryStream.of(mapGlobal)
.append(EntryStream.of(mapAdded))
.toMap((v1, v2) -> {
List<String> combined = new ArrayList<>();
combined.addAll(v1);
combined.addAll(v2);
return combined;
});
If you have even more maps to merge just append to the stream
.append(EntryStream.of(mapAdded2))
.append(EntryStream.of(mapAdded3))

Here is the full code to Iterate Two HashMap which has values stored in the form of a list. Merging all the keys and values in first hashmap. Below is the example.
HashMap<String, List<String>> hmap1 = new HashMap<>();
List<String> list1 = new LinkedList<>();
list1.add("000");
list1.add("111");
List<String> list2 = new LinkedList<>();
list2.add("222");
list2.add("333");
hmap1.put("Competitor", list1);
hmap1.put("Contractor", list2);
// System.out.println(hmap1);
HashMap<String, List<String>> hmap2 = new HashMap<>();
List<String> list3 = new LinkedList<>();
list3.add("aaa");
list3.add("bbb");
List<String> list4 = new LinkedList<>();
list4.add("ccc");
list4.add("ddd");
hmap2.put("Competitor", list3);
hmap2.put("Contractor", list4);
//******* Java 8 Feature *****
hmap1.forEach((k, v) -> hmap2.merge(k, v, (v1, v2) -> {
List<String> li = new LinkedList<>(v1);
li.addAll(v2);
hmap2.put(k,li);
return new ArrayList<>(li);
}));
System.out.println(hmap2);
Output:
{Competitor=[aaa, bbb, 000, 111], Contractor=[ccc, ddd, 222, 333]}

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 print HashMap values in descending order, but if two or more values are equal, print them by keys ascending? (JAVA)

For example we have
Map<String, Integer> map = new HashMap<>();
map.put("fragments", 5);
map.put("motes", 3);
map.put("shards", 5);
I want to print them like this:
fragments: 5
shards: 5
motes: 3
I would solve this by first putting the values in a TreeMap
Then I would sort the keys based on equal values and put them in a
LinkedHashMap to preserve the order.
Map<String, Integer> map = new TreeMap<>();
map.put("motes", 3);
map.put("shards", 5);
map.put("fragments", 5);
map = map.entrySet().stream().sorted(Comparator.comparing(
Entry<String, Integer>::getValue).reversed()).collect(
LinkedHashMap<String, Integer>::new,
(map1, e) -> map1.put(e.getKey(), e.getValue()),
LinkedHashMap::putAll);
map.entrySet().forEach(System.out::println);
Based on the excellent answer here, consider the following solution:
public static void main(String[] args) {
final Map<String, Integer> originalMap = new HashMap<>();
originalMap.put("fragments", 5);
originalMap.put("motes", 3);
originalMap.put("shards", 5);
final Map<String, Integer> sortedMap = sortByValue(originalMap, false);
sortedMap
.entrySet()
.stream()
.forEach((entry) -> System.out.println(entry.getKey() + " : " + entry.getValue()));
}
private static Map<String, Integer> sortByValue(Map<String, Integer> unsortedMap, final boolean ascending) {
List<Entry<String, Integer>> list = new LinkedList<>(unsortedMap.entrySet());
// Sorting the list based on values
list.sort((o1, o2) -> ascending ? o1.getValue().compareTo(o2.getValue()) == 0
? o1.getKey().compareTo(o2.getKey())
: o1.getValue().compareTo(o2.getValue()) : o2.getValue().compareTo(o1.getValue()) == 0
? o2.getKey().compareTo(o1.getKey())
: o2.getValue().compareTo(o1.getValue()));
return list.stream().collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> b, LinkedHashMap::new));
}

How to Filter a map of map based on another map using Lambda expressions

I have a Map of Map which needs to be filtered based of another Map using lambda expressions
I tried to do filter on the map and find all matches based of another map but it doesnot seem to work. It seems the values are not filtered correctly.
Is there a way I can do streams and map and put the filtering logic there?
Can someone please help
public static void main(String []args){
System.out.println("Hello World");
Map<String,List<String>> items = new HashMap<>();
List<String> ut1=new ArrayList<>();
ut1.add("S");
ut1.add("C");
List<String> ut2=new ArrayList<>();
ut2.add("M");
List<String> ut3=new ArrayList<>();
ut3.add("M");
ut3.add("C");
items .put("1010016",ut1);
items .put("1010019",ut2);
items .put("1010012",ut3);
System.out.println("Map"+items);
Map<String,Map<String,String>> sKey = new HashMap<>();
Map<String,String> utKey1 = new HashMap<>();
utKey1.put("S","1001");
utKey1.put("M","1002");
utKey1.put("C","1003");
Map<String,String> utKey2 = new HashMap<>();
utKey2.put("S","1004");
Map<String,String> utKey3 = new HashMap<>();
utKey3.put("S","1005");
utKey3.put("M","1006");
Map<String,String> utKey4 = new HashMap<>();
utKey4.put("S","1007");
utKey4.put("M","1008");
utKey4.put("C","1009");
sKey.put("1010016",utKey1);
sKey.put("1010019",utKey2);
sKey.put("1010012",utKey3);
sKey.put("1010011",utKey4);
System.out.println("Map2"+sKey);
Map<String,Map<String,String>> map3 =
sKey.entrySet().stream()
.filter(x ->
items.containsKey(x.getKey())
&& x.getValue().entrySet().stream().allMatch(y ->
items.entrySet().stream().anyMatch(list ->
list.getValue().contains(y.getKey()))))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
System.out.println("Map3"+map3);
}
the filtered map is returning as:
Map3{1010012={S=1005, M=1006}, 1010016={S=1001, C=1003, M=1002}, 1010019={S=1004}}
But the actual result should be:
Map3{1010012={M=1006}, 1010016={S=1001, C=1003}}
I will rather say this is a work around to achieve your expected output using stream.
Map<String, Map<String, String>> result =
sKey.entrySet().stream()
.filter(detail -> items.keySet().contains(detail.getKey()) &&
!Collections.disjoint(detail.getValue().keySet(), items.get(detail.getKey())))
.collect(HashMap::new,
(m,v) -> m.put(v.getKey(), v.getValue().entrySet().stream()
.filter(detail -> items.get(v.getKey()).contains(detail.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))),
HashMap::putAll);
Output
{1010012={M=1006}, 1010016={S=1001, C=1003}}

collecting HashMap<String, List<String>> java 8

I want to be able to convert a List to a HashMap where the key is the elementName and the values is a list of something random (in this case its the Element Name). So in short I want (A->List(A), B->List(B), C-> List(C)). I tried using toMap() and passing it the keyMapper and ValueMapper but I get a compilation error. I would really appreciate if someone can help me out.
Thanks!
public static void main(String[] args) {
// TODO Auto-generated method stub
List<String> list = Arrays.asList("A","B","C","D");
Map<String, List<String>> map = list.stream().map((element)->{
Map<String, List<String>> map = new HashMap<>();
map.put(element, Arrays.asList(element));
return map;
}).collect(??);
}
Function<Map<String, String>, String> key = (map) -> {
return map.keySet().stream().findFirst().get();
};
Function<Map<String, String>, String> value = (map) -> {
return map.values().stream().findFirst().get();
};
=== This worked for me
Thanks for all the help guys! #izstas "they should operate on the elements" helped a lot :). Actually this is what I was looking for to be exact
public static void test2 (){
Function<Entry<String, List<String>>, String> key = (entry) -> {
return entry.getKey();
};
Function<Entry<String, List<String>>, List<String>> value = (entry) -> {
return new ArrayList<String>(entry.getValue());
};
BinaryOperator<List<String>> merge = (old, latest)->{
old.addAll(latest);
return old;
};
Map<String, List<String>> map1 = new HashMap<>();
map1.put("A", Arrays.asList("A1", "A2"));
map1.put("B", Arrays.asList("B1"));
map1.put("D", Arrays.asList("D1"));
Map<String, List<String>> map2 = new HashMap<>();
map2.put("C", Arrays.asList("C1","C2"));
map2.put("D", Arrays.asList("D2"));
Stream<Map<String, List<String>>> stream =Stream.of(map1, map2);
System.out.println(stream.flatMap((map)->{
return map.entrySet().stream();
}).collect(Collectors.toMap(key, value, merge)));
}
You can use the groupingBy method to manage aggregation, for example:
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "B", "C", "D", "A");
Map<String, List<String>> map = list.stream().collect(Collectors.groupingBy(Function.identity()));
}
If you want more flexibility (for example to map the value and return a Set instead of a List) you can always use the groupingBy method with more parameters as specified in javadoc:
Map<City, Set<String>> namesByCity = people.stream().collect(Collectors.groupingBy(Person::getCity, mapping(Person::getLastName, toSet())));
Functions key and value you have defined in your code are not correct because they should operate on the elements of your list, and your elements are not Maps.
The following code works for me:
List<String> list = Arrays.asList("A", "B", "C", "D");
Map<String, List<String>> map = list.stream()
.collect(Collectors.toMap(Function.identity(), Arrays::asList));
First argument to Collectors.toMap defines how to make a key from the list element (leaving it as is), second argument defines how to make a value (making an ArrayList with a single element).
Thanks for all the help guys! #izstas "they should operate on the elements" helped a lot :). Actually this is what I was looking for to be exact
public static void test2 (){
Function<Entry<String, List<String>>, String> key = (entry) -> {
return entry.getKey();
};
Function<Entry<String, List<String>>, List<String>> value = (entry) -> {
return new ArrayList<String>(entry.getValue());
};
BinaryOperator<List<String>> merge = (old, latest)->{
old.addAll(latest);
return old;
};
Map<String, List<String>> map1 = new HashMap<>();
map1.put("A", Arrays.asList("A1", "A2"));
map1.put("B", Arrays.asList("B1"));
map1.put("D", Arrays.asList("D1"));
Map<String, List<String>> map2 = new HashMap<>();
map2.put("C", Arrays.asList("C1","C2"));
map2.put("D", Arrays.asList("D2"));
Stream<Map<String, List<String>>> stream =Stream.of(map1, map2);
System.out.println(stream.flatMap((map)->{
return map.entrySet().stream();
}).collect(Collectors.toMap(key, value, merge)));
}

Categories

Resources