5

I want to use streams in java to group long list of objects based on multiple fields. This will result in map of map of map of map of map of .... of map of lists.

How can I only extract lists from that complex stream?

Here is some example code for demonstration (list of strings, looking for groups with same length and first letter). I'm not interested in keys, just in resulting grouped entities.

List<String> strings = ImmutableList.of("A", "AA", "AAA", "B", "BB", "BBB", "C", "CC", "CCC", "ABA", "BAB", "CAC");

Map<Character, Map<Integer, List<String>>> collect = strings.stream().collect(
        groupingBy(s -> s.charAt(0),
                groupingBy(String::length)
        )
);

This will produce following result

My Map =
{
    A =
    {
        1 = [A]
        2 = [AA]
        3 = [AAA, ABA]
    }
    B =
    {
        1 = [B]
        2 = [BB]
        3 = [BBB, BAB]
    }
    C =
    {
        1 = [C]
        2 = [CC]
        3 = [CCC, CAC]
    }
}

What I'm interested in is actually just lists from the above results and I want to do it ideally as part of groupby operation. I know it can be done for example by looping resulting maps structure. But is there a way to achieve it using streams?

 [
     [A],
     [AA],
     [AAA, ABA],
     [B],
     [BB],
     [BBB, BAB],
     [C],
     [CC],
     [CCC, CAC]
 ]

3 Answers 3

7

Instead of creating nested groups by using cascaded Collectors.groupingBy, you should group by a composite key:

Map<List<Object>, List<String>> map = strings.stream()
        .collect(Collectors.groupingBy(s -> Arrays.asList(s.charAt(0), s.length())));

Then, simply grab the map values:

List<List<String>> result = new ArrayList<>(map.values());

If you are on Java 9+, you might want to change from Arrays.asList to List.of to create the composite keys.

This approach works very well for your case because you stated that you were not interested in keeping the keys, and because the List implementation returned by both Arrays.asList and List.of are well-defined in terms of their equals and hashCode methods, i.e. they can be safely used as keys in any Map.

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

2 Comments

Thanks, thats basically what I was looking for, I've somehow missed there is an option for composed key.
JDK9‘s List.of(…) requires an array. You would need JDK10’s List.copyOf(…), but considering the question’s example code, ImmutableList.copyOf(map.values()) would be an option. Edit: I see, you mean the map key; for the key, either List.of(s.charAt(0), s.length()) or ImmutableList.of(s.charAt(0), s.length()) would be fine.
0

I want to use streams in java to group long list of objects based on multiple fields.

This is trickier than your (invalid) example code leads me to think you expect. Nevertheless, you can flatten a stream via its appropriately-named flatMap() method. For a stream such as you describe, you might need to flatten multiple times or to define a custom mapping method or a complex lambda to flatten all the way down to what you're after.

In the case of a Map of the form presented in the question, you might do something like this:

List<List<String>> result = myMap.values().stream()
        .flatMap(m -> m.values().stream())  // as many of these as needed
        .collect(Collectors.toList());

Comments

0

If you want to get List<List<String>> as in your example you can use :

List<List<String>> list = collect.entrySet().stream()
            .flatMap(e -> e.getValue().entrySet().stream())
            .map(Map.Entry::getValue)
            .collect(Collectors.toList());

Also if you want to get single list of strings, you can add one more flatMap operation:

     ...
     .flatMap(e -> e.getValue().entrySet().stream())
     .flatMap(e -> e.getValue().stream())
     ...

As @John Bollinger mentioned, using stream of values, but not an entries will be more simpler.

1 Comment

Why stream the entry set and then map entries to values instead of streaming the map's values collection directly?

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.