【问题标题】:How to sort a list/stream using an unknown number of comparators?如何使用未知数量的比较器对列表/流进行排序?
【发布时间】:2017-08-03 15:05:27
【问题描述】:

我有以下sn-p:

List<O> os = new ArrayList<>();
os.add(new O("A", 3, "x"));
os.add(new O("A", 2, "y"));
os.add(new O("B", 1, "z"));

Comparator<O> byA = Comparator.comparing(O::getA);
Comparator<O> byB = Comparator.comparing(O::getB);

// I want to use rather this...    
List<Comparator<O>> comparators = new ArrayList<>();
comparators.add(byA);
comparators.add(byB);

os.stream()
    .sorted(byB.thenComparing(byA))
    .forEach(o -> System.out.println(o.getC()));

如您所见,我明确使用两个比较器进行排序。但是,如果我在某个列表中有未知数量的比较器并且我想按它们全部排序怎么办?有什么办法吗?还是应该使用带有多个 if 的老式方式比较器?

【问题讨论】:

  • 最好让O 实现Comparable,这样你就可以只使用Stream.sorted(),除非你使用不止一种排序方法(或者不能改变O类)。

标签: java sorting java-8 java-stream


【解决方案1】:

如果您在一个列表或任何其他集合中有多个比较器,您可以通过对 Stream 执行归约来将它们替换为单个比较器:

List<Comparator<String>> comparators = ...

Comparator<String> combined = comparators.stream()
  .reduce(Comparator::thenComparing)
  .orElse(someDefaultComparator); // empty list case

所有实例将根据输入列表中的顺序使用thenComparing 组合在一起。


通过使用简单的 for 循环,使用非流方法也可以实现相同的目标:

Comparator<String> result = comparators.get(0);
for (int i = 1; i < comparators.size(); i++) {
    result = result.thenComparing(comparators.get(i));
}

【讨论】:

  • 这很好很干净,但是 Comparator::naturalOrder 不适合。它的类型是 T。
  • @JiKra 来了一个非常重要的问题,你想如何处理一个空列表的场景?重点是要记住传入空列表的边缘情况 - 显然naturalOrder 不适用于所有场景
  • 并行执行归约不会有任何问题,因为组合比较器是一个关联函数。但是,比较器列表的大小不太可能证明并行执行这种缩减是合理的。
  • 您可以使用.orElseGet((a,b) -&gt; 0) 获得一个适用于所有类型且不会更改顺序的比较器。
  • @pivovarit 我想我可以保留一些我准备好的默认比较器。
【解决方案2】:

您可以通过在迭代中对累积比较器和当前比较器调用 .thenComparing() 来将比较器流 reduce 转换为单个比较器:

Optional<Comparator<O>> comparator = Optional.ofNullable(comparators.stream()
    .reduce(null, (acc, current) -> acc == null ? current : acc.thenComparing(current), (a, b) -> a));

os.stream()
    .sorted(comparator.orElse((a,b) -> 0))
    .forEach(o -> System.out.println(o.getC()));

在此示例中,我使用Optional&lt;Comparator&lt;O&gt;&gt; 并使用Optional.ofNullable() 包装缩减结果来处理比较器列表为空的情况。然后,您可以决定何时将结果传递给 sorted() 方法,以防列表为空 - 您可以使用不会对任何内容进行排序的 (a,b)-&gt;0 比较器。

那么,您想应用多少比较器并不重要。但是有一个 but - 给定集合中比较器的顺序很重要。在给定的示例中,我按升序应用比较器(从列表的第一个元素到最后一个元素)。严重影响排序结果。

例如,在您的示例中,您调用byB.thenComparing(byA)。它产生与byA.thenComparing(byB) 不同的结果。我可以假设在某些情况下您希望控制应用比较器的顺序。

现场演示:

https://jdoodle.com/a/4Xz

【讨论】:

  • 我不理解空标识。另一方面,如果列表为空,则抛出 NPE。
  • 我猜你有点搞混了。 (a, b) -&gt; 0 是不影响顺序的比较器(对于所有稳定的排序操作),而 (a,b) -&gt; 1 已损坏。
  • 当然,(a, b) -&gt; a 是一个损坏的组合器,因为它只是丢弃了b 比较器链。应该是简单的thenComparing,但由于功能相同,你可以使用reduce(null, (acc, current) -&gt; acc.thenComparing(current)),或者更好的reduce((acc, current) -&gt; acc.thenComparing(current)),它返回一个Optional,所以根本不需要处理null……
  • 完全同意,这是我今天学到的东西,感谢这个问题:)
【解决方案3】:

如果您被允许使用 google guava,您可以简单地复合您的 Comparables。

https://google.github.io/guava/releases/snapshot/api/docs/com/google/common/collect/Ordering.html#compound-java.lang.Iterable-

Comparator 比较器 = Ordering.compound(comparators);

如果没有给出比较器,将保持原来的顺序。

guava 的 Javadoc 关于这个方法虽然建议用于 Java 8:

list.stream().sorted(comparators.stream().reduce(defaultComparator, Comparator::thenComparing));

因此对于 Java 7(或更低版本)推荐使用“复合”的解决方案。

【讨论】:

    猜你喜欢
    • 2018-09-10
    • 1970-01-01
    • 2013-02-13
    • 1970-01-01
    • 2014-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多