使用 Java 流 API 基于每个项目频率作为键将值分组在一起

Using the Java Stream API to group values together based upon each item frequency as the key

我接触过使用 Java 流 API 通过 groupingBycounting 根据项目在集合中出现的频率对项目进行分组的模式。例如,

Map<String,Long> counts = Arrays.stream(words)
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

我想知道是否有一种简单的方法来执行此操作的逆操作。更明确地定义,我想对项目进行分组,键是它们出现的频率,值是值的集合。基本上这段代码的作用:

      Map<String,Long> counts = Arrays.stream(words)
        .collect(Collectors.groupingBy(
          Function.identity(),
          Collectors.counting())
      );
      
      Map<Long,List<String>> map = new HashMap<>();
      for(Map.Entry<String,Long> entry : counts.entrySet())
        map.computeIfAbsent(
          entry.getValue(),
          i -> new ArrayList<>()
        ).add(entry.getKey());

所以单词的示例输入可以是

["lorem","ipsum","lorem","lorem","dolor","dolor","sit"]

产生

的输出

{1:["ipsum","sit"],2:["dolor"],3:["lorem"]}

到目前为止,我能够使用 Stream API 最接近的是这个怪物(必须有更好的方法)

Map<Long,List<String>> map =
        Arrays.stream(words)
        .collect(
          Collectors.collectingAndThen(
            Collectors.groupingBy(
              Function.identity(),
              Collectors.counting()
            ),
           stringLongMap -> stringLongMap.entrySet().stream()
                        .collect(
                          Collectors.collectingAndThen(
                            Collectors.groupingBy(entry -> entry.getValue()),
                            longEntryMap -> longEntryMap.entrySet()
                                          .stream()
                                          .collect(
                                            Collectors.toMap(Map.Entry::getKey,
                                                             e -> e.getValue().stream()
                                                             .map(i -> i.getKey())
                                                             .collect(Collectors.toList())))))));

以上方式超级迂回,不切实际,不可读,其他方面很糟糕。我什至觉得想出来很恶心。我希望有一种方法可以做到这一点,类似于收集器 API 页面

中的示例
// Group employees by department
Map<Department, List<Employee>> byDept = employees.stream()
                    .collect(Collectors.groupingBy(Employee::getDepartment));

当我将 Collectors.counting() 放在 groupingBy 中时,编译器会感到不安。最终,这就是我希望分组的那个。有没有更优雅的流方式来获得 Map<Long,List<String>>,其中键对应于频率,值对应于具有该频率的所有项目的集合?

谢谢。

最简单的方法是对单词进行频率计数,然后流式传输该映射的条目并反转键和值。

String[] arr = { "lorem", "ipsum", "lorem", "lorem", "dolor",
        "dolor", "sit" };

Map<Long, List<String>> freq = Arrays.stream(arr).collect(Collectors
        .groupingBy(str -> str, Collectors.counting())).entrySet()
        .stream()
        .collect(Collectors.groupingBy(Entry::getValue,
                Collectors.mapping(Entry::getKey,
                        Collectors.toList())));

freq.entrySet().forEach(System.out::println);

打印

1=[ipsum, sit]
2=[dolor]
3=[lorem]

如果您要获取之前的计数地图,则只需执行此操作。

Map<Long, List<String>> result = counts.entrySet().stream()
        .collect(Collectors.groupingBy(Entry::getValue,
                Collectors.mapping(Entry::getKey,
                        Collectors.toList())));