GroupBy a list of strings

Viewed 212

I have this class:

class A {
    private List<String> keys;
    private String otherData;
    private int otherDate2;

    // getter and setters for each
}

For this class I have a simple list filled with some data. List<A> listOfA. Now I want to convert this data to a map. Map<String, List<A>>

Currently we using a bunch of methods to archive this in a very complicated way. I think, we can solve it with a simple stream()-operation.

I tried this

// first
listOfA.stream()
    .collect(Colletors.groupingBy(a -> a.getKeys()))
// produces a Map<String, List<A>>     

// second
listOfA.stream()
    .flatMap(a -> a.getKeys().stream())
    .collect(Colletors.groupingBy(string -> string))
// produces a Map<String, List<String>>

What is the right way for this situation?

Edit: To be clear, I want a Map<String, List<A>>.

3 Answers

You don't need streams for this. It's easier this way:

Map<String, List<A>> result = new HashMap<>();

listOfA.forEach(a -> a.getKeys().forEach(key -> 
        result.computeIfAbsent(key, k -> new ArrayList<>()).add(a)));

This iterates outer and inner lists and fills a Map using computeIfAbsent, which creates an empty list if there is still no value for the given key, then A instances are simply added to the corresponding list.

The first code will group by Map<List<String>, List<A>> and not Map<String, List<A>>.
The second code makes no sense : you group strings by themselves...

A simple way would be creating the set of all possible key-A couples.
You could use a Map for each couple but it looks like an overhead.
SimpleImmutableEntry that represents a key and value suits better. Once you get the whole couples, you can easily group A elements by key.

You could try something like :

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;

    // ...
    List<A> listOfA = new ArrayList<>();
    listOfA.add(new A(Arrays.asList("1", "2"), "foo1", 1));
    listOfA.add(new A(Arrays.asList("2", "3"), "foo2", 2));
    listOfA.add(new A(Arrays.asList("3", "4"), "foo3", 3));

    Map<String, List<A>> map =
    listOfA.stream()
           .flatMap(a -> a.keys.stream()
                               .map(k -> new SimpleImmutableEntry<>(k, a)))
           .collect(groupingBy(e -> e.getKey(), mapping(e -> e.getValue(), toList())));

    map.forEach((k, v) -> System.out.println("key=" + k + ", value=" + v + "\n"));

Output :

key=1, value=[A [keys=[1, 2], otherData=foo1, otherDate2=1]]

key=2, value=[A [keys=[1, 2], otherData=foo1, otherDate2=1], A [keys=[2, 3], otherData=foo2, otherDate2=2]]

key=3, value=[A [keys=[2, 3], otherData=foo2, otherDate2=2], A [keys=[3, 4], otherData=foo3, otherDate2=3]]

key=4, value=[A [keys=[3, 4], otherData=foo3, otherDate2=3]]

In case you want to group given collection by some field, and each group could contain multiple objects. This is part of my GroupUtils:

public static <K, V> Map<K, List<V>> groupMultipleBy(Collection<V> data, Function<V, K> classifier) {
    return Optional.ofNullable(data).orElse(Collections.emptyList()).stream().collect(Collectors.groupingBy(classifier, Collectors.mapping(Function.identity(), Collectors.toList())));
}

Using example.

You have following class:

class A {
    private String id;
    private String name;
}

and to group collection of this class, you could with:

List<A> collection = Collections.emptyList();
Map<String, List<A>> groupedById = GroupUtils.groupMultipleBy(collection, A::getId);

P.S.

To give you more options, this relative part of my GroupUtils:

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class GroupUtils {

    public static <K, V> Map<K, List<V>> groupMultipleBy(Collection<V> data, Function<V, K> classifier) {
        return groupMultipleBy(data, classifier, Function.identity());
    }

    public static <K, V, S> Map<K, List<S>> groupMultipleBy(Collection<V> data, Function<V, K> classifier, Function<V, S> mapper) {
        return groupMultipleBy(data, classifier, mapper, Collectors.toList());
    }

    public static <K, V, S, R extends Collection<S>> Map<K, R> groupMultipleBy(Collection<V> data, Function<V, K> classifier,
                                                                               Collector<S, ?, R> downstream) {
        return groupMultipleBy(data, classifier, (Function<V, S>)Function.identity(), downstream);
    }

    public static <K, V, S, R extends Collection<S>> Map<K, R> groupMultipleBy(Collection<V> data, Function<V, K> classifier,
                                                                               Function<V, S> mapper, Collector<S, ?, R> downstream) {
        return Optional.ofNullable(data).orElse(Collections.emptyList()).stream()
                       .collect(Collectors.groupingBy(classifier, Collectors.mapping(mapper, downstream)));
    }

    public static <K, V> Map<K, V> groupSingleBy(Collection<V> data, Function<V, K> keyMapper) {
        return groupSingleBy(data, keyMapper, Function.identity());
    }

    public static <K, V, S> Map<K, S> groupSingleBy(Collection<V> data, Function<V, K> keyMapper, Function<V, S> valueMapper) {
        return Optional.ofNullable(data).orElse(Collections.emptyList()).stream().collect(Collectors.toMap(keyMapper, valueMapper));
    }

}
Related