4

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?

0

4 Answers 4

7

E.g. 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 (e.g. this one), though not precisely tailored to this example.

Sign up to request clarification or add additional context in comments.

Comments

5

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.

Comments

5

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).

Comments

1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.