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?
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.)
Here is example for list of Objects
Map<String, Long> requirementCountMap = requirements.stream().collect(Collectors.groupingBy(Requirement::getRequirementType, Collectors.counting()));
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()));
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));
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.
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.
Success story sharing
and then performing a reduction operation on the values associated with a given key using the specified downstream Collector