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



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



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



пример:



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


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



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



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

591   16  

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

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

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

параметры:

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

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

// 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 ' s equals метод, который (конечно) игнорирует заказ.

если мощность имеет значение, то вы должны ограничиться услугами, предоставляемыми 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 я был сначала разочарован, что ни один из CollectionUtils methods предоставляет перегруженную версию, позволяющую вам наложить свой собственный 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);
}

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

list1.equals(list2)

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

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

Comments

    Ничего не найдено.