Jak mogę obliczyć różnicę między dwoma ArrayLists?

81

Mam dwie ArrayLists.

ArrayList A zawiera:

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

ArrayList B zawiera:

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

Muszę porównać ArrayList A i ArrayList B. Wynik ArrayList powinien zawierać listę, która nie istnieje w ArrayList A.

Wynik ArrayList powinien wyglądać następująco:

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

jak porównać?

naveen
źródło

Odpowiedzi:

194

W Javie możesz użyć metody Collectioninterfejsu 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);

Powyższy kod wygeneruje następujący wynik:

First List: [apple, orange]
Second List: [apple, orange, banana, strawberry]
Result: [banana, strawberry]
William Brendel
źródło
7
Jeśli twoja lista jest klasą niestandardową, będziesz musiał zastąpić metodę equals swojej klasy, prawda?
RTF
5
@RTF Tak, musisz zapewnić implementację, equalsktóra umożliwi porównywanie obiektów. Przeczytaj również o wdrażaniu hashCode. Na przykład zwróć uwagę, jak rozróżnianaString::equals jest wielkość liter , więc „jabłko” i „Apple” nie będą traktowane tak samo.
Basil Bourque
1
Właściwie odpowiedź zależy od tego, co chcesz zrobić. RemoveAll nie zachowa duplikatów. Jeśli dodasz kolejny ciąg „jabłka” do drugiej listy, zostanie on również usunięty, co nie zawsze może być tym, czego chcesz.
Jules Testard
2
To jest tak nieefektywne. To smutne, że jest to zarówno wybrana, jak i najlepiej oceniana odpowiedź. removeAllodwołuje firstList.containssię do każdego elementu secondList. Użycie HashSetlitery zapobiegłoby temu, a kilka dobrych odpowiedzi jest niższych.
Vlasec,
20

Masz już właściwą odpowiedź. A jeśli chcesz wykonać bardziej skomplikowane i interesujące operacje między Listami (kolekcjami), użyj kolekcji apache commons ( CollectionUtils ). Pozwala to na tworzenie koniukcji / rozłączania, znajdowanie przecięcia, sprawdzanie, czy jedna kolekcja jest podzbiorem innej i innych fajnych rzeczy.

andrii
źródło
12

W Javie 8 ze strumieniami jest to całkiem proste. EDYCJA: Może być wydajna bez strumieni, patrz niżej.

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

Zwróć uwagę, że zestaw skrótów jest tworzony tylko raz: odwołanie do metody jest powiązane z metodą zawierającą. Zrobienie tego samego z lambdą wymagałoby posiadania zestawu w zmiennej. Tworzenie zmiennej nie jest złym pomysłem, zwłaszcza jeśli uznasz to za brzydkie lub trudniejsze do zrozumienia.

Nie możesz łatwo zanegować predykatu bez czegoś takiego jak ta metoda narzędziowa (lub jawne rzutowanie), ponieważ nie możesz bezpośrednio wywołać odwołania do metody negacji (najpierw wymagane jest wnioskowanie o typie).

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

Gdyby strumienie miały jakąś filterOutmetodę lub coś, wyglądałoby to ładniej.


Również @Holger dał mi pomysł. ArrayListma swoją removeAllmetodę zoptymalizowaną pod kątem wielokrotnych usunięć, zmienia układ elementów tylko raz. Jednak używa containsmetody dostarczonej przez daną kolekcję, więc musimy zoptymalizować tę część, jeśli nie listAjest mała.

Z listAi listBoświadczył wcześniej, to rozwiązanie nie wymaga Javy 8 i jest bardzo wydajny.

List<String> result = new ArrayList(listB);
result.removeAll(new HashSet<>(listA));
Vlasec
źródło
1
@Bax Dlaczego edycja? Oryginał był czystszy i funkcjonalnie identyczny.
shmosel
1
@Bax Nie, tak nie jest.
shmosel,
1
Z guawą możesz to zrobić Predicates.in(new HashSet<>(listA)).negate().
shmosel,
1
Po prostu przeprowadzam test i to rozwiązanie jest ~ 10-20% szybsze niż listB.removeAll (new HashSet <> (listA)). i Guava Sets.difference (...) si 2 razy wolniej niż strumienie.
telebog
1
@Vlasec ArrayList.removema liniową złożoność, ale ArrayList.removeAllnie polega na, removeale wykonuje liniową operację aktualizacji tablicy, kopiując każdy pozostały element na jego ostateczne miejsce. W przeciwieństwie do tego implementacja referencyjna LinkedListnie została zoptymalizowana, removeAllale wykonuje removeoperację dla każdego elementu, którego dotyczy problem, i za każdym razem aktualizuje do pięciu odniesień. Tak więc, w zależności od stosunku pomiędzy usuniętymi i pozostałych elementów, ArrayListjest removeAllmoże jeszcze znacznie lepiej niż wykonać LinkedLists ', nawet dla dużych list.
Holger
9

EDYCJA: Oryginalne pytanie nie określało języka. Moja odpowiedź jest w C #.

W tym celu należy zamiast tego użyć HashSet. Jeśli musisz użyć ArrayList, możesz użyć następujących metod rozszerzających:

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

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

przy użyciu 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
Josh
źródło
8

Użyłem Guava Sets.difference .

Parametry są zestawami, a nie zbiorami ogólnymi, ale wygodnym sposobem tworzenia zestawów z dowolnej kolekcji (z unikalnymi przedmiotami) jest Guava ImmutableSet. (Iterable).

(Po raz pierwszy opublikowałem to w pytaniu pokrewnym / fałszywym , ale kopiuję to również tutaj, ponieważ uważam, że jest to dobra opcja, której do tej pory brakuje.)

Peter Lamberg
źródło
8

Chociaż jest to bardzo stare pytanie w Javie 8, możesz zrobić coś takiego

 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());
jesantana
źródło
Uwielbiam Javę 8, ale nadal powinniśmy myśleć o złożoności. Chociaż listy mają również Collectionmetodę contains, jest ona bardzo nieefektywna. Jeśli nie zostanie znaleziony, musi przejść przez całą listę. Robienie tego dla każdego elementu a2może być boleśnie powolne na większych listach, dlatego a1w mojej odpowiedzi zestawiam.
Vlasec
2

Myślę, że mówisz o C #. Jeśli tak, możesz spróbować

    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;
    }
Pavels
źródło
Przepraszam, że nie wspomniałem o języku programowania, jest ok, ale potrzebuję java dzięki za powtórkę
naveen
To jest poprawne. Jest to również bardzo nieefektywny sposób na zrobienie tego. Zasadniczo przejdziesz przez całą blistę a.Countrazy. Możesz HashSetzamiast tego utworzyć metodę, która będzie używana dla metody Containslub użyć RemoveAllmetody w zestawie, aby uzyskać dokładnie takie wyniki, jakie chcesz.
Vlasec,
1

Po prostu porównujesz struny.

Umieść wartości w ArrayList A jako klucze w HashTable A.
Umieść wartości w ArrayList B jako klucze w HashTable B.

Następnie dla każdego klucza w HashTable A usuń go z HashTable B, jeśli istnieje.

W HashTable B pozostały ci ciągi (klucze), które nie były wartościami w ArrayList A.

Przykład C # (3.0) dodany w odpowiedzi na żądanie kodu:

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>();
Demi
źródło
W kodzie C # hashAzmienna jest praktycznie bezużyteczna. Zamiast tego można utworzyć foreach, listAktóre hashAjest tylko iterowane i Containsnigdy nie jest wywoływane.
Vlasec,
(Ponadto, zakładając, że C # ma metodę RemoveAll, taką jak Java, możesz uniknąć tworzenia własnego cyklu ... ale znowu, zagłosowałem za tobą, ponieważ to rozwiązanie jest co najmniej o wiele bardziej wydajne niż wybrane).
Vlasec
1

Cześć, używam tej klasy, to porównuje obie listy i pokazuje dokładnie niezgodność b / w obu list.

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

}
Raj Mohamad
źródło
Czy wiesz, że klony wcale nie są klonami?
Vlasec,
1

TO DZIAŁA RÓWNIEŻ Z 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);
psycho
źródło
1
wynik: Pierwsza lista: [jabłko, pomarańcza, pippo] Druga lista: [jabłko, pomarańcza, banan, truskawka] Wynik: [banan, truskawka]
psycho
To robi. Ale kiedy tak mówisz, nie zapomnij zauważyć, że na dużych listach może to być boleśnie powolne. Pamiętaj, że metody lubią removei containsmuszą przeszukiwać całą listę. Jeśli zostaniesz wywołany wielokrotnie w cyklu (co dzieje się w removeAll), otrzymasz kwadratową złożoność. Możesz jednak użyć zestawu skrótu i ​​mieć go tylko liniowo.
Vlasec,