web-dev-qa-db-fra.com

Java ArrayList - comment savoir si deux listes sont égales, l'ordre n'a pas d'importance?

J'ai deux ArrayLists de type Answer (classe autodidacte). 

Je voudrais comparer les deux listes pour voir si elles contiennent le même contenu, mais sans ordre d'importance.

Exemple:

//These should be equal.
ArrayList<String> listA = {"a", "b", "c"}
ArrayList<String> listB = {"b", "c", "a"}

List.equals indique que deux listes sont égales si elles contiennent la même taille, le même contenu et le même ordre d'éléments. Je veux la même chose, mais sans ordre.

Y a-t-il un moyen simple de faire cela? Ou devrais-je créer une boucle imbriquée et vérifier manuellement chaque index des deux listes?

Note: Je ne peux pas les changer de ArrayList à un autre type de liste, ils doivent rester comme ça.

105
iaacp

Vous pouvez trier les deux listes à l'aide de Collections.sort(), puis utiliser la méthode equals. Une solution légèrement meilleure consiste à vérifier d’abord si elles ont la même longueur avant de commander. Si ce n’est pas le cas, elles ne sont pas égales, puis triez puis utilisez égal. Par exemple, si vous aviez deux listes de chaînes, ce serait quelque chose comme:

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);
}
110
Jacob Schoen

La manière la plus simple de any list serait probablement:

listA.containsAll(listB) && listB.containsAll(listA)
117
user381105

_ {Apache Commons _ Des collections à la rescousse:

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

Docs:

org.Apache.commons.collections4.CollectionUtils

public static boolean isEqualCollection(Java.util.Collection a,
                                        Java.util.Collection b)

Retourne true si le Collections donné contient exactement le même éléments avec exactement les mêmes cardinalités.

C'est-à-dire, si le cardinalité de e dans a est égale à la cardinalité de e dans b, pour chaque élément e dans a ou b.

Paramètres:

  • a - la première collection, ne doit pas être null
  • b - le second collection, ne doit pas être null

Retourne:truesi les collections contiennent Les mêmes éléments avec les mêmes cardinalités.

69
acdcjunior
// 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;
}
8
cHao

Si la cardinalité des éléments n'a pas d'importance (ce qui signifie: les éléments répétés sont considérés comme un), il existe alors un moyen de le faire sans avoir à trier:

boolean result = new HashSet<>(listA).equals(new HashSet<>(listB));

Cela créera une Set sur chaque List, puis utilisera la méthode HashSet, equals, qui (bien sûr) ne tient pas compte de l'ordre.

Si la cardinalité compte, vous devez vous limiter aux fonctionnalités fournies par List; La réponse de @ jschoen serait plus appropriée dans ce cas.

6
Isaac

Ceci est basé sur la solution @ cHao. J'ai inclus plusieurs correctifs et améliorations des performances. Cette solution est environ deux fois plus rapide que la solution de copie ordonnée. Fonctionne pour tout type de collection. Les collections vides et nulles sont considérées comme égales. Utilisez à votre avantage;)

/**
 * 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 list1
    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 list2
    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--;
    }

    // If any count is nonzero at this point, then the two lists don't match
    for (final Count count : counts.values())
        if (count.count != 0)
            return false;

    return true;
}
5
DiddiZ

J'ai eu le même problème et j'ai trouvé une solution différente. Celui-ci fonctionne également lorsque des doublons sont impliqués:

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

Avantages par rapport à d'autres solutions:

  • complexité inférieure à O (N²) (bien que je n’aie pas testé ses performances réelles par rapport aux solutions proposées dans d’autres réponses ici);
  • sort tôt;
  • vérifie la valeur null;
  • fonctionne même lorsque des doublons sont impliqués: si vous avez un tableau [1,2,3,3] et un autre tableau [1,2,2,3], la plupart des solutions proposées ici indiquent qu'elles sont identiques, même si vous n'envisagez pas l'ordre. Cette solution évite cela en supprimant des éléments égaux des listes temporaires;
  • utilise l'égalité sémantique (equals) et ne fait pas référence à l'égalité (==);
  • ne trie pas les éléments, ils n'ont donc pas besoin d'être triables (par implement Comparable) pour que cette solution fonctionne.
4
Chalkos

Je dirais que ces réponses manquent un tour.

Bloch, dans son essentiel, merveilleux, concis Effective Java _, explique au point 47, intitulé "Connaître et utiliser les bibliothèques", "Pour résumer, ne réinventez pas la roue". Et il donne plusieurs raisons très claires pour lesquelles pas.

Quelques réponses ici suggèrent des méthodes de CollectionUtils dans la bibliothèque de collections d'Apache Commons, mais aucune n'a repéré la manière la plus belle et la plus élégante de répondre à cette question }:

Collection<Object> culprits = CollectionUtils.disjunction( list1, list2 );
if( ! culprits.isEmpty() ){
  // ... then in most cases you will ardently wish to do something to these culprits: 
  // at the very least, name-and-shame them.

}

Culprits: c’est-à-dire les éléments qui ne sont pas communs aux deux Lists. Déterminer quels coupables appartiennent à list1 et lesquels à list2 est relativement simple en utilisant CollectionUtils.intersection( list1, culprits ) et CollectionUtils.intersection( list2, culprits ).
Cependant, il a tendance à se désagréger dans des cas comme {"a", "a", "b"} disjunction avec {"a", "b", "b"} ... sauf que ce n'est pas un défaut de le logiciel, mais inhérent à la nature des subtilités/ambiguïtés de la tâche souhaitée.


NB J'ai été déçu au début qu'aucune des méthodes CollectionUtils ne fournit une version surchargée vous permettant d'imposer votre propre Comparator (vous pouvez donc redéfinir equals en fonction de vos objectifs).

Mais à partir de collections4 4.0, il existe une nouvelle classe, Equator, qui "détermine l'égalité entre les objets de type T". Lors de l'examen du code source de collections4 CollectionUtils.Java, ils semblent l'utiliser avec certaines méthodes, mais pour autant que je sache, cela ne s'applique pas aux méthodes figurant en haut du fichier, à l'aide de la classe CardinalityHelper ... qui incluent disjunction et intersection

Je suppose que le peuple Apache n’a pas encore compris cela parce que ce n’est pas trivial: vous devez créer quelque chose comme une classe "AbstractEquatingCollection" qui, au lieu d’utiliser les méthodes inhérentes equals et hashCode de ses éléments, devrait plutôt utilisez celles de Equator pour toutes les méthodes de base, telles que add, contains, etc. NB en fait, lorsque vous examinez le code source, AbstractCollection n'implémente pas add, ni ses sous-classes abstraites telles que AbstractSet... vous devez attendre que les classes concrètes telles que HashSet et ArrayList avant que add soit implémentée. Assez mal à la tête.

En attendant surveillez cet espace, je suppose. La solution provisoire évidente consiste à envelopper tous vos éléments dans une classe d'encapsulage sur mesure qui utilise equals et hashCode pour implémenter le type d'égalité que vous souhaitez ... puis manipulez Collections de ces objets enveloppants.

4
mike rodent

Pensez à la façon dont vous le feriez vous-même, sans ordinateur ni langage de programmation. Je vous donne deux listes d'éléments et vous devez me dire si elles contiennent les mêmes éléments. Comment le ferais-tu?

Une approche, comme mentionné ci-dessus, consiste à trier les listes et ensuite, élément par élément, pour voir si elles sont égales (ce que fait List.equals). Cela signifie que vous êtes autorisé à modifier les listes ou à les copier - et sans connaître l'affectation, je ne peux pas savoir si l'une ou les deux sont autorisées.

Une autre approche consisterait à parcourir chaque liste en comptant combien de fois chaque élément apparaît. Si les deux listes ont les mêmes comptes à la fin, elles contiennent les mêmes éléments. Le code pour cela serait de traduire chaque liste en une carte de elem -> (# of times the elem appears in the list), puis d’appeler equals sur les deux cartes. Si les mappes sont HashMap, chacune de ces traductions est une opération O(N), de même que la comparaison. Cela va vous donner un algorithme assez efficace en termes de temps, au prix de mémoire supplémentaire.

4
yshavit

La conversion des listes en Guava's Multiset fonctionne très bien. Ils sont comparés indépendamment de leur ordre et les éléments en double sont également pris en compte.

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));
3
Natix

Solution qui exploite la méthode de soustraction 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;
  }
}
2
maxdanilkin

Si vous n'espérez pas trier les collections et que vous devez obtenir le résultat suivant: ["A" "B" "C"] n'est pas égal à ["B" "B" "A" "C"],

l1.containsAll(l2)&&l2.containsAll(l1)

ne suffit pas, vous devez également vérifier la taille:

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

Ma solution pour cela. Ce n'est pas si cool, mais ça marche bien.

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();
}
0
Cícero Moura

Si vous vous souciez de l'ordre, utilisez simplement la méthode equals:

list1.equals(list2)

Si vous ne vous souciez pas de l'ordre, utilisez ce

Collections.sort(list1);
Collections.sort(list2);      
list1.equals(list2)
0
Ramesh Kumar

C'est un moyen alternatif de vérifier l'égalité des listes de tableaux pouvant contenir des valeurs 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);
}
0
Sa Qada

Le meilleur des deux mondes [@DiddiZ, @Chalkos]: celui-ci repose principalement sur la méthode @Chalkos, mais corrige un bogue (ifst.next ()), et améliore les contrôles initiaux copier la première collection (supprime simplement les éléments d'une copie de la seconde collection).

Ne nécessitant pas de fonction de hachage ni de tri, et permettant une existence précoce sur la non-égalité, c'est la mise en œuvre la plus efficace à ce jour. C'est à moins que vous n'ayez une longueur de collection de plusieurs milliers ou plus et une fonction de hachage très simple.

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