查找两个不同列表是不是包含完全相同元素的简单方法?

Posted

技术标签:

【中文标题】查找两个不同列表是不是包含完全相同元素的简单方法?【英文标题】:Simple way to find if two different lists contain exactly the same elements?查找两个不同列表是否包含完全相同元素的简单方法? 【发布时间】:2010-11-07 17:17:33 【问题描述】:

在标准 Java 库中,查找两个 List 是否包含完全相同的元素的最简单方法是什么?

两个Lists是否是同一个实例无关紧要,Lists的类型参数是否不同也无关紧要。

例如

List list1
List<String> list2; 
// ... construct etc

list1.add("A");
list2.add("A"); 
// the function, given these two lists, should return true

我知道可能有什么东西在盯着我看 :-)


编辑:为了澄清,我正在寻找完全相同的元素和元素数量,按顺序。

【问题讨论】:

元素的顺序必须相同吗? 这可能永远不会影响您,但要注意休眠持久集有时不遵守 equals 合同 - 搜索请参阅 opensource.atlassian.com/projects/hibernate/browse/HHH-3799 【参考方案1】:

List 上的 equals 方法会做到这一点,List 是有序的,所以两个 List 必须以相同的顺序具有相同的元素。

return list1.equals(list2);

【讨论】:

列表没有排序,除非您对其进行排序。 叹气@我自己。这么明显的答案。您知道,您甚至无法再按 Ctrl+F 访问网页已经太久了。 :) @mmyers:in 列表中的项目没有排序,除非您对其进行排序。列表本身具有项目的隐式排序(按索引),除非您更改列表中的项目,否则它们不会更改。 (与 Set 或 Collections 相比,如果您遍历它们两次,则无法保证其顺序一致) 我认为 daveb 所说的列表是有序的意思是 List.equals 考虑了元素的顺序来确定相等性。请参阅 Javadoc。 我的意思是包含 "A", "B" 的列表和包含 "B", "A" 的列表将不等于这种方法。这很可能是本意,但我想确保没有人忽视它。【参考方案2】:
list1.equals(list2);

如果您的列表包含自定义类 MyClass,则此类必须覆盖 equals 函数。

 class MyClass
  
  int field=0;
  @0verride
  public boolean equals(Object other)
        
        if(this==other) return true;
        if(other==null || !(other instanceof MyClass)) return false;
        return this.field== MyClass.class.cast(other).field;
        
  

注意:如果您想在 java.util.Set 而不是 java.util.List 上测试 equals,那么您的对象必须覆盖 hashCode 函数。

【讨论】:

应该行:return this.field== MyClass.class.cast(other);是返回 this.field== MyClass.class.cast(other).field; @alpere 哦!你说得对 !我会修复它。谢谢! 检查other==null 是多余的——只需if (!(other instanceof MyClass)) return false; 就足够了,因为null 不是任何类的实例,即null instanceof anyclass 返回false【参考方案3】:

这取决于您使用的具体 List 类。抽象类 AbstractCollection 有一个名为 containsAll(Collection) 的方法,该方法接受另一个集合(List 是一个集合),并且:

如果此集合包含指定集合中的所有元素,则返回 true。

所以如果传入的是一个ArrayList,你可以调用这个方法看看它们是否完全一样。

       List foo = new ArrayList();
    List bar = new ArrayList();
    String str = "foobar";

    foo.add(str);
    bar.add(str);

    foo.containsAll(bar);

containsAll() 的原因是因为它遍历第一个列表来寻找第二个列表中的匹配项。因此,如果它们出现故障,equals() 将不会提取它。

编辑: 我只想在这里就执行所提供的各种选项的摊销运行时间发表评论。跑步时间重要吗?当然。这是你唯一应该考虑的事情吗?没有。

将列表中的每个元素复制到其他列表中需要时间,而且还占用大量内存(实际上使您正在使用的内存增加一倍)。

因此,如果您的 JVM 中的内存不是问题(通常应该如此),那么您仍然需要考虑将每个元素从两个列表复制到两个 TreeSet 所需的时间。请记住,它会在输入每个元素时对其进行排序。

我最后的建议是什么?您需要考虑您的数据集以及数据集中有多少元素,以及数据集中每个对象有多大,然后才能在这里做出正确的决定。和他们一起玩,每种方式都创建一个,看看哪个跑得更快。这是一个很好的锻炼。

【讨论】:

不是必须是 foo.containsAll(bar) && bar.containsAll(foo); ? 不,它遍历 foo 中的每个元素并查看 bar 是否包含该元素。然后它确保两个列表的长度相同。如果对于每个 foo 在 bar 中都有一个元素使得 foo.element == bar.element 和 foo.length == bar.length 那么它们包含相同的元素。 我们知道是否有效率保证吗?或者这通常是 O(n^2)? 像任何其他遍历查找匹配元素的数组一样,最坏情况下的运行时间将是 O(n^2)。在这种情况下,看起来实现确实是一次遍历一个元素来寻找匹配项。我不会推测摊销的运行时间,但最坏的情况是 O(n^2)。 这不起作用:1,2,2.containsAll(1,1,2) 反之亦然,并且两个列表的大小相同。【参考方案4】:

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

list1.equals(list2)

来自 javadoc:

将指定对象与 这个平等的清单。返回真 当且仅当指定的对象是 也是一个列表,两个列表具有相同的 大小,以及所有对应的对 两个列表中的元素相等。 (两个元素 e1 和 e2 相等,如果 (e1==null ? e2==null : e1.equals(e2)).) 换句话说,两个 列表被定义为相等,如果它们 包含相同的元素 命令。该定义确保 equals 方法正常工作 跨越不同的实现 列表界面。

如果您想独立检查顺序,您可以将所有元素复制到 Sets 并在结果 Sets 上使用 equals:

public static <T> boolean listEqualsIgnoreOrder(List<T> list1, List<T> list2) 
    return new HashSet<>(list1).equals(new HashSet<>(list2));

这种方法的一个限制是它不仅忽略了顺序,而且还忽略了重复元素的频率。例如,如果 list1 是 ["A", "B", "A"] 并且 list2 是 ["A", "B", "B"],Set 方法会认为它们是相等的.

如果您需要对订单不敏感,但对重复的频率敏感,您可以:

在比较它们之前对两个列表(或副本)进行排序,如this answer to another question 中所做的那样 或将所有元素复制到Multiset

【讨论】:

如果你想独立于订单检查,你不能使用 containsAll 吗? 我不知道 containsAll 的实现细节,但似乎它可能很糟糕。如果 containsAll 一遍又一遍地调用 contains(),你将得到一个 O(n^2) 算法。集合总体应该是 O(nlogn) 实际上,如果集合只是 O(nlogn),另一种方法是在列表上调用 Collections.sort(),然后使用 equals。但是,如果您想保留订单,则需要复制列表,这可能会很昂贵并且有利于设置解决方案……因此您必须考虑自己的情况:-)。 就像给使用containsAll()的人的快速评论一样:记得事先检查两个列表的大小是否相同!这使您只需调用一次containsAll() @Dennis 大小检查只有在您知道每个列表只包含不同元素时才真正起作用。例如,给定a = [x, y, x]b = [x, y, z],那么大小相等并且b.containsAll(a) 将返回true,但b 包含不在a 中的元素。【参考方案5】:

我在 cmets 中发布了一堆东西,我认为它值得自己回答。

正如大家所说的,使用 equals() 取决于顺序。如果您不关心订单,您有 3 种选择。

选项 1

使用containsAll()。在我看来,这个选项并不理想,因为它提供了最坏情况下的性能,O(n^2)。

选项 2

这有两种变体:

2a) 如果您不关心维护列表的顺序...在两个列表中都使用Collections.sort()。然后使用equals()。这是 O(nlogn),因为您先进行两次排序,然后进行 O(n) 比较。

2b) 如果您需要维护列表的顺序,您可以先复制两个列表。那么您可以在两个复制的列表上使用解决方案 2a。但是,如果复制非常昂贵,这可能没有吸引力。

这导致:

选项 3

如果您的要求与2b部分相同,但复制成本太高。您可以使用 TreeSet 为您进行排序。将每个列表转储到它自己的 TreeSet 中。它将在集合中排序,原始列表将保持不变。然后对两个TreeSets 执行equals() 比较。 TreeSetss 可以在 O(nlogn) 时间内构建,equals() 是 O(n)。

任你选 :-)。

编辑:我几乎忘记了Laurence Gonsalves 指出的相同警告。 TreeSet 实现将消除重复。如果您关心重复,您将需要某种排序的多重集。

【讨论】:

如果你关心重复,你总是可以在任何其他测试之前测试集合的大小是否相等。 更具体地说,如果有重复表示不相等,则列表的大小必须相同,然后任何相等检查才有机会成功。 @laz:如果不同的元素在两个列表中重复,则无法检查大小。例如:[A, A, B] vs [A, B, B] 大小相等。 @Laurence:我同意 laz 的帖子有点混乱(我读了几遍才明白)。我认为他只是试图为满足以下两个条件的特殊情况提供“快捷方式”:(1)重复问题,以及(2)列表大小不同。在您的示例中,我认为 laz 仍然说有必要进行我们讨论过的所有相同检查。 (至少我是这么读的)。如果重复无关紧要,那么您不能使用 size 作为特殊情况检查。但是当这两个条件成立时,你可以说“if (list1.size() != list2.size()) return false;. ContainsAll 我认为会给出错误的答案,你需要 containsAll 两种方式。 a.containsAll(b) &amp;&amp; b.containsAll(a)【参考方案6】:

您可以使用 Apache 的 org.apache.commons.collections 库: http://commons.apache.org/collections/apidocs/org/apache/commons/collections/ListUtils.html

public static boolean isEqualList(java.util.Collection list1,
                              java.util.Collection list2)

【讨论】:

这也要求列表元素的顺序相同。 比较前可以先排序 当然,只要存储在列表中或可排序的类型(或者您设置了比较器),您就可以这样做。然而,Apache 的实现算法与常规的 list1.equals(list2) 没有什么不同,除了是静态的。我确实看到了我误解了这个问题的地方,它实际上是在询问如何以相同的顺序比较列表项。我的错! @DavidZhao : 链接已失效。 commons.apache.org/proper/commons-collections/apidocs/org/…【参考方案7】:

示例代码:

public static '<'T'>' boolean isListDifferent(List'<'T'>' previousList,
        List'<'T'>' newList) 

    int sizePrevoisList = -1;
    int sizeNewList = -1;

    if (previousList != null && !previousList.isEmpty()) 
        sizePrevoisList = previousList.size();
    
    if (newList != null && !newList.isEmpty()) 
        sizeNewList = newList.size();
    

    if ((sizePrevoisList == -1) && (sizeNewList == -1)) 
        return false;
    

    if (sizeNewList != sizePrevoisList) 
        return true;
    

    List n_prevois = new ArrayList(previousList);
    List n_new = new ArrayList(newList);

    try 
        Collections.sort(n_prevois);
        Collections.sort(n_new);
     catch (ClassCastException exp) 
        return true;
    

    for (int i = 0; i < sizeNewList; i++) 
        Object obj_prevois = n_prevois.get(i);
        Object obj_new = n_new.get(i);
        if (obj_new.equals(obj_prevois)) 
            // Object are same
         else 
            return true;
        
    

    return false;

【讨论】:

【参考方案8】:

试试这个版本,它不需要顺序相同,但支持具有多个相同的值。仅当每个具有相同数量的任何值时,它们才匹配。

public boolean arraysMatch(List<String> elements1, List<String> elements2) 
    // Optional quick test since size must match
    if (elements1.size() != elements2.size()) 
        return false;
    
    List<String> work = newArrayList(elements2);
    for (String element : elements1) 
        if (!work.remove(element)) 
            return false;
        
    
    return work.isEmpty();

【讨论】:

work.remove(element) 是 O(n),所以这个解是 O(n^2) 或 O(n1 * n2) 有点相同 我也使用了相同的策略,因为它处理所有场景并且集合大小不是那么大,那么 O(n^2) 无关紧要【参考方案9】:

我知道这是一个旧线程,但其他答案都没有完全解决我的用例(我猜 Guava Multiset 可能会这样做,但这里没有示例)。请原谅我的格式。我仍然是在堆栈交换上发帖的新手。另外,如果有任何错误,请告诉我

假设您有List&lt;T&gt; a 和List&lt;T&gt; b,并且您想检查它们是否与以下条件相等:

1) O(n) 预期运行时间 2)相等定义为:对于a或b中的所有元素,该元素在a中出现的次数等于该元素在b中出现的次数。元素相等定义为 T.equals()

private boolean listsAreEquivelent(List<? extends Object> a, List<? extends Object> b) 
    if(a==null) 
        if(b==null) 
            //Here 2 null lists are equivelent. You may want to change this.
            return true;
         else 
            return false;
        
    
    if(b==null) 
        return false;
    
    Map<Object, Integer> tempMap = new HashMap<>();
    for(Object element : a) 
        Integer currentCount = tempMap.get(element);
        if(currentCount == null) 
            tempMap.put(element, 1);
         else 
            tempMap.put(element, currentCount+1);
        
    
    for(Object element : b) 
        Integer currentCount = tempMap.get(element);
        if(currentCount == null) 
            return false;
         else 
            tempMap.put(element, currentCount-1);
        
    
    for(Integer count : tempMap.values()) 
        if(count != 0) 
            return false;
        
    
    return true;

运行时间是 O(n),因为我们在 hashmap 中进行 O(2*n) 次插入,并且 O(3*n) 次 hashmap 选择。我还没有完全测试过这段代码,所以要小心:)

//Returns true:
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("B","A","A"));
listsAreEquivelent(null,null);
//Returns false:
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("B","A","B"));
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("A","B"));
listsAreEquivelent(Arrays.asList("A","A","B"),null);

【讨论】:

【参考方案10】:

如果您正在使用(或乐于使用)Apache Commons Collections,您可以使用CollectionUtils.isEqualCollection,“如果给定的 Collections 包含完全相同的元素和完全相同的基数,则返回 true。”

【讨论】:

非常好的基于哈希图的实现。运行时间应该是 O(n),如果有很多重复元素,它会使用最少的内存来跟踪(基本上使用每个集合的映射来跟踪元素的频率(基数))。缺点是它有额外的 O(n) 内存使用。【参考方案11】:

两个列表元素相同但顺序不同的解决方案:

public boolean isDifferentLists(List<Integer> listOne, List<Integer> listTwo) 
    if(isNullLists(listOne, listTwo)) 
        return false;
    

    if (hasDifferentSize(listOne, listTwo)) 
        return true;
    

    List<Integer> listOneCopy = Lists.newArrayList(listOne);
    List<Integer> listTwoCopy = Lists.newArrayList(listTwo);
    listOneCopy.removeAll(listTwoCopy);

    return CollectionUtils.isNotEmpty(listOneCopy);


private boolean isNullLists(List<Integer> listOne, List<Integer> listTwo) 
    return listOne == null && listTwo == null;


private boolean hasDifferentSize(List<Integer> listOne, List<Integer> listTwo) 
    return (listOne == null && listTwo != null) || (listOne != null && listTwo == null) || (listOne.size() != listTwo.size());

【讨论】:

我认为你不需要复制 listTwo。 您可能还想注意为什么您使用removeAll() 而不是containsAll()(我的理解是,如果 listTwo 包含在 listOne 中只包含一次的重复项,则 containsAll() 方法会错误地报告列表相等)。【参考方案12】:

除了劳伦斯的回答,如果你还想让它为空安全:

private static <T> boolean listEqualsIgnoreOrder(List<T> list1, List<T> list2) 
    if (list1 == null)
        return list2==null;
    if (list2 == null)
        return list1 == null;
    return new HashSet<>(list1).equals(new HashSet<>(list2));

【讨论】:

您可以简化检查:if (list1 == null) return list2==null; if (list2 == null) return false; 如果列表为 [a,a,b,c] 和 [a,b,c] 则无效,除非添加额外检查以确保列表大小为一样。【参考方案13】:

聚会很晚,但想添加这个空安全检查:

Objects.equals(list1, list2)

【讨论】:

【参考方案14】:

汤姆的回答非常好,我完全同意他的回答!

这个问题的一个有趣方面是,您是否需要 List 类型本身及其固有的顺序。

如果不是,您可以降级为 IterableCollection,这使您可以灵活地传递按插入时间排序的数据结构,而不是在您想要检查的时间。

如果顺序无关紧要(并且您没有重复的元素),请考虑使用Set

如果顺序很重要但由插入时间定义(并且您没有重复项),请考虑使用 LinkedHashSet,它类似于 TreeSet 但按插入时间排序(不计入重复项)。这也为您提供了O(1) O(log n) 的摊销访问权限。

【讨论】:

【参考方案15】:

我的解决方案是针对您不关心 Lists 中的排序的情况 - 换句话说:Lists 具有相同的元素但不同的排序将被视为具有相同的内容。

例如:["word1", "word2"]["word2", "word1"] 被认为具有相同的内容。

我已经解决了订购问题,我还需要说一下关于重复的问题。 Lists 需要具有相同数量的元素才能被视为相等。

示例:["word1"]["word1", "word1"] 被认为具有不同的内容。

我的解决方案:

public class ListUtil 

    public static <T> boolean hasSameContents(List<T> firstList, List<T> secondList)       
        if (firstList == secondList)  // same object
            return true;
        
        if (firstList != null && secondList != null) 
            if (firstList.isEmpty() && secondList.isEmpty()) 
                return true;
            
            if (firstList.size() != secondList.size()) 
                return false;
            
            List<T> tmpSecondList = new ArrayList<>(secondList);
            Object currFirstObject = null;
            for (int i=1 ; i<=firstList.size() ; i++) 
                currFirstObject = firstList.get(i-1);
                boolean removed = tmpSecondList.remove(currFirstObject);
                if (!removed) 
                    return false;
                
                if (i != firstList.size())  // Not the last element
                    if (tmpSecondList.isEmpty()) 
                        return false;
                    
                
            
            if (tmpSecondList.isEmpty()) 
                return true;
            
        
        return false;
    

我已经用Strings 对其进行了如下测试:

@Test
public void testHasSameContents() throws Exception 
    // comparing with same list => no duplicate elements
    Assert.isTrue(ListUtil.hasSameContents(List.of("one", "two", "three"), List.of("one", "two", "three")));
    // comparing with same list => duplicate elements
    Assert.isTrue(ListUtil.hasSameContents(List.of("one", "two", "three", "one"), List.of("one", "two", "three", "one")));
    // compare with disordered list => no duplicate elements
    Assert.isTrue(ListUtil.hasSameContents(List.of("one", "two", "three"), List.of("three", "two", "one")));
    // compare with disordered list => duplicate elements
    Assert.isTrue(ListUtil.hasSameContents(List.of("one", "two", "three", "one"), List.of("three", "two", "one", "one")));
    // comparing with different list => same size, no duplicate elements
    Assert.isFalse(ListUtil.hasSameContents(List.of("one", "two", "three"), List.of("four", "five", "six")));
    // comparing with different list => same size, duplicate elements
    Assert.isFalse(ListUtil.hasSameContents(List.of("one", "two", "two"), List.of("one", "two", "three")));
    Assert.isFalse(ListUtil.hasSameContents(List.of("one", "two", "three"), List.of("one", "two", "two")));
    // comparing with different list => different size, no duplicate elements
    Assert.isFalse(ListUtil.hasSameContents(List.of("one", "two", "three", "four"), List.of("one", "two", "three")));
    Assert.isFalse(ListUtil.hasSameContents(List.of("one", "two", "three"), List.of("one", "two", "three", "four")));
    // comparing with different list => different sizes, duplicate elements
    Assert.isFalse(ListUtil.hasSameContents(List.of("one", "two", "three", "one"), List.of("one", "two", "three")));
    Assert.isFalse(ListUtil.hasSameContents(List.of("one", "two", "three"), List.of("one", "two", "three", "one")));

【讨论】:

【参考方案16】:

我知道这可能非常晚,但我个人使用此功能。 如果有人想做一些基准测试,那就太好了。

public static<X> boolean areEqual(List<X> a, List<X> b, BiPredicate<X, X> AEqualsB) 
        boolean aIsNull = a == null;
        boolean bIsNull = b == null;
        if (aIsNull || bIsNull) 
            return aIsNull == bIsNull;
        
        int size = a.size();
        boolean sameSize = size == b.size();
        if (!sameSize) return false; else 
            for (int i = 0; i < size; i++) 
                X aX = a.get(i), bX = b.get(i);
                boolean areEqual = AEqualsB.test(aX, bX);
                if (!areEqual) 
                    return false;
                
            
            return true;
        
    

顺便说一句,我知道前 5 行可以用 XOR "^" 加一个 else 来简化,但信不信由你,我很难用正确的 XOR。

我猜它的效率取决于谓词的类型,但同时它允许您检查自定义潜在等式,同时忽略对编码器可能无关紧要的差异。

这是代码示例。

ListUtils.areEqual(newElements, oldElements, Element::areEqual)

public boolean areEqual(Element e) 
        return optionalAdapterId() == e.optionalAdapterId()
                && value == e.value
                && valueTotal == e.valueTotal
                && stockTotal == e.stockTotal
                && element_title.equals(e.element_title);
    

至于效率如何,我认为任何迭代总是昂贵的,这就是为什么每当我需要对大列表使用这个函数时,我在一个单独的线程上执行它的操作,并在一个线程上检索响应这需要它,即使知道在哪一点上会非常好,在不同的线程上执行它是否有益,需要这种线程的项目数量是多少,该信息将添加到文档中。

【讨论】:

【参考方案17】:

这是一种比较两个集合的方法,该集合考虑了其中的重复。因此,集合大小不一定相同。因此,它将在“实际”中寻找“预期”:

    private static <T> boolean containsAllExpected(Collection<T> actual, Collection<T> expected) 
        if (actual == null && expected == null) 
            return true;
        
        if (actual == null || expected == null) 
            return false;
        
        Collection<T> a = new ArrayList<>(actual);
        Collection<T> e = new ArrayList<>(expected);

        Iterator<T> ei = e.iterator();
        while (ei.hasNext()) 
            T item = ei.next();
            if (a.contains(item)) 
                ei.remove();
                a.remove(item);
             else 
                return false;
            
        

        return true;
    

享受:)

【讨论】:

【参考方案18】:

这应该在 O(n) 时间内完成。

public static <T> boolean isEqualCollection(Collection<T> c1, Collection<T> c2)
    if(nonNull(c1) && nonNull(c2))
        Map<T, Long> c1Counts = c1.stream().collect(Collectors.groupingBy(i -> i, Collectors.counting()));
        for(T item : c2) 
            Long count  = c1Counts.getOrDefault(item, 0L);
            if(count.equals(0L))
                return false;
             else 
                c1Counts.put(item, count - 1L);
            
        
        return true;
    
    return isNull(c1) && isNull(c2);

【讨论】:

【参考方案19】:

!Collections.disjoint(Collection1, Collection2) 如果它们具有相同的元素,将返回 true

【讨论】:

以上是关于查找两个不同列表是不是包含完全相同元素的简单方法?的主要内容,如果未能解决你的问题,请参考以下文章

Python比较两组数组中元素是不是完全相同的问题

java中关于取两个集合交集的retainAll方法 1、是不是两个集合要完全相同? 2、retainAll方法的原理是啥?

从Java中具有不同大小的2个数组列表中查找非相似元素

如何在Unity3D中查找列表中的3个元素是不是相同

检查两个无序列表是不是相等[重复]

散列表