Przecięcie i połączenie ArrayLists w Javie


134

Czy są jakieś metody, aby to zrobić? Szukałem, ale nie mogłem znaleźć.

Kolejne pytanie: potrzebuję tych metod, aby móc filtrować pliki. Niektóre są ANDfiltrami, a inne ORfiltrami (jak w teorii zbiorów), więc muszę filtrować według wszystkich plików i unite / intersects ArrayLists, które przechowują te pliki.

Czy powinienem używać innej struktury danych do przechowywania plików? Czy jest coś jeszcze, co zapewniłoby lepsze środowisko wykonawcze?


1
Jeśli nie chcesz tworzyć nowej listy, Vector.retainAll (Vector) przycina twój oryginalny wektor tylko do przecięcia z drugim wektorem.
user2808054

@ user2808054 dlaczego Vector? Ta klasa jest odradzana od wersji Java 1.2.
dimo414

@ dimo414 interfejs, którego używam (nie mam opcji) zwraca rzeczy jako wektory. Nie wiedziałem, że było to zniechęcone! Dzięki za informację .. Zniechęcony przez kogo? Nie widziałem żadnej notatki o tym, że został wycofany, więc jest to niespodzianka
user2808054

1
Z Javadocs: „ Od platformy Java 2 v1.2 ... zaleca się użycie ArrayList zamiast Vector. ”. Jedynym razem, kiedy może potrzebować Vectorjest dla interakcji cross-wątku, ale są bezpieczniejsze struktury danych dla tych przypadkach używać zbyt. Zobacz także to pytanie . VectorMoim zdaniem każda biblioteka, która nadal korzysta z 2016 roku, jest bardzo podejrzana.
dimo414

@ dimo414 to biblioteka IBM, haha! (API danych Lotus Domino). Dzięki za informacje, bardzo pomocne
user2808054

Odpowiedzi:


126

Oto prosta implementacja bez użycia biblioteki innej firmy. Główną zaletą nad retainAll, removeAlli addAllto, że metody te nie zmieniają pierwotnego wejścia wykazy metod.

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
można utworzyć nową listę z elementami listy1 a następnie zadzwonić retainAll, AddAll metod
lukastymo

dlaczego w tym rozwiązaniu używasz strictefp?
lukastymo

9
Należy użyć HashSetfor, intersectionaby średnia wydajność przypadku wynosiła O (n) zamiast O (n ^ 2).
Zong

1
W tym poście można by użyć aktualizacji, aby zademonstrować zalety interfejsu API Java 8 Stream.
SME_Dev

Występuje błąd, gdy próbuję przypisać tę wartość -> Przykład: ArrayList <String> total total = (ArrayList <String>) intersection (list2, list1) ---> nie można przesłać java.util.arraylist do java.util.arraylist < string>

124

Kolekcja (więc ArrayList również) ma:

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

Użyj implementacji List, jeśli akceptujesz powtórzenia, implementacji Set, jeśli nie:

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
Sugerowano edycję, że ta unia „jest niepoprawna, ponieważ dwukrotnie będzie zawierała wspólne elementy” . W edycji zaleca się użycie HashSetzamiast tego.
Kos

5
Właściwie to było edytowane, zobacz: "Użyj implementacji List, jeśli akceptujesz powtórzenia, implementacji Set, jeśli nie:"
lukastymo

7
Nie, retainAll nie jest przecięciem listy. Powyżej wszystkie elementy w col, których nie ma w otherCol, są usuwane. Powiedzmy, że otherCol to {a, b, b, c}, a col to {b, b, b, c, d}. Wtedy col kończy się na {b, b, b, c}, które nie jest ściśle przecięciem tych dwóch. Spodziewałbym się, że będzie to {b, b, c}. Wykonywana jest inna operacja.
demongolem

1
Nie widzę też, jak addAll()jest unia dla list; to po prostu konkatenacja drugiej listy na końcu pierwszej. Operacja sumująca pozwoli uniknąć dodania elementu, jeśli pierwsza lista już go zawiera.
dimo414

70

Ten post jest dość stary, ale mimo wszystko był to pierwszy, który pojawił się w Google podczas szukania tego tematu.

Chcę zaktualizować za pomocą strumieni Java 8, które (w zasadzie) robią to samo w jednym wierszu:

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

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

Jeśli ktoś ma lepsze / szybsze rozwiązanie to daj mi znać, ale to fajne rozwiązanie, które można łatwo włączyć do metody bez dodawania niepotrzebnej klasy / metody pomocniczej i nadal zachować czytelność.


21
Ooo, może to być niezły tekst, ale zajmuje to O (n ^ 2) czasu. Przekonwertuj jedną z list na a, Seta następnie użyj metody zestawu contains. Nie wszystko w życiu trzeba robić za pomocą strumieni.
dimo414

31
list1.retainAll(list2) - is intersection

związek będzie removeAlli wtedyaddAll .

Znajdź więcej w dokumentacji kolekcji (ArrayList to zbiór) http://download.oracle.com/javase/1.5.0/docs/api/java/util/Collection.html


1
Obie retainAll()i removeAll()są operacjami O (n ^ 2) na listach. Możemy zrobić lepiej.
dimo414

1
Głosowałem w górę, ale teraz mam pytanie. retainAllz {1, 2, 2, 3, 4, 5} ponad {1, 2, 3} daje {1, 2, 2, 3}. Czy nie powinno to być {1, 2, 3} jako przecięcie?
GyuHyeon Choi

21

Związki i skrzyżowania zdefiniowane tylko dla zbiorów, a nie list. Jak wspomniałeś.

Sprawdź w bibliotece guawy filtry. Również guawa zapewnia prawdziwe skrzyżowania i połączenia

 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

Możesz użyć CollectionUtilsz apache commons .


9
Jeśli ktoś uzna tę odpowiedź za trochę za krótką: metodami są „CollectionUtils.containsAny” i „CollectionUtils.containsAll”.
Sebastian

2
to dziwne, że CollectionUtils z apache commons nie obsługuje generyków
Vasyl Sarzhynskyi

7

Oznaczone rozwiązanie nie jest wydajne. Ma złożoność czasową O (n ^ 2). To, co możemy zrobić, to posortować obie listy i wykonać algorytm przecięcia, taki jak ten poniżej.

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

Ten ma złożoność O (n log n + n), która jest równa O (n log n). Związek odbywa się w podobny sposób. Po prostu upewnij się, że dokonałeś odpowiednich modyfikacji w instrukcjach if-elseif-else.

Możesz także użyć iteratorów, jeśli chcesz (wiem, że są bardziej wydajne w C ++, nie wiem, czy tak jest również w Javie).


1
Nie dość ogólne, T może nie być porównywalne, aw niektórych przypadkach porównywanie jest drogie ...
Boris Churzin

Nie ogólne, całkowicie się zgadzam. Porównanie jest drogie? jak byś to rozwiązał?
AJed

Niestety - taniej byłoby zrobić to w O (n ^ 2) :) Dla Liczb to rozwiązanie jest dobre ...
Boris Churzin

Niestety - nie odpowiedziałeś na moje pytanie. Pozwólcie, że wyrażę to inaczej, w jaki sposób O (n ^ 2) jest lepsze, biorąc pod uwagę funkcję porównawczą kosztu c (n)?
AJed

1
Przekształcenie jednego wejścia w zbiór i wywołanie contains()w pętli (jak sugeruje Devenv) zajęłoby O (n + m) czasu. Sortowanie jest niepotrzebnie skomplikowane i zajmuje O (n log n + m log n + n) czasu. To prawda, że ​​czas skraca się do O (n log n), ale jest to wciąż gorsze niż czas liniowy i znacznie bardziej złożone.
dimo414

4

Myślę, że powinieneś użyć Setdo przechowywania plików, jeśli chcesz zrobić na nich przecięcie i połączenie. Następnie można użyć Guava „s Zestawy klasa zrobić union, intersectioni filtrując przez Predicaterównież. Różnica między tymi metodami a innymi sugestiami polega na tym, że wszystkie te metody tworzą leniwe widoki sumy, przecięcia itp. Dwóch zbiorów. Apache Commons tworzy nową kolekcję i kopiuje do niej dane. retainAllzmienia jedną z twoich kolekcji, usuwając z niej elementy.


4

Oto sposób, w jaki sposób można wykonać skrzyżowanie ze strumieniami (pamiętaj, że do strumieni musisz używać języka Java 8):

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

Przykład list z różnymi typami. Jeśli masz związek między foo i bar i możesz uzyskać obiekt baru z foo, to możesz zmodyfikować swój strumień:

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
  • retainAll zmodyfikuje twoją listę
  • Guava nie ma interfejsów API dla listy (tylko dla zestawu)

Znalazłem ListUtils bardzo przydatne w tym przypadku użycia.

Użyj ListUtils z org.apache.commons.collections, jeśli nie chcesz modyfikować istniejącej listy.

ListUtils.intersection(list1, list2)


3

Możesz użyć commons-collections4 CollectionUtils

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

W Javie 8 używam prostych metod pomocniczych, takich jak ta:

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

Jeśli obiekty na liście są hashable (tj. Mają przyzwoity hashCode i funkcję equals), najszybsze podejście między tabelami ok. size> 20 to skonstruowanie HashSet dla większej z dwóch list.

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

Ja też pracowałem nad podobną sytuacją i dotarłem tutaj w poszukiwaniu pomocy. Skończyło się na znalezieniu własnego rozwiązania dla tablic. ArrayList AbsentDates = new ArrayList (); // Przechowuje Array1-Array2

Uwaga: opublikowanie tego, jeśli może to pomóc komuś dotrzeć do tej strony w celu uzyskania pomocy.

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

Przecięcie dwóch list różnych obiektów opartych na wspólnym kluczu - 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());
    }

a co z różnicą między tymi 2 listami?
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 + (prawdopodobnie najlepsza wydajność)

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

Jeśli nie zależy Ci na wydajności i wolisz mniejszy kod, po prostu użyj:

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

1

One-liners od wersji Java 8

import statyczny java.util.stream.Stream.concat;
import statyczny java.util.stream.Collectors.toList;
import statyczny java.util.stream.Collectors.toSet;

Suma, jeśli nie ma duplikatów:

  return concat(a.stream(), b.stream()).collect(toList());

Unia i odrębne:

  return concat(a.stream(), b.stream()).distinct().collect(toList());

Suma i odrębne, jeśli typ zwrotu kolekcji / zestawu:

  return concat(a.stream(), b.stream()).collect(toSet());

Przetnij, jeśli nie ma duplikatów:

  return a.stream().filter(b::contains).collect(toList());

Jeśli zbiór bjest duży i nie ma O (1), zoptymalizuj wstępnie działanie filtra, dodając 1 wiersz wcześniej return. Skopiuj do HasSet ( import java.util.Set;) :

... b = Set.copyOf (b);

Przecinają się i wyróżniają:

  return a.stream().distinct().filter(b::contains).collect(toList());

0

Ostateczne rozwiązanie:

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

Najpierw kopiuję wszystkie wartości tablic do jednej tablicy, a następnie usuwam zduplikowane wartości z tablicy. Linia 12, wyjaśniająca, czy ta sama liczba występuje dłużej niż czas, a następnie umieść dodatkową wartość śmieci w pozycji „j”. Na koniec przejdź od początku do końca i sprawdź, czy występuje ta sama wartość śmieci, a następnie odrzuć.

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
Witamy w Stack Overflow! Zwróć uwagę, że pytanie dotyczy ArrayList. Obawiam się również, że ta konkretna implementacja pozostawia wiele do życzenia. Wartość 99999999, która jest używana jako wartownik, może pojawić się na wejściu. Lepiej byłoby użyć dynamicznej struktury, na przykład ArrayListdo przechowywania wyniku unii.
SL Barth - Przywróć Monikę

1
Proszę wyjaśnić kod, który przedstawiłeś, zamiast tylko odpowiedzi na kod.
tmarois

Podaję tylko wskazówkę, że musisz określić jakąkolwiek wartość śmieci
Ashutosh

Cieszę się, że dodałeś wyjaśnienie. Niestety sama odpowiedź jest nadal zła. Nie ma powodu, aby używać tablic. Powinieneś użyć dynamicznej struktury, takiej jak ArrayList. Jeśli (z jakiegoś powodu) musisz używać tablic, powinieneś rozważyć użycie tablicy Integerzamiast int. Wtedy możesz użyć nullzamiast swojej „wartości śmieciowej”. „Wartości śmieci” lub „wartości wartowników” są zwykle złym pomysłem, ponieważ te wartości mogą nadal występować w danych wejściowych.
SL Barth - Przywróć Monikę

0

Po przeprowadzeniu testów, oto moje najlepsze podejście do skrzyżowania.

Większa prędkość w porównaniu do czystego podejścia HashSet. Poniższe HashSet i HashMap mają podobną wydajność dla tablic z ponad 1 milionem rekordów.

Jeśli chodzi o podejście Java 8 Stream, szybkość jest dość niska dla wielkości tablicy większej niż 10 KB.

Mam nadzieję, że to pomoże.

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

0

retainAll () użycie metody do znalezienia wspólnego elementu..ie; intersection list1.retainAll (list2)




-1

Jeśli liczba pasuje, to sprawdzam, pojawia się za pierwszym razem lub nie przy pomocy "indexOf ()", jeśli liczba pasuje za pierwszym razem, wydrukuj i zapisz w ciągu, aby następnym razem pasowała ta sama liczba, wygrywa ' t print, ponieważ ze względu na warunek „indexOf ()” będzie fałszywy.

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
Nie wysyłaj kodu jako odpowiedzi, ale trochę wyjaśnij, co robisz
Brandon Zamudio.

to mój pierwszy program, który wgrałem
Ashutosh,

2
Chociaż ten kod może pomóc w rozwiązaniu problemu, nie wyjaśnia, dlaczego i / lub jak odpowiada na pytanie. Zapewnienie tego dodatkowego kontekstu znacznie poprawiłoby jego długoterminową wartość. Proszę edytować swoje odpowiedzi, aby dodać wyjaśnienie, w tym, co stosuje się ograniczenia i założenia.
Toby Speight
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.