【问题标题】:Remove first N occurrence of an element from a List从 List 中删除第一个 N 出现的元素
【发布时间】:2021-05-26 14:40:03
【问题描述】:

Java 8 引入了 Lambda,它允许我们有效地从 List 中删除所有元素。下面从myList 中删除所有 2 的实例。

List<Integer> myList;
...
myList.removeIf(x -> x==2);

如果我想删除 N 个元素(在本例中为三个),我会使用 for 循环。

for (int i = 0; i < 3; i++) {
    myList.remove(Integer.valueOf(2));
}

有没有办法使用 Lambda 从列表中删除指定数量的元素?如果是这样,它是否比for循环代码更有效?

【问题讨论】:

  • 怎么样 - IntStream.range(0,3).forEach(a-&gt; myList.remove(2));?
  • @MostNeededRabbit 如果列表中 2 的数量小于 3 java.lang.IndexOutOfBoundsException: Index: 2, Size: 2,您的解决方案将生成错误@
  • @MostNeededRabbit,检查this,因此OP需要使用Integer.valueOf(2)
  • @samabcde 但它们不是两个整数。它是一个整数和一个 int 字面量。在这种情况下,整数将被自动拆箱,使比较 int == int,其行为直观。唯一的潜在问题是,如果x 为空,自动拆箱将 NPE,但您的代码也会遇到该问题。
  • @Michael,你是对的,我想念那个。而且我认为使用equals 更安全,因为 OP 可能会传递一个 Integer 而不是 int 来替换硬编码的 2,== 会有问题。

标签: java lambda java-8


【解决方案1】:

当您反复调用 remove(Object) 时,您会从两者中获得 O(n²) 时间复杂度,从头开始重复搜索(适用于所有 List 类型)并在删除元素之后重复复制元素,当list 是 ArrayList 或类似的。

可以通过使用专用的搜索和删除循环来避免搜索的时间复杂度,例如使用Iterator 及其remove 方法。但是复制时间复杂度仍然存在,除非您使用 removeIf 并且列表类使用适当的实现覆盖它(如 ArrayList 所做的那样)。

利用这一优势删除 n 个匹配项的一种方法是

int n = 3;
int last = IntStream.range(0, myList.size())
    .filter(ix -> myList.get(ix) == 2)
    .limit(n)
    .reduce((a,b) -> b)
    .orElse(-1);
myList.subList(0, last + 1).removeIf(x -> x == 2);

它更复杂,对于小型列表,它会更昂贵。但是,对于时间复杂度很重要的非常大的列表,它将受益于 O(n) 时间复杂度。

注意,当谓词是一个简单的匹配操作时,你也可以使用,例如removeAll(Collections.singleton(2)) 而不是 removeIf(x -&gt; x == 2)

【讨论】:

  • 当我重新表述这个问题时,我正在考虑同一行,以获取第 N 次出现的索引并更新子列表,但后来我尝试在 JDK 中查找 NthindexOf...(认为for 要好得多)....现在我可以看到我正在寻找的 API 的实现 :)
  • 真正的大有多大?现在我会使用的最大列表是
  • 只要 n 保持 3 并且匹配谓词保持 int 比较,400 就不算大。但实际的问题必须是你能保证假设的上限始终适用吗?这是最常见的问题之一,严重的扩展算法在生产环境中遇到了意想不到的大问题。您可以从另一面来看:如果这个 O(n) 解决方案在您当前的列表大小下运行得足够快,那么您可以确信它适用于所有大小。
  • 如果n 是 400 会怎样?我正在使用的列表是固定大小的,我完全控制了限制。我完全不反对使用这种方法,但我只是想确保它对于我需要的东西不会太多。如果列表绝对不会超过几百个,您会建议只运行一个replace 循环吗?
  • replace 循环”是什么意思?问题是关于移除的。在 400 个元素的列表上调用 remove 最多 400 次可以获得执行 160000 次操作的数量级。这就是 O(n²) 的意思。有了这样的数字,当经常调用该方法时,您就会越过使用此答案的 O(n) 版本变得有益的界限。但是,正如已经说过的那样,要么进行测试和测量,要么只使用这个答案的代码,知道它可以很好地扩展并且您不必担心实际数字。
【解决方案2】:

使用 lambda:

Runnable runnable = () -> {
    for (int i = 0; i < 3; i++) {
        myList.remove(Integer.valueOf(2));
    }
};
runnable.run();

开个玩笑。

【讨论】:

    【解决方案3】:

    你可以做一些接近 for 循环的事情:

    IntStream.range(0,3).forEach(a-> integers.remove(Integer.valueOf(2)));
    

    就性能而言,它应该相当于一个for循环。

    【讨论】:

    • 您是否尝试重现“这将引发异常(最后一个 remove() 将失败)”? remove(2) 可能会导致 IndexOutOfBoundsException,因为它在索引 2 处删除,但 remove(Integer.valueOf(2)) 正在删除值为 2 的元素。
    • 这会比for(remove()) 循环更有效/更快吗?
    • @samabcde 你是对的,再次:)谢谢
    • @dinorider 应该同样高效
    • ...同样低效(与removeIf 相比)
    【解决方案4】:

    有点脏,但只需要通过一次:

    public static <T extends Comparable<? super T>> List<T> removeFirstN(List<T> list, T t, int N) {
        int[] a = {0};
        return list.stream().filter(p -> !p.equals(t) || ++a[0] > N).collect(toList());
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-14
      • 1970-01-01
      • 1970-01-01
      • 2015-07-19
      相关资源
      最近更新 更多