【问题标题】:How can I calculate the difference between two ArrayLists?如何计算两个 ArrayList 之间的差异?
【发布时间】:2010-10-29 11:54:42
【问题描述】:

我有两个 ArrayList。

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 和 ArrayList B。结果 ArrayList 应该包含 ArrayList A 中不存在的 List。

ArrayList 结果应该是:

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

如何比较?

【问题讨论】:

    标签: java arraylist


    【解决方案1】:

    在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]
    

    【讨论】:

    • 如果你的列表是一个自定义类,那么你必须重写你的类的equals方法,对吧?
    • @RTF 是的,您需要提供equals 的实现,以便比较您的对象。阅读有关实施hashCode 的信息。例如,注意String::equalscase-sensitive,因此“apple”和“Apple”将不会被视为相同。
    • 其实答案取决于你想做什么。 RemoveAll 不会保留重复项。如果您在第二个列表中添加另一个“apple”字符串,它也会被删除,这可能并不总是您想要的。
    • 这太低效了。很遗憾,这既是选定的答案,也是评分最高的答案。 removeAllsecondList 的每个元素上调用 firstList.contains。使用HashSet 可以防止这种情况发生,并且有一些好的答案。
    【解决方案2】:

    你已经有了正确的答案。 如果您想在列表(集合)之间进行更复杂和有趣的操作,请使用apache commons collections (CollectionUtils) 它允许您进行合取/析取、查找交集、检查一个集合是否是另一个集合的子集以及其他好东西。

    【讨论】:

    【解决方案3】:

    在带有流的 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());
    

    请注意,哈希集只创建一次:方法引用与其包含方法相关联。对 lambda 执行相同操作需要将集合放入变量中。制作变量并不是一个坏主意,尤其是当您发现它不美观或难以理解时。

    如果没有这种实用方法(或显式强制转换),您将无法轻松 negate the predicate,因为您无法直接调用否定方法引用(首先需要类型推断)。

    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));
    

    【讨论】:

    • @Bax 为什么要编辑?原版更简洁,功能相同。
    • @Bax 不,它没有。
    • 使用 Guava,你可以做到Predicates.in(new HashSet&lt;&gt;(listA)).negate()
    • 我刚刚运行了一些测试,这个解决方案比 listB.removeAll(new HashSet(listA)) 快 10-20%。和 Guava Sets.difference(...) si 比流慢 2 倍。
    • @Vlasec ArrayList.remove 具有线性复杂度,但ArrayList.removeAll 不依赖remove 而是执行线性数组更新操作,将每个剩余元素复制到其最终位置。相比之下,LinkedList 的参考实现没有优化removeAll,而是对每个受影响的元素执行remove 操作,每次最多更新五个引用。因此,根据已删除元素和剩余元素之间的比率,ArrayListremoveAll 的性能仍可能明显优于 LinkedList,即使对于大型列表也是如此。
    【解决方案4】:

    编辑:原始问题未指定语言。我的答案是 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
    

    【讨论】:

      【解决方案5】:

      我用过番石榴Sets.difference

      参数是集合而不是一般集合,但是从任何集合(具有唯一项)创建集合的便捷方法是 Guava ImmutableSet.copyOf(Iterable)。

      (我第一次发布了这个on a related/dupe question,但我也在这里复制它,因为我觉得这是一个很好的选择,但到目前为止还没有。)

      【讨论】:

        【解决方案6】:

        虽然这是 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 进行设置。
        【解决方案7】:

        我猜你说的是 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 谢谢你的重播
        • 这是正确的。不过,这也是一种非常低效的方法。你基本上会循环整个b 列表a.Count 次。您可以创建一个HashSet 来代替Contains 使用,或者在设置上使用RemoveAll 方法来获得您想要的结果。
        【解决方案8】:

        你只是在比较字符串。

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

        然后,对于 HashTable A 中的每个键,如果存在,则将其从 HashTable 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 变量实际上是无用的。您可以使用 listA 进行 foreach,而不是因为 hashA 仅被迭代,而 Contains 永远不会被调用。
        • (另外,如果 C# 有一个像 Java 一样的 RemoveAll 方法,你可以避免自己创建循环......但我再次支持你,因为这个解决方案至少比选定的​​解决方案更有效一)
        【解决方案9】:

        你好,使用这个类,这将比较两个列表并准确显示两个列表的不匹配。

        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;
            }
        
        }
        

        【讨论】:

        • 你知道克隆其实根本就不是克隆吗?
        【解决方案10】:

        这也适用于 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);
        

        【讨论】:

        • 输出:第一个列表:[apple, orange, pippo] 第二个列表:[apple, orange,banana, strawberry] 结果:[banana, strawberry]
        • 确实如此。但是当你这么说的时候,你不应该忘记注意在大型列表上它可能会非常缓慢。请记住,removecontains 之类的方法需要搜索整个列表。如果在一个循环中重复调用(发生在removeAll),你会得到二次复杂度。但是,您可以使用散列集并将其设为线性。
        猜你喜欢
        • 2017-04-02
        • 1970-01-01
        • 2011-05-21
        • 2010-10-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多