Подтвердить что ты не робот

Java ArrayList - как я могу определить, равны ли два списка, порядок не имеет значения?

У меня есть два ArrayList типа Answer (самодельный класс).

Я хотел бы сравнить два списка, чтобы увидеть, содержат ли они одинаковое содержимое, но без учета порядка.

Пример:

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

List.equals утверждает, что два списка равны, если они содержат одинаковый размер, содержимое и порядок элементов. Я хочу то же самое, но без значения порядка.

Есть ли простой способ сделать это? Или мне нужно будет сделать вложенный цикл for и вручную проверить каждый индекс обоих списков?

Примечание: я не могу изменить их с ArrayList на другой тип списка, они должны остаться такими.

4b9b3361

Ответ 1

Вы можете отсортировать оба списка с помощью 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);
}

Ответ 2

Вероятно, самый простой способ для любого списка:

listA.containsAll(listB) && listB.containsAll(listA)

Ответ 3

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)); // 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)

Возвращает true, если данный Collection содержит точно такие же элементы с точно такой же мощностью.

То есть, если мощность e в равна мощности e в b, для каждого элемент e в или b.

Параметры:

  • a - первая коллекция не должна быть null
  • b - второй коллекции не должно быть null

Возвращает: true, если коллекции содержат те же элементы с одинаковыми мощностями.

Ответ 4

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

Ответ 5

Если количество элементов не имеет значения (имеется в виду, что повторяющиеся элементы рассматриваются как единое целое), то есть способ сделать это без сортировки:

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

Это создаст Set из каждого List, а затем будет использовать метод equals HashSet который (конечно) игнорирует порядок.

Если кардинальность имеет значение, вы должны ограничиться возможностями, предоставляемыми List; @jschoen ответ будет более уместным в этом случае.

Ответ 6

Это основано на решении @cHao. Я включил несколько исправлений и улучшений производительности. Это работает примерно вдвое быстрее, чем решение с равным порядком копирования. Работает для любого типа коллекции. Пустые коллекции и ноль считаются равными. Используйте в ваших интересах;)

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

Ответ 7

Я бы сказал, что эти ответы пропускают хитрость.

Блох в своей основной, замечательной, лаконичной Effective Java говорит в пункте 47 заголовок "Знай и используй библиотеки", "Подводя итог, не изобретай велосипед". И он приводит несколько очень четких причин, почему нет.

Здесь есть несколько ответов, которые предлагают методы из CollectionUtils в библиотеке коллекций Apache Commons, но ни один не нашел самый красивый и элегантный способ ответить на этот вопрос:

Collection<Object> culprits = CollectionUtils.disjunction( list1, list2 );
if( ! culprits.isEmpty() ){
  // ... do something with the culprits, i.e. elements which are not common

}

Виновные: то есть элементы, которые не являются общими для обоих Lists. Определить, какие преступники принадлежат list1, а какие list2, относительно просто, используя CollectionUtils.intersection( list1, culprits ) и CollectionUtils.intersection( list2, culprits ).
Однако он имеет тенденцию распадаться в таких случаях, как {"a", "a", "b"} disjunction с {"a", "b", "b"}... за исключением того, что это не является ошибкой программное обеспечение, но присуще тонкостям/неясностям желаемой задачи.


NB. Сначала я был разочарован тем, что ни один из методов CollectionUtils не предоставляет перегруженной версии, позволяющей вам навязывать свой собственный Comparator (поэтому вы можете переопределить equals в соответствии со своими целями).

Но из коллекций 4 4.0 появился новый класс, Equator, который "определяет равенство между объектами типа T". При проверке исходного кода collection4 CollectionUtils.java они, кажется, используют это с некоторыми методами, но, насколько я могу судить, это не применимо к методам в верхней части файла, использующим класс CardinalityHelper... которые включают в себя disjunction и intersection.

Я предполагаю, что люди Apache еще не дошли до этого, потому что это нетривиально: вам нужно создать что-то вроде класса "AbstractEquatingCollection", который вместо использования методов, присущих его элементам equals и hashCode вместо этого придется использовать те из Equator для всех основных методов, таких как add, contains и т.д. В действительности, когда вы смотрите на исходный код, AbstractCollection не реализует add, ни сделать его абстрактные подклассы, такие как AbstractSet... вам нужно подождать, пока конкретные классы, такие как HashSet и ArrayList, прежде чем add будет реализован. Довольно головная боль.

Я полагаю, что пока наблюдаю это пространство. Очевидным промежуточным решением было бы обернуть все ваши элементы в специальный класс-оболочку, который использует equals и hashCode для реализации желаемого вами типа равенства, а затем манипулировать Collections этих объектов-оболочек.

Ответ 8

Подумайте, как вы это сделаете самостоятельно, без компьютера или языка программирования. Я даю вам два списка элементов, и вы должны сказать мне, содержат ли они одни и те же элементы. Как вы это сделаете?

Один из подходов, как упоминалось выше, заключается в сортировке списков, а затем поэтапных элементах, чтобы убедиться, что они равны (что и делает List.equals). Это означает, что вам разрешено изменять списки или вам разрешено копировать их - и, не зная назначения, я не знаю, разрешено ли это/оба из них.

Другой подход состоял бы в том, чтобы пройти через каждый список, подсчитывая, сколько раз каждый элемент появляется. Если оба списка имеют одинаковые подсчеты в конце, они имеют одинаковые элементы. Код для этого должен был бы перевести каждый список на карту elem -> (# of times the elem appears in the list), а затем вызвать equals на двух картах. Если карты HashMap, каждый из этих переводов является операцией O (N), как и сравнение. Это даст вам довольно эффективный алгоритм с точки зрения времени, за счет некоторой дополнительной памяти.

Ответ 9

У меня была такая же проблема, и я придумал другое решение. Это также работает, когда задействованы дубликаты:

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 (N²) (хотя я не тестировал ее реальную производительность по сравнению с решениями в других ответах здесь);
  • выходит раньше;
  • проверяет значение null;
  • работает даже при использовании дубликатов: если у вас есть массив [1,2,3,3] и еще один массив [1,2,2,3], большинство решений здесь говорят вам, что они одинаковы, если не учитывать порядок. Это решение позволяет избежать этого путем удаления равных элементов из временных списков;
  • использует семантическое равенство (equals), а не ссылочное равенство (==);
  • не сортирует itens, поэтому для этого решения не нужно сортировать (implement Comparable).

Ответ 10

Преобразование списков в 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));

Ответ 11

Решение, которое использует метод вычитания 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;
  }
}

Ответ 12

Если вы не хотите сортировать коллекции, и вам нужен результат, который [ "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);
}

Ответ 13

Если вы заботитесь о порядке, просто используйте метод equals:

list1.equals(list2)

Если вам не нужен порядок, используйте это

Collections.sort(list1);
Collections.sort(list2);      
list1.equals(list2)

Ответ 14

Лучшее из обоих миров [@DiddiZ, @Chalkos]: этот в основном основан на методе @Chalkos, но исправляет ошибку (ifst.next()) и улучшает начальные проверки (взяты из @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;
}

Ответ 15

Это альтернативный способ проверки равенства списков массивов, который может содержать нулевые значения:

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

Ответ 16

Мое решение для этого. Это не так круто, но работает хорошо.

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

Ответ 17

В этом случае списки { "a", "b" } и { "b", "a" } равны. И { "a", "b" } и { "b", "a", "c" } не равны. Если вы используете список сложных объектов, не забудьте переопределить метод equals, как containsAll использует его внутри.

if (oneList.size() == secondList.size() && oneList.containsAll(secondList)){
        areEqual = true;
}