Wie kann ich die Differenz zwischen zwei ArrayLists berechnen?

81

Ich habe zwei ArrayLists.

ArrayList A enthält:

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

ArrayList B enthält:

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

Ich muss ArrayList A und ArrayList B vergleichen. Das Ergebnis ArrayList sollte die Liste enthalten, die in ArrayList A nicht vorhanden ist.

Das ArrayList-Ergebnis sollte sein:

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

wie zu vergleichen?

Naveen
quelle

Antworten:

192

In Java können Sie Collectiondie removeAllMethode der Schnittstelle verwenden.

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

Der obige Code erzeugt die folgende Ausgabe:

First List: [apple, orange]
Second List: [apple, orange, banana, strawberry]
Result: [banana, strawberry]
William Brendel
quelle
7
Wenn Ihre Liste eine benutzerdefinierte Klasse ist, müssen Sie die Methode equals Ihrer Klasse überschreiben, oder?
RTF
5
@RTF Ja, Sie müssen eine Implementierung bereitstellen equals, mit der Ihre Objekte verglichen werden können. Lesen Sie auch über die Implementierung hashCode. Beachten Sie beispielsweise , wie String::equalsist case-sensitive , so „Apfel“ und „Apple“ wird nicht die gleiche betrachtet werden.
Basil Bourque
1
Eigentlich hängt die Antwort davon ab, was Sie tun möchten. RemoveAll behält keine Duplikate bei. Wenn Sie Ihrer zweiten Liste eine weitere "Apfel" -String hinzufügen, wird diese ebenfalls entfernt, was möglicherweise nicht immer Ihren Wünschen entspricht.
Jules Testard
2
Das ist so ineffizient. Es ist traurig, dass dies sowohl die ausgewählte als auch die am besten bewertete Antwort ist. removeAllruft firstList.containsjedes Element von auf secondList. Die Verwendung von a HashSetwürde dies verhindern und es gibt ein paar gute Antworten niedriger.
Vlasec
20

Sie haben bereits die richtige Antwort. Und wenn Sie kompliziertere und interessantere Operationen zwischen Listen (Sammlungen) durchführen möchten, verwenden Sie Apache Commons-Sammlungen ( CollectionUtils ). Sie können Konjunktion / Disjunktion vornehmen, Schnittpunkte finden und prüfen, ob eine Sammlung eine Teilmenge einer anderen ist, und andere nette Dinge.

andrii
quelle
12

In Java 8 mit Streams ist das eigentlich ziemlich einfach. BEARBEITEN: Kann ohne Streams effizient sein, siehe unten.

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

Beachten Sie, dass der Hash-Satz nur einmal erstellt wird: Die Methodenreferenz ist an die enthaltene Methode gebunden. Um dasselbe mit Lambda zu tun, müsste die Menge in einer Variablen enthalten sein. Das Erstellen einer Variablen ist keine schlechte Idee, insbesondere wenn Sie sie unansehnlich oder schwerer zu verstehen finden.

Sie können das Prädikat ohne diese Dienstprogrammmethode (oder explizite Umwandlung) nicht einfach negieren , da Sie die Negativmethodenreferenz nicht direkt aufrufen können (Typinferenz ist zuerst erforderlich).

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

Wenn Streams eine filterOutMethode oder etwas anderes hätten, würde es besser aussehen.


Auch @Holger gab mir eine Idee. ArrayListhat seine removeAllMethode für mehrere Entfernungen optimiert, es ordnet seine Elemente nur einmal neu. Es wird jedoch die containsvon der angegebenen Sammlung bereitgestellte Methode verwendet , sodass wir diesen Teil optimieren müssen, wenn er listAalles andere als winzig ist.

Mit listAund listBzuvor deklariert benötigt diese Lösung kein Java 8 und ist sehr effizient.

List<String> result = new ArrayList(listB);
result.removeAll(new HashSet<>(listA));
Vlasec
quelle
1
@Bax Warum die Bearbeitung? Das Original war sauberer und funktionell identisch.
Shmosel
1
@Bax Nein, das tut es nicht.
Shmosel
1
Mit Guave können Sie tun Predicates.in(new HashSet<>(listA)).negate().
Shmosel
1
Ich habe gerade einen Test durchgeführt und diese Lösung ist ~ 10-20% schneller als listB.removeAll (neues HashSet <> (listA)). und Guava Sets.difference (...) sind 2 mal langsamer als Streams.
Telebog
1
@Vlasec ArrayList.removehat eine lineare Komplexität, ist jedoch ArrayList.removeAllnicht auf removeeine lineare Array-Aktualisierungsoperation angewiesen, sondern führt diese durch, wobei jedes verbleibende Element an seinen endgültigen Platz kopiert wird. Im Gegensatz dazu wurde die Referenzimplementierung von LinkedListnicht optimiert, removeAllsondern führt removefür jedes betroffene Element eine Operation aus, bei der jedes Mal bis zu fünf Referenzen aktualisiert werden. Also, je nach dem Verhältnis zwischen entfernt und den verbleibenden Elementen, ArrayList‚s removeAlldurchführen kann immer noch deutlich besser als LinkedList‘ s, sogar für große Listen.
Holger
9

BEARBEITEN: In der ursprünglichen Frage wurde keine Sprache angegeben. Meine Antwort ist in C #.

Sie sollten stattdessen HashSet für diesen Zweck verwenden. Wenn Sie ArrayList verwenden müssen, können Sie die folgenden Erweiterungsmethoden verwenden:

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

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

mit 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
quelle
8

Obwohl dies eine sehr alte Frage in Java 8 ist, könnten Sie so etwas tun

 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
quelle
Ich liebe Java 8, aber wir sollten immer noch an Komplexität denken. Während Listen auch die CollectionMethode haben contains, ist sie sehr ineffizient. Es muss die gesamte Liste durchlaufen, wenn es nicht gefunden wird. Es a2kann auf größeren Listen schmerzhaft langsam sein, es für jedes Element von zu tun , weshalb ich a1in meiner Antwort einen Satz daraus mache .
Vlasec
2

Ich denke du redest über C #. Wenn ja, können Sie dies versuchen

    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
quelle
Entschuldigung, ich habe die Programmiersprache nicht erwähnt, es ist in Ordnung, aber ich brauche Java, danke für
deine
Das ist richtig. Es ist jedoch auch eine sehr ineffiziente Methode. Sie über grundsätzlich zyklisch durch die ganze bListe a.Countmal. Sie können HashSetstattdessen eine erstellen , die für die verwendet werden soll, Containsoder die RemoveAllMethode am Set verwenden, um genau die gewünschten Ergebnisse zu erzielen.
Vlasec
1

Sie vergleichen nur Zeichenfolgen.

Fügen Sie die Werte in ArrayList A als Schlüssel in HashTable A ein. Fügen
Sie die Werte in ArrayList B als Schlüssel in HashTable B ein.

Entfernen Sie dann für jeden Schlüssel in HashTable A ihn aus HashTable B, falls vorhanden.

Was Ihnen in HashTable B übrig bleibt, sind die Zeichenfolgen (Schlüssel), die in ArrayList A keine Werte waren.

Beispiel für C # (3.0) als Antwort auf die Anforderung eines Codes hinzugefügt:

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
quelle
In Ihrem C # -Code ist die hashAVariable praktisch nutzlos. Sie können listAstattdessen einen foreach mit erstellen, da dieser hashAnur durchlaufen wird und Containsniemals aufgerufen wird.
Vlasec
(Vorausgesetzt, C # verfügt über eine RemoveAll-Methode wie Java, können Sie vermeiden, einen eigenen Zyklus zu erstellen. Ich habe Sie jedoch erneut positiv bewertet, da diese Lösung zumindest wesentlich effizienter ist als die ausgewählte.)
Vlasec
1

Hallo, benutze diese Klasse, dies vergleicht beide Listen und zeigt genau die Nichtübereinstimmung zwischen beiden Listen.

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
quelle
Wussten Sie, dass die Klone eigentlich gar keine Klone sind?
Vlasec
1

DIESE ARBEIT AUCH MIT 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
quelle
1
die Ausgabe: Erste Liste: [Apfel, Orange, Pippo] Zweite Liste: [Apfel, Orange, Banane, Erdbeere] Ergebnis: [Banane, Erdbeere]
Psycho
Es tut. Aber wenn Sie dies sagen, sollten Sie nicht vergessen zu beachten, dass es auf großen Listen schmerzhaft langsam sein kann. Denken Sie daran, dass Methoden die gesamte Liste mögen removeund containsdurchsuchen müssen. Wenn Sie in einem Zyklus wiederholt aufgerufen werden (was in passiert removeAll), erhalten Sie eine quadratische Komplexität. Sie können jedoch ein Hash-Set verwenden und es nur linear haben.
Vlasec