Schnittmenge und Vereinigung von ArrayLists in Java


130

Gibt es dafür Methoden? Ich suchte, konnte aber keine finden.

Eine andere Frage: Ich benötige diese Methoden, um Dateien filtern zu können. Einige sind ANDFilter und andere ORFilter (wie in der Mengenlehre), daher muss ich nach allen Dateien und den ArrayLists, die diese Dateien enthalten, filtern.

Sollte ich eine andere Datenstruktur verwenden, um die Dateien zu speichern? Gibt es noch etwas, das eine bessere Laufzeit bietet?


1
Wenn Sie keine neue Liste erstellen möchten, schneidet Vector.retainAll (Vector) Ihren ursprünglichen Vektor nur auf den Schnittpunkt mit dem zweiten Vektor.
user2808054

@ user2808054 warum Vector? Diese Klasse wurde seit Java 1.2 nicht mehr empfohlen.
dimo414

@ dimo414 Eine Schnittstelle, die ich benutze (ich habe keine Option), gibt Dinge als Vektoren zurück. Ich wusste nicht, dass es entmutigt worden war! Danke für die Info .. Von wem entmutigt? Ich habe keine Notiz darüber gesehen, dass es veraltet ist, daher ist dies eine Überraschung
user2808054

1
Aus den Javadocs: " Ab der Java 2-Plattform v1.2 ... wird empfohlen, ArrayList anstelle von Vector zu verwenden. " Möglicherweise benötigen Sie nur VectorThread-übergreifende Interaktionen, aber auch für diese Anwendungsfälle gibt es sicherere Datenstrukturen. Siehe auch diese Frage . Jede Bibliothek, die Vector2016 noch benutzt wird, ist meiner Meinung nach sehr verdächtig.
dimo414

@ dimo414 Es ist eine IBM Bibliothek, haha! (Lotus Domino Daten-API). Vielen Dank für die Info, sehr hilfreich
user2808054

Antworten:


122

Hier ist eine einfache Implementierung ohne Verwendung einer Bibliothek eines Drittanbieters. Hauptvorteil gegenüber retainAll, removeAllund addAllist , dass diese Methoden nicht verändern den ursprünglichen Listen Eingang zu den Methoden.

public class Test {

    public static void main(String... args) throws Exception {

        List<String> list1 = new ArrayList<String>(Arrays.asList("A", "B", "C"));
        List<String> list2 = new ArrayList<String>(Arrays.asList("B", "C", "D", "E", "F"));

        System.out.println(new Test().intersection(list1, list2));
        System.out.println(new Test().union(list1, list2));
    }

    public <T> List<T> union(List<T> list1, List<T> list2) {
        Set<T> set = new HashSet<T>();

        set.addAll(list1);
        set.addAll(list2);

        return new ArrayList<T>(set);
    }

    public <T> List<T> intersection(List<T> list1, List<T> list2) {
        List<T> list = new ArrayList<T>();

        for (T t : list1) {
            if(list2.contains(t)) {
                list.add(t);
            }
        }

        return list;
    }
}

16
Sie können eine neue Liste mit list1-Elementen erstellen und dann die Methoden
keepAll

Warum verwenden Sie strictfp in dieser Lösung?
Lukastymo

9
Sollte a HashSetfür verwenden, intersectiondamit die durchschnittliche Fallleistung O (n) anstelle von O (n ^ 2) ist.
Zong

1
Dieser Beitrag könnte ein Update verwenden, um die Vorteile der Java 8 Stream-API zu demonstrieren.
SME_Dev

Ich erhalte eine Fehlermeldung, wenn ich versuche, diesen Wert zuzuweisen -> Beispiel: ArrayList <String> total total = (ArrayList <String>) Schnittpunkt (list2, list1) ---> kann java.util.arraylist nicht in java.util.arraylist <umwandeln Zeichenfolge>
liefern

123

Sammlung (also auch ArrayList) haben:

col.retainAll(otherCol) // for intersection
col.addAll(otherCol) // for union

Verwenden Sie eine Listenimplementierung, wenn Sie Wiederholungen akzeptieren, eine Set-Implementierung, wenn Sie dies nicht tun:

Collection<String> col1 = new ArrayList<String>(); // {a, b, c}
// Collection<String> col1 = new TreeSet<String>();
col1.add("a");
col1.add("b");
col1.add("c");

Collection<String> col2 = new ArrayList<String>(); // {b, c, d, e}
// Collection<String> col2 = new TreeSet<String>();
col2.add("b");
col2.add("c");
col2.add("d");
col2.add("e");

col1.addAll(col2);
System.out.println(col1); 
//output for ArrayList: [a, b, c, b, c, d, e]
//output for TreeSet: [a, b, c, d, e]

3
Es wurde vorgeschlagen, dass diese Vereinigung "falsch ist, da sie zweimal gemeinsame Elemente enthält" . Bei der Bearbeitung wurde empfohlen, HashSetstattdessen a zu verwenden.
Kos

5
Tatsächlich wurde es bearbeitet, siehe: "Verwenden Sie eine
Listenimplementierung,

7
Nein, RetainAll ist kein Schnittpunkt für die Liste. Oben werden alle Elemente in col entfernt, die nicht in otherCol enthalten sind. Angenommen, otherCol ist {a, b, b, c} und col ist {b, b, b, c, d}. Dann endet col mit {b, b, b, c}, was nicht der strikte Schnittpunkt der beiden ist. Ich würde erwarten, dass das {b, b, c} ist. Eine andere Operation wird ausgeführt.
Demongolem

1
Ich sehe auch nicht, wie addAll()Union für Listen ist; Es wird nur die zweite Liste mit dem Ende der ersten verknüpft. Eine Vereinigungsoperation würde das Hinzufügen eines Elements vermeiden, wenn die erste Liste es bereits enthält.
dimo414

66

Dieser Beitrag ist ziemlich alt, aber dennoch war er der erste, der bei der Suche nach diesem Thema bei Google auftauchte.

Ich möchte ein Update mit Java 8-Streams geben, die (im Grunde) dasselbe in einer einzigen Zeile tun:

List<T> intersect = list1.stream()
    .filter(list2::contains)
    .collect(Collectors.toList());

List<T> union = Stream.concat(list1.stream(), list2.stream())
    .distinct()
    .collect(Collectors.toList());

Wenn jemand eine bessere / schnellere Lösung hat, lassen Sie es mich wissen, aber diese Lösung ist ein netter Einzeiler, der leicht in eine Methode aufgenommen werden kann, ohne eine unnötige Hilfsklasse / -methode hinzuzufügen, und dennoch die Lesbarkeit beibehält.


19
Ooof, es mag ein schöner Einzeiler sein, aber es dauert O (n ^ 2) Zeit. Konvertieren Sie eine der Listen in eine Setund verwenden Sie dann die containsMethode des Sets . Nicht alles im Leben muss mit Streams gemacht werden.
dimo414

31
list1.retainAll(list2) - is intersection

Gewerkschaft wird removeAllund dann seinaddAll .

Weitere Informationen finden Sie in der Dokumentation der Sammlung (ArrayList ist eine Sammlung) http://download.oracle.com/javase/1.5.0/docs/api/java/util/Collection.html


1
Beide retainAll()und removeAll()sind O (n ^ 2) Operationen auf Listen. Wir können es besser machen.
dimo414

1
Ich habe abgestimmt, aber jetzt habe ich eine Frage. retainAllvon {1, 2, 2, 3, 4, 5} über {1, 2, 3} ergibt {1, 2, 2, 3}. Sollte es nicht {1, 2, 3} sein, um die Kreuzung zu sein?
GyuHyeon Choi

21

Gewerkschaften und Schnittpunkte werden nur für Mengen definiert, nicht für Listen. Wie du erwähnt hast.

Überprüfen Sie die Guavenbibliothek auf Filter. Auch Guave bietet echte Schnittpunkte und Gewerkschaften

 static <E> Sets.SetView<E >union(Set<? extends E> set1, Set<? extends E> set2)
 static <E> Sets.SetView<E> intersection(Set<E> set1, Set<?> set2)

12

Sie können CollectionUtilsvon Apache Commons verwenden .


7
Für den Fall, dass jemand diese Antwort etwas zu kurz findet: 'CollectionUtils.containsAny' und 'CollectionUtils.containsAll' sind die Methoden.
Sebastian

2
Es ist seltsam, dass CollectionUtils von Apache Commons keine Generika unterstützt
Vasyl Sarzhynskyi

7

Die markierte Lösung ist nicht effizient. Es hat eine O (n ^ 2) -Zeitkomplexität. Was wir tun können, ist, beide Listen zu sortieren und einen Schnittalgorithmus wie den folgenden auszuführen.

private  static ArrayList<Integer> interesect(ArrayList<Integer> f, ArrayList<Integer> s) { 
    ArrayList<Integer> res = new ArrayList<Integer>();

    int i = 0, j = 0; 
    while (i != f.size() && j != s.size()) { 

        if (f.get(i) < s.get(j)) {
            i ++;
        } else if (f.get(i) > s.get(j)) { 
            j ++;
        } else { 
            res.add(f.get(i)); 
            i ++;  j ++;
        }
    }


    return res; 
}

Dieser hat eine Komplexität von O (n log n + n), die in O (n log n) liegt. Die Vereinigung erfolgt auf ähnliche Weise. Stellen Sie einfach sicher, dass Sie die entsprechenden Änderungen an den if-elseif-else-Anweisungen vornehmen.

Sie können auch Iteratoren verwenden, wenn Sie möchten (ich weiß, dass sie in C ++ effizienter sind, ich weiß nicht, ob dies auch in Java zutrifft).


1
Nicht generisch genug, T ist möglicherweise nicht vergleichbar und in einigen Fällen ist das Vergleichen teuer ...
Boris Churzin

Nicht generisch, da stimme ich voll und ganz zu. Vergleich ist teuer? Wie würden Sie das lösen?
AJed

Leider - es wäre billiger, es in O (n ^ 2) zu tun :) Für Zahlen ist diese Lösung gut ...
Boris Churzin

Leider haben Sie meine Frage nicht beantwortet. Lassen Sie es mich umformulieren: Wie ist O (n ^ 2) bei einer Vergleichsfunktion der Kosten c (n) besser?
AJed

1
Das Konvertieren eines Eingangs in eine Menge und das Aufrufen contains()einer Schleife (wie Devenv vorschlägt) würde O (n + m) Zeit in Anspruch nehmen. Das Sortieren ist unnötig kompliziert und benötigt O (n log n + m log n + n) Zeit. Zugegeben, das reduziert sich auf O (n log n) Zeit, aber das ist immer noch schlimmer als die lineare Zeit und viel komplexer.
dimo414

4

Ich denke, Sie sollten a verwenden Set, um die Dateien zu halten, wenn Sie Schnittpunkte und Vereinigungen an ihnen vornehmen möchten. Dann können Sie verwenden Guava ‚s Sets Klasse zu tun union, intersectionund das Filtern durch eine Predicateauch. Der Unterschied zwischen diesen Methoden und den anderen Vorschlägen besteht darin, dass alle diese Methoden träge Ansichten der Vereinigung, Schnittmenge usw. der beiden Mengen erzeugen . Apache Commons erstellt eine neue Sammlung und kopiert Daten in diese. retainAllÄndert eine Ihrer Sammlungen, indem Sie Elemente daraus entfernen.


4

So können Sie eine Schnittmenge mit Streams erstellen (denken Sie daran, dass Sie Java 8 für Streams verwenden müssen):

List<foo> fooList1 = new ArrayList<>(Arrays.asList(new foo(), new foo()));
List<foo> fooList2 = new ArrayList<>(Arrays.asList(new foo(), new foo()));
fooList1.stream().filter(f -> fooList2.contains(f)).collect(Collectors.toList());

Ein Beispiel für Listen mit verschiedenen Typen. Wenn Sie eine Beziehung zwischen foo und bar haben und ein Balkenobjekt von foo erhalten können, können Sie Ihren Stream ändern:

List<foo> fooList = new ArrayList<>(Arrays.asList(new foo(), new foo()));
List<bar> barList = new ArrayList<>(Arrays.asList(new bar(), new bar()));

fooList.stream().filter(f -> barList.contains(f.getBar()).collect(Collectors.toList());

3
  • keepAll ändert Ihre Liste
  • Guava hat keine APIs für List (nur für Set)

Ich fand ListUtils für diesen Anwendungsfall sehr nützlich.

Verwenden Sie ListUtils aus org.apache.commons.collections, wenn Sie die vorhandene Liste nicht ändern möchten.

ListUtils.intersection(list1, list2)


3

Sie können commons-collection4 CollectionUtils verwenden

Collection<Integer> collection1 = Arrays.asList(1, 2, 4, 5, 7, 8);
Collection<Integer> collection2 = Arrays.asList(2, 3, 4, 6, 8);

Collection<Integer> intersection = CollectionUtils.intersection(collection1, collection2);
System.out.println(intersection); // [2, 4, 8]

Collection<Integer> union = CollectionUtils.union(collection1, collection2);
System.out.println(union); // [1, 2, 3, 4, 5, 6, 7, 8]

Collection<Integer> subtract = CollectionUtils.subtract(collection1, collection2);
System.out.println(subtract); // [1, 5, 7]

2

In Java 8 verwende ich einfache Hilfsmethoden wie diese:

public static <T> Collection<T> getIntersection(Collection<T> coll1, Collection<T> coll2){
    return Stream.concat(coll1.stream(), coll2.stream())
            .filter(coll1::contains)
            .filter(coll2::contains)
            .collect(Collectors.toSet());
}

public static <T> Collection<T> getMinus(Collection<T> coll1, Collection<T> coll2){
    return coll1.stream().filter(not(coll2::contains)).collect(Collectors.toSet());
}

public static <T> Predicate<T> not(Predicate<T> t) {
    return t.negate();
}

1

Wenn die Objekte in der Liste hashbar sind (dh einen anständigen hashCode haben und gleich funktionieren), ist der schnellste Ansatz zwischen Tabellen ca. Bei einer Größe> 20 wird ein HashSet für die größere der beiden Listen erstellt.

public static <T> ArrayList<T> intersection(Collection<T> a, Collection<T> b) {
    if (b.size() > a.size()) {
        return intersection(b, a);
    } else {
        if (b.size() > 20 && !(a instanceof HashSet)) {
            a = new HashSet(a);
        }
        ArrayList<T> result = new ArrayList();
        for (T objb : b) {
            if (a.contains(objb)) {
                result.add(objb);
            }
        }
        return result;
    }
}

1

Ich arbeitete auch an der ähnlichen Situation und erreichte hier die Suche nach Hilfe. Am Ende fand ich meine eigene Lösung für Arrays. ArrayList AbsentDates = new ArrayList (); // speichert Array1-Array2

Hinweis: Wenn Sie dies veröffentlichen, kann dies dazu beitragen, dass jemand diese Seite um Hilfe bittet.

ArrayList<String> AbsentDates = new ArrayList<String>();//This Array will store difference
      public void AbsentDays() {
            findDates("April", "2017");//Array one with dates in Month April 2017
            findPresentDays();//Array two carrying some dates which are subset of Dates in Month April 2017

            for (int i = 0; i < Dates.size(); i++) {

                for (int j = 0; j < PresentDates.size(); j++) {

                    if (Dates.get(i).equals(PresentDates.get(j))) {

                        Dates.remove(i);
                    }               

                }              
                AbsentDates = Dates;   
            }
            System.out.println(AbsentDates );
        }

1

Schnittpunkt zweier Listen verschiedener Objekte basierend auf dem gemeinsamen Schlüssel - Java 8

 private List<User> intersection(List<User> users, List<OtherUser> list) {

        return list.stream()
                .flatMap(OtherUser -> users.stream()
                        .filter(user -> user.getId()
                                .equalsIgnoreCase(OtherUser.getId())))
                .collect(Collectors.toList());
    }

Wie wäre es mit einem Unterschied zwischen diesen beiden Listen?
Jean

1
public static <T> Set<T> intersectCollections(Collection<T> col1, Collection<T> col2) {
    Set<T> set1, set2;
    if (col1 instanceof Set) {
        set1 = (Set) col1;
    } else {
        set1 = new HashSet<>(col1);
    }

    if (col2 instanceof Set) {
        set2 = (Set) col2;
    } else {
        set2 = new HashSet<>(col2);
    }

    Set<T> intersection = new HashSet<>(Math.min(set1.size(), set2.size()));

    for (T t : set1) {
        if (set2.contains(t)) {
            intersection.add(t);
        }
    }

    return intersection;
}

JDK8 + (wahrscheinlich beste Leistung)

public static <T> Set<T> intersectCollections(Collection<T> col1, Collection<T> col2) {
    boolean isCol1Larger = col1.size() > col2.size();
    Set<T> largerSet;
    Collection<T> smallerCol;

    if (isCol1Larger) {
        if (col1 instanceof Set) {
            largerSet = (Set<T>) col1;
        } else {
            largerSet = new HashSet<>(col1);
        }
        smallerCol = col2;
    } else {
        if (col2 instanceof Set) {
            largerSet = (Set<T>) col2;
        } else {
            largerSet = new HashSet<>(col2);
        }
        smallerCol = col1;
    }

    return smallerCol.stream()
            .filter(largerSet::contains)
            .collect(Collectors.toSet());
}

Wenn Sie sich nicht für die Leistung interessieren und kleineren Code bevorzugen, verwenden Sie einfach:

col1.stream().filter(col2::contains).collect(Collectors.toList());

0

Endgültige Lösung:

//all sorted items from both
public <T> List<T> getListReunion(List<T> list1, List<T> list2) {
    Set<T> set = new HashSet<T>();
    set.addAll(list1);
    set.addAll(list2);
    return new ArrayList<T>(set);
}

//common items from both
public <T> List<T> getListIntersection(List<T> list1, List<T> list2) {
    list1.retainAll(list2);
    return list1;
}

//common items from list1 not present in list2
public <T> List<T> getListDifference(List<T> list1, List<T> list2) {
    list1.removeAll(list2);
    return list1;
}

0

Zuerst kopiere ich alle Werte von Arrays in ein einzelnes Array, dann entferne ich doppelte Werte in das Array. Zeile 12, in der erklärt wird, ob dieselbe Zahl länger als die Zeit vorkommt, und ein zusätzlicher Müllwert in die Position "j" gebracht wird. Am Ende von Anfang bis Ende durchlaufen und prüfen, ob derselbe Müllwert auftritt, dann verwerfen.

public class Union {
public static void main(String[] args){

    int arr1[]={1,3,3,2,4,2,3,3,5,2,1,99};
    int arr2[]={1,3,2,1,3,2,4,6,3,4};
    int arr3[]=new int[arr1.length+arr2.length];

    for(int i=0;i<arr1.length;i++)
        arr3[i]=arr1[i];

    for(int i=0;i<arr2.length;i++)
        arr3[arr1.length+i]=arr2[i];
    System.out.println(Arrays.toString(arr3));

    for(int i=0;i<arr3.length;i++)
    {
        for(int j=i+1;j<arr3.length;j++)
        {
            if(arr3[i]==arr3[j])
                arr3[j]=99999999;          //line  12
        }
    }
    for(int i=0;i<arr3.length;i++)
    {
        if(arr3[i]!=99999999)
            System.out.print(arr3[i]+" ");
    }
}   
}

1
Willkommen bei Stack Overflow! Bitte beachten Sie, dass es sich bei der Frage um ArrayList handelt. Ich befürchte auch, dass diese spezielle Implementierung zu wünschen übrig lässt. Der Wert 99999999, der als Sentinel verwendet wird, kann in der Eingabe auftreten. Es wäre besser, eine dynamische Struktur zu verwenden, ArrayListum das Ergebnis der Vereinigung zu speichern.
SL Barth - Wiedereinsetzung Monica

1
Bitte erläutern Sie den von Ihnen angegebenen Code anstelle einer Code-Antwort.
Tmarois

Ich gebe nur einen Hinweis, dass Sie irgendeinen Müllwert setzen müssen
Ashutosh

Ich bin froh zu sehen, dass Sie eine Erklärung hinzugefügt haben. Leider ist die Antwort selbst immer noch schlecht. Es gibt keinen Grund, Arrays zu verwenden. Sie sollten eine dynamische Struktur wie ArrayList verwenden. Wenn Sie (aus irgendeinem Grund) Arrays verwenden müssen, sollten Sie ein Array von Integeranstelle von in Betracht ziehen int. Dann können Sie nullanstelle Ihres "Müllwerts" verwenden. "Garbage-Werte" oder "Sentinel-Werte" sind normalerweise eine schlechte Idee, da diese Werte möglicherweise noch in der Eingabe vorkommen.
SL Barth - Wiedereinsetzung Monica

0

Nach dem Testen ist hier mein bester Kreuzungsansatz.

Schnellere Geschwindigkeit im Vergleich zum reinen HashSet-Ansatz. HashSet und HashMap unten haben eine ähnliche Leistung für Arrays mit mehr als 1 Million Datensätzen.

Beim Java 8 Stream-Ansatz ist die Geschwindigkeit bei Arrays mit einer Größe von mehr als 10 KB recht langsam.

Hoffe das kann helfen.

public static List<String> hashMapIntersection(List<String> target, List<String> support) {
    List<String> r = new ArrayList<String>();
    Map<String, Integer> map = new HashMap<String, Integer>();
    for (String s : support) {
        map.put(s, 0);
    }
    for (String s : target) {
        if (map.containsKey(s)) {
            r.add(s);
        }
    }
    return r;
}
public static List<String> hashSetIntersection(List<String> a, List<String> b) {
    Long start = System.currentTimeMillis();

    List<String> r = new ArrayList<String>();
    Set<String> set = new HashSet<String>(b);

    for (String s : a) {
        if (set.contains(s)) {
            r.add(s);
        }
    }
    print("intersection:" + r.size() + "-" + String.valueOf(System.currentTimeMillis() - start));
    return r;
}

public static void union(List<String> a, List<String> b) {
    Long start = System.currentTimeMillis();
    Set<String> r= new HashSet<String>(a);
    r.addAll(b);
    print("union:" + r.size() + "-" + String.valueOf(System.currentTimeMillis() - start));
}


-1

Wenn Sie Ihre Daten in Sets hätten, könnten Sie die SetsKlasse von Guava verwenden .


-1

Wenn die Zahl mit der von mir überprüften übereinstimmt, tritt sie mit Hilfe von "indexOf ()" zum ersten Mal auf oder nicht. Wenn die Zahl zum ersten Mal übereinstimmt, drucken Sie sie aus und speichern Sie sie in einer Zeichenfolge, damit sie beim nächsten Mal, wenn dieselbe Zahl übereinstimmt, gewonnen wird. ' t print, da die Bedingung aufgrund von "indexOf ()" falsch ist.

class Intersection
{
public static void main(String[] args)
 {
  String s="";
    int[] array1 = {1, 2, 5, 5, 8, 9, 7,2,3512451,4,4,5 ,10};
    int[] array2 = {1, 0, 6, 15, 6, 5,4, 1,7, 0,5,4,5,2,3,8,5,3512451};


       for (int i = 0; i < array1.length; i++)
       {
           for (int j = 0; j < array2.length; j++)
           {
               char c=(char)(array1[i]);
               if(array1[i] == (array2[j])&&s.indexOf(c)==-1)
               {    
                System.out.println("Common element is : "+(array1[i]));
                s+=c;
                }
           }
       }    
}

}}


2
Geben Sie nicht nur die Postleitzahl als Antwort ein, sondern geben Sie auch eine kleine Erklärung, was Sie tun
Brandon Zamudio

Es ist mein erstes Programm, das ich hochgeladen habe
Ashutosh

2
Obwohl dieser Code zur Lösung des Problems beitragen kann, erklärt er nicht, warum und / oder wie er die Frage beantwortet. Die Bereitstellung dieses zusätzlichen Kontextes würde seinen langfristigen Wert erheblich verbessern. Bitte bearbeiten Sie Ihre Antwort, um eine Erklärung hinzuzufügen, einschließlich der Einschränkungen und Annahmen.
Toby Speight
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.