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)));
Related
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
));
I have a map myMap of type <String, Object> which looks like:
(
"header", "a string value"
"mapObject", {object which is always of type map<String, String>}
)
I basically want to pull out the value of "mapObject" into a Map<String, String>
Initially I just cast it to an ImmutableMap like so:
(ImmutableMap<String, String>) myMap.get("mapObject");
But am wondering if there is a way to do this by making use of Stream.
I've currently got:
myMap.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> (String) entry.getValue()));
But I'm getting the following exception:
class com.google.common.collect.RegularImmutableMap cannot be cast to class java.lang.String
Is there a way to do this or am I better just sticking with the cast?
A possible solution could look like this:
Optional<Map<String, String>> result = myMap.entrySet()
.stream()
.filter(entry -> "mapObject".equals(entry.getKey()))
.map(entry -> (Map<String, String>) entry.getValue())
.findFirst();
Your error message came from the cast of the second entry set (of type Map) to string here: entry -> (String) entry.getValue()
Update:
Like Holger stated the best solution nevertheless is myMap.get("mapObject"). The above solution is only to show how the problem could be solved using the Streams API.
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())))));
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())));
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));