J'ai deux ArrayList
s 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.
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);
}
La manière la plus simple de any list serait probablement:
listA.containsAll(listB) && listB.containsAll(listA)
_ {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
org.Apache.commons.collections4.CollectionUtils
public static boolean isEqualCollection(Java.util.Collection a, Java.util.Collection b)
Retourne
true
si leCollection
s 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 êtrenull
b
- le second collection, ne doit pas êtrenull
Retourne:
true
si les collections contiennent Les mêmes éléments avec les mêmes cardinalités.
// 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;
}
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.
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;
}
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:
[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;equals
) et ne fait pas référence à l'égalité (==
);implement Comparable
) pour que cette solution fonctionne.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.
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.
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));
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;
}
}
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);
}
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();
}
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)
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);
}
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;
}