Mam dwa ArrayList
typy Answer
(klasa własna).
Chciałbym porównać dwie listy, aby sprawdzić, czy zawierają tę samą treść, ale bez znaczenia.
Przykład:
//These should be equal.
ArrayList<String> listA = {"a", "b", "c"}
ArrayList<String> listB = {"b", "c", "a"}
List.equals
stwierdza, że dwie listy są równe, jeśli zawierają ten sam rozmiar, zawartość i kolejność elementów. Chcę tego samego, ale bez porządku.
Czy jest na to prosty sposób? A może będę musiał wykonać zagnieżdżoną pętlę for i ręcznie sprawdzić każdy indeks obu list?
Uwaga: nie mogę zmienić ich ArrayList
na inny typ listy, muszą taką pozostać.
Odpowiedzi:
Możesz posortować obie listy za pomocą,
Collections.sort()
a następnie użyć metody equals. Nieco lepszym rozwiązaniem jest najpierw sprawdzenie, czy mają tę samą długość przed zamówieniem, jeśli nie, to nie są równe, następnie posortuj, a następnie użyj równych. Na przykład, gdybyś miał dwie listy ciągów znaków, byłoby to coś takiego:public boolean equalLists(List<String> one, List<String> two){ if (one == null && two == null){ return true; } if((one == null && two != null) || one != null && two == null || one.size() != two.size()){ return false; } //to avoid messing the order of the lists we will use a copy //as noted in comments by A. R. S. one = new ArrayList<String>(one); two = new ArrayList<String>(two); Collections.sort(one); Collections.sort(two); return one.equals(two); }
źródło
Collections.sort
robi) - tj. Przekazać kopię.one = new ArrayList<String>(one); two = new ArrayList<String>(two);
aby uniknąć zrujnowania argumentów.if(one == null || two == null || one.size() != two.size()){ return false; }
ponieważ już sprawdzasz, czy zarówno jedno, jak i dwa są pustePrawdopodobnie najłatwiejszym sposobem na jakąkolwiek listę byłoby:
źródło
[a, b, c]
i czy[c, b, a, b]
mają taką samą zawartość. W tej odpowiedzi można by powiedzieć, że tak, ale może się zdarzyć, że w przypadku PO nie (ponieważ jeden zawiera duplikat, a drugi nie). Nie wspominając o problemach z wydajnością.Kolekcje Apache Commons ponownie na ratunek:
List<String> listA = Arrays.asList("a", "b", "b", "c"); List<String> listB = Arrays.asList("b", "c", "a", "b"); System.out.println(CollectionUtils.isEqualCollection(listA, listB)); // true
List<String> listC = Arrays.asList("a", "b", "c"); List<String> listD = Arrays.asList("a", "b", "c", "c"); System.out.println(CollectionUtils.isEqualCollection(listC, listD)); // false
Dokumenty:
źródło
false
? Zobacz moją odpowiedź.implementation 'org.apache.commons:commons-collections4:4.3'
zostawił miCaused by: com.android.builder.dexing.DexArchiveBuilderException: Failed to process
ścieżkę błędu .// helper class, so we don't have to do a whole lot of autoboxing private static class Count { public int count = 0; } public boolean haveSameElements(final List<String> list1, final List<String> list2) { // (list1, list1) is always true if (list1 == list2) return true; // If either list is null, or the lengths are not equal, they can't possibly match if (list1 == null || list2 == null || list1.size() != list2.size()) return false; // (switch the two checks above if (null, null) should return false) Map<String, Count> counts = new HashMap<>(); // Count the items in list1 for (String item : list1) { if (!counts.containsKey(item)) counts.put(item, new Count()); counts.get(item).count += 1; } // Subtract the count of items in list2 for (String item : list2) { // If the map doesn't contain the item here, then this item wasn't in list1 if (!counts.containsKey(item)) return false; counts.get(item).count -= 1; } // If any count is nonzero at this point, then the two lists don't match for (Map.Entry<String, Count> entry : counts.entrySet()) { if (entry.getValue().count != 0) return false; } return true; }
źródło
count
się ujemne, upraszczając korpus pętli doif(!counts.containsKey(item) || --counts.get(item).count < 0) return false;
Ponadto trzecia pętla może zostać uproszczona dofor(Count c: counts.values()) if(c.count != 0) return false;
counts
jest puste”), ale nie chciałem tego zaciemniać główny punkt: użycie mapy zasadniczo zmienia to w problem O (N + M) i jest największym pojedynczym impulsem, jaki prawdopodobnie otrzymasz.Map.merge
, używanie liczb całkowitych w pudełku może być prostsze i bardziej wydajne w większości przypadków użycia. Zobacz także tę odpowiedź …Powiedziałbym, że te odpowiedzi pomijają sztuczkę.
Bloch w swojej podstawowej, cudownej, zwięzłej Efektywnej Javie , mówi w pozycji 47, zatytułowanej „Poznaj i używaj bibliotek”, „Podsumowując, nie odkrywaj na nowo koła”. I podaje kilka bardzo wyraźnych powodów, dla których nie.
Jest tutaj kilka odpowiedzi, które sugerują metody z
CollectionUtils
biblioteki Apache Commons Collections, ale żadna nie znalazła najpiękniejszego, najbardziej eleganckiego sposobu odpowiedzi na to pytanie :Collection<Object> culprits = CollectionUtils.disjunction( list1, list2 ); if( ! culprits.isEmpty() ){ // ... do something with the culprits, i.e. elements which are not common }
Winowajcy : tj. Elementy, które nie są wspólne dla obu
Lists
. Określenie, do kogo należą,list1
a do kogo winowajcy,list2
jest stosunkowo proste przy użyciuCollectionUtils.intersection( list1, culprits )
iCollectionUtils.intersection( list2, culprits )
.Jednak ma tendencję do rozpadania się w przypadkach takich jak {"a", "a", "b"}
disjunction
z {"a", "b", "b"} ... z wyjątkiem tego, że nie jest to awaria oprogramowania, ale nieodłączne od charakteru subtelności / niejasności pożądanego zadania.Zawsze możesz sprawdzić kod źródłowy (l. 287) dla takiego zadania, jak to zostało stworzone przez inżynierów Apache. Jedną z korzyści płynących z używania ich kodu jest to, że został on gruntownie wypróbowany i przetestowany, z wieloma skrajnymi przypadkami i problemami przewidzianymi i rozwiązanymi. W razie potrzeby możesz skopiować i ulepszyć ten kod według własnego uznania.
Uwaga: Na początku byłem rozczarowany, że żadna z
CollectionUtils
metod nie zapewnia przeładowanej wersji, umożliwiającej narzucenie własnejComparator
(dzięki czemu można przedefiniowaćequals
ją tak, aby odpowiadała Twoim celom).Ale z kolekcji4 4.0 jest nowa klasa,
Equator
która „określa równość między obiektami typu T”. Podczas badania kodu źródłowego collections4 CollectionUtils.java wydaje się, że używają tego z niektórymi metodami, ale o ile wiem, nie ma to zastosowania do metod znajdujących się na początku pliku, przy użyciuCardinalityHelper
klasy ... która obejmujądisjunction
iintersection
.Przypuszczam, że ludzie z Apache jeszcze do tego nie doszli, ponieważ jest to nietrywialne: należałoby utworzyć coś w rodzaju klasy „AbstractEquatingCollection”, która zamiast używać elementów
equals
ihashCode
metod jej elementów , musiałaby zamiast tego używać tych zEquator
dla wszystkich podstawowych metod, takich jakadd
,contains
itp. Uwaga: w rzeczywistości, gdy patrzysz na kod źródłowy,AbstractCollection
nie implementujeadd
, ani jego abstrakcyjnych podklas, takich jakAbstractSet
... musisz poczekać, aż konkretne klasy takie jakHashSet
iArrayList
wcześniejadd
jest zaimplementowane. Dość ból głowy.Przypuszczam, że w międzyczasie obserwuj tę przestrzeń. Oczywistym tymczasowym rozwiązaniem byłoby umieszczenie wszystkich elementów w specjalnie zaprojektowanej klasie opakowania, która używa
equals
ihashCode
zaimplementuje taki rodzaj równości, jaki chcesz, a następnie manipulowanieCollections
tymi obiektami opakowania.źródło
Jeśli liczność elementów nie ma znaczenia (co oznacza: powtarzające się elementy są uważane za jeden), można to zrobić bez konieczności sortowania:
boolean result = new HashSet<>(listA).equals(new HashSet<>(listB));
To utworzy
Set
OUT każdyList
, a następnie za pomocąHashSet
„Sequals
metodę, (oczywiście) pominięcia zamawiania.Jeśli liczność ma znaczenie, musisz ograniczyć się do udogodnień zapewnianych przez
List
; W takim przypadku odpowiedź @ jschoen byłaby bardziej odpowiednia.źródło
Konwersja list do Multiset Guavy działa bardzo dobrze. Są one porównywane niezależnie od ich kolejności i uwzględniane są również zduplikowane elementy.
static <T> boolean equalsIgnoreOrder(List<T> a, List<T> b) { return ImmutableMultiset.copyOf(a).equals(ImmutableMultiset.copyOf(b)); } assert equalsIgnoreOrder(ImmutableList.of(3, 1, 2), ImmutableList.of(2, 1, 3)); assert !equalsIgnoreOrder(ImmutableList.of(1), ImmutableList.of(1, 1));
źródło
Jest to oparte na rozwiązaniu @cHao. Dodałem kilka poprawek i ulepszeń wydajności. Działa to mniej więcej dwa razy szybciej niż rozwiązanie z równą zamówieniem kopii. Działa dla każdego typu kolekcji. Puste kolekcje i null są uważane za równe. Użyj na swoją korzyść;)
/** * Returns if both {@link Collection Collections} contains the same elements, in the same quantities, regardless of order and collection type. * <p> * Empty collections and {@code null} are regarded as equal. */ public static <T> boolean haveSameElements(Collection<T> col1, Collection<T> col2) { if (col1 == col2) return true; // If either list is null, return whether the other is empty if (col1 == null) return col2.isEmpty(); if (col2 == null) return col1.isEmpty(); // If lengths are not equal, they can't possibly match if (col1.size() != col2.size()) return false; // Helper class, so we don't have to do a whole lot of autoboxing class Count { // Initialize as 1, as we would increment it anyway public int count = 1; } final Map<T, Count> counts = new HashMap<>(); // Count the items in col1 for (final T item : col1) { final Count count = counts.get(item); if (count != null) count.count++; else // If the map doesn't contain the item, put a new count counts.put(item, new Count()); } // Subtract the count of items in col2 for (final T item : col2) { final Count count = counts.get(item); // If the map doesn't contain the item, or the count is already reduced to 0, the lists are unequal if (count == null || count.count == 0) return false; count.count--; } // At this point, both collections are equal. // Both have the same length, and for any counter to be unequal to zero, there would have to be an element in col2 which is not in col1, but this is checked in the second loop, as @holger pointed out. return true; }
źródło
false
gdy klucz nie istnieje lub jego liczba staje się ujemna. Ponieważ łączny rozmiar obu list jest zgodny (zostało to sprawdzone z góry), nie można mieć wartości niezerowych po drugiej pętli, ponieważ nie może istnieć dodatnich wartości dla klucza bez ujemnych wartości dla innego klucza.Pomyśl, jak byś to zrobił sam, nie mając komputera lub języka programowania. Podaję ci dwie listy elementów i musisz mi powiedzieć, czy zawierają te same elementy. Jak byś to zrobił?
Jedno podejście, jak wspomniano powyżej, polega na sortowaniu list, a następnie przechodzeniu je element po elemencie, aby sprawdzić, czy są równe (i tak
List.equals
jest). Oznacza to, że albo możesz modyfikować listy, albo możesz je kopiować - i bez znajomości przypisania nie mogę wiedzieć, czy którekolwiek z nich są dozwolone.Innym podejściem byłoby przejrzenie każdej listy i zliczenie, ile razy pojawia się każdy element. Jeśli obie listy mają na końcu te same liczby, mają te same elementy. Kodem do tego byłoby przetłumaczenie każdej listy na mapę,
elem -> (# of times the elem appears in the list)
a następnie wywołanieequals
dwóch map. Jeśli mapy sąHashMap
, każde z tych tłumaczeń jest operacją O (N), podobnie jak porównanie. To da ci całkiem wydajny algorytm pod względem czasu, kosztem dodatkowej pamięci.źródło
Miałem ten sam problem i wpadłem na inne rozwiązanie. Ten działa również w przypadku duplikatów:
public static boolean equalsWithoutOrder(List<?> fst, List<?> snd){ if(fst != null && snd != null){ if(fst.size() == snd.size()){ // create copied lists so the original list is not modified List<?> cfst = new ArrayList<Object>(fst); List<?> csnd = new ArrayList<Object>(snd); Iterator<?> ifst = cfst.iterator(); boolean foundEqualObject; while( ifst.hasNext() ){ Iterator<?> isnd = csnd.iterator(); foundEqualObject = false; while( isnd.hasNext() ){ if( ifst.next().equals(isnd.next()) ){ ifst.remove(); isnd.remove(); foundEqualObject = true; break; } } if( !foundEqualObject ){ // fail early break; } } if(cfst.isEmpty()){ //both temporary lists have the same size return true; } } }else if( fst == null && snd == null ){ return true; } return false; }
Zalety w porównaniu z niektórymi innymi rozwiązaniami:
[1,2,3,3]
i inną tablicę,[1,2,2,3]
większość rozwiązań mówi, że są one takie same, gdy nie rozważasz kolejności. To rozwiązanie pozwala tego uniknąć, usuwając równe elementy z tymczasowych list;equals
) i nie odwołuje się do równości (==
);implement Comparable
), aby to rozwiązanie działało.źródło
Jeśli nie masz nadziei na sortowanie zbiorów i potrzebujesz wyniku, że ["A" "B" "C"] nie jest równe ["B" "B" "A" "C"],
to za mało, prawdopodobnie trzeba też sprawdzić rozmiar:
List<String> l1 =Arrays.asList("A","A","B","C"); List<String> l2 =Arrays.asList("A","B","C"); List<String> l3 =Arrays.asList("A","B","C"); System.out.println(l1.containsAll(l2)&&l2.containsAll(l1));//cautions, this will be true System.out.println(isListEqualsWithoutOrder(l1,l2));//false as expected System.out.println(l3.containsAll(l2)&&l2.containsAll(l3));//true as expected System.out.println(isListEqualsWithoutOrder(l2,l3));//true as expected public static boolean isListEqualsWithoutOrder(List<String> l1, List<String> l2) { return l1.size()==l2.size() && l1.containsAll(l2)&&l2.containsAll(l1); }
źródło
Rozwiązanie wykorzystujące metodę odejmowania CollectionUtils:
import static org.apache.commons.collections15.CollectionUtils.subtract; public class CollectionUtils { static public <T> boolean equals(Collection<? extends T> a, Collection<? extends T> b) { if (a == null && b == null) return true; if (a == null || b == null || a.size() != b.size()) return false; return subtract(a, b).size() == 0 && subtract(a, b).size() == 0; } }
źródło
Jeśli zależy Ci na porządku, skorzystaj z metody equals:
Jeśli nie obchodzi Cię kolejność, użyj tego
źródło
Najlepsze z obu światów [@DiddiZ, @Chalkos]: ten opiera się głównie na metodzie @Chalkos, ale naprawia błąd (ifst.next ()) i poprawia wstępne testy (zaczerpnięte z @DiddiZ), a także eliminuje potrzebę skopiuj pierwszą kolekcję (po prostu usuwa elementy z kopii drugiej kolekcji).
Nie wymaga funkcji haszującej ani sortowania i umożliwiając wczesne istnienie braku równości, jest to najbardziej wydajna implementacja. Chyba że masz zbiór tysięcy lub więcej i bardzo prostą funkcję haszującą.
public static <T> boolean isCollectionMatch(Collection<T> one, Collection<T> two) { if (one == two) return true; // If either list is null, return whether the other is empty if (one == null) return two.isEmpty(); if (two == null) return one.isEmpty(); // If lengths are not equal, they can't possibly match if (one.size() != two.size()) return false; // copy the second list, so it can be modified final List<T> ctwo = new ArrayList<>(two); for (T itm : one) { Iterator<T> it = ctwo.iterator(); boolean gotEq = false; while (it.hasNext()) { if (itm.equals(it.next())) { it.remove(); gotEq = true; break; } } if (!gotEq) return false; } // All elements in one were found in two, and they're the same size. return true; }
źródło
Jest to alternatywny sposób sprawdzenia równości list tablic, które mogą zawierać wartości null:
List listA = Arrays.asList(null, "b", "c"); List listB = Arrays.asList("b", "c", null); System.out.println(checkEquality(listA, listB)); // will return TRUE private List<String> getSortedArrayList(List<String> arrayList) { String[] array = arrayList.toArray(new String[arrayList.size()]); Arrays.sort(array, new Comparator<String>() { @Override public int compare(String o1, String o2) { if (o1 == null && o2 == null) { return 0; } if (o1 == null) { return 1; } if (o2 == null) { return -1; } return o1.compareTo(o2); } }); return new ArrayList(Arrays.asList(array)); } private Boolean checkEquality(List<String> listA, List<String> listB) { listA = getSortedArrayList(listA); listB = getSortedArrayList(listB); String[] arrayA = listA.toArray(new String[listA.size()]); String[] arrayB = listB.toArray(new String[listB.size()]); return Arrays.deepEquals(arrayA, arrayB); }
źródło
Moje rozwiązanie na to. To nie jest takie fajne, ale działa dobrze.
public static boolean isEqualCollection(List<?> a, List<?> b) { if (a == null || b == null) { throw new NullPointerException("The list a and b must be not null."); } if (a.size() != b.size()) { return false; } List<?> bCopy = new ArrayList<Object>(b); for (int i = 0; i < a.size(); i++) { for (int j = 0; j < bCopy.size(); j++) { if (a.get(i).equals(bCopy.get(j))) { bCopy.remove(j); break; } } } return bCopy.isEmpty(); }
źródło
Metoda jednowierszowa :)
Elementy kolekcji NIE implementują interfejsu Comparable <? super T>
static boolean isEqualCollection(Collection<?> a, Collection<?> b) { return a == b || (a != null && b != null && a.size() == b.size() && a.stream().collect(Collectors.toMap(Function.identity(), s -> 1L, Long::sum)).equals(b.stream().collect(Collectors.toMap(Function.identity(), s -> 1L, Long::sum)))); }
Elementy kolekcji implementują interfejs Comparable <? super T>
static <T extends Comparable<? super T>> boolean isEqualCollection2(Collection<T> a, Collection<T> b) { return a == b || (a != null && b != null && a.size() == b.size() && a.stream().sorted().collect(Collectors.toList()).equals(b.stream().sorted().collect(Collectors.toList()))); }
obsługuje Android5 i Android6 przez https://github.com/retrostreams/android-retrostreams
static boolean isEqualCollection(Collection<?> a, Collection<?> b) { return a == b || (a != null && b != null && a.size() == b.size() && StreamSupport.stream(a).collect(Collectors.toMap(Function.identity(), s->1L, Longs::sum)).equals(StreamSupport.stream(b).collect(Collectors.toMap(Function.identity(), s->1L, Longs::sum)))); }
//// Przypadek testowy
boolean isEquals1 = isEqualCollection(null, null); //true boolean isEquals2 = isEqualCollection(null, Arrays.asList("1", "2")); //false boolean isEquals3 = isEqualCollection(Arrays.asList("1", "2"), null); //false boolean isEquals4 = isEqualCollection(Arrays.asList("1", "2", "2"), Arrays.asList("1", "1", "2")); //false boolean isEquals5 = isEqualCollection(Arrays.asList("1", "2"), Arrays.asList("2", "1")); //true boolean isEquals6 = isEqualCollection(Arrays.asList("1", 2.0), Arrays.asList(2.0, "1")); //true boolean isEquals7 = isEqualCollection(Arrays.asList("1", 2.0, 100L), Arrays.asList(2.0, 100L, "1")); //true boolean isEquals8 = isEqualCollection(Arrays.asList("1", null, 2.0, 100L), Arrays.asList(2.0, null, 100L, "1")); //true
źródło
W takim przypadku listy {"a", "b"} i {"b", "a"} są równe. A {"a", "b"} i {"b", "a", "c"} nie są równe. Jeśli korzystasz z listy obiektów złożonych, pamiętaj o nadpisaniu metody equals , ponieważ zawiera ją wewnątrz.
if (oneList.size() == secondList.size() && oneList.containsAll(secondList)){ areEqual = true; }
źródło
AbstractCollection.containsAll()
. Musisz pozwolić na zduplikowane elementy, o których mówimyLists
, a nie oSets
. Zobacz moją odpowiedź.