Modo semplice per scoprire se due elenchi diversi contengono esattamente gli stessi elementi?


253

Qual è il modo più semplice per trovare se due elenchi contengono esattamente gli stessi elementi, nelle librerie Java standard?

Non dovrebbe importare se le due liste sono la stessa istanza o meno, e non dovrebbe importare se il parametro type degli elenchi è diverso.

per esempio

List list1
List<String> list2; 
// ... construct etc

list1.add("A");
list2.add("A"); 
// the function, given these two lists, should return true

Probabilmente c'è qualcosa che mi fissa in faccia che conosco :-)


EDIT: Per chiarire, stavo cercando gli ESATTI stessi elementi e numero di elementi, in ordine.


Gli elementi devono essere nello stesso ordine?
Michael Myers

Questo potrebbe non influire mai su di te, ma attenzione che a volte gli insiemi persistenti non rispettano il contratto uguale - cerca vedi opensource.atlassian.com/projects/hibernate/browse/HHH-3799
Pablojim

Risposte:


367

Se ti interessa l'ordine, usa semplicemente il metodo equals:

list1.equals(list2)

Dal javadoc:

Confronta l'oggetto specificato con questo elenco per l'uguaglianza. Restituisce vero se e solo se l'oggetto specificato è anche un elenco, entrambi gli elenchi hanno le stesse dimensioni e tutte le coppie corrispondenti di elementi nei due elenchi sono uguali. (Due elementi e1 ed e2 sono uguali se (e1 == null? E2 == null: e1.equals (e2)). In altre parole, due elenchi sono definiti uguali se contengono gli stessi elementi nello stesso ordine . Questa definizione garantisce che il metodo equals funzioni correttamente in diverse implementazioni dell'interfaccia Elenco.

Se si desidera verificare indipendentemente dall'ordine, è possibile copiare tutti gli elementi in Set e utilizzare uguali sui Set risultanti:

public static <T> boolean listEqualsIgnoreOrder(List<T> list1, List<T> list2) {
    return new HashSet<>(list1).equals(new HashSet<>(list2));
}

Una limitazione di questo approccio è che non solo ignora l'ordine, ma anche la frequenza di elementi duplicati. Ad esempio, se list1fosse ["A", "B", "A"] e list2[[A "," B "," B "] l' Setapproccio li considererebbe uguali.

Se devi essere insensibile all'ordine ma sensibile alla frequenza dei duplicati puoi:


54
Non potresti usare contiene Tutto per controllare indipendentemente dall'ordine?
Laz

6
Non conosco i dettagli di implementazione di IncludesAll, ma sembra che potrebbe essere male. Se contiene Tutte le chiamate contiene () più e più volte, avrai un O (n ^ 2) alg. Il set in generale dovrebbe essere O (nlogn)
Tom

6
In realtà, se gli insiemi saranno solo O (nlogn), un altro approccio è quello di chiamare Collections.sort () in un elenco e quindi utilizzare uguale. Se vuoi preservare l'ordine, dovrai copiare l'elenco, e questo potrebbe essere costoso e favorire la soluzione impostata ... quindi devi pensare alla tua situazione :-).
Tom

1
@amischiefr: stai suggerendo che O (n ^ 2) è il meglio che puoi fare?
Tom

8
@Dennis Il controllo dimensioni funziona davvero solo se sai che ogni elenco contiene solo elementi distinti. Ad esempio, dato a = [x, y, x]e b = [x, y, z]quindi le dimensioni sono uguali e b.containsAll(a)restituiranno true, ma bcontiene un elemento non presente a.
Laurence Gonsalves,

95

Ho pubblicato un sacco di cose nei commenti, penso che meriti la sua risposta.

Come tutti dicono qui, usare equals () dipende dall'ordine. Se non ti interessa l'ordine, hai 3 opzioni.

opzione 1

Uso containsAll() . Questa opzione non è l'ideale, secondo me, perché offre le prestazioni peggiori, O (n ^ 2).

opzione 2

Ci sono due varianti a questo:

2a) Se non ti interessa mantenere l'ordine dei tuoi elenchi ... usa Collections.sort()entrambi gli elenchi. Quindi utilizzare il equals(). Questo è O (nlogn), perché fai due tipi, quindi un confronto O (n).

2b) Se è necessario mantenere l'ordine delle liste, è possibile prima copiare entrambe le liste. ALLORA puoi usare la soluzione 2a su entrambi gli elenchi copiati. Tuttavia, ciò potrebbe non essere attraente se la copia è molto costosa.

Questo porta a:

Opzione 3

Se i requisiti sono gli stessi della parte 2b , ma la copia è troppo costosa. È possibile utilizzare un TreeSet per eseguire l'ordinamento per te. Scarica ogni elenco nel proprio TreeSet. Sarà ordinato nel set e gli elenchi originali rimarranno intatti. Quindi eseguire un equals()confronto su entrambi TreeSet. Le TreeSetss possono essere costruite in tempo O (nlogn), eequals() is O (n).

Fai la tua scelta :-).

EDIT: ho quasi dimenticato lo stesso avvertimento che Laurence Gonsalves sottolinea. L'implementazione di TreeSet eliminerà i duplicati. Se ti interessano i duplicati, avrai bisogno di una sorta di multiset ordinato.


Se ti interessano i duplicati, puoi sempre verificare che le dimensioni delle raccolte siano uguali prima di qualsiasi altro test.
Laz

Più specificamente, se i duplicati indicano disuguaglianza, la dimensione delle liste deve essere la stessa prima che qualsiasi controllo di uguaglianza abbia la possibilità di avere successo.
Laz

7
@laz: il controllo delle dimensioni non funzionerà se elementi diversi sono duplicati nei due elenchi. ad es .: [A, A, B] vs [A, B, B] hanno le stesse dimensioni.
Laurence Gonsalves il

@Laurence: concordo sul fatto che il post di Laz sia un po 'confuso (l'ho letto alcune volte prima di capirlo). Suppongo che stia solo cercando di fornire una "scorciatoia" per il caso speciale quando valgono 2 condizioni: (1) i duplicati contano e (2) le dimensioni dell'elenco sono diverse. Nel tuo esempio, penso che Laz stia ancora dicendo che è necessario fare tutti gli stessi controlli di cui abbiamo discusso. (Almeno è così che l'ho letto). Se i duplicati NON contano, non è possibile utilizzare le dimensioni come controllo di un caso speciale. Ma quando valgono le 2 condizioni, puoi semplicemente dire "if (list1.size ()! = List2.size ()) restituisce false ;.
Tom

9
Contiene Tutto penserei di dare la risposta sbagliata, dovresti contenere Tutti in entrambi i modi. a.containsAll(b) && b.containsAll(a)
Richard Tingle,

24

Se stai usando (o sei felice di usare) le raccolte Apache Commons, puoi usare CollectionUtils.isEqualCollection che "restituisce vero se le raccolte indicate contengono esattamente gli stessi elementi con esattamente le stesse cardinalità".


Ottima implementazione basata su hashmap. Il runtime dovrebbe essere O (n), e se ci sono molti elementi ripetitivi, usa una memoria minima per tenere traccia (fondamentalmente tiene traccia della frequenza (cardinalità) degli elementi usando una mappa per ogni raccolta). Unico inconveniente è che ha un ulteriore utilizzo della memoria O (n).
Muhd,

17

Molto tardi alla festa, ma volevo aggiungere questo controllo null sicuro:

Objects.equals(list1, list2)

8

So che questo è un vecchio thread, ma nessuna delle altre risposte ha risolto completamente il mio caso d'uso (suppongo che Guava Multiset potrebbe fare lo stesso, ma qui non c'è alcun esempio). Mi scusi per la mia formattazione. Sono ancora nuovo a pubblicare post sullo scambio di stack. Fammi sapere se ci sono errori

Diciamo che hai List<T>a e List<T>b e vuoi verificare se sono uguali alle seguenti condizioni:

1) O (n) tempo di esecuzione previsto
2) L'uguaglianza è definita come: Per tutti gli elementi in a o b, il numero di volte in cui l'elemento si presenta in è uguale al numero di volte in cui si verifica l'elemento in b. L'uguaglianza degli elementi è definita come T.equals ()

private boolean listsAreEquivelent(List<? extends Object> a, List<? extends Object> b) {
    if(a==null) {
        if(b==null) {
            //Here 2 null lists are equivelent. You may want to change this.
            return true;
        } else {
            return false;
        }
    }
    if(b==null) {
        return false;
    }
    Map<Object, Integer> tempMap = new HashMap<>();
    for(Object element : a) {
        Integer currentCount = tempMap.get(element);
        if(currentCount == null) {
            tempMap.put(element, 1);
        } else {
            tempMap.put(element, currentCount+1);
        }
    }
    for(Object element : b) {
        Integer currentCount = tempMap.get(element);
        if(currentCount == null) {
            return false;
        } else {
            tempMap.put(element, currentCount-1);
        }
    }
    for(Integer count : tempMap.values()) {
        if(count != 0) {
            return false;
        }
    }
    return true;
}

Il tempo di esecuzione è O (n) perché stiamo eseguendo inserimenti O (2 * n) in una hashmap e O (3 * n) seleziona hashmap. Non ho testato completamente questo codice, quindi attenzione :)

//Returns true:
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("B","A","A"));
listsAreEquivelent(null,null);
//Returns false:
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("B","A","B"));
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("A","B"));
listsAreEquivelent(Arrays.asList("A","A","B"),null);

5

Prova questa versione che non richiede che l'ordine sia uguale ma che supporta multipli dello stesso valore. Corrispondono solo se ognuno ha la stessa quantità di qualsiasi valore.

public boolean arraysMatch(List<String> elements1, List<String> elements2) {
    // Optional quick test since size must match
    if (elements1.size() != elements2.size()) {
        return false;
    }
    List<String> work = newArrayList(elements2);
    for (String element : elements1) {
        if (!work.remove(element)) {
            return false;
        }
    }
    return work.isEmpty();
}

work.remove (element) è O (n), quindi questa soluzione è O (n ^ 2)
Andrew

O O (n1 * n2) che è più o meno lo stesso
Lee Meador,

Ho anche usato la stessa strategia perché sta gestendo tutti gli scenari e la dimensione della raccolta non è così grande quindi O (n ^ 2) non importa
Naresh Joshi

3

Il metodo uguale su Elenco farà questo, le liste sono ordinate, quindi per essere uguali due liste devono avere gli stessi elementi nello stesso ordine.

return list1.equals(list2);

3
Gli elenchi non vengono ordinati a meno che non vengano ordinati.
Michael Myers

Sigh @ Me stesso. Una risposta così ovvia. Sai che è passato troppo tempo un giorno in cui non puoi più nemmeno premere Ctrl + F su una pagina web. :)
Grundlefleck

2
@mmyers: gli articoli negli elenchi non vengono ordinati a meno che non vengano ordinati. Gli elenchi stessi hanno un ordinamento implicito di articoli (per indice), che non cambiano a meno che tu non modifichi gli articoli nell'elenco. (rispetto ai set o alle raccolte in cui non esiste una garanzia di ordinamento coerente se si esegue l'iterazione due volte)
Jason S

Penso che il significato di daveb dicendo che gli elenchi sono ordinati è che List.equals prende in considerazione l'ordine degli elementi per determinare l'uguaglianza. Vedi il Javadoc.
Laz

2
Ciò che intendo è che un elenco contenente {"A", "B"} e un elenco contenente {"B", "A"} sarebbero ineguali con questo metodo. Potrebbe benissimo essere quello che intendiamo, ma volevo assicurarmi che nessuno lo trascurasse.
Michael Myers

3

Soluzione per caso in cui due elenchi hanno gli stessi elementi, ma un ordine diverso:

public boolean isDifferentLists(List<Integer> listOne, List<Integer> listTwo) {
    if(isNullLists(listOne, listTwo)) {
        return false;
    }

    if (hasDifferentSize(listOne, listTwo)) {
        return true;
    }

    List<Integer> listOneCopy = Lists.newArrayList(listOne);
    List<Integer> listTwoCopy = Lists.newArrayList(listTwo);
    listOneCopy.removeAll(listTwoCopy);

    return CollectionUtils.isNotEmpty(listOneCopy);
}

private boolean isNullLists(List<Integer> listOne, List<Integer> listTwo) {
    return listOne == null && listTwo == null;
}

private boolean hasDifferentSize(List<Integer> listOne, List<Integer> listTwo) {
    return (listOne == null && listTwo != null) || (listOne != null && listTwo == null) || (listOne.size() != listTwo.size());
}

2
Penso che non sia necessario copiare l'elenco Due.
AjahnCharles il

1
Potresti anche notare perché hai usato removeAll()invece di containsAll()(la mia comprensione è che se listTwo contiene duplicati che sono contenuti solo una volta in listOne, l'approccio contieneAll () segnalerebbe erroneamente gli elenchi come uguali).
AjahnCharles il

3

La risposta di Tom è eccellente, concordo pienamente con le sue risposte!

Un aspetto interessante di questa domanda è se hai bisogno di List tipo stesso e del suo ordinamento intrinseco.

Altrimenti puoi degradare a IterableoCollection che ti dà una certa flessibilità nel passare attorno alle strutture di dati che sono ordinate al momento dell'inserimento, piuttosto che nel momento in cui vuoi controllare.

Se l'ordine non ha mai importanza (e non hai elementi duplicati), considera l'utilizzo di a Set.

Se l'ordine conta ma è definito dal tempo di inserimento (e non hai duplicati) considera un LinkedHashSetche è come un TreeSet ma è ordinato dal tempo di inserimento (i duplicati non vengono conteggiati). Questo ti dà anche O(1)accesso ammortizzato O(log n).


2

Codice di esempio:

public static '<'T'>' boolean isListDifferent(List'<'T'>' previousList,
        List'<'T'>' newList) {

    int sizePrevoisList = -1;
    int sizeNewList = -1;

    if (previousList != null && !previousList.isEmpty()) {
        sizePrevoisList = previousList.size();
    }
    if (newList != null && !newList.isEmpty()) {
        sizeNewList = newList.size();
    }

    if ((sizePrevoisList == -1) && (sizeNewList == -1)) {
        return false;
    }

    if (sizeNewList != sizePrevoisList) {
        return true;
    }

    List n_prevois = new ArrayList(previousList);
    List n_new = new ArrayList(newList);

    try {
        Collections.sort(n_prevois);
        Collections.sort(n_new);
    } catch (ClassCastException exp) {
        return true;
    }

    for (int i = 0; i < sizeNewList; i++) {
        Object obj_prevois = n_prevois.get(i);
        Object obj_new = n_new.get(i);
        if (obj_new.equals(obj_prevois)) {
            // Object are same
        } else {
            return true;
        }
    }

    return false;
}

2

Oltre alla risposta di Laurence, se vuoi anche renderlo null-sicuro:

private static <T> boolean listEqualsIgnoreOrder(List<T> list1, List<T> list2) {
    if (list1 == null)
        return list2==null;
    if (list2 == null)
        return list1 == null;
    return new HashSet<>(list1).equals(new HashSet<>(list2));
}

1
Puoi semplificare i controlli:if (list1 == null) return list2==null; if (list2 == null) return false;
Xerus,

Non funziona se gli elenchi sono [a, a, b, c] & [a, b, c] e restituirà true a meno che non si aggiunga un ulteriore controllo per assicurarsi che le dimensioni degli elenchi siano uguali.
Venkat Madhav,

2
list1.equals(list2);

Se l'elenco contiene una classe MyClass personalizzata, questa classe deve sovrascrivere la equalsfunzione.

 class MyClass
  {
  int field=0;
  @0verride
  public boolean equals(Object other)
        {
        if(this==other) return true;
        if(other==null || !(other instanceof MyClass)) return false;
        return this.field== MyClass.class.cast(other).field;
        }
  }

Nota: se si desidera testare equivale a java.util.Set anziché a java.util.List, l'oggetto deve sovrascrivere la hashCode funzione.


1
dovrebbe la riga: return this.field == MyClass.class.cast (altro); be return this.field == MyClass.class.cast (altro) .field;
alpere,

@alpere oh! hai ragione ! Lo riparero '. Grazie !
Pierre,


0

Puoi utilizzare la libreria org.apache.commons.collections di Apache: http://commons.apache.org/collections/apidocs/org/apache/commons/collections/ListUtils.html

public static boolean isEqualList(java.util.Collection list1,
                              java.util.Collection list2)

Ciò richiede anche che gli elementi dell'elenco siano nello stesso ordine.
josh-cain,

puoi ordinare l'elenco prima di confrontare
David Zhao

Certo, puoi farlo purché i tipi siano memorizzati nell'elenco o ordinabili (o hai un comparatore impostato). Tuttavia, l'algoritmo di implementazione di Apache non è diverso dal normale list1.equals (list2), tranne che per essere statico. Vedo dove ho frainteso la domanda e in effetti stava chiedendo come confrontare gli elementi dell'elenco nello stesso ordine. Colpa mia!
josh-cain,

@DavidZhao: il link è morto.
Aniket Kulkarni il


0

Controlla che entrambi gli elenchi non siano nulli. Se le loro dimensioni sono diverse, questi elenchi non sono uguali. Costruisci mappe costituite dagli elementi delle liste come chiavi e dalle loro ripetizioni come valori e confronta le mappe.

Ipotesi, se entrambe le liste sono nulle, le considero uguali.

private boolean compareLists(List<?> l1, List<?> l2) {
    if (l1 == null && l2 == null) {
        return true;
    } else if (l1 == null || l2 == null) {
        return false;
    }

    if (l1.size() != l2.size()) {
        return false;
    }

    Map<?, Integer> m1 = toMap(l1);
    Map<?, Integer> m2 = toMap(l2);

    return m1.equals(m2);
}

private Map<Object, Integer> toMap(List<?> list) {
    //Effective size, not to resize in the future.
    int mapSize = (int) (list.size() / 0.75 + 1);
    Map<Object, Integer> map = new HashMap<>(mapSize);

    for (Object o : list) {
        Integer count = map.get(o);
        if (count == null) {
            map.put(o, 1);
        } else {
            map.put(o, ++count);
        }
    }

    System.out.println(map);
    return map;
}

Si noti che il metodo uguale a deve essere definito correttamente per questi oggetti. https://stackoverflow.com/a/24814634/4587961


1
Hai supposto che un elemento non possa essere presente un numero diverso di volte in ogni elenco, ad es. [x, x, y]Vs [x, y, y]verrebbe restituito vero con la tua implementazione.
AjahnCharles il

@CodeConfident, grazie mille! Ho aggiornato la risposta. Userò un mao!
Yan Khonski,

-2

Dipende dalla classe di elenco concreta che si sta utilizzando. La classe astratta AbstractCollection ha un metodo chiamato IncludesAll (Collection) che accetta un'altra raccolta (una List è una raccolta) e:

Restituisce vero se questa raccolta contiene tutti gli elementi nella raccolta specificata.

Quindi se viene passato un ArrayList puoi chiamare questo metodo per vedere se sono esattamente gli stessi.

       List foo = new ArrayList();
    List bar = new ArrayList();
    String str = "foobar";

    foo.add(str);
    bar.add(str);

    foo.containsAll(bar);

Il motivo per contiene All () è perché scorre nella prima lista cercando la corrispondenza nella seconda lista. Quindi se sono fuori uso equals () non lo raccoglierà.

EDIT: Voglio solo fare un commento qui sul tempo di esecuzione ammortizzato dell'esecuzione delle varie opzioni offerte. Il tempo di esecuzione è importante? Sicuro. È l'unica cosa che dovresti considerare? No.

Il costo della copia di OGNI singolo elemento dai tuoi elenchi in altri elenchi richiede tempo e occupa anche una buona porzione di memoria (raddoppiando effettivamente la memoria che stai utilizzando).

Quindi, se la memoria nella tua JVM non è un problema (come dovrebbe essere generalmente), devi comunque considerare il tempo necessario per copiare ogni elemento da due elenchi in due TreeSet. Ricorda che sta ordinando tutti gli elementi mentre li inserisce.

Il mio consiglio finale? È necessario considerare il set di dati e il numero di elementi presenti nel set di dati, nonché la dimensione di ciascun oggetto nel set di dati prima di poter prendere una buona decisione qui. Gioca con loro, creane uno per ogni direzione e vedi quale corre più veloce. È un buon esercizio.


2
Non dovrebbe essere foo.containsAll (bar) && bar.containsAll (foo); ?
Carl Manaster,

No, passa attraverso ogni elemento in foo e vede se la barra contiene quell'elemento. Assicura quindi che la lunghezza sia la stessa delle due liste. Se per ogni foo c'è un elemento nella barra tale che foo.element == bar.element e foo.length == bar.length allora contengono gli stessi elementi.
amischiefr,

sappiamo se esiste una garanzia di efficienza? o è tipicamente O (n ^ 2)?
Tom

Come qualsiasi altro array che scorre alla ricerca di un elemento corrispondente, il tempo di esecuzione peggiore sarà O (n ^ 2). In questo caso, sembra che l'implementazione stia iterando attraverso un elemento alla volta cercando la corrispondenza. Non speculerò sul tempo di esecuzione ammortizzato, ma sì, il caso peggiore è O (n ^ 2).
amischiefr,

1
Questo non funziona: {1,2,2} .containsAll ({1,1,2}) e ​​viceversa, e le due liste hanno le stesse dimensioni.
comco,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.