152

我有两个ArrayLists型Answer(自制班)。

我想比较这两个列表,看看它们是否包含相同的内容,但顺序无关紧要。

例子:

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

List.equals表示如果两个列表包含相同的大小、内容和元素的顺序,则它们是相等的。我想要同样的东西,但顺序无关紧要。

有没有一种简单的方法可以做到这一点?还是我需要做一个嵌套的 for 循环,并手动检查两个列表的每个索引?

注意:我无法将它们更改ArrayList为另一种类型的列表,它们需要保持不变。

4

18 回答 18

167

对于任何列表,最简单的方法可能是:

listA.containsAll(listB) && listB.containsAll(listA)
于 2012-11-21T20:33:58.453 回答
139

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);
}
于 2012-11-21T20:06:05.683 回答
100

Apache Commons Collections 再次救援:

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 包含具有完全相同基数的完全相同的元素,则 返回。

也就是说,对于ab中的每个元素e,如果 a 中的e基数等于b中的e的基数。

参数:

  • a- 第一次收藏,一定不能null
  • b- 第二个集合,一定不能null

返回: true如果集合包含具有相同基数的相同元素。

于 2014-03-24T19:53:12.517 回答
14
// 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;
}
于 2012-11-21T21:02:55.890 回答
13

我会说这些答案错过了一个窍门。

Bloch 在他的基本、精彩、简洁的Effective Java中,在第 47 项的标题“了解和使用库”中说,“总而言之,不要重新发明轮子”。他给出了几个非常明确的理由。

这里有一些答案建议来自CollectionUtilsApache Commons Collections 库中的方法,但没有人发现回答这个问题的最漂亮、最优雅的方式

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

}

罪魁祸首:即两者都不相同的元素Lists。使用andlist1来确定哪些罪魁祸首list2是相对简单的。 然而,在 { "a", "a", "b" } 和 { "a", "b", "b" } 的情况下,它往往会崩溃……除非这不是软件的失败,而是所需任务的微妙之处/模糊性的性质所固有的。CollectionUtils.intersection( list1, culprits )CollectionUtils.intersection( list2, culprits )
disjunction

您始终可以检查由 Apache 工程师制作的此类任务的源代码(l. 287)。使用他们的代码的一个好处是它已经过彻底的试验和测试,许多边缘情况和陷阱都可以预见和处理。如果需要,您可以根据需要复制和调整此代码。


NB 一开始我很失望,没有一种CollectionUtils方法提供重载版本,使您能够强加自己的Comparator(因此您可以重新定义equals以适应您的目的)。

但是从 collections4 4.0 开始有一个新类,Equator它“确定 T 类型的对象之间的相等性”。在检查 collections4 CollectionUtils.java 的源代码时,他们似乎将其与某些方法一起使用,但据我所知,这不适用于文件顶部的方法,使用CardinalityHelper类...包括disjunctionintersection

我推测 Apache 的人还没有解决这个问题,因为它不是微不足道的:你必须创建类似“AbstractEquatingCollection”类的东西,而不是使用其元素的固有方法equalshashCode方法而是必须使用那些的Equator所有基本方法,例如addcontains等。注意实际上,当您查看源代码时,AbstractCollection并没有实现add,也没有实现它的抽象子类,例如AbstractSet...您必须等到具体的类,例如HashSetArrayList之前add被实施。很头疼。

我想,与此同时,请注意这个空间。显而易见的临时解决方案是将所有元素包装在一个定制的包装类中,该类使用equalshashCode实现你想要的那种平等……然后操纵Collections这些包装对象。

于 2016-12-09T20:25:09.573 回答
10

如果项目的基数无关紧要(意思是:重复的元素被视为一个),那么有一种方法可以做到这一点而不必排序:

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

这将创建一个Setout of each List,然后使用HashSet'sequals方法,该方法(当然)忽略排序。

如果基数很重要,那么您必须将自己限制在由List; 在这种情况下,@jschoen 的回答会更合适。

于 2012-11-21T20:33:24.120 回答
6

这是基于@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 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;
}
于 2013-12-01T20:31:31.720 回答
6

将列表转换为 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));
于 2016-05-13T12:26:01.640 回答
5

想想如果没有计算机或编程语言,你将如何自己做这件事。我给你两个元素列表,你必须告诉我它们是否包含相同的元素。你会怎么做?

如上所述,一种方法是对列表进行排序,然后逐个元素地查看它们是否相等(这是什么List.equals)。这意味着要么你被允许修改列表,要么你被允许复制它们——在不知道分配的情况下,我不知道是否允许其中一个/两个。

另一种方法是遍历每个列表,计算每个元素出现的次数。如果两个列表最后的计数相同,则它们具有相同的元素。其代码是将每个列表转换为一个映射,elem -> (# of times the elem appears in the list)然后调用equals这两个映射。如果映射是HashMap,则每个转换都是 O(N) 操作,比较也是如此。这将在时间方面为您提供一个非常有效的算法,但代价是一些额外的内存。

于 2012-11-21T20:22:46.960 回答
5

我遇到了同样的问题,并提出了不同的解决方案。当涉及重复时,这也适用:

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²) 复杂度(尽管与此处其他答案中的解决方案相比,我还没有测试过它的真实性能);
  • 提前退出;
  • 检查是否为空;
  • 即使涉及重复项也可以工作:如果您有一个数组[1,2,3,3]和另一个数组,[1,2,2,3]这里的大多数解决方案都会告诉您,在不考虑顺序时它们是相同的。该解决方案通过从临时列表中删除相等的元素来避免这种情况;
  • 使用语义相等 ( equals) 而不是引用相等 ( ==);
  • 不对itens进行排序,因此不需要对它们进行排序(按implement Comparable)即可使此解决方案起作用。
于 2015-07-24T12:31:04.583 回答
3

如果您不希望对集合进行排序并且您需要 ["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);
}
于 2017-02-20T08:54:25.987 回答
2

Solution which leverages CollectionUtils subtract method:

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;
  }
}
于 2014-01-28T11:12:04.407 回答
1

如果您关心顺序,那么只需使用 equals 方法:

list1.equals(list2)

如果您不在乎订单,请使用此

Collections.sort(list1);
Collections.sort(list2);      
list1.equals(list2)
于 2016-11-24T13:13:10.360 回答
1

单线法:)

  1. 集合的项目没有实现接口 Comparable<? 超级T>

     static boolean isEqualCollection(Collection<?> a, Collection<?> b) {
         return a == b || (a != null && b != null && a.size() == b.size()
             && a.stream().collect(Collectors.toMap(Function.identity(), s -> 1L, Long::sum)).equals(b.stream().collect(Collectors.toMap(Function.identity(), s -> 1L, Long::sum))));
     }
    
  2. 集合的项目实现接口 Comparable<? 超级T>

     static <T extends Comparable<? super T>> boolean  isEqualCollection2(Collection<T> a, Collection<T> b) {
       return a == b || (a != null && b != null && a.size() == b.size() && a.stream().sorted().collect(Collectors.toList()).equals(b.stream().sorted().collect(Collectors.toList())));
     }
    
  3. 通过https://github.com/retrostreams/android-retrostreams支持 Android5 & Android6

    static boolean isEqualCollection(Collection<?> a, Collection<?> b) {
     return a == b || (a != null && b != null && a.size() == b.size()
             && StreamSupport.stream(a).collect(Collectors.toMap(Function.identity(), s->1L, Longs::sum)).equals(StreamSupport.stream(b).collect(Collectors.toMap(Function.identity(), s->1L, Longs::sum))));
    }
    

////测试用例

    boolean isEquals1 = isEqualCollection(null, null); //true
    boolean isEquals2 = isEqualCollection(null, Arrays.asList("1", "2")); //false
    boolean isEquals3 = isEqualCollection(Arrays.asList("1", "2"), null); //false
    boolean isEquals4 = isEqualCollection(Arrays.asList("1", "2", "2"), Arrays.asList("1", "1", "2")); //false
    boolean isEquals5 = isEqualCollection(Arrays.asList("1", "2"), Arrays.asList("2", "1")); //true
    boolean isEquals6 = isEqualCollection(Arrays.asList("1", 2.0), Arrays.asList(2.0, "1")); //true
    boolean isEquals7 = isEqualCollection(Arrays.asList("1", 2.0, 100L), Arrays.asList(2.0, 100L, "1")); //true
    boolean isEquals8 = isEqualCollection(Arrays.asList("1", null, 2.0, 100L), Arrays.asList(2.0, null, 100L, "1")); //true
于 2020-09-16T03:46:58.963 回答
0

这是检查可以包含空值的数组列表是否相等的另一种方法:

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);
}
于 2016-08-02T14:28:10.630 回答
0

我的解决方案。它不是那么酷,但效果很好。

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();
}
于 2019-01-03T02:23:11.370 回答
0

两全其美[@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;
}
于 2016-01-08T12:00:10.967 回答
-1

在这种情况下,列表 {"a", "b"} 和 {"b","a"} 是相等的。并且 {"a", "b"} 和 {"b","a","c"} 不相等。如果您使用复杂对象列表,请记住覆盖equals方法,因为containsAll在内部使用它。

if (oneList.size() == secondList.size() && oneList.containsAll(secondList)){
        areEqual = true;
}
于 2016-12-12T14:39:22.787 回答