如何使用groupBy计算出现次数?

250

我希望将流中的项目收集到一个映射中,将相等的对象分组在一起,并映射到出现次数。

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

所以在这种情况下,我希望地图由以下条目组成:

Hello -> 2
World -> 1

我该怎么做呢?

6个回答

497

我认为你只是在寻找另一个Collector重载,用于指定对每个组执行什么操作... 然后使用Collectors.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);
    }
}

结果:

{Hello=2, World=1}

(还有一种使用groupingByConcurrent更高效的可能性。如果在上下文中安全,这是您真正代码要记住的事情。)


1
完美!...从javadoc中然后使用指定的downstream Collector对与给定键关联的值执行减少操作 - Muhammad Hewedy
7
使用静态导入的 Function.identity() 替代 e -> e 会让代码更易读: Map<String, Long> counted = list.stream().collect(groupingBy(identity(), counting())); - Kuchi
你好,我有另一个问题,如果我想要按降序显示怎么办? 如果我有4个World和2个Hello并且想要展示它们 {World=4, Hello=2} - Celestine Babayaro
2
@MichaelKors:如果你有其他问题,应该在进行适当的研究后,将其作为单独的帖子提出。 - Jon Skeet

39

这是一个对象列表的例子。

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

16

以下是实现当前任务的稍微不同的选项。

使用toMap

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

使用 Map::merge

Map<String, Integer> accumulator = new HashMap<>();
list.forEach(s -> accumulator.merge(s, 1, Math::addExact));

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

6
以下是来自 StreamEx 的简单解决方案:

StreamEx 提供了以下解决方案:

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

这样做的优点是减少Java流模板代码:collect(Collectors.

3
与Java8 Stream相比,使用它的原因是什么? - Torsten Ojaperv
@TorstenOjaperv 唯一真正的原因是它更加简洁(减少样板代码)。 - M. Justin

2
如果你愿意使用第三方库,你可以使用Eclipse Collections中的Collectors2类来将List转换为Bag,使用StreamBag是一种专门用于计数的数据结构。
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());

输出:

{World=1, Hello=2}

在这种情况下,你可以直接将 List 收集到一个 Bag 中。
Bag<String> counted = 
        list.stream().collect(Collectors2.toBag());

您还可以通过使用Eclipse Collections协议来适应List,而无需使用Stream来创建Bag。
Bag<String> counted = Lists.adapt(list).countBy(each -> each);

在这种特定情况下:
Bag<String> counted = Lists.adapt(list).toBag();

你也可以直接创建袋子。
Bag<String> counted = Bags.mutable.with("Hello", "Hello", "World");

一个 `Bag` 类似于一个 `Map`,它们都内部跟踪key及其计数。但是,如果你向一个 `Map` 请求一个它没有包含的key,它将返回 `null`。如果你使用 `occurrencesOf` 向一个 `Bag` 请求一个它没有包含的key,它将返回 0。
注意:我是Eclipse Collections的一个committer。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接