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


252

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

两个列表是否为同一实例无关紧要,并且列表的类型参数是否不同也无关紧要。

例如

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

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

我认识的脸上可能有些东西盯着我:-)


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


元素必须顺序相同吗?
迈克尔·迈尔斯

这可能永远不会影响您,但是请注意,休眠持久化集有时不履行平等合同-搜索,请参阅opensource.atlassian.com/projects/hibernate/browse/HHH-3799
Pablojim,2009年

Answers:


366

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

list1.equals(list2)

从javadoc:

比较指定对象与此列表是否相等。当且仅当指定对象也是一个列表,并且两个列表具有相同的大小,并且两个列表中所有对应的元素对相等时,才返回true。(如果(e1 == null?e2 == null:e1.equals(e2)),则两个元素e1和e2相等。)换句话说,如果两个列表包含相同顺序的相同元素,则它们被定义为相等。 。此定义确保equals方法可在List接口的不同实现中正常工作。

如果要独立于顺序进行检查,则可以将所有元素复制到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方法将认为它们相等。

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


54
如果要独立于订单进行检查,是否可以使用containsAll?
laz

6
我不知道containsAll的实现细节,但是似乎很糟糕。如果containsAll反复调用contains(),您将获得一个O(n ^ 2)alg。总的来说应该是O(nlogn)
汤姆

6
实际上,如果这些集将只是O(nlogn),则另一种方法是在列表上调用Collections.sort(),然后使用equals。但是,如果您想保留订单,则需要复制列表,这可能会很昂贵,并且偏爱设置的解决方案...因此,您必须考虑自己的情况:-)。
汤姆

1
@amischiefr:您是否建议O(n ^ 2)是最好的选择?
汤姆

8
@Dennis只有在您知道每个列表仅包含不同元素的情况下,大小检查才真正起作用。例如,给定a = [x, y, x]b = [x, y, z]大小相等,b.containsAll(a)将返回true,但b包含in中的元素a
劳伦斯·贡萨尔维斯

95

我在评论中张贴了一堆东西,我认为它值得自己回答。

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

选项1

使用containsAll()。我认为该选项不是理想的,因为它提供了最差情况的性能O(n ^ 2)。

选项2

有两种变体:

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

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

这导致:

选项3

如果您的要求与第2b部分相同,但是复制过于昂贵。您可以使用TreeSet为您进行排序。将每个列表转储到其自己的TreeSet中。它将在集合中排序,并且原始列表将保持不变。然后equals()对两个进行比较TreeSetTreeSets可以在O(nlogn)时间建立,而s equals()是O(n)。

选择:-)。

编辑:我几乎忘了劳伦斯·贡萨尔维斯(Laurence Gonsalves)指出的警告。TreeSet实现将消除重复项。如果您关心重复项,则需要某种排序的多重集。


如果您关心重复项,则可以始终在所有其他测试之前测试集合的大小是否相等。
laz

更具体地说,如果重复表示不平等,则列表的大小必须相同,然后任何相等性检查才有机会成功。
laz

7
@laz:如果在两个列表中重复了不同的元素,则无法检查大小。例如:[A,A,B]与[A,B,B]大小相等。
劳伦斯·贡萨尔维斯(Lawrence Gonsalves),2009年

@劳伦斯:我同意laz的帖子有点令人困惑(在理解之前,我读了几次)。我认为他只是想在以下两个条件成立的情况下为特殊情况提供“捷径”:(1)重复的事项,和(2)列表大小不同。在您的示例中,我认为laz仍在说有必要进行我们讨论过的所有相同检查。(至少那是我的阅读方式)。如果重复没有关系,则您不能将大小用作特殊情况检查。但是,当两个条件成立,你就可以说:“如果(list1.size()= list2.size()!)将返回false ;.
汤姆

9
我认为ContainsAll会给出错误的答案,您将需要双向包含containsAll。a.containsAll(b) && b.containsAll(a)
理查德·廷格

24

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


非常好的基于哈希图的实现。运行时应为O(n),并且如果有很多重复的元素,它将使用最少的内存进行跟踪(基本上使用每个集合的映射来跟踪元素的频率(基数))。缺点是它有额外的O(n)内存使用量。
Muhd

17

参加聚会很晚,但想添加此null安全检查:

Objects.equals(list1, list2)

8

我知道这是一个旧线程,但是没有其他答案可以完全解决我的用例(我猜Guava Multiset可能会做同样的事情,但是这里没有示例)。请原谅我的格式。我还是刚开始在堆栈交换上发布。另外让我知道是否有任何错误

假设您有List<T>一个a和List<T>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),因为我们正在将O(2 * n)插入到哈希图中,然后选择O(3 * n)哈希图。我尚未完全测试此代码,所以请注意:)

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

5

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

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

或O(n1 * n2)
差不多

我也使用了相同的策略,因为它可以处理所有情况,并且集合的大小不是那么大,那么O(n ^ 2)无关紧要
Naresh Joshi

3

List上的equals方法将执行此操作,因为List是有序的,因此两个List必须具有相同顺序的相同元素才能相等。

return list1.equals(list2);

3
除非对列表进行排序,否则不对列表进行排序。
迈克尔·迈尔斯

叹气@我自己。如此明显的答案。您知道一天已经太久了,甚至连Ctrl + F都无法再浏览网页了。:)
Grundlefleck

2
@mmyers:项目列表中是没有顺序的,除非你对其进行排序。列表本身具有项的隐式排序(按索引),除非更改列表中的项,否则它们不会更改。(相对于集合或集合,如果您两次迭代它们,则不能保证顺序一致)
Jason S

我认为daveb通过说列表是有序的意味着List.equals考虑元素的顺序来确定相等性。请参阅Javadoc。
laz

2
我的意思是,包含{“ A”,“ B”}的列表和包含{“ B”,“ A”}的列表对于此方法将是不相等的。那很可能就是我们想要的,但是我想确保没有人忽略它。
迈克尔·迈尔斯

3

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

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

2
我认为您不需要复制listTwo。
AjahnCharles

1
您可能还想指出为什么使用它removeAll()而不是containsAll()(我的理解是,如果listTwo包含在listOne中仅包含一次的重复项,containsAll()方法将错误地报告列表相等)。
AjahnCharles

3

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

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

如果不是这样,您可以降级为IterableCollection为您提供一些灵活性,以便传递按插入时间而不是您要检查的时间排序的数据结构。

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

如果顺序很重要,但是由插入时间定义(并且您没有重复项),请考虑LinkedHashSet类似于TreeSet的a ,但按插入时间进行排序(不计算重复项)。这也为您O(1)分配了的分期付款访问权O(log n)


2

样例代码:

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

2

除了Laurence的答案,如果您还想使其成为null安全的:

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

1
您可以简化检查:if (list1 == null) return list2==null; if (list2 == null) return false;
Xerus

如果列表为[a,a,b,c]和[a,b,c]则不起作用,除非返回额外的检查以确保列表的大小相同,否则返回true。
Venkat Madhav,

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而不是a上测试是否等于java.util.List,则您的对象必须覆盖该hashCode 函数。


1
该行:return this.field == MyClass.class.cast(other); 返回this.field == MyClass.class.cast(other).field;
alpere

@alpere哦!你是对的 !我要修复它。谢谢 !
Pierre


0

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

这还要求列表元素具有相同的顺序。
2012年

您可以在比较之前对列表进行排序
David Zhao

当然,只要提供存储在列表中或可排序的类型,就可以这样做(或者已设置比较器)。但是,Apache实现算法与常规list1.equals(list2)一样,只是静态的。我确实看到我误解了这个问题,实际上是在问如何以相同的顺序比较列表项。我的错!
josh-cain 2012年

@DavidZhao:链接已死。
Aniket Kulkarni


0

检查两个列表都不为空。如果它们的大小不同,则这些列表不相等。构建由列表元素作为键,并将其重复元素作为值组成的地图,并比较这些地图。

假设,如果两个列表都为空,则我认为它们相等。

private boolean compareLists(List<?> l1, List<?> l2) {
    if (l1 == null && l2 == null) {
        return true;
    } else if (l1 == null || l2 == null) {
        return false;
    }

    if (l1.size() != l2.size()) {
        return false;
    }

    Map<?, Integer> m1 = toMap(l1);
    Map<?, Integer> m2 = toMap(l2);

    return m1.equals(m2);
}

private Map<Object, Integer> toMap(List<?> list) {
    //Effective size, not to resize in the future.
    int mapSize = (int) (list.size() / 0.75 + 1);
    Map<Object, Integer> map = new HashMap<>(mapSize);

    for (Object o : list) {
        Integer count = map.get(o);
        if (count == null) {
            map.put(o, 1);
        } else {
            map.put(o, ++count);
        }
    }

    System.out.println(map);
    return map;
}

请注意,应该为这些对象正确定义方法等于。 https://stackoverflow.com/a/24814634/4587961


1
您已经假设一个元素在每个列表中不能出现不同的次数,例如[x, x, y]vs [x, y, y]将在您的实现中返回true。
AjahnCharles

@CodeConfident,非常感谢!我更新了答案。我会用毛!
Yan Khonski

-2

这取决于您使用的具体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中所花费的时间。请记住,它是对进入每个元素的每个元素进行排序。

我的最终建议?您需要考虑数据集以及数据集中有多少个元素,以及数据集中每个对象的大小,然后才能在此处做出正确的决定。与他们一起玩耍,每种方式创建一种,看看哪种运行速度更快。这是一个很好的锻炼。


2
不必一定是foo.containsAll(bar)&& bar.containsAll(foo); ?
卡尔·马纳斯特

不,它将遍历foo中的每个元素,并查看bar是否包含该元素。然后,确保两个列表的长度相同。如果bar中的每个foo都有一个元素,例如foo.element == bar.element和foo.length == bar.length,则它们包含相同的元素。
amischiefr

我们是否知道是否有效率保证?还是通常是O(n ^ 2)?
汤姆

像其他任何通过查找匹配元素进行迭代的数组一样,最坏的情况下运行时间将是O(n ^ 2)。在这种情况下,看起来实现确实确实一次遍历了一个元素以寻找匹配项。我不会推测摊销的运行时间,但是是的,最坏的情况是O(n ^ 2)。
amischiefr

1
这不起作用:{1,2,2} .containsAll({1,1,2})和反之亦然,并且两个列表的大小相同。
comco 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.