225

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
  • 37,794
  • 9
  • 70
  • 113
Muhammad Hewedy
  • 27,782
  • 43
  • 120
  • 210

6 Answers6

452

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
  • 41,170
  • 22
  • 156
  • 240
Jon Skeet
  • 1,335,956
  • 823
  • 8,931
  • 9,049
  • 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
  • 1
    @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
23

Here is example for list of Objects

Map<String, Long> requirementCountMap = requirements.stream().collect(Collectors.groupingBy(Requirement::getRequirementType, Collectors.counting()));
fjkjava
  • 1,164
  • 16
  • 22
12
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,406
  • 4
  • 27
  • 34
Sivakumar
  • 324
  • 3
  • 8
11

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,406
  • 4
  • 27
  • 34
Ousmane D.
  • 52,579
  • 8
  • 80
  • 117
5

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
  • 9,474
  • 3
  • 68
  • 98
user_3380739
  • 1,503
  • 12
  • 13
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,038
  • 2
  • 33
  • 38