user1692342
user1692342

Reputation: 5237

Java 8 stream to collect a Map of List of items

I have a List of Maps which stores the roles and the names of people. For ex:

List<Map<String, String>> listOfData

1) Role:  Batsman
   Name:  Player1

2)Role:  Batsman
   Name:  Player2

3)Role:  Bowler
   Name:  Player3

Role and Name are the Keys of the map. I want to convert this into a Map<String, List<String>> result, which will give me a list of names for each role, i.e

k1: Batsman  v1: [Player1, Player2]
k2: Bowler   v2: [Player3]


listOfData
    .stream()
    .map(entry -> new AbstractMap.SimpleEntry<>(entry.get("Role"), entry.get("Name"))
    .collect(Collectors.toList());

Doing this way will not give me a list of names for the role, it will give me a single name. How do i keep collecting the elements of the list and then add it to a key ?

Java code to create base structure:

Map<String, String> x1 = ImmutableMap.of("Role", "Batsman", "Name", "Player1");

        Map<String, String> y1 = ImmutableMap.of("Role", "Batsman", "Name", "Player2");

        Map<String, String> z1 = ImmutableMap.of("Role", "Bowler", "Name", "Player3");


        List<Map<String, String>> list = ImmutableList.of(x1, y1, z1);
        Map<String, List<String>> z = list.stream()
                    .flatMap(e -> e.entrySet().stream())
                    .collect(Collectors.groupingBy(Map.Entry::getKey,
                            Collectors.mapping(Map.Entry::getValue, Collectors.toList())));

Upvotes: 33

Views: 69328

Answers (4)

Mr. Polywhirl
Mr. Polywhirl

Reputation: 48610

Here is a generic approach to this problem. The groupBy method below takes 2 to 3 arguments.

  1. A collection of type List<E>
  2. A keyFn (key) of type Function<E, K>
  3. (Optional) A valueFn (value) of type Function<E, V> or simply E

I included some unit tests below.

CollectionUtils.java

package org.example.util;

import static java.util.Collections.unmodifiableMap;
import static java.util.stream.Collectors.*;

import java.util.*;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import java.util.function.Function;

public class CollectionUtils {
  public static <E, K, V> Map<K, List<V>> groupBy(
      Collection<E> collection, Function<E, K> keyFn, Function<E, V> valueFn) {
    return collection.stream()
        .map(item -> new SimpleEntry<K, V>(keyFn.apply(item), valueFn.apply(item)))
        .collect(groupingBy(Entry::getKey, mapping(Entry::getValue, toList())));
  }

  public static <E, K> Map<K, List<E>> groupBy(Collection<E> collection, Function<E, K> keyFn) {
    return groupBy(collection, keyFn, Function.identity());
  }

  public static <K, V> Map<K, V> immutableMapOf(K k1, V v1, K k2, V v2) {
    Map<K, V> mutableMap = new HashMap<>();
    mutableMap.put(k1, v1);
    mutableMap.put(k2, v2);
    return unmodifiableMap(mutableMap);
  }
}

CollectionUtilsTest.java

package org.example.util;

import static java.util.Arrays.asList;
import static org.example.util.CollectionUtils.*;
import static org.junit.Assert.*;

import java.util.*;
import org.junit.Test;
import org.slf4j.*;

public class CollectionUtilsTest {

  private static final Logger logger = LoggerFactory.getLogger(CollectionUtilsTest.class);

  private static final List<Map<String, String>> data =
      asList(
          immutableMapOf("Username", "Batman", "Role", "Leader"),
          immutableMapOf("Username", "Robin", "Role", "Subordinate"),
          immutableMapOf("Username", "Superman", "Role", "Leader"));

  @Test
  public void testGroupBy() {
    logger.info("Test groupBy(Collection<E>, Function<E, K>, Function<E, V>)");
    Map<String, List<String>> grouped = groupBy(data, m -> m.get("Role"), m -> m.get("Username"));

    logger.info("Checking keys...");
    assertNotNull(grouped.get("Leader"));
    assertNotNull(grouped.get("Subordinate"));

    logger.info("Checking values...");
    assertEquals("Batman", grouped.get("Leader").get(0));
    assertEquals("Superman", grouped.get("Leader").get(1));
    assertEquals("Robin", grouped.get("Subordinate").get(0));
  }

  @Test
  public void testGroupBySimple() {
    logger.info("Test groupBy(Collection<E>, Function<E, K>)");
    Map<String, List<Map<String, String>>> grouped = groupBy(data, m -> m.get("Role"));

    logger.info("Checking keys...");
    assertNotNull(grouped.get("Leader"));
    assertNotNull(grouped.get("Subordinate"));

    logger.info("Checking values...");
    assertEquals("Batman", grouped.get("Leader").get(0).get("Username"));
    assertEquals("Superman", grouped.get("Leader").get(1).get("Username"));
    assertEquals("Robin", grouped.get("Subordinate").get(0).get("Username"));
  }
}

Upvotes: 0

user12838225
user12838225

Reputation: 191

The Collectors class provides convenience methods in the form of i.e. groupingBy which allow to group similar objects by a certain classifier. The classifier method is the input to that particular grouping function. This function will generate a Map with the respective classifier methods value as key and a list of objects that share the same classifier method value as value.

Therefore a code like

Map<String, List<Person>> roles2Persons = 
    lis.stream().collect(Collectors.groupingBy(Person::getRole));

will generate a mapping for the respective roles Person objects may fulfill to a list of Person objects that share the same role as the key in the map.

After the above collector was applied the resulting map will contain the desired form of

key1: Batsman, values: List(Player1, Player2)
key2: Bowler, values: List(Player3)

Upvotes: 19

Ousmane D.
Ousmane D.

Reputation: 56433

listOfData.stream()
          .flatMap(e -> e.entrySet().stream())
          .collect(Collectors.groupingBy(Map.Entry::getKey, 
                         Collectors.mapping(Map.Entry::getValue, 
                                    Collectors.toList())));

update:

Slightly different variant to user1692342's answer for completeness.

list.stream()
    .map(e -> Arrays.asList(e.get("Role"), e.get("Name")))
    .collect(Collectors.groupingBy(e -> e.get(0),
             Collectors.mapping(e -> e.get(1), Collectors.toList())));

Upvotes: 38

user1692342
user1692342

Reputation: 5237

Based on the idea given by Aomine:

list.stream()
    .map(e -> new AbstractMap.SimpleEntry<>(e.get("Role"), e.get("Name")))
    .collect(Collectors.groupingBy(Map.Entry::getKey,
                    Collectors.mapping(Map.Entry::getValue, Collectors.toList())));

Upvotes: 23

Related Questions