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

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())));

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

How to revert a Map<String, Collection<XYZ>> to Map<XYZ, Collection<String>> using streams

I have a map like this Map<String, Collection<XYZ>> which I iterated through traditional for each loop to get a result like Map<XYZ, Collection<String>> but the same I couldn't do it with stream on map. Any suggestions on how to do this?
This can be done by flattening your input Map into pairs of String and XYZ, and then collecting them into the desired output Map:
Map<String, Collection<XYZ>> input = ...
Map<XYZ, List<String>> output =
input.entrySet()
.stream()
.flatMap(e -> e.getValue()
.stream()
.map(xyz -> new SimpleEntry<XYZ,String>(xyz,e.getKey())))
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue,
Collectors.toList())));
Note that the values in the output Map are Lists instead of Collections. I hope you don't mind (since Lists are also Collections).

java conversion: MultivalueMap<String,String> to Map<String,String[]>

I'm working with Java and Spring.
I'm obtaining a MultiValueMap<String,String>. This is helpful to me because I can store more than one value for a each key. But a library I'm using requires a type of Map<String,String[]> instead. More particularly I am working with a ParameterMap. How do I perform such conversion?
Grab the map's entry set and collect it yourself:
Map<String, String[]> result = map.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().toArray(new String[e.getValue().size()])
));
Map<String, String> registeredUserDetails = userInfo.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));

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

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.

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