Construct new HashMap by compressing the other map with stream() - java

I need to get new HashMap<Integer, Set<Integer>> which is {10: [100,101], 20:[200,201]} from {100: [100], 101: [101], 200:[200], 201:[201]} using stream()
I try below code but of course does not work.
HashMap<Integer, Set<Integer>> map1 = new HashMap<>();
map1.put(100, new HashSet(Arrays.asList(100));
...
HashMap<Integer, Set<Integer>> map2 = map1.entrySet().stream().collect(
Collectors.toMap(entry -> ((Entry<Integer, Set<Integer>>) entry).getKey()/10,
entry -> ((Entry<Integer, Set<Integer>>) entry).getValue()));
This raises java.lang.IllegalStateException: Duplicate key.

You should try Collectors.groupingBy :
map2 = map1.entrySet()
.stream()
.collect (Collectors.groupingBy (
entry -> entry.getKey()/10,
Collectors.mapping(entry -> entry.getValue(),Collectors.toSet()));
I'm not sure what's the type of the input Map. If it's HashMap<Integer,Integer>, my code should work as is. If it's HashMap<Integer, Set<Integer>> where the Set<Integer> contains just one integer (as in your example), you can change entry.getValue() to entry.getValue().iterator().next() to get that single integer.
map2 = map1.entrySet()
.stream()
.collect (Collectors.groupingBy (
entry -> entry.getKey()/10,
Collectors.mapping(entry -> entry.getValue().iterator().next(),Collectors.toSet()));
Come to think of it, if your input Map always contains for each key a value that is a Set with a single integer equal to that key, you can ignore the value :
map2 = map1.entrySet()
.stream()
.collect (Collectors.groupingBy (
entry -> entry.getKey()/10,
Collectors.mapping(entry -> entry.getKey(),Collectors.toSet()));

You can do this without a stream:
Map<Integer, Set<Integer>> map2 = new HashMap<>();
map1.forEach((i, s) -> map2.computeIfAbsent(i / 10, ii -> new HashSet<>()).addAll(s));
If you still want to use a stream this will work even if your sets have more than one value:
Map<Integer, Set<Integer>> map3 = map1.entrySet().stream()
.collect(Collectors.groupingBy(e -> e.getKey() / 10, HashMap::new,
Collector.of(HashSet::new, (s, e) -> s.addAll(e.getValue()),
(a, b) -> {a.addAll(b); return a;},
Collector.Characteristics.UNORDERED)));
These both assume map1 does not contain a null key or any null values.

Related

How to Merge the Duplicate Values and its Keys present in the HashMap

HashMap<String, String> map = new HashMap<String, String>();
HashMap<String, String> newMap = new HashMap<String, String>();
map.put("A","1");
map.put("B","2");
map.put("C","2");
map.put("D","1");
Expected Output: "AD", "1" and "BC", "2" present inside the newMap which means, if the data values were same it needs combine its keys to have only one data value by combining its keys inside the newMap created how to achieve this in Java?
You want to group by the "integer" value using Collectors.groupingBy and collect the former keys as a new value. By default, grouping yields in List. You can further use downstream collector Collectors.mapping and another downstream collector Collectors.reducing to map and concatenate the individual items (values) as a single String.
Map<String, String> groupedMap = map.entrySet().stream()
.collect(Collectors.groupingBy(
Map.Entry::getValue,
Collectors.mapping(
Map.Entry::getKey,
Collectors.reducing("", (l, r) -> l + r))));
{1=AD, 2=BC}
Now, you can switch keys with values for the final result, though I really think you finally need what is already in the groupedMap as further processing might cause an error on duplicated keys:
Map<String, String> newMap = groupedMap.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getValue,
Map.Entry::getKey));
{BC=2, AD=1}
It is possible, put it all together using Collectors.collectingAndThen (matter of taste):
Map<String, String> newMap = map.entrySet().stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(
Map.Entry::getValue,
Collectors.mapping(
Map.Entry::getKey,
Collectors.reducing("", (l, r) -> l + r))),
m -> m.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getValue,
Map.Entry::getKey))));
Based on logic:
Loop through your map
For each value, get the corresponding key from the new map (based on the value)
If the new map key exists, remove it and put it again with the extra letter at the end
If not exists, just put it without any concatenation.
for (var entry : map.entrySet())
{
String newMapKey = getKey(newMap, entry.getValue());
if (newMapKey != null)
{
newMap.remove(newMapKey);
newMap.put(newMapKey + entry.getKey(), entry.getValue());
continue;
}
newMap.put(entry.getKey(), entry.getValue());
}
The extra method:
private static String getKey(HashMap<String, String> map, String value)
{
for (String key : map.keySet())
if (value.equals(map.get(key)))
return key;
return null;
}
{BC=2, AD=1}
Using Java 8
You can try the below approach in order to get the desired result.
Code:
public class Test {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
Map<String, String> newMap;
map.put("A","1");
map.put("B","2");
map.put("C","2");
map.put("D","1");
Map<String, String> tempMap = map.entrySet().stream()
.collect(Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey,Collectors.joining(""))));
newMap = tempMap.entrySet().stream().sorted(Map.Entry.comparingByValue())
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey,(a,b) -> a, LinkedHashMap::new));
System.out.println(newMap);
}
}
Output:
{AD=1, BC=2}
If you want the keys of the source map to be concatenated in alphabetical order like in your example "AD", "BC" (and not "DA" or "CB"), then you can ensure that by creating an intermediate map of type Map<String,List<String>> associating each distinct value in the source map with a List of keys. Then sort each list and generate a string from it.
That how it might be implemented:
Map<String, String> map = Map.of(
"A", "1", "B", "2","C", "2","D", "1"
);
Map<String, String> newMap = map.entrySet().stream()
.collect(Collectors.groupingBy( // intermediate Map<String, List<String>>
Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toList())
))
.entrySet().stream()
.collect(Collectors.toMap(
e -> e.getValue().stream().sorted().collect(Collectors.joining()),
Map.Entry::getKey
));
newMap.forEach((k, v) -> System.out.println(k + " -> " + v));
Output:
BC -> 2
AD -> 1

Inserting Map into Map using streams

I'd like to know how I can insert a map into another map using streams in java.
I have a two maps
Map<String, List<Character>> map1
Map<String, List<Integer>> map2
I d like to merge both maps such that we have
Map<String, Map<Character, Integer>> finalmap
if map1 is something like
{String1 = [Character1, Character2], String2 = [Character3, Character4], etc}
and map2 is
{String1 = [Integer1, Integer2], String2 = [Integer3, Integer4], etc}
I want it to merge such that the innermap maps Character1 with Integer1 and so on.
Does someone have an idea how to solve this problem? :)
Map<String, Map<Character, Integer>> map3 = map1.entrySet()
.stream()
.flatMap(entry -> {
if (map2.containsKey(entry.getKey())) {
List<Integer> integers = map2.get(entry.getKey());
List<Character> characters = entry.getValue();
Map<Character, Integer> innerMap = IntStream.range(0, Math.min(integers.size(), characters.size()))
.mapToObj(i -> Map.entry(characters.get(i), integers.get(i)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
return Stream.of(Map.entry(entry.getKey(), innerMap));
}
return Stream.empty();
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
This is a bit late but in case the length of Character and Integer lists is different, it may be possible to build an inner map containing all Character keys and null for missing Integer values:
// class MyClass
static Map<String, Map<Character, Integer>> joinMaps(
Map<String, List<Character>> map1, Map<String, List<Integer>> map2)
{
return map1
.entrySet()
.stream()
.filter(e -> map2.containsKey(e.getKey())) // keep the keys from both maps
.map(e -> Map.entry(
e.getKey(), // String key for result
IntStream.range(0, e.getValue().size()) // build map <Character, Integer>
.mapToObj(i -> new AbstractMap.SimpleEntry<>(
e.getValue().get(i),
i < map2.get(e.getKey()).size() ? map2.get(e.getKey()).get(i) : null
))
// use custom collector to allow null Integer values
.collect(
MyClass::innerMap,
(hm, e2) -> hm.put(e2.getKey(), e2.getValue()),
Map::putAll
)
))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
static LinkedHashMap<Character, Integer> innerMap() {
return new LinkedHashMap<>();
}
Custom collector for inner map is needed to allow adding null values which is not possible with Collectors.toMap where NPE is thrown.
Tests:
Map<String, List<Character>> map1 = Map.of(
"S0", Arrays.asList('#', '#'),
"S1", Arrays.asList('a', 'b'),
"S2", Arrays.asList('c', 'd'),
"S3", Arrays.asList('e', 'f')
);
System.out.println("map1=" + map1);
Map<String, List<Integer>> map2 = Map.of(
"S1", Arrays.asList(1),
"S2", Arrays.asList(3, 4, 5),
"S3", Arrays.asList(5, 6),
"S4", Arrays.asList(7, 8)
);
System.out.println("map2=" + map2);
Map<String, Map<Character, Integer>> res = joinMaps(map1, map2);
System.out.println("----\nResult:");
res.forEach((k, v) -> System.out.printf("%s -> %s%n", k, v));
Output:
map1={S0=[#, #], S1=[a, b], S2=[c, d], S3=[e, f]}
map2={S1=[1], S2=[3, 4, 5], S3=[5, 6], S4=[7, 8]}
----
Result:
S1 -> {a=1, b=null}
S2 -> {c=3, d=4}
S3 -> {e=5, f=6}
Update
Another solution using Stream::flatMap and groupingBy + mapping collectors with a custom collector used as a downstream collector is shown below:
static Map<String, Map<Character, Integer>> joinMaps(
Map<String, List<Character>> map1, Map<String, List<Integer>> map2)
{
return map1
.entrySet()
.stream()
.filter(e -> map2.containsKey(e.getKey()))
.flatMap(e -> IntStream.range(0, e.getValue().size())
.mapToObj(i -> Map.entry(
e.getKey(),
new AbstractMap.SimpleEntry<>(
e.getValue().get(i),
i < map2.get(e.getKey()).size() ? map2.get(e.getKey()).get(i) : null
)
))) // Stream<Map.Entry<String, Map.Entry<Character, Integer>>>
.collect(Collectors.groupingBy(
Map.Entry::getKey, // use String as key in outer map
Collectors.mapping(e -> e.getValue(), // build inner map
Collector.of( // using custom collector
MyClass::innerMap, // supplier
(hm, e2) -> hm.put(e2.getKey(), e2.getValue()), // accumulator
MyClass::mergeMaps // combiner
))
));
}
static <T extends Map> T mergeMaps(T acc, T map) {
acc.putAll(map);
return acc;
}
Here mergeMaps is a BinaryOperator<A> combiner argument in the method Collector.of which slightly differs from Stream::collect used above where BiConsumer<R, R> is used as combiner.

merge two Map Values in Java and if key is same append the Values not overwrite in Java 7 or Java 8 [duplicate]

This question already has answers here:
Merge two maps with Java 8
(5 answers)
How can I combine two HashMap objects containing the same types?
(17 answers)
Closed 2 years ago.
I want to merge 2 Maps, but when the key is the same, the values should be appended instead of overwritten.
Let's say
Map<String, Set<String>> map1 = new HashMap<>();
Set<String> set1 = new HashSet<>();
set1.add("AB");
set1.add("BC");
map1.put("ABCD",set1);
Map<String, Set<String>> map2 = new HashMap<>();
Set<String> set2 =new HashSet<>();
set2.add("CD");
set2.add("EF");
map2.put("ABCD",set2);
map1.putAll(map2);
So here the key is same.I know putAll will overwrite the values if key is same
But I am looking for an output like
{ABCD=[AB,BC,CD,ED]}
If someone can help me to resolve, will be so thankful.
You can concat two maps using Stream.concat then collect using groupingBy map key and value as Set.
Map<String, Set<String>> res =
Stream.concat(map1.entrySet().stream(), map2.entrySet().stream())
.collect(Collectors.groupingBy(e-> e.getKey(),
Collectors.flatMapping(e -> e.getValue().stream(), Collectors.toSet())));
Note: Solution use Java 9+ flatMapping
Or
You can use merge function of map. Here merge map2 data into map1
map2.forEach((key, val) -> map1.merge(key, val, (a, b) -> {a.addAll(b); return a;}));
Output: {ABCD=[AB, BC, CD, EF]}
You make use of the merging function provided to Collectors.toMap that specifies what to do with values of duplicate keys with Streams. Demo
final Map<String, Set<String>> map3 = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
(a, b) -> Stream.concat(a.stream(), b.stream()).collect(Collectors.toSet())));
You can apply a similar approach using Map#merge. Demo
final Map<String, Set<String>> map3 = new HashMap<>(map1);
map2.forEach((key, val) -> map3.merge(key, val,
(a, b) -> Stream.concat(a.stream(), b.stream()).collect(Collectors.toSet())));
You can use Stream API to merge the values of the same keys, Check if the map2 has the same keys from map1 and loop over them and merge the values together using addAll()
map1.entrySet().stream().filter(entry -> map2.containsKey(entry.getKey()))
.forEach(entry -> entry.getValue().addAll(map2.get(entry.getKey())));
, main function
public static void main(String[] args) {
Map<String, Set<String>> map1 = new HashMap<>();
Set<String> set1 = new HashSet<>();
set1.add("AB");
set1.add("BC");
map1.put("ABCD", set1);
Map<String, Set<String>> map2 = new HashMap<>();
Set<String> set2 = new HashSet<>();
set2.add("CD");
set2.add("EF");
map2.put("ABCD", set2);
map1.entrySet()
.stream()
.filter(entry -> map2.containsKey(entry.getKey()))
.forEach(entry -> entry.getValue().addAll(map2.get(entry.getKey())));
System.out.println(map1);
}
, output
{ABCD=[AB, BC, CD, EF]}

convert from Map<Object,Set<Object>> to Map<String,Set<String>>

I have a map Map<String, Set<String>>
Map<String, Set<String> result = map.entrySet().parallelStream().collect(
Collectors.groupingBy(Map.Entry::getValue, Collectors.mapping(Map.Entry::getKey, Collectors.toSet())));
I want to convert it to Map<String, Set<String>> . by grouping the values and swapping the places of key and value.
But this line gives me
Type mismatch: cannot convert from Map<Object,Set<Object>> to Map<String,Set<String>>
The problem that you've got here is the type of the map you are creating is:
Map<Set<String>, Set<String>>
not Map<String, Set<String>>.
As such, you need to expand the map's values first, for example:
Map<String, Set<String>> collect = map.entrySet()
.parallelStream()
// Expand (k, {v1, v2, v3}) to [(v1, k), (v2, k), (v3, k)]
.flatMap(e -> e.getValue().stream().map(ee -> new SimpleEntry<>(ee, e.getKey())))
.collect(
Collectors.groupingBy(
Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.toSet())));
Unless you really need the parallel processing, I think it would be much easier to use loops:
Map<String, Set<String>> collect = new HashSet<>();
for (Map.Entry<String, Set<String>> entry : map.entrySet()) {
for (String v : entry.values()) {
collect.computeIfAbsent(v -> new HashSet<>())
.add(entry.getKey()));
}
}
Here is an example considering your initial Map is Object to Object. Adapt as needed.
Map<Object,Object> map = new HashMap<>();
Map<String, Set<String>> result = map
.entrySet()
.parallelStream()
.collect(Collectors.groupingBy(entry -> (String) entry.getKey(),
Collectors.mapping(entry -> (String) entry.getKey(), Collectors.toSet())));
The problem with your code is that Map.Entry::getKey returns an Object, not a String.
Just to avoid the confusion, I'm answering my question. Thanks to #AndyTurner #alexrolea for pointing out the solution.
Map<Set<String>, Set<String>> result = map.entrySet().parallelStream()
.collect(
Collectors.groupingBy(entry -> (Set<String>) entry.getValue(),
Collectors.mapping(entry -> entry.getKey(), Collectors.toSet())));
I had to replace Map.Entry::getValue with entry -> (Set<String>) entry.getValue() and the other one too.
This helped me group the map by values and use them as keys. Thanks #nullpointer
In fact, this also works. The problem is I was not returning the right datatype.
Map<Set<String>, Set<String>> result = map.entrySet().parallelStream()
.collect(
Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toSet())));

java 8 streams unwind or emit lists to get single values

I have a map like so:
Map<String, List<String>> map
Now I want do take all the list entries from the map values and use them as key in another map.
Map<String, String> newMap = map.entrySet()
.stream()
.fiter(somefilter -> true)
.collect(Collectors.toMap(
k -> k.getValue(), // I want to have every single value as key
v -> v.getKey());
Is there a way to "unwind" arrays in Java 8 streams? In MongoDB I would write something like this:
Map<String, String> newMap = map.entrySet()
.stream()
.fiter(somefilter -> true)
.unwind(entry -> entry.getValue())
.collect(Collectors.toMap(
k -> k.getValue(),
v -> v.getKey());
One way would be to create streams of pairs of value/key (for example with an array) and flat map them for collection:
Map<String, String> newMap = map.entrySet().stream()
.flatMap(e -> e.getValue().stream().map(s -> new String[] { s, e.getKey() }))
.collect(toMap(array -> array[0], array -> array[1]));
The String array looks a bit hacky though...

Categories

Resources