principal-ideal-domain
principal-ideal-domain

Reputation: 4316

Counting elements of a Stream

I want to count the different elements of a stream and am wondering why

Stream<String> stream = Stream.of("a", "b", "a", "c", "c", "a", "a", "d");
Map<String, Integer> counter1 = stream.collect(Collectors.toMap(s -> s, 1, Integer::sum));

doesn't work. Eclipse tells me

The method toMap(Function, Function, BinaryOperator) in the type Collectors is not applicable for the arguments (( s) -> {}, int, Integer::sum)

By the way, I know about that solution:

Map<String, Long> counter2 = stream.collect(Collectors.groupingBy(s -> s, Collectors.counting()));

So I have two questions:

  1. What is the mistake in my first approach?
  2. How would you implement such a counter?

EDIT: I solved the first question by myself:

Map<String, Integer> counter1 = stream.collect(Collectors.toMap(s -> s, s -> 1, Integer::sum)); 

Java is expecting a function as second argument.

Upvotes: 16

Views: 25857

Answers (3)

Vishal Seshagiri
Vishal Seshagiri

Reputation: 824

    private Collector<CustomObject.class, int[], Integer> customIntegerCountingCollector() {
    return Collector.of(
        () -> new int[1],
        (result, ) -> result[0] += 1,
        (result1, result2) -> {
            result1[0] += result2[0];
            return result1;
        },
        total -> Integer.valueOf(total[0])
    );
}

Inspired by : https://www.deadcoderising.com/2017-03-07-java-8-creating-a-custom-collector-for-your-stream/

Upvotes: 1

Roland
Roland

Reputation: 7875

The following should work if you are not using parallel streams:

final int[] counter = {0};
stream.peek(s -> counter[0]++);

Upvotes: -3

Misha
Misha

Reputation: 28183

There are indeed several ways to do it. The one you haven't mentioned is .collect(groupingBy(x -> x, summingInt(x -> 1)));

There are some differences in performance.

Approach #1 is going to be at its best if there are very few objects per bucket. In the ideal case of only 1 object per bucket, you end up with the final map right away with no need to modify the entries. In the worst case of having a very large number of repeated objects, it will have to do a lot of boxing/unboxing.

Approach #2 relies on counting() collector, which doesn't specify exactly how it should do the counting. The current implementation forwards to reducing but that might change.

The summingInt approach will accumulate the count in int rather than Integer and thus will not require any boxing/unboxing. It will be at its best if objects repeat a very large number of times.

As for which one to choose, it is best to code for clarity and optimize when it becomes necessary. To me, groupingBy(x->x, counting()) expresses the intent most clearly, so that's the one I would favor.

Upvotes: 14

Related Questions