使用Comparator.comparing 定义“is-in-list”比较的键函数,并附加一个thenComparing 用于字典排序。
List<String> reference = Arrays.asList("foo", "bar", "blub");
List<String> toBeSorted = Arrays.asList("foo", "ccc", "AAAA", "bbb", "blub");
Collections.sort(toBeSorted, Comparator
.comparingInt((String s) -> reference.contains(s) ? reference.indexOf(s) : Integer.MAX_VALUE)
.thenComparing(Comparator.naturalOrder()));
或者更简洁一些,使用三个比较器,用于包含、位置和字典:
Collections.sort(toBeSorted, Comparator
.comparing((String s) -> ! reference.contains(s))
.thenComparingInt(reference::indexOf)
.thenComparing(Comparator.naturalOrder()));
(仅使用reference::contains 在这里不起作用;似乎将其视为Comparator<Object>,而不是Comparator<String>)
之后,在这两种情况下,toBeSorted 都是 [foo, blub, AAAA, bbb, ccc]。
更新:这种方法的问题是它非常浪费:对于每次比较,我首先检查元素是否在 列表中,然后再次迭代列表以找到 索引。
您可以通过使用by Holger 指出的index + Integer.MIN_VALUE 技巧来改进这一点(如果您喜欢这个,请投票他的 答案)。
Collections.sort(toBeSorted, Comparator
.comparingInt((String s) -> reference.indexOf(s) + Integer.MIN_VALUE)
.thenComparing(Comparator.naturalOrder()));
这利用了-1 + Integer.MIN_VALUE 产生整数溢出(或者更确切地说是下溢)的事实,即结果不是Integer.MIN_VALUE - 1 而是Integer.MAX_VALUE。在任何其他情况下,像这样的“聪明的 hacks”都非常不受欢迎,但在这里它将比较的复杂性降低了 50%。
但是如果速度是个问题,我们可以做得更好!即使使用 Holger 的技巧,reference 列表也会循环多次,toBeSorted 列表中的每个元素至少循环一次(实际上比这更频繁,因为比较器没有缓存值)。如果列表很大,那么可能值得创建一个 hashmap,将字符串映射到它们在引用列表中的位置,然后在实际排序中使用该 hashmap(查找时间为 O(1))。
Map<String, Integer> index = IntStream.range(0, reference.size()).boxed()
.collect(Collectors.toMap(reference::get, Function.identity()));
Collections.sort(toBeSorted, Comparator
.comparingInt((String s) -> index.getOrDefault(s, Integer.MAX_VALUE))
.thenComparing(Comparator.naturalOrder()));
因此,不是 2*n*logn 次(假设 nlogn 比较每次有 2 个 indexof 操作),这只会循环引用列表一次。
更新 2:您还可以定义一个通用的 memoize 函数,而不是手动预先计算列表中每个元素的索引,在第一次计算时缓存每个比较键:
public static <X, Y> Function<X, Y> memoize(Function<X, Y> function) {
Map<X, Y> cache = new IdentityHashMap<>();
return (X x) -> cache.computeIfAbsent(x, function);
}
您可以像Comparator.comparing(memoize(originalKeyFunction)) 一样使用它。您可以将其用于各种比较(我想知道为什么 Comparator.comparing 默认不这样做)。
对于更长的列表,缓存索引(无论如何)对速度有巨大的影响:
# Items Naive Overflow Index-Map Memoize
--------------------------------------------------
10 0.014 0.0048 0.0082 0.0085
100 0.4016 0.3272 0.0566 0.085
1000 50.29 26.12 1.15 2.7
10000 6441.4 5595.2 18.2 151.6
(参考列表中有 N 个项目(整数)的测量值和要排序的列表中的两倍;每个排序重复多次(5-10000),以毫秒为单位显示平均执行时间。)