263

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?

0

7 Answers 7

518

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.)

Sign up to request clarification or add additional context in comments.

2 Comments

Perfect! ... from javadoc and then performing a reduction operation on the values associated with a given key using the specified downstream Collector
Using Function.identity() (with static import) instead of e -> e makes it a little nicer to read: Map<String, Long> counted = list.stream().collect(groupingBy(identity(), counting()));
39

Here is example for list of Objects

Map<String, Long> requirementCountMap = requirements.stream().collect(Collectors.groupingBy(Requirement::getRequirementType, Collectors.counting()));

1 Comment

The proposed answer is a generalization from String (what the OP asked) to an Object (in this case: a custom Requirement class, which contains a requirementType field). The answer is correct, but goes beyond the scope of the original question (so a comment on the accepted answer should have been sufficient).
17

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));

Comments

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()));

1 Comment

Keeping the list of objects is not useful when counting Strings. However, it can make sense if the grouping criteria is not the object itself, e.g. the String's first letter.
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.

2 Comments

What's the reason to use it over Java8 streams?
@TorstenOjaperv The only real reason is that it's more concise (it reduces boilerplate).
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.

Comments

-1
import java.util.*;
import java.util.stream.Stream;

import static java.util.function.Function.identity;
import static java.util.stream.Collectors.counting;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.summingInt;

/**
 * Utility-Class to build a counting-map from the {@link Stream} of any objects
 * that have properly implemented {@link Object#equals(Object)} and {@link Object#hashCode()}
 * (and {@link Comparable#compareTo(Object) for sorted counting-maps}.
 */
public class CountingUtils {

    public static <V> Map<V, Long> countingMapLong(Stream<V> valuesStream) {
        return valuesStream.collect(groupingBy(identity(), counting()));
    }
    public static <V> Map<V, Integer> countingMap(Stream<V> valuesStream) {
        return valuesStream.collect(groupingBy(identity(), summingInt(v -> 1)));
    }
    public static <V> NavigableMap<V, Integer> countingSortedMap(Stream<V> valuesStream) {
        return valuesStream.collect(groupingBy(identity(), TreeMap::new, summingInt(v -> 1)));
    }
    public static <V> LinkedHashMap<V, Integer> countingLinkedMap(Stream<V> valuesStream) {
        return valuesStream.collect(groupingBy(identity(), LinkedHashMap::new, summingInt(v -> 1)));
    }

    private CountingUtils() {
        // prohibit the creation of utility-class instance
        throw new UnsupportedOperationException("Cannot instantiate a utility-class " + getClass().getName());
    }
}

1 Comment

There is an accepted answer with high score since 10 years. I think you should explain what is wrong with the accepted answer and explain why your solution is better.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.