查找两个不同列表是不是包含完全相同元素的简单方法?
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 中。它将在集合中排序,原始列表将保持不变。然后对两个TreeSet
s 执行equals()
比较。 TreeSets
s 可以在 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) && 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<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),因为我们在 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
类型本身及其固有的顺序。
如果不是,您可以降级为 Iterable
或 Collection
,这使您可以灵活地传递按插入时间排序的数据结构,而不是在您想要检查的时间。
如果顺序无关紧要(并且您没有重复的元素),请考虑使用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
【讨论】:
以上是关于查找两个不同列表是不是包含完全相同元素的简单方法?的主要内容,如果未能解决你的问题,请参考以下文章
java中关于取两个集合交集的retainAll方法 1、是不是两个集合要完全相同? 2、retainAll方法的原理是啥?