Java ArrayList - 我如何判断两个列表是不是相等,顺序无关紧要?

Posted

技术标签:

【中文标题】Java ArrayList - 我如何判断两个列表是不是相等,顺序无关紧要?【英文标题】:Java ArrayList - how can I tell if two lists are equal, order not mattering?Java ArrayList - 我如何判断两个列表是否相等,顺序无关紧要? 【发布时间】:2012-11-10 04:03:52 【问题描述】:

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

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

例子:

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

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

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

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

【问题讨论】:

查看这个问题的答案:***.com/a/1075699/1133011 在 java 中查看 List.containsAll(list) 【参考方案1】:

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

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

【讨论】:

其中的乐趣在哪里。说真的,虽然这可能是更好的解决方案。 取决于[a, b, c][c, b, a, b]是否被认为具有相同的内容。这个答案会说他们这样做,但对于 OP 他们可能没有(因为一个包含重复而另一个不包含)。更不用说效率问题了。 基于 cmets 的增强 - System.out.println(((l1.size() == l2.size())&&l2.containsAll(l1)&&l1.containsAll(l2))); @Nrj ,[1,2,2] 和 [2,1,1] 呢? 这种方法的复杂度为 O(n^2)。考虑两个顺序相反的列表,例如:[1,2,3] 和 [3,2,1]。对于第一个元素,它必须扫描 n 个元素,对于第二个 n-1 个元素,依此类推。所以复杂性将是 n^2 的顺序。我认为更好的方法是排序然后使用equals。复杂度为 O(n * log(n))【参考方案2】:

您可以使用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);

【讨论】:

请记住不要破坏原始列表的顺序(就像Collections.sort 所做的那样) - 即传递一个副本。 @A.R.S.是的,这是一个明确的副作用,但前提是它在他们的特定情况下很重要。 您可以添加one = new ArrayList&lt;String&gt;(one); two = new ArrayList&lt;String&gt;(two); 以避免破坏论点。 @jschoen Trying to do Collections.sort() 给了我这个错误: Bound mismatch: The generic method sort(List) of type Collections is not applicable for the arguments (ArrayList)。推断的类型 Answer 不是有界参数 > 函数内的第二个“if”语句可以简化为if(one == null || two == null || one.size() != two.size()) return false; ,因为您已经在检查一个和两个是否都为空【参考方案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

文档:

org.apache.commons.collections4.CollectionUtils

public static boolean isEqualCollection(java.util.Collection a,
                                        java.util.Collection b)

如果给定的 Collections 包含完全相同的内容,则返回 true 具有完全相同基数的元素。

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

参数:

a - 第一个集合,不能是null b - 第二个 收藏,不得为null

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

【讨论】:

实现似乎与 DiddiZ 的答案或多或少相似。 好的...但是如果答案是false,那么如何抓住罪魁祸首(两个列表中不常见的元素)呢?看我的回答。 implementation 'org.apache.commons:commons-collections4:4.3' 给我留下了一个错误Caused by: com.android.builder.dexing.DexArchiveBuilderException: Failed to process path【参考方案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;

【讨论】:

哇,发现它的执行速度比所有其他解决方案都快,真的很惊讶。并且支持提前退出。 这也可能在第二个循环中短路,如果count 变为负数,则将循环体简化为if(!counts.containsKey(item) || --counts.get(item).count &lt; 0) return false; 此外,第三个循环可以简化为for(Count c: counts.values()) if(c.count != 0) return false; @Holger 我考虑过类似于第一个的东西(当它达到零时删除计数,这将产生相同的效果,但也会将最后的检查变成“返回 counts 是否为empty"),但不想掩盖重点:使用地图基本上会将其变成 O(N+M) 问题,并且是您可能获得的最大提升。 @cHao 是的,你应该得到荣誉,因为你指出了一个时间复杂度比以前的解决方案更好的解决方案。我只是碰巧想到了它,因为最近有一个关于可迭代的类似问题。由于我们现在也有了 Java 8,因此值得重新考虑。如果在数字变为负数时在第二个循环中短路,则第三个循环将过时。此外,避免装箱可能是一把双刃剑,有了新的Map.merge,对于大多数用例来说,使用装箱的整数可能更简单、更有效。另见this answer…【参考方案5】:

我会说这些答案漏掉了一个窍门。

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

这里有一些答案建议来自 Apache Commons Collections 库中 CollectionUtils 的方法,但没有发现 the most beautiful, elegant way of answering this question:

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


罪魁祸首:即Lists 不共同的元素。使用CollectionUtils.intersection( list1, culprits )CollectionUtils.intersection( list2, culprits ) 来确定哪些罪魁祸首属于list1 和哪些属于list2 相对简单。 然而,在 "a", "a", "b" disjunction with "a", "b", "b" 等情况下,它往往会分崩离析……除非这不是软件,但与所需任务的微妙性/模糊性的本质有关。

您总是可以检查source code (l. 287) 来完成这样的任务,由 Apache 工程师制作。使用他们的代码的一个好处是它已经过彻底的尝试和测试,许多边缘案例和陷阱都可以预见和处理。如果需要,您可以根据需要复制和调整此代码。


NB 一开始我很失望,因为没有一个 CollectionUtils 方法提供重载版本,让您可以强加自己的 Comparator(因此您可以重新定义 equals 以满足您的目的)。

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

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

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

【讨论】:

另外,有人说“知道依赖的代价” @StanislawBaranski 这是一个有趣的评论。是否建议不要过于依赖此类库?当您在计算机上使用操作系统时,这已经是一个巨大的信仰飞跃,不是吗?我很高兴使用 Apache 库的原因是因为我认为它们确实质量很高,并假设它们的方法符合他们的“合同”并且已经过彻底的测试。你会花多少时间来开发你自己更信任的代码?从开源 Apache 库中复制代码并仔细检查它可能需要考虑...【参考方案6】:

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

boolean result = new HashSet&lt;&gt;(listA).equals(new HashSet&lt;&gt;(listB));

这将从每个List 中创建一个Set,然后使用HashSetequals 方法(当然)忽略排序。

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

【讨论】:

如果 listA = [a, b, c, c] 和 listB = [ a, b, c] 会怎样。结果为真,但列表不相等。【参考方案7】:

将列表转换为 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));

【讨论】:

【参考方案8】:

这是基于@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;

【讨论】:

您可以使用求和计数器跳过最后的 for 循环。总和计数器将计算每个阶段的总计数。在第一个 for 循环中增加和计数器,并在第二个 for 循环中减少它。如果和计数器大于 0,则列表不匹配,否则匹配。目前,在最后的 for 循环中,您检查所有计数是否为零,或者换句话说,是否所有计数的总和为零。使用总和计数器类型会反转此检查,如果计数总数为零,则返回 true,否则返回 false。 IMO,值得跳过该 for 循环,因为当列表匹配时(最坏情况),for 循环会添加另一个不必要的 O(n)。 @SatA 实际上,您可以删除第三个循环而无需任何替换。当键不存在或其计数变为负数时,第二个循环确实已经返回 false。由于两个列表的总大小匹配(已预先检查过),因此在第二个循环之后不可能有非零值,因为一个键不能有正值而另一个键没有负值。跨度> @holger 看来你是绝对正确的。据我所知,第三个循环根本没有必要。 @SatA ...并且使用 Java 8,这可以简洁地实现,就像在 this answer 中一样。【参考方案9】:

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

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

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

【讨论】:

【参考方案10】:

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

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) 而不是引用相等 (==); 不对元素进行排序,因此不需要对它们进行排序(按implement Comparable),此解决方案也能正常工作。

【讨论】:

【参考方案11】:

如果您不希望对集合进行排序并且您需要 ["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);

【讨论】:

【参考方案12】:

利用 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;
  

【讨论】:

【参考方案13】:

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

list1.equals(list2)

如果您不关心订单,请使用此

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

【讨论】:

他说他关心秩序。【参考方案14】:

单行法:)

    集合的项目没有实现接口 Comparable

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

    集合的项目实现接口 Comparable

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

    通过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

【讨论】:

【参考方案15】:

两全其美[@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;

【讨论】:

如果我没记错的话,这个算法在值得的情况下(列表相等但以相反的方式排序)的复杂度将是 O(N*N!)。 其实是O(N*(N/2)),随着每次迭代,数组大小都会减小。【参考方案16】:

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

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

【讨论】:

在列表和数组之间进行所有这些复制有什么意义?【参考方案17】:

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

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

【讨论】:

【参考方案18】:

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

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

【讨论】:

-1: 用 "a", "a", "b" 和 "a", "b", "b" 给出错误答案:查看 @ 的源代码987654322@。当我们谈论Lists而不是Sets时,您必须允许有重复的元素。请看我的回答。

以上是关于Java ArrayList - 我如何判断两个列表是不是相等,顺序无关紧要?的主要内容,如果未能解决你的问题,请参考以下文章

ArrayList<ArrayList<String>> 这种结构的数据,java如何删除其中的某一列元素??

Java读取列文件中具有不同数字的txt并将数据存储在arraylist中

Java求助:如何判断某对象是不是存在?如果不存在,创建该对象

[Java]ArrayList集合的contains方法

java - 如何从java中的另一个ArrayList中删除一个ArrayList的元素?

java中判断两个对象是否相等