Grouping items in a list using Stream API

154 views Asked by At

I'm trying to group items in a list using Stream.

public static void main(String[] args) {
    List<String> l = Arrays.asList("A", "C", "B", "A", "B");

    Map<String, List<String>> b = l.stream().collect(Collectors.groupingBy(Function.identity()));
    System.out.println(b.toString());
}

This returns {A=[A, A], B=[B, B], C=[C]}.

How can I get the index of each of those elements? Something like this, {A=[(A,0), (A,3)], B=[(B,2), (B,4)], C=[(C,1)]}.

This question is different from this question, where they want only the index, but I want both the index and the list item.

4

There are 4 answers

2
Youcef LAIDANI On BEST ANSWER

You can use groupingBy with List::get(int index) like this:

Map<String, List<Integer>> result = IntStream.range(0, l.size())
        .boxed()
        .collect(Collectors.groupingBy(l::get));

Output

{A=[0, 3], B=[2, 4], C=[1]}

As you can see, the response is a little different from what you expected. I believe displaying (B,2) is redundant.


Edit

As the OP insiste to show the same output, I would suggest to create a custom object that hold a letter and the index, like this:

@AllArgsConstructor
class MyOutput{
    private String letter;
    private Integer index;

    @Override
    public String toString() {
        return "(" + letter + "," + index + ')';
    }
}

And your stream should look like this:

Map<String, List<MyOutput>> response = IntStream.range(0, l.size())
        .boxed()
        .collect(groupingBy(l::get, mapping(i -> new MyOutput(l.get(i), i), toList())));

Outputs

{A=[(A,0), (A,3)], B=[(B,2), (B,4)], C=[(C,1)]}
0
WJS On

Here is how to do what you wanted.

  • first, declare a record to hold the data.
  • then stream the information and create an instance of that record.
  • then grouping it into map using groupingBy.
record Pair(String letter, int index) {
    @Override
    public String toString() {
        return "(%s, %d)".formatted(letter, index);
    }
}

List<String> l = Arrays.asList("A", "C", "B", "A", "B");

Map<String, List<Pair>> result = IntStream.range(0, l.size())
        .mapToObj(i -> new Pair(l.get(i), i))
        .collect(Collectors.groupingBy(Pair::letter));

result.entrySet().forEach(System.out::println);

prints

A=[(A, 0), (A, 3)]
B=[(B, 2), (B, 4)]
C=[(C, 1)]

Using a record allows you to retrieve each element using the record's getter once you retrieve the record from the map.

0
Aashish Jha On

You will need to specifically map the elements in such order. Here's the code in which you need the output exactly.

Map<String, List<String>> b = IntStream.range(0, l.size())
            .boxed()
            .collect(Collectors.groupingBy(
                    l::get,
                    Collectors.mapping(
                            index -> "(" + l.get(index) + "," + index + ")",
                            Collectors.toList()
                    )
            ));

The output is: {A=[(A,0), (A,3)], B=[(B,2), (B,4)], C=[(C,1)]}

If it helps, kindly upvote it and mark it as correct.

0
Shashi On

Got this solution working using streaming and groupingBy.

Map<String, List<Map.Entry<String, Integer>>> data = IntStream.range(0, l.size())
                .mapToObj(i -> Map.entry(l.get(i), i))
                .collect(Collectors.groupingBy(Map.Entry::getKey,
                        Collectors.mapping(Function.identity(), Collectors.toList())));
System.out.println(data);

Output:

{A=[A=0, A=3], B=[B=2, B=4], C=[C=1]}