简体   繁体   中英

Convert a list of maps to a map of lists using Java Streams

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]}

See also: Convert a map of lists into a list of maps

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM