从一个arrayList中减去另一个arrayList

37

我有两个ArrayList,我想要从一个ArrayList中“减去”另一个。例如,如果我有一个ArrayList [1,2,3],我想要减去[0, 2, 4],那么结果的ArrayList应该是[1,3]。

List<Integer> a = new ArrayList<>(Arrays.asList(1, 2, 3));
List<Integer> b = Arrays.asList(0, 2, 4);
subtract(a,b) // should return [1,3]

这是我的代码。

//returns a new IntSet after subtracting a from b
// .minus().toString()
ArrayList<Integer> minusArray = new ArrayList<Integer>();

    minusArray.addAll(array1);

    for(int i =0; i< minusArray.size(); i++){
        for(int j = 0; j < array2.size(); j++){
            if(minusArray.get(i).equals(array2.get(j))){
                minusArray.remove(i);
                if(i == 0){
                    ;
                }
                else if(j == 0){
                    ;
                }
                else{
                    i = 0;
                    j = 0;
                }
            }
            else{}
        }
    }

return minusArray;

我的代码在某些情况下能够工作,比如当 arrayList1 = [4,6]arrayList2 = [6] 时,会得到一个结果为 [4] 的输出。但如果我尝试使用 [1,2,4][0,4,8] 这样的输入时,就会出现以下异常:

java.lang.IndexOutOfBoundsException: Index: 2, Size: 2
    at java.util.ArrayList.rangeCheck(Unknown Source)
    at java.util.ArrayList.get(Unknown Source)
    at IntSet.minus(IntSet.java:119)
    at IntSetDriver.main(IntSetDriver.java:62)

以下是我编写的代码。我已经对其进行了测试运行,我认为它应该可以工作。用户输入这些ArrayList,它们已经被预先排序了,我也不知道哈希或大O。

ArrayList<Integer> minusArray = new ArrayList<Integer>();

    minusArray.addAll(array1);

    for(int i =0; i< minusArray.size(); i++){
        for(int j = 0; j < array2.size(); j++){
            if(minusArray.get(i).equals(array2.get(j))){
                minusArray.remove(i);
            }
            else{}
        }
    }

return minusArray;

由于您的代码在数组中测试了equals()并删除了if true,因此您可以像此答案建议的那样简单地使用removeAll():https://dev59.com/2Wkw5IYBdhLWcg3wY5jq#23172547。或者我有什么遗漏吗?如果您只想删除第一个出现,则可以使用Apache Utils或纯Java,就像这里建议的那样:https://dev59.com/2Wkw5IYBdhLWcg3wY5jq#49415419。否则,我建议以答案的形式提供您的解决方案,并解释为什么它是最佳的。将答案包含在问题中会破坏问答风格。 - jschnasse
Java 8:https://dev59.com/2Wkw5IYBdhLWcg3wY5jq#49850546 - akhil_mittal
9个回答

63

你有什么理由不能简单地使用List.removeAll(List)吗?

    List<Integer> one = new ArrayList<Integer>();
    one.add(1);
    one.add(2);
    one.add(3);
    List<Integer> two = new ArrayList<Integer>();
    two.add(0);
    two.add(2);
    two.add(4);
    one.removeAll(two);
    System.out.println(one);

    result: "[1, 3]"

2
removeAll(2) 将删除所有出现的 2,这通常不是减法的定义。通常它只是指删除一个 2。对吗?请参见 https://dev59.com/2Wkw5IYBdhLWcg3wY5jq#49415419。 - jschnasse
没有具体指定,但是代码贴出来似乎试图在内部的 for{} 循环中移除所有内容,看起来是这个意图? - Mark Phillips
嗯,你是对的。它测试了所有元素上的 remove()。因此,这显然应该是被接受的答案。我仍然会把我的答案留在这个位置供参考。 - jschnasse
1
这显然应该是被接受的答案。可以告诉原问题提出者。;) - Mark Phillips

45

尝试使用org.apache.commons.collections.CollectionUtils类的subtract方法。

返回一个新Collection,其中包含a-b。返回的集合中每个元素e的基数将是e在a中的基数减去e在b中的基数,或零,以较大者为准。

CollectionUtils.subtract(java.util.Collection a, java.util.Collection b) 

来自Apache Commons Collections


2
@kukis CS 251将是某所大学的计算机科学二年级课程。 - WW.
1
在Java 8中,您可以执行b.forEach((i)->a.remove(i));。请参见:https://dev59.com/2Wkw5IYBdhLWcg3wY5jq#49415419 获取更多信息。 - jschnasse

23

Java 8

你也可以使用流(streams):

List<Integer> list1 =  Arrays.asList(1, 2, 3);
List<Integer> list2 =  Arrays.asList(1, 2, 4, 5);
List<Integer> diff = list1.stream()
                          .filter(e -> !list2.contains(e))
                          .collect (Collectors.toList()); // (3)

这个答案不会改变原始列表。如果意图是修改原始列表,则可以使用remove。另外,我们可以使用forEach(在Iterator中默认的方法)或流与过滤器。

使用ListUtils

如果我们正在使用Apache common,则另一个选项是使用ListUtils

ListUtils.subtract(list, list2)

这从第一个列表中减去第二个列表中的所有元素,并将结果放入一个新列表中。这与List.removeAll(Collection)不同之处在于考虑了重复次数; 如果list1包含两个null出现,而list2只包含一个,则返回的列表仍将包含一个出现。


这种方法比list1.removeAll(list2)更好在哪里?在我看来(以及其他人),这不是减法,因为它从list1中删除了list2中所有值的出现。或者我有什么遗漏吗? - jschnasse
7
它返回两个列表之间的差异,是减法运算。这里的意图是返回结果而非更改原始列表。如果我们想要从原始列表中删除元素,可以使用remove函数。 - akhil_mittal

7
使用索引遍历minusArray是一种方法,但我建议您使用contains(Object)方法,这将允许您使用remove(Object)删除array2的特定元素。
当然,还有removeAll(Collection)可以做几乎所有你需要的事情...

6
你可以使用org.apache.commons.collections.ListUtils,只需一行代码就能实现你想要的全部功能 =)
List resultList = ListUtils.subtract(list, list2);

5

您的问题在于在 minusArray.remove(...) 调用中,您可能会缩小 minusArray 的大小。要解决此问题,请从 array.size()-1 开始向后计数到 0。

请注意,即使这样也无法解决问题。您需要颠倒循环的顺序。


2
我猜你遇到了范围问题,因为你已经排除了一个元素,这改变了内部循环所寻找的内容(我知道在处理普通列表和集合时会出现这个问题)。
过去我所做的解决方法是创建一个需要被移除的项目列表(即原始列表中找到的项目)。遍历新列表并直接消除原始列表的元素,而不必通过迭代器来移动它。

2
如果removeAll()不符合您的需求,请尝试以下答案。例如,如果您对类似于计算具有重复项的两个列表的差异的内容感兴趣。subtract(a,b)
b.forEach((i)->a.remove(i));

"a"现在包含
[1, 3]

这是根据Guava实现者的建议实现减法的步骤:
“创建一个包含a的ArrayList,然后对b中的每个元素调用remove。”
这类似于Apache commons中使用的这种实现方式。
与removeAll()的区别在哪里?
[1,2,2,3].removeAll([1,2,3]) //is empty
[1,2,3].forEach((i)->[1,2,2,3].remove(i)); //a is [2] 

1
我的参数化解决方案将会是这样的:

<T> ArrayList<T> subtract(ArrayList<T> alpha, ArrayList<T> beta) {
    ArrayList<T> gamma = new ArrayList<T>();
    alpha.forEach(n -> {if (!beta.contains(n)) gamma.add(n); });
    return gamma;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接