Home > Mobile >  Java Stream: Grouping arraylist by multiple fields
Java Stream: Grouping arraylist by multiple fields

Time:10-23

I have the following data

List<ArrayList<String>> dummy = new ArrayList<>();
dummy.add(new ArrayList<>(List.of("1", "SP1", "SW1")));
dummy.add(new ArrayList<>(List.of("2", "SP1", "SW2")));
dummy.add(new ArrayList<>(List.of("3", "SP2", "SW1")));
dummy.add(new ArrayList<>(List.of("4", "SP2", "SW2")));
dummy.add(new ArrayList<>(List.of("5", "SP2", "SW1")));
dummy.add(new ArrayList<>(List.of("6", "SP1", "SW1")));
dummy.add(new ArrayList<>(List.of("7", "SP3", "SW2")));

I need to group them as follows
SW1-SP1-list{1,6}
SW1-SP2-list{3,5}
SW2-SP1-list{2}
SW2-SP2-list{4}
SW2-SP3-list{7}

What I have tried is as follows. This is not the way I want actually.

var test = dummy.stream()
            .collect(Collectors.groupingBy(s -> s.get(2), TreeMap::new,
                    Collectors.groupingBy(s -> s.get(1), Collectors.toList())));

Any help would be very much appreciated. Thanks in advance

CodePudding user response:

Here's how you can apply nested groupingBy

Map<String, Map<String, List<String>>> result = dummy.stream()
        .collect(Collectors.groupingBy(l -> l.get(2),
                Collectors.groupingBy(l -> l.get(1),
                        Collectors.mapping(l -> l.get(0), Collectors.toList()))));

To collect it in the order encountered, you can use a LinkedHashMap,

Map<String, Map<String, List<String>>> result = dummy.stream()
        .collect(Collectors.groupingBy(l -> l.get(2), LinkedHashMap::new,
                Collectors.groupingBy(l -> l.get(1), LinkedHashMap::new,
                        Collectors.mapping(l -> l.get(0), Collectors.toList()))));
System.out.println(result);

Result:

{SW1={SP1=[1, 6], SP2=[3, 5]}, SW2={SP1=[2], SP2=[4], SP3=[7]}}

CodePudding user response:

Here's a solution:

var test = dummy.stream()
                .collect(Collectors.groupingBy(z -> z.get(2)   "-"   z.get(1),
                        Collectors.mapping(z -> z.get(0), Collectors.toList())));

Output:

SW2-SP1=[2]
SW2-SP3=[7] 
SW1-SP2=[3, 5] 
SW2-SP2=[4] 
SW1-SP1=[1, 6]

CodePudding user response:

Nested Maps are impractical because they are not very convenient.

And since you basically need to associate every distinct combination of SW and SP with a corresponding list of values, you can define an auxiliary object that would server a key.

That would allow to achieve the desired result without a need to resort to nested maps.

A Java 16 record would fit into this role perfectly well (if you're using a lower version of JDK, you can implement it as a plain class):

record SwSp(String sw, String sp) {}

In order to use it as a key in a TreeMap it needs either implement Comparable interface, or you can provide a Comparator while creating a TreeMap as shown below.

To generate comparator you can use Java 8 static methods comparing() and thenComparing().

With it, the stream might look like this:

public static void main(String[] args) {
    List<List<String>> dummy = List.of(
        new ArrayList<>(List.of("1", "SP1", "SW1")),
        new ArrayList<>(List.of("2", "SP1", "SW2")),
        new ArrayList<>(List.of("3", "SP2", "SW1")),
        new ArrayList<>(List.of("4", "SP2", "SW2")),
        new ArrayList<>(List.of("5", "SP2", "SW1")),
        new ArrayList<>(List.of("6", "SP1", "SW1")),
        new ArrayList<>(List.of("7", "SP3", "SW2"))
    );

    NavigableMap<SwSp, List<String>> test = dummy.stream()
        .collect(Collectors.groupingBy(
            list -> new SwSp(list.get(2), list.get(1)),
            () -> new TreeMap<>(
                Comparator.comparing(SwSp::sw).thenComparing(SwSp::sp)
            ),
            Collectors.mapping(list -> list.get(0),
                Collectors.toList())
        ));
    
    test.forEach((k, v) -> System.out.println(k   " -> "   v));
}

Output:

SwSp[sw=SW1, sp=SP1] -> [1, 6]
SwSp[sw=SW1, sp=SP2] -> [3, 5]
SwSp[sw=SW2, sp=SP1] -> [2]
SwSp[sw=SW2, sp=SP2] -> [4]
SwSp[sw=SW2, sp=SP3] -> [7]
  • Related