Stream.collect(groupingBy(identity(),counting())然后按值对结果进行排序

whi*_*mot 9 java java-8 java-stream

我可以将一个单词列表收集到一个包中(也称为多组):

Map<String, Long> bag =
        Arrays.asList("one o'clock two o'clock three o'clock rock".split(" "))
        .stream()
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
Run Code Online (Sandbox Code Playgroud)

但是,包的条目不保证按任何特定顺序排列.例如,

{rock=1, o'clock=3, one=1, three=1, two=1}
Run Code Online (Sandbox Code Playgroud)

我可以将它们放入列表中,然后使用我的值比较器实现对它们进行排序:

ArrayList<Entry<String, Long>> list = new ArrayList<>(bag.entrySet());
Comparator<Entry<String, Long>> valueComparator = new Comparator<Entry<String, Long>>() {

    @Override
    public int compare(Entry<String, Long> e1, Entry<String, Long> e2) {
        return e2.getValue().compareTo(e1.getValue());
    }
};
Collections.sort(list, valueComparator);
Run Code Online (Sandbox Code Playgroud)

这给出了期望的结果:

[o'clock=3, rock=1, one=1, three=1, two=1]
Run Code Online (Sandbox Code Playgroud)

有没有更优雅的方式来做到这一点?我敢肯定这是很多人必须解决的问题.我可以使用Java Streams API内置的东西吗?

Tun*_*aki 10

您不需要创建比较器,已经有一个用于此任务:Map.Entry.comparingByValue.这将创建一个比较器,用于比较地图的条目值.在这种情况下,我们对它们的逆序感兴趣所以我们可以:

Map.Entry.comparingByValue(Comparator.reverseOrder())
Run Code Online (Sandbox Code Playgroud)

作为比较器.然后你的代码就可以了

Collections.sort(list, Map.Entry.comparingByValue(Comparator.reverseOrder()));
Run Code Online (Sandbox Code Playgroud)

没有自定义比较器.


要对结果Map进行排序,您还可以使用Stream管道.此外,如果你有很长的字符串要处理Stream.of(Arrays.asList("...").split(" ")),你可能想要调用,而不是调用Pattern.compile(" ").splitAsStream("...").

Map<String, Long> bag =
   Pattern.compile(" ")
          .splitAsStream("one o'clock two o'clock three o'clock rock")
          .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
Map<String, Long> sortedBag = 
    bag.entrySet()
       .stream()
       .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
       .collect(Collectors.toMap(
           Map.Entry::getKey,
           Map.Entry::getValue,
           (v1, v2) -> { throw new IllegalStateException(); },
           LinkedHashMap::new
       ));
Run Code Online (Sandbox Code Playgroud)

此代码创建地图条目的流,按值的相反顺序对其进行排序,并将其收集到a中LinkedHashMap以保持遭遇顺序.

输出:

{o'clock=3, rock=1, one=1, three=1, two=1}
Run Code Online (Sandbox Code Playgroud)

或者,您可以查看StreamEx库,您可以拥有:

Map<String, Long> bag =
    StreamEx.split("one o'clock two o'clock three o'clock rock", " ")
            .sorted()
            .runLengths()
            .reverseSorted(Map.Entry.comparingByValue())
            .toCustomMap(LinkedHashMap::new);
Run Code Online (Sandbox Code Playgroud)

此代码对每个String进行排序,然后调用runLengths().此方法将相邻的相等元素折叠到一个Stream<String, Long> 值,即元素出现的次数.例如,在Stream上["foo", "foo", "bar"],此方法将生成Stream [Entry("foo", 2), Entry("bar", 1)].最后,这是按值的降序排序并收集到一个LinkedHashMap.

请注意,这样可以得到正确的结果,而无需执行2个不同的Stream流水线.