Java collect(groupingBy(identity(),counting()),然后按值对结果排序

Java collect(groupingBy(identity(),counting()),然后按值对结果排序,java,java-8,java-stream,Java,Java 8,Java Stream,我可以(又称多组): 我可以将它们放入列表中,然后使用我的值比较器实现对它们进行排序: ArrayList<Entry<String, Long>> list = new ArrayList<>(bag.entrySet()); Comparator<Entry<String, Long>> valueComparator = new Comparator<Entry<String, Long>>() {

我可以(又称多组):

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

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

有没有更优雅的方法可以做到这一点?我相信这是许多人必须解决的问题。Java Streams API中是否有内置的东西我可以使用?

您不需要创建比较器,已经有一个比较器用于此任务:。这将创建一个比较器来比较映射条目的值。在这种情况下,我们对它们的特性感兴趣颠倒顺序,这样我们就可以:

Map.Entry.comparingByValue(Comparator.reverseOrder())
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);
作为比较器,您的代码可以

Collections.sort(list, Map.Entry.comparingByValue(Comparator.reverseOrder()));
没有自定义比较器


要根据其值对生成的
Map
进行排序,还可以使用流管道。此外,如果要处理长字符串,您可能需要调用
Pattern.compile(“”).splitAsStream(“…”)
,而不是调用
Stream.of(Arrays.asList(“…”).split(“)

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

或者,您可以查看库,您可以:

Map.Entry.comparingByValue(Comparator.reverseOrder())
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);
地图包=
StreamEx.split(“一点二点三点摇滚乐”)
.已排序()
.runLength()
.reverseSorted(Map.Entry.comparingByValue())
.toCustomMap(LinkedHashMap::new);
此代码对每个字符串进行排序,然后调用
runlength()
。此方法将把相邻的相等元素折叠成一个
流,其中的值是元素出现的次数。例如,在流
[“foo”、“foo”、“bar”]
上,此方法将生成流
[Entry(“foo”,2),Entry(“bar”,1)]
。最后,按值的降序排序,并收集到
LinkedHashMap


请注意,这会给出正确的结果,而无需执行两个不同的流管道。

如果您愿意使用内置
包类型的第三方库,则可以使用:

虽然订单的值已排序,但当存在联系时,键没有可预测的顺序。如果希望键有可预测的顺序,可以使用
SortedBag

Bag<String> bag =
    SortedBags.mutable.with("one o'clock two o'clock three o'clock rock".split(" "));
ListIterable<ObjectIntPair<String>> pairs = bag.topOccurrences(bag.sizeDistinct());
Assert.assertEquals(PrimitiveTuples.pair("o'clock", 3), pairs.getFirst());
Assert.assertEquals(PrimitiveTuples.pair("two", 1), pairs.getLast());
System.out.println(pairs);
如果您想按照Brian的建议使用Pattern.splitAsStream,那么您可以按如下方式更改代码,以使用
Collector.toCollection
处理流:

Bag<String> bag =
    Pattern.compile(" ").splitAsStream("one o'clock two o'clock three o'clock rock")
        .collect(Collectors.toCollection(TreeBag::new));
ListIterable<ObjectIntPair<String>> pairs = bag.topOccurrences(bag.sizeDistinct());
Assert.assertEquals(PrimitiveTuples.pair("o'clock", 3), pairs.getFirst());
Assert.assertEquals(PrimitiveTuples.pair("two", 1), pairs.getLast());
System.out.println(pairs);
包=
Pattern.compile(“”).splitAsStream(“一点二点三点摇滚”)
.collect(收集器.toCollection(TreeBag::new));
ListIterable pairs=bag.topOccurrences(bag.sizeDistinct());
Assert.assertEquals(PrimitiveTuples.pair(“o'clock”,3),pairs.getFirst());
Assert.assertEquals(PrimitiveTuples.pair(“two”,1),pairs.getLast());
系统输出打印项数(对);

注意:我是Eclipse集合的提交者。

查看Pattern.splitAsStream.Thank@Brian。我不知道Pattern.splitAsStream方法。注意,StreamX解决方案(具有运行长度)在大输入时可能会变慢。当数据已经预排序(或者您不需要排序)时效果最好。根据我的测试,显式排序步骤较慢。另一方面,我优化了一个字符的regexp,如
“”
,因此通过StreamEx进行拆分会更快。有没有办法摆脱第二条流管道?第一个包只是一个中间结果,我们根本不需要。@Roland StreamEx解决方案在单个管道中运行,但确实需要创建中间映射:要对映射的值进行排序,所有条目都需要首先出席。
Bag<String> bag =
    Bags.mutable.with("one o'clock two o'clock three o'clock rock".split(" "));
ListIterable<ObjectIntPair<String>> pairs = bag.topOccurrences(bag.sizeDistinct());
Assert.assertEquals(PrimitiveTuples.pair("o'clock", 3), pairs.getFirst());
Assert.assertEquals(PrimitiveTuples.pair("rock", 1), pairs.getLast());
System.out.println(pairs);
[o'clock:3, two:1, one:1, three:1, rock:1]
Bag<String> bag =
    SortedBags.mutable.with("one o'clock two o'clock three o'clock rock".split(" "));
ListIterable<ObjectIntPair<String>> pairs = bag.topOccurrences(bag.sizeDistinct());
Assert.assertEquals(PrimitiveTuples.pair("o'clock", 3), pairs.getFirst());
Assert.assertEquals(PrimitiveTuples.pair("two", 1), pairs.getLast());
System.out.println(pairs);
[o'clock:3, one:1, rock:1, three:1, two:1]
Bag<String> bag =
    Pattern.compile(" ").splitAsStream("one o'clock two o'clock three o'clock rock")
        .collect(Collectors.toCollection(TreeBag::new));
ListIterable<ObjectIntPair<String>> pairs = bag.topOccurrences(bag.sizeDistinct());
Assert.assertEquals(PrimitiveTuples.pair("o'clock", 3), pairs.getFirst());
Assert.assertEquals(PrimitiveTuples.pair("two", 1), pairs.getLast());
System.out.println(pairs);