如何使用未知数量的比较器对列表/流进行排序?

JiK*_*Kra 5 java sorting java-8 java-stream

我有以下代码段:

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()));
Run Code Online (Sandbox Code Playgroud)

如您所见,我使用显式两个比较器进行排序.但是如果我在某个列表中有未知数量的比较器并且我想按它们排序呢?有什么办法吗?或者应该使用旧时尚方式比较器与多个ifs?

Grz*_*rek 15

如果列表或任何其他集合中有多个比较器,则可以通过执行以下操作的缩减来替换它们Stream:

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

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

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


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

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

  • 您可以使用`.orElseGet((a,b) - > 0)`来获得适用于所有类型的比较器,并且不会更改顺序. (3认同)
  • 并行执行缩减不会有任何问题,因为组合比较器是一个关联函数。但是比较器列表的大小不太可能证明并行执行这种减少是合理的。 (2认同)

Szy*_*iak 6

reduce通过.thenComparing()在迭代中调用累加的比较器和电流比较器,可以将比较器流传输到单个比较器:

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()));
Run Code Online (Sandbox Code Playgroud)

在这个例子中,我使用Optional<Comparator<O>>和包装缩小结果Optional.ofNullable()来处理具有空比较器列表的情况.然后,您可以决定何时将结果传递给sorted()方法,以便在空列表的情况下执行操作 - 您可以使用(a,b)->0不对任何内容进行排序的比较器.

那么你想要应用多少比较器并不重要.但是有一个但是 - 在给定的收集问题上比较器的顺序.在给定的示例中,我按升序应用比较器(从列表的第一个元素开始到最后一个元素).它会严重影响排序结果.

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

现场演示:

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

  • 而且,当然`(a,b) - > a`是一个破碎的组合器,因为它简单地丢弃了`b`比较器链.它应该只是`thenComparing`,但由于函数是相同的,你可以使用`reduce(null,(acc,current) - > acc.thenComparing(current))`,甚至更好`reduce((acc,current) ) - > acc.thenComparing(current))`返回一个`Optional`,所以根本不需要处理`null` ... (2认同)