I would like to convert a list of maps to a map of lists. It is guaranteed that all the maps have the same keys.
For example, the following list
[{key1: val1, key2: val2}, {key1: val3, key2: val4}]
gets converted to
{key1: [val1, val3], key2: [val2, val4]}
The below code does it using for loops
List<Map<String, Object>> data = getData();
Map<String, List<Object>> result = new HashMap<>();
for (Map<String, Object> element: data) {
element.forEach((key, val) -> {
if (result.containsKey(key))
result.get(key).add(val);
else
result.put(key, Arrays.asList(val));
});
}
How would I be able to achieve the same result using streams?
Eg like this, relying on the collect stage of java streams.
List<Map<String, Object>> data = new ArrayList<>();
HashMap<String, List<Object>> transposed = data.stream()
.flatMap(i -> i.entrySet().stream())
.collect(Collectors.groupingBy(
Map.Entry::getKey,
HashMap::new,
Collectors.mapping(Map.Entry::getValue,
Collectors.toCollection(ArrayList::new))));
There are a number of such application on SO (eg this one ), though not precisely tailored to this example.
This version relying on stream.reduce
should do it:
// some values for testing
List<Map<String, Object>> list = new ArrayList<>();
list.add(Map.of("1", "One", "2", "Two"));
list.add(Map.of("3", "Three", "4", "Four", "5", "Five"));
list.add(Map.of("3", "Teen", "5", "Penta"));
list.add(Map.of("3", "Trice", "4", "Quattro"));
// conversion with stream reduction
Map<String, List<Object>> result = list.stream().reduce(new HashMap<>(),
(acc, map) -> {
map.forEach((k, v) -> acc.computeIfAbsent(k,
l -> new ArrayList<Object>()).add(v));
return acc;
},
(acc1, acc2) -> {
acc1.putAll(acc2);
return acc1;
});
// output
System.out.println(result);
//{1=[One], 2=[Two], 3=[Three, Teen, Trice], 4=[Four, Quattro], 5=[Five, Penta]}
Explanation: this code goes through each map in the list and accumulates it in the hashmap (1st parameter to the reduce function), with the accumulating function (2nd parameter) adding a new list to the hashmap if it sees a key for the first time (otherwise it accumulates in the list already present); the third function is required for consistency between running the stream function serially or in parallel (it combines partial results in parallel runs).
You could do it without streams, yet in a modern fashion, as follows:
Map<String, List<Object>> result = new HashMap<>();
data.forEach(element ->
element.forEach((k, v) - >
result.computeIfAbsent(k, x -> new ArrayList<>()).add(v)));
This iterates the list and then each map and uses Map.computeIfAbsent
to accomodate entries in the result
map.
You can use Collectors.toMap
method:
List<Map<String, String>> listOfMaps = List.of(
Map.of("key1", "val1", "key2", "val2"),
Map.of("key1", "val3", "key2", "val4"));
Map<String, List<String>> mapOfLists = listOfMaps.stream()
// Stream<Map<String,String>>
.flatMap(map -> map.entrySet().stream())
// Stream<Map.Entry<String,String>>
.collect(Collectors.toMap(
// key
Map.Entry::getKey,
// value
e -> List.of(e.getValue()),
// merge function
(list1, list2) -> {
List<String> list = new ArrayList<>();
list.addAll(list1);
list.addAll(list2);
return list;
}, // map factory
LinkedHashMap::new));
System.out.println(listOfMaps);
// [{key1=val1, key2=val2}, {key1=val3, key2=val4}]
System.out.println(mapOfLists);
// {key1=[val1, val3], key2=[val2, val4]}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.