İki Dizi Listesi arasındaki farkı nasıl hesaplayabilirim?


81

İki Dizi Listem var.

DiziListesi A şunları içerir:

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

DiziListesi B şunları içerir:

['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 ve ArrayList B'yi karşılaştırmalıyım. Sonuç ArrayList, ArrayList A'da bulunmayan Listeyi içermelidir.

ArrayList sonucu şöyle olmalıdır:

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

nasıl karşılaştırılır?

Yanıtlar:


194

Java'da Collectionarayüzün removeAllyöntemini kullanabilirsiniz.

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

The above code will produce the following output:

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

7
If your list is of a custom class, then you'll have to override the equals method of your class, right?
RTF

5
@RTF Yes, you need to provide an implementation of equals that enables your objects to be compared. Read about implementing hashCode as well. For example, note how String::equals is case-sensitive, so "apple" and "Apple" will not be considered the same.
Basil Bourque

1
Actually the answer depends on what you want to do. RemoveAll will not preserve duplicates. If you add another "apple" string to your second list, it will be removed as well, which may not always be what you want.
jules testard

2
This is so inefficient. It's sad this is both the selected and best rated answer. removeAll calls firstList.contains on every element of secondList. Using a HashSet would prevent that and there are a few good answers lower.
Vlasec


12

In Java 8 with streams, it's pretty simple actually. EDIT: Can be efficient without streams, see lower.

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

Note that the hash set is only created once: The method reference is tied to its contains method. Doing the same with lambda would require having the set in a variable. Making a variable is not a bad idea, especially if you find it unsightly or harder to understand.

You can't easily negate the predicate without something like this utility method (or explicit cast), as you can't call the negate method reference directly (type inference is needed first).

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

If streams had a filterOut method or something, it would look nicer.


Also, @Holger gave me an idea. ArrayList has its removeAll method optimized for multiple removals, it only rearranges its elements once. However, it uses the contains method provided by given collection, so we need to optimize that part if listA is anything but tiny.

With listA and listB declared previously, this solution doesn't need Java 8 and it's very efficient.

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

1
@Bax Why the edit? The original was cleaner and functionally identical.
shmosel

1
@Bax No, it doesn't.
shmosel

1
With Guava, you can do Predicates.in(new HashSet<>(listA)).negate().
shmosel

1
I just run some test and this solutions is ~10-20% faster than listB.removeAll(new HashSet<>(listA)). and Guava Sets.difference(...) si 2 times slower than streams.
telebog

1
@Vlasec ArrayList.remove has linear complexity, but ArrayList.removeAll does not rely on remove but performs a linear array update operation, copying each remaining element into its final place. In contrast, the reference implementation of LinkedList has no optimized removeAll but performs a remove operation for each affected element, which will update up to five references each time. So, depending on the ratio between removed and remaining elements, ArrayList’s removeAll may still perform significantly better than LinkedList’s, even for huge lists.
Holger

9

EDIT: Original question did not specify language. My answer is in C#.

You should instead use HashSet for this purpose. If you must use ArrayList, you could use the following extension methods:

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

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

using 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

Although this is a very old question in Java 8 you could do something like

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

I love Java 8, but we should still think of complexity. While lists also have Collection's method contains, it is very inefficient. It needs to pass through the whole list if not found. Doing it for every element of a2 can be painfully slow on larger lists, which is why I make a set out of a1 in my answer.
Vlasec

2

I guess you're talking about C#. If so, you can try this

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

Sorry I did not mention the progrsmming language,it's ok ,but i need for java thanks for ur replay
naveen

This is correct. It is also a very inefficient way of doing it though. You'll basically cycle through the whole b list a.Count times. You could create a HashSet instead to use for the Contains or use the RemoveAll method on the set to get exactly the results you want.
Vlasec

1

You are just comparing strings.

Put the values in ArrayList A as keys in HashTable A.
Put the values in ArrayList B as keys in HashTable B.

Then, for each key in HashTable A, remove it from HashTable B if it exists.

What you are left with in HashTable B are the strings (keys) that were not values in ArrayList A.

C# (3.0) example added in response to request for code:

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

In your C# code, the hashA variable is effectively useless. You could make a foreach with listA instead as hashA is only iterated through and Contains is never called.
Vlasec

(Also, provided that C# has a RemoveAll method like Java does, you could avoid making your own cycle ... but again, I upvoted you as this solution is at least way more efficient than the selected one.)
Vlasec

1

Hi use this class this will compare both lists and shows exactly the mismatch b/w both lists.

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

}

Did you know that the clones are actually not clones at all?
Vlasec

1

THIS WORK ALSO WITH 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
the output: First List: [apple, orange, pippo] Second List: [apple, orange, banana, strawberry] Result: [banana, strawberry]
psycho

It does. But when you say so, you shouldn't forget to note that it can be painfully slow on large lists. Bear in mind that methods like remove and contains need to search through the whole list. If called repeatedly in a cycle (which happens in removeAll), you get a quadratic complexity. You could however use a hash set and have it just linear.
Vlasec
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.