【问题标题】: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

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


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

【问题讨论】:

标签: java collections


【解决方案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 方法会认为它们是相等的.

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

【讨论】:

  • 如果你想独立于订单检查,你不能使用 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 : 链接已失效。
【解决方案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

                  【讨论】:

                    猜你喜欢
                    • 2010-12-13
                    • 1970-01-01
                    • 2023-03-18
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多