249

I want to collect the items in a stream into a map which groups equal objects together, and maps to the number of occurrences.

List<String> list = Arrays.asList("Hello", "Hello", "World");
Map<String, Long> wordToFrequency = // what goes here?

So in this case, I would like the map to consist of these entries:

Hello -> 2
World -> 1

How can I do that?

Michael
  • 41,989
  • 11
  • 82
  • 128
Muhammad Hewedy
  • 29,102
  • 44
  • 127
  • 219

6 Answers6

494

I think you're just looking for the overload which takes another Collector to specify what to do with each group... and then Collectors.counting() to do the counting:

import java.util.*;
import java.util.stream.*;

class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("Hello");
        list.add("Hello");
        list.add("World");

        Map<String, Long> counted = list.stream()
            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

        System.out.println(counted);
    }
}

Result:

{Hello=2, World=1}

(There's also the possibility of using groupingByConcurrent for more efficiency. Something to bear in mind for your real code, if it would be safe in your context.)

TWiStErRob
  • 44,762
  • 26
  • 170
  • 254
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    Perfect! ... from javadoc `and then performing a reduction operation on the values associated with a given key using the specified downstream Collector` – Muhammad Hewedy Aug 22 '14 at 07:04
  • 7
    Using Function.identity() (with static import) instead of e -> e makes it a little nicer to read: Map counted = list.stream().collect(groupingBy(identity(), counting())); – Kuchi Oct 11 '15 at 23:36
  • Hello, I have another question, what if I want to show it in descending order? If I have 4 World and 2 Hello and what to show them {World=4, Hello=2} – Celestine Babayaro Apr 14 '22 at 09:35
  • 2
    @MichaelKors: If you have another question, you should ask it as a separate post, after performing appropriate research. – Jon Skeet Apr 14 '22 at 09:39
39

Here is example for list of Objects

Map<String, Long> requirementCountMap = requirements.stream().collect(Collectors.groupingBy(Requirement::getRequirementType, Collectors.counting()));
fjkjava
  • 1,414
  • 1
  • 19
  • 24
16

Here are slightly different options to accomplish the task at hand.

using toMap:

list.stream()
    .collect(Collectors.toMap(Function.identity(), e -> 1, Math::addExact));

using Map::merge:

Map<String, Integer> accumulator = new HashMap<>();
list.forEach(s -> accumulator.merge(s, 1, Math::addExact));
RubioRic
  • 2,442
  • 4
  • 28
  • 35
Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
13
List<String> list = new ArrayList<>();

list.add("Hello");
list.add("Hello");
list.add("World");

Map<String, List<String>> collect = list.stream()
                                        .collect(Collectors.groupingBy(o -> o));
collect.entrySet()
       .forEach(e -> System.out.println(e.getKey() + " - " + e.getValue().size()));
RubioRic
  • 2,442
  • 4
  • 28
  • 35
Sivakumar
  • 344
  • 3
  • 8
6

Here is the simple solution by StreamEx:

StreamEx.of(list).groupingBy(Function.identity(), MoreCollectors.countingInt());

This has the advantage of reducing the Java stream boilerplate code: collect(Collectors.

M. Justin
  • 14,487
  • 7
  • 91
  • 130
user_3380739
  • 1
  • 14
  • 14
2

If you're open to using a third-party library, you can use the Collectors2 class in Eclipse Collections to convert the List to a Bag using a Stream. A Bag is a data structure that is built for counting.

Bag<String> counted =
        list.stream().collect(Collectors2.countBy(each -> each));

Assert.assertEquals(1, counted.occurrencesOf("World"));
Assert.assertEquals(2, counted.occurrencesOf("Hello"));

System.out.println(counted.toStringOfItemToCount());

Output:

{World=1, Hello=2}

In this particular case, you can simply collect the List directly into a Bag.

Bag<String> counted = 
        list.stream().collect(Collectors2.toBag());

You can also create the Bag without using a Stream by adapting the List with the Eclipse Collections protocols.

Bag<String> counted = Lists.adapt(list).countBy(each -> each);

or in this particular case:

Bag<String> counted = Lists.adapt(list).toBag();

You could also just create the Bag directly.

Bag<String> counted = Bags.mutable.with("Hello", "Hello", "World");

A Bag<String> is like a Map<String, Integer> in that it internally keeps track of keys and their counts. But, if you ask a Map for a key it doesn't contain, it will return null. If you ask a Bag for a key it doesn't contain using occurrencesOf, it will return 0.

Note: I am a committer for Eclipse Collections.

Donald Raab
  • 6,458
  • 2
  • 36
  • 44