如何计算两个ArrayList之间的差异?


81

我有两个ArrayLists。

ArrayList A包含:

['2009-05-18','2009-05-19','2009-05-21']

ArrayList B包含:

['2009-05-18','2009-05-18','2009-05-19','2009-05-19','2009-05-20','2009-05-21','2009-05-21','2009-05-22']

我必须比较ArrayList A和ArrayListB。结果ArrayList应该包含ArrayList A中不存在的List。

ArrayList结果应为:

['2009-05-20','2009-05-22']

怎么比较?

Answers:


193

在Java中,您可以使用Collection接口的removeAll方法。

// Create a couple ArrayList objects and populate them
// with some delicious fruits.
Collection firstList = new ArrayList() {{
    add("apple");
    add("orange");
}};

Collection secondList = new ArrayList() {{
    add("apple");
    add("orange");
    add("banana");
    add("strawberry");
}};

// Show the "before" lists
System.out.println("First List: " + firstList);
System.out.println("Second List: " + secondList);

// Remove all elements in firstList from secondList
secondList.removeAll(firstList);

// Show the "after" list
System.out.println("Result: " + secondList);

上面的代码将产生以下输出:

First List: [apple, orange]
Second List: [apple, orange, banana, strawberry]
Result: [banana, strawberry]

7
如果您的列表属于自定义类,则必须重写类的equals方法,对吗?
RTF 2014年

5
@RTF是的,您需要提供一个实现,equals可以比较您的对象。阅读有关实现的信息hashCode。例如,注意如何String::equals大小写敏感的,所以“苹果”和“苹果”不会被认为是相同的。
罗勒·布尔克

1
答案实际上取决于您要做什么。RemoveAll将不会保留重复项。如果您在第二个列表中添加另一个“苹果”字符串,则该字符串也会被删除,但不一定总是您想要的。
jules testard '16

2
这是如此低效。不幸的是,这既是最佳答案,也是最佳答案。removeAll调用firstList.contains的每个元素secondList。使用aHashSet可以防止这种情况,并且还有一些好的答案。
Vlasec


12

实际上,在带有流的Java 8中,这非常简单。编辑:没有流可以高效,请参阅下。

List<String> listA = Arrays.asList("2009-05-18","2009-05-19","2009-05-21");
List<String> listB = Arrays.asList("2009-05-18","2009-05-18","2009-05-19","2009-05-19",
                                   "2009-05-20","2009-05-21","2009-05-21","2009-05-22");

List<String> result = listB.stream()
                           .filter(not(new HashSet<>(listA)::contains))
                           .collect(Collectors.toList());

请注意,哈希集仅创建一次:方法引用绑定到其contains方法。对lambda进行相同操作将需要将集合包含在变量中。制作变量并不是一个坏主意,尤其是当您发现它难看或难以理解时。

没有这种实用程序方法(或显式强制转换)之类的东西,您不能轻易地否定谓词,因为您不能直接调用否定方法引用(首先需要类型推断)。

private static <T> Predicate<T> not(Predicate<T> predicate) {
    return predicate.negate();
}

如果流具有filterOut方法或某种方法,它将看起来更好。


另外,@ Holger也给了我一个主意。ArrayListremoveAll方法针对多次移除进行了优化,它仅重新排列元素一次。但是,它使用contains给定集合提供的方法,因此如果listA很小,我们需要优化该部分。

使用listAlistB之前已声明,此解决方案不需要Java 8,并且非常有效。

List<String> result = new ArrayList(listB);
result.removeAll(new HashSet<>(listA));

1
@Bax为什么要编辑?原稿更干净且功能相同。
shmosel

1
@Bax不,不是。
shmosel

1
使用番石榴,您可以做到Predicates.in(new HashSet<>(listA)).negate()
shmosel

1
我只是进行了一些测试,此解决方案比listB.removeAll(new HashSet <>(listA))快10-20%。和Guava Sets.difference(...)si比流慢2倍。
telebog

1
@VlasecArrayList.remove具有线性复杂度,但ArrayList.removeAll不依赖remove而是执行线性数组更新操作,将其余的每个元素复制到其最终位置。相反,的参考实现LinkedList没有进行优化,removeAll而是remove对每个受影响的元素执行一个操作,每次都会更新多达五个参考。因此,根据取出,剩下的元素之间的比例ArrayListremoveAll可能仍显著优于执行LinkedListS',甚至是巨大的列表。
Holger

9

编辑:原始问题未指定语言。我的答案是在C#中。

您应为此目的使用HashSet。如果必须使用ArrayList,则可以使用以下扩展方法:

var a = arrayListA.Cast<DateTime>();
var b = arrayListB.Cast<DateTime>();    
var c = b.Except(a);

var arrayListC = new ArrayList(c.ToArray());

使用HashSet ...

var a = new HashSet<DateTime>(); // ...and fill it
var b = new HashSet<DateTime>(); // ...and fill it
b.ExceptWith(a); // removes from b items that are in a


8

尽管这是Java 8中的一个非常老的问题,但是您可以执行以下操作

 List<String> a1 = Arrays.asList("2009-05-18", "2009-05-19", "2009-05-21");
 List<String> a2 = Arrays.asList("2009-05-18", "2009-05-18", "2009-05-19", "2009-05-19", "2009-05-20", "2009-05-21","2009-05-21", "2009-05-22");

 List<String> result = a2.stream().filter(elem -> !a1.contains(elem)).collect(Collectors.toList());

我喜欢Java 8,但我们仍然应该考虑复杂性。虽然列表也有的Collection方法contains,但效率很低。如果找不到,它需要遍历整个列表。a2在较大的列表上,对每个元素进行操作都可能会非常缓慢,这就是为什么我a1在回答中做出一些选择的原因。
Vlasec

2

我猜您在谈论C#。如果是这样,您可以尝试一下

    ArrayList CompareArrayList(ArrayList a, ArrayList b)
    {
        ArrayList output = new ArrayList();
        for (int i = 0; i < a.Count; i++)
        {
            string str = (string)a[i];
            if (!b.Contains(str))
            {
                if(!output.Contains(str)) // check for dupes
                    output.Add(str);
            }
        }
        return output;
    }

抱歉,我没有提到编程语言,可以,但是我需要Java语言,谢谢您的重播
naveen

这是对的。但是,这也是一种非常低效的方式。您将基本上遍历整个b列表a.Count时间。您可以创建一个HashSet代替用于Contains或使用RemoveAll集合上的方法来获得所需的准确结果。
Vlasec

1

您只是在比较字符串。

将ArrayList A中的值作为键放入HashTable A中。
将ArrayList B中的值作为键放入HashTable B中。

然后,对于哈希表A中的每个键,将其从哈希表B中删除(如果存在)。

在HashTable B中剩下的是不是ArrayList A中的值的字符串(键)。

添加了C#(3.0)示例以响应代码请求:

List<string> listA = new List<string>{"2009-05-18","2009-05-19","2009-05-21'"};
List<string> listB = new List<string>{"2009-05-18","2009-05-18","2009-05-19","2009-05-19","2009-05-20","2009-05-21","2009-05-21","2009-05-22"};

HashSet<string> hashA = new HashSet<string>();
HashSet<string> hashB = new HashSet<string>();

foreach (string dateStrA in listA) hashA.Add(dateStrA);
foreach (string dateStrB in listB) hashB.Add(dateStrB);

foreach (string dateStrA in hashA)
{
    if (hashB.Contains(dateStrA)) hashB.Remove(dateStrA);
}

List<string> result = hashB.ToList<string>();

在您的C#代码中,该hashA变量实际上是无用的。您可以使用foreach进行一次foreach,listA因为hashA它只会被迭代并且Contains永远不会被调用。
Vlasec

(此外,只要C#像Java一样具有RemoveAll方法,您就可以避免自己编写循环……但是,我再次向您投票,因为该解决方案至少比所选解决方案更有效。)
Vlasec

1

嗨,使用此类,它将比较两个列表并准确显示两个列表的不匹配。

import java.util.ArrayList;
import java.util.List;


public class ListCompare {

    /**
     * @param args
     */
    public static void main(String[] args) {
        List<String> dbVinList;
        dbVinList = new ArrayList<String>();
        List<String> ediVinList;
        ediVinList = new ArrayList<String>();           

        dbVinList.add("A");
        dbVinList.add("B");
        dbVinList.add("C");
        dbVinList.add("D");

        ediVinList.add("A");
        ediVinList.add("C");
        ediVinList.add("E");
        ediVinList.add("F");
        /*ediVinList.add("G");
        ediVinList.add("H");
        ediVinList.add("I");
        ediVinList.add("J");*/  

        List<String> dbVinListClone = dbVinList;
        List<String> ediVinListClone = ediVinList;

        boolean flag;
        String mismatchVins = null;
        if(dbVinListClone.containsAll(ediVinListClone)){
            flag = dbVinListClone.removeAll(ediVinListClone);   
            if(flag){
                mismatchVins = getMismatchVins(dbVinListClone);
            }
        }else{
            flag = ediVinListClone.removeAll(dbVinListClone);
            if(flag){
                mismatchVins = getMismatchVins(ediVinListClone);
            }
        }
        if(mismatchVins != null){
            System.out.println("mismatch vins : "+mismatchVins);
        }       

    }

    private static String getMismatchVins(List<String> mismatchList){
        StringBuilder mismatchVins = new StringBuilder();
        int i = 0;
        for(String mismatch : mismatchList){
            i++;
            if(i < mismatchList.size() && i!=5){
                mismatchVins.append(mismatch).append(",");  
            }else{
                mismatchVins.append(mismatch);
            }
            if(i==5){               
                break;
            }
        }
        String mismatch1;
        if(mismatchVins.length() > 100){
            mismatch1 = mismatchVins.substring(0, 99);
        }else{
            mismatch1 = mismatchVins.toString();
        }       
        return mismatch1;
    }

}

您知道克隆实际上根本不是克隆吗?
Vlasec

1

这也可以使用Arraylist

    // Create a couple ArrayList objects and populate them
    // with some delicious fruits.
    ArrayList<String> firstList = new ArrayList<String>() {/**
         * 
         */
        private static final long serialVersionUID = 1L;

    {
        add("apple");
        add("orange");
        add("pea");
    }};

    ArrayList<String> secondList = new ArrayList<String>() {

    /**
         * 
         */
        private static final long serialVersionUID = 1L;

    {
        add("apple");
        add("orange");
        add("banana");
        add("strawberry");
    }};

    // Show the "before" lists
    System.out.println("First List: " + firstList);
    System.out.println("Second List: " + secondList);

    // Remove all elements in firstList from secondList
    secondList.removeAll(firstList);

    // Show the "after" list
    System.out.println("Result: " + secondList);

1
输出:第一个列表:[apple,orange,pippo]第二个列表:[apple,orange,香蕉,草莓]结果:[banana,Strawberry]
psycho

是的 但是,当您这么说时,您应该记住,在大型列表上,它可能会非常缓慢。请记住,方法类似remove并且contains需要在整个列表中进行搜索。如果在一个周期中重复调用(发生在中removeAll),您将得到二次复杂度。但是,您可以使用哈希集并将其设置为线性。
Vlasec
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.