Java ArrayList-как я могу сказать, если два списка равны, порядок не имеет значения?
У меня есть два ArrayLists типа ответа(самодельный класс).
Я хотел бы сравнить два списка, чтобы увидеть, если они содержат то же самое содержимое, но без порядка имеет значение.
пример:
//These should be equal.
ArrayList<String> listA = {"a", "b", "c"}
ArrayList<String> listB = {"b", "c", "a"}
список.equals указывает, что два списка равны, если они содержат одинаковый размер, содержимое и порядок элементов. Я хочу то же самое, но без порядка.
есть ли простой способ сделать это? Или мне нужно сделать вложенный цикл for, и вручную проверить каждый индекс обоих списков?
примечание: Я не могу изменить их из класса ArrayList в другой тип списка, они должны оставаться.
16 ответов:
Вы можете отсортировать оба списка с помощью
Collections.sort()а затем использовать метод equals. Слабо лучшее решение-сначала проверить, одинаковы ли они по длине перед заказом, если нет, то они не равны, затем сортировать, а затем использовать равные. Например, если бы у вас было два списка строк, это было бы что-то вроде: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); }
наверное, самый простой способ для любой список будет такой:
listA.containsAll(listB) && listB.containsAll(listA)
Apache Commons коллекции на помощь еще раз:
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)); // trueList<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)Возвращает
trueесли данныйCollections содержат точно такие же элементы с точно такими же мощностями.то есть, если мощность e in a равно мощности e на b для каждого элемент e на a или b.
параметры:
a- первая коллекция, не должно бытьnullb- второй коллекция, не должно бытьnullвозвращает:
trueiff коллекции содержать те же элементы с теми же мощностями.
// 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; }
это основано на решении @cHao. Я включил несколько исправлений и улучшений производительности. Это работает примерно в два раза быстрее решения equals-ordered-copy. Работает для любого типа коллекции. Пустые коллекции и null считаются равными. Используйте в своих интересах;)
/** * 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; }
если количество объектов не имеет значения (т. е. повторяющиеся элементы считать один раз), то есть способ сделать это без необходимости сортировки:
boolean result = new HashSet<>(listA).equals(new HashSet<>(listB));это создаст
SetС каждогоList, а затем использоватьHashSet' sequalsметод, который (конечно) игнорирует заказ.если мощность имеет значение, то вы должны ограничиться услугами, предоставляемыми
List; @jschoen ответ был бы более подходящим в этом случай.
подумайте, как бы вы сделали это сами, без компьютера или языка программирования. Я даю вам два списка элементов, и вы должны сказать мне, если они содержат одинаковые элементы. Как бы вы это сделали?
один из подходов, как упоминалось выше, состоит в том, чтобы отсортировать списки, а затем перейти от элемента к элементу, чтобы увидеть, равны ли они (что такое
List.equalsделает). Это означает, что либо вам разрешено изменять списки, либо вам разрешено их копировать - и, не зная назначения, я не могу знать если любой/оба из них разрешены.другой подход мог бы пройти через каждый список, подсчитывая, сколько раз каждый элемент. Если оба списка имеют одинаковые счетчики в конце, они имеют одинаковые элементы. Код для этого будет переводить каждый список на карту
elem -> (# of times the elem appears in the list)а потом позвонитеequalsна двух картах. Если картыHashMap, каждый из этих переводов является операцией O(N), как и сравнение. Это даст вам довольно эффективный алгоритм в терминах времени, за счет некоторой дополнительной памяти.
у меня была такая же проблема, и я придумал другое решение. Это также работает, когда задействованы дубликаты:
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; }преимущества по сравнению с другими решениями:
- меньше, чем сложность O(N2) (хотя я не проверял его реальную производительность по сравнению с решениями в других ответах здесь);
- выходит рано;
- проверка на null;
- работает даже при наличии дубликатов: если у вас есть массив
[1,2,3,3]и еще один массив[1,2,2,3]большинство решений здесь говорят вам, что они одинаковы, когда не рассматривают заказ. Это решение позволяет избежать этого путем удаления равных элементов из временных списков;- использует семантическое равенство (
equals) и не равенство ссылок (==);- не сортирует itens, поэтому они не должны быть сортируемыми (by
implement Comparable) для этого решения для работы.
я бы сказал, что эти ответы упустит.
блох, в своем сущностном, чудесном, лаконичном Эффективная Java, говорит, в пункте 47, название "знать и использовать библиотеки", "подводя итог, не изобретать колесо". И он приводит несколько очень четких причин, почему нет.
здесь есть несколько ответов, которые предлагают методы от
CollectionUtilsв библиотеке Apache Commons Collections, но никто не заметил самый красивый, элегантный способ ответить на этот вопрос: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. }виновники: т. е. элементы, которые не являются общими для обоих
Lists. Определение того, какие преступники принадлежатlist1и дляlist2относительно просто с помощьюCollectionUtils.intersection( list1, culprits )иCollectionUtils.intersection( list2, culprits ).
Однако он имеет тенденцию разваливаться в таких случаях, как { "a", "a", "b" }disjunctionС {"a", "b", "b"}... разве что это не недостаток программного обеспечения, а присущие природе тонкости / неоднозначности желаемого задача.
NB я был сначала разочарован, что ни один из
CollectionUtilsmethods предоставляет перегруженную версию, позволяющую вам наложить свой собственныйComparator(так что вы можете переопределитьequalsв соответствии с собственными целями).но из collections4 4.0 есть новый класс,
Equatorкоторый "определяет равенство между объектами типа T". На экспертизе исходного кода коллекций 4 CollectionUtils.Java они, кажется, используют это с некоторыми методами, но, насколько я могу сделать это не применимо к методам в верхней части файла, используяCardinalityHelperкласса... которые включаютdisjunctionиintersection.я предполагаю, что люди Apache еще не дошли до этого, потому что это нетривиально: вам нужно будет создать что-то вроде класса "AbstractEquatingCollection", который вместо того, чтобы использовать присущие ему элементы
equalsиhashCodeметоды вместо этого придется использовать те изEquatorдля всех основных методов, такие какadd,containsи т. д. NB на самом деле, когда вы смотрите на исходный код,AbstractCollectionне выполнятьadd, а также абстрактные подклассы, такие какAbstractSet... вы должны ждать, пока конкретные классы, такие какHashSetиArrayListдо есть. Довольно головная боль.тем временем наблюдайте за этим пространством, я полагаю. Очевидным промежуточным решением было бы обернуть все ваши элементы в специальный класс-оболочку, который использует
equalsиhashCodeдля реализации вида вы хотите равенства... тогда манипулируйтеCollectionsиз этих объектов обертки.
преобразование списков в Guava Multiset очень хорошо работает. Они сравниваются независимо от их порядка, а также учитываются повторяющиеся элементы.
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));
решение, которое использует метод вычитания 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; } }
Если вы не надеетесь сортировать коллекции, и вам нужен результат, который ["A" "B" "C"] не равен ["B" "B" "A" "C"],
l1.containsAll(l2)&&l2.containsAll(l1)недостаточно, вам нужно проверить размер тоже:
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); }
лучший из обоих миров [@DiddiZ, @Chalkos]: этот в основном основан на методе @Chalkos, но исправляет ошибку (ifst.далее ()) и улучшает начальные проверки (взятые из @DiddiZ), а также устраняет необходимость копирования первой коллекции (просто удаляет элементы из копии второй коллекции).
Не требуя функции хэширования или сортировки и позволяя раннее существовать на неравенстве, это наиболее эффективная реализация. То есть, если у вас нет длины коллекции в тысячи или больше, и очень простая функция хэширования.
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; }
Это альтернативный способ проверить равенство списков массивов, которые могут содержать нулевые значения:
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); }
в этом случае списки {"a", "b"} и {"b","a"} равны. И {"a", "b"} и {"b","a", "c"} не равны. Если вы используете список сложных объектов, не забудьте переопределить равна метод, как containsAll использует его внутри.
if (oneList.size() == secondList.size() && oneList.containsAll(secondList)){ areEqual = true; }
Comments