【问题标题】:Special order on String with the power of Java 8使用 Java 8 对 String 进行特殊排序
【发布时间】:2016-02-03 14:44:47
【问题描述】:

我有一个有限的字符串列表(或数组)。此列表应通过以下方式描述我的新订单:如果以下三个语句之一成立,则字符串 s1 小于字符串 s2

  • 两者都在列表中,s1 在列表中的索引低于s2
  • 两者都不在列表中,并且s1 按字符串的自然顺序小于s2
  • s1 在列表中,s2 不在列表中

我想利用 Java 8 的强大功能编写一个单行比较器。最短的这条线是什么?你可以假设我的列表是ArrayList<String>

编辑:我不想订购我正在谈论的列表。我想使用该列表来定义允许我订购许多其他列表的顺序。

【问题讨论】:

  • Comparator.comparing(is-in-list).thenComparing(lexicographic-order)

标签: java lambda java-8 comparator


【解决方案1】:

您可以为您的任务获得的最短 Comparator 定义是:

Comparator.comparingInt((String s) -> reference.indexOf(s)+Integer.MIN_VALUE)
          .thenComparing(Comparator.naturalOrder())

但它需要读者理解整数比较中的+Integer.MIN_VALUE 成语。它的基本意思是“执行无符号比较”(另见Integer.compareUnsigned(…)),因此-1(由List.indexOf 返回的缺失值)被视为可能的最高数字,因此当前值的每个索引被认为小于那个,以满足您的规范。

然后,您可以简单地链接另一个比较器,这里是自然顺序,对于两个索引相同的情况,包括两者都不存在的可能性。

如果您认为无符号比较对读者来说难以理解,您必须明确编码您的三个案例,如 tobias_k’s answer 中所示。

【讨论】:

  • 这是一个非常好的技巧,我以前不知道。但是,如果速度真的很重要,你应该缓存关键函数的结果,否则你必须一遍又一遍地计算索引。
  • @tobias_k:当然,有几种方法可以实现更有效的解决方案。这里的这个只是针对 shortest 解决 OP 的问题
【解决方案2】:

使用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&lt;Object&gt;,而不是Comparator&lt;String&gt;

之后,在这两种情况下,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),以毫秒为单位显示平均执行时间。)

【讨论】:

  • 记住indexOf在不包含对象时返回-1Comparator .comparingInt((String s) -&gt; reference.indexOf(s)+Integer.MIN_VALUE) .thenComparing(Comparator.naturalOrder())
  • 当单个! 可以达到相同效果时,为什么还要使用reversed()
  • @Holger 再次正确;我再次尝试使用reference::contains,在这种情况下! 不是一个选项。无论如何,我认为第一种方式最终会更清晰,因为第二种方式你必须记住布尔值是如何排序的,这对每个人来说可能并不完全直观。
  • 仍然,使用comparingInt resp。 thenComparingInt 非常推荐用于 int 属性。
  • 不错的补充。不幸的是,我只能投票一次。
猜你喜欢
  • 2016-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-13
  • 1970-01-01
  • 1970-01-01
  • 2015-02-08
  • 2014-07-05
相关资源
最近更新 更多