Mapping map values in Java using streams - java

I have a map, Map<String, Map<String, String>> myMap = new HashMap<>(); that I would like to remap to get it's values, so that I get as a result Map<String, String>.
Is it possible to do the mapping using stream API?
I have solved the problem using a for loop but I'm interested if that could be done using streams.
My solution:
Map<String, String> result = new HashMap<>();
myMap.forEach((k, v) -> {
result.putAll(v);
});
What I want is to get all the values from myMap and put them in a new Map.

If you are certain there are no duplicate keys, you can do it like this.
Map<String, String> res = myMap.values()
.stream()
.flatMap(value -> value.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue);
If there may be duplicate keys between the inner maps, you will have to introduce merge function to resolve conflicts. Simple resolution keeping the value of the second encountered entry may look like this:
Map<String, String> res = myMap.values()
.stream()
.flatMap(value -> value.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v2));
Basically, stream the values, which are Maps, flatten them to a stream of entries and collect the entries in a new Map.

You need to flatten the entries of the nested maps which can be done using either flatMap() or mapMulty().
And then apply collect() with the minimalistic two-args flavor of Collector toMap() passed as an argument. It would be sufficient since you don't expect duplicates.
Here's an example using flatMap():
Map<String, Map<String, String>> myMap = new HashMap<>();
Map<String, String> res = myMap.entrySet().stream() // stream of maps
.flatMap(entry -> entry.getValue().entrySet().stream()) // stream of map entries
.collect(Collectors.toMap(
Map.Entry::getKey, // key mapper
Map.Entry::getValue // value mapper
));
Example with Java 16 mapMulti() used for flattening the data:
Map<String, Map<String, String>> myMap = new HashMap<>();
Map<String, String> res = myMap.entrySet().stream() // stream of maps
.<Map.Entry<String, String>>mapMulti((entry, consumer) ->
entry.getValue().entrySet().forEach(consumer) // stream of map entries
)
.collect(Collectors.toMap(
Map.Entry::getKey, // key mapper
Map.Entry::getValue // value mapper
));

Related

Convert List of Maps to a single Map with VAVR

I'm struggling to reduce a List of Maps to a single Map using Vavr
<List<Map<String, Object>>>
to
<Map<String, Object>>
I tried it with flatmap/reduce/merge but unfortunately with no happy end :-(
Can someone provided an example how to do it with io.vavr.collection.List/io.vavr.collection.Map ?
You have several options.
For instance, given the following data set (sorry for its simplicity):
Map<String, Object> map1 = HashMap.of("key1", "value1", "key2", "value2");
Map<String, Object> map2 = HashMap.of("key3", "value3", "key4", "value4");
Map<String, Object> map3 = HashMap.of("key5", "value5", "key6", "value6");
List<Map<String, Object>> list = List.of(map1, map2, map3);
You can combine the different Maps with fold, for instance:
Map<String, Object> result = list.fold(HashMap.empty(), (m1, m2) -> m1.merge(m2));
You can use reduce as well:
Map<String, Object> result = list.reduce((m1, m2) -> m1.merge(m2));
Both of them use the Map merge method. There is an overloaded version of this method that allows you to define how collisions should be resolved.
I have no idea what vavr has to do with this.
list.stream() // A
.flatMap(map -> map.entrySet().stream()) // B
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
[A] Now you have a stream of Map<String, Object>.
[B] Now you have a stream of map entries.
Now you have a stream of key/value pairs.
Then just collect them into a map by providing a function to extract a key from a Map.Entry object, and a function that extracts a value.
Note that if you have a map of type Map<String, Object>, I give it 99% odds you're doing something wrong. Java is nominally, statically, and strongly typed, after all.

What is the easiest way to change the inner value type in a nested map in java?

I have a nested map Map<String, Map<String, List<ObjectA>>> passed to me, and I want to change it to type Map<String, Map<String, Set<ObjectA>>>, what is the easiest way to do so in Java using stream? I have tried to use Collectors.groupingBy but can't get it working.
The best way is you have to iterate through each entry in outer map and inner map, and then convert the inner map entry value List<ObjectA> to Set<ObjectA>
Map<String, Map<String, Set<ObjectA>>> resultMap = map.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, val -> new HashSet<>(val.getValue())))));
Note : If you are converting List to HashSet then you will not maintain same order, so you can choose LinkedHashSet over HashSet to maintain order
Map<String, Map<String, Set<ObjectA>>> resultMap = map.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, val -> new LinkedHashSet<>(val.getValue())))));

How to filter, using stream, Map<String, Object> by List<String> which contains keys

I have Map<String, Object> which has to be become Map<String, String>. Filtering should be done by List<String>.
That list contains keys of map elements that should be in new map.
For this I need to use streams.
Map<String, Object> oldMap;
List<String> keysForFiltering;
Map<String, String> newMap;
It would be more efficient if the filter would operate on a Set of keys instead of a List of keys, since searching a Set is more efficient than searching a List.
Map<String, String> newMap =
oldMap.entrySet()
.stream()
.filter(e -> keysForFiltering.contains(e.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey,
e -> e.getValue().toString()));
since you have a map then you can get the stream of that and use a custom predicate, that predicate need to check if the Entry.key is present in your list or not
Map<String, String> myMap = new HashMap<>();
myMap.put("A", "fortran");
myMap.put("B", "java");
myMap.put("C", "c++");
myMap.put("D", "php");
List<String> keysForFiltering = Arrays.asList("A", "C");
Predicate<Entry<String, String>> myPredicate = t -> keysForFiltering.contains(t.getKey());
Map<String, String> filteredMap = myMap
.entrySet().stream().filter(myPredicate)
.collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
System.out.println(filteredMap);

How do I transform a Map<K,V1> to another Map<K,V2> using a map of functions in Java 8?

I want to transform a Map<String, Integer> to another Map<String, Long> using a map of functions in Java 8, matching the data with the functions by key in both maps. You can assume that both maps have the same keys.
I tried the following approach:
Map<String, Integer> inputData = new HashMap<>();
inputData.put("A",8);
inputData.put("B",7);
inputData.put("C",6);
Map<String, Function<Integer, Long>> transformers = new HashMap<>();
transformers.put("A", x -> x*2L);
transformers.put("B", x -> x+3L);
transformers.put("C", x -> x+11L);
Map<String, Long> mappedData = inputData.entrySet().stream()
.map(entry -> new AbstractMap.SimpleEntry<>(
entry.getKey(),
transformers.get(entry.getKey()).apply(entry.getValue())))
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
Expected result: {A=16, B=10, C=17}.
Is there any simpler way of expressing "apply map of transformers to the map of inputData matching by key" in Java Streams API?
You may transform directly in the collector:
Map<String, Long> mappedData = inputData.entrySet().stream()
.collect(toMap(Map.Entry::getKey,
entry -> transformers.get(entry.getKey()).apply(entry.getValue())));
Such solution is shorter and does not create intermediate objects.
Were your inputData had the same value type (Map<String, Long>), you could perform the transformation in-place:
inputData.replaceAll((key, value) -> transformers.get(key).apply(value));
You can also start from transformers map itself, which looks slightly easier:
Map<String, Long> collect = transformers.entrySet().stream()
.collect(toMap(Map.Entry::getKey,
e -> e.getValue().apply(inputData.get(e.getKey()))));
If one extra line does not bother you, try
Map<String, Long> result = new HashMap<>(); // this line
inputData.forEach((k, v) -> result.put(k, transformers.get(k).apply(v));

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