How to compare equality of lists of arrays with modern Java?
我有两个数组列表。
如何在不使用外部库的情况下轻松地将这些与Java 8及其特性进行比较?我正在寻找一个比这种蛮力代码更好(更高级、更短、更高效)的解决方案(未经测试的代码,可能包含拼写错误等,而不是问题的关键):
1 2 3 4 5 6 7 8 9 10 11 12 13
| boolean compare (List <String[]> list1, List <String[]> list2 )
{
// tests for nulls etc omitted
if(list1. size() != list2. size()) {
return false;
}
for(i =0; i <list1. size(); ++i ) {
if(!Arrays. equals(list1. get(i ), list2. get(i ))) {
return false;
}
}
return true;
} |
或者,如果没有更好的方法,那也是一个有效的答案。
奖励:如果Java 9提供了一个更好的方式,Java 8可以提供什么,请随意提及。
编辑:在看了这些评论,并看到这个问题是如何变得相当热门之后,我认为"更好的"应该包括在检查数组内容之前,首先检查所有数组的长度,因为如果内部数组很长的话,这有可能更快地发现不等式。
- 如果您只是使用list
>而不是list则可以执行list1.equals(list2)。不过,实际的比较(在封面下)仍将是野蛮的。
- 该代码工作迅速,易于阅读/理解和维护…为什么要改变它?为什么越短越好?
- 记住,较短的解决方案也可能较慢。
- "更好"是什么?更快?更容易维护?使用更少的内存?
- @关于这一点,我稍微澄清了一下这个问题。
for循环至少可以简化,导致:
1 2 3
| return (list1. size()==list2. size() &&
IntStream. range(0, list1. size())
. allMatch(i -> Arrays. equals(list1. get(i ), list2. get(i ))); |
- 很好的解决方案。值得补充的是,它对于非RandomAccess列表是无效的。
- 这是O(1)吗?
- @cat,在一般情况下,没有办法比o(n)更快地比较两组n元素。
- @YMBIRTT:事实上,有一种方法,但是它需要你编写自己的集合或者扩展一个现有的集合并修改它。只需在集合上保留哈希代码。每次添加项目时,相应地调整哈希(例如,乘以素数并添加项目的哈希代码)。然后,您所要做的就是比较2个集合的哈希值,即O(1)。因为有可能发生冲突,所以您可以选择只在哈希匹配时进行完全比较,这样剩下的时间将是O(N),但速度更快。此外,删除或编辑集合中的项可能很复杂。
- 对于这些问题,zip更为自然,它是许多不同语言中的一个众所周知的习语,如Hahn的答案所示。在某些情况下,更不用说性能优势了(正如@jaroslawpawlak在顶部评论中提到的那样)。
- @为了精确比较,如果哈希匹配,则必须比较每个元素。
- @immibis:我认为整体摊销绩效取决于您比较匹配与不匹配收款的频率。不过,有了一个合适的散列函数,两个不匹配的集合拥有匹配散列的几率非常小,因此可以很快确定绝大多数不匹配项。如果您希望经常得到匹配(或者您打算定期更改/删除项目,这可能需要O(N)重新刷新),那么这也不会起作用。
- 当心溪流和羊羔的头顶。这很可能比原来的解决方案慢,消耗更多的内存。
- @cat如果大小不同,则此解决方案中为O(1)。
- @不,你不必。只需略微修改两个列表就足够了,例如在两个列表中添加一个空元素。如果函数确实是散列函数并且散列值仍然相同,则列表是相等的:)散列函数的三个公理之一。
使用来自https://stackoverflow.com/a/23529010/755183的zip函数(源自lambda b93),代码可能看起来像:
1 2 3
| boolean match = a. size() == b. size() &&
zip (a. stream(), b. stream(), Arrays::deepEquals ).
allMatch(equal -> equal ) |
。更新
为了首先检查数组的大小,然后检查内容,这可能是一个需要考虑的解决方案
1 2 3 4 5
| final boolean match = a. size() == b. size()
&& zip (a. stream(), b. stream(), (as, bs ) -> as. length == bs. length).
allMatch(equal -> equal )
&& zip (a. stream(), b. stream(), Arrays::deepEquals ).
allMatch(equal -> equal ); |
1)基于Java 8流的解决方案:
1 2 3
| List <List <String >> first = list1. stream(). map(Arrays::asList ). collect(toList ());
List <List <String >> second = list2. stream(). map(Arrays::asList ). collect(toList ());
return first. equals(second ); |
。
2)更简单的解决方案(在Java 5 +中工作):
1
| return Arrays. deepEquals(list1. toArray(), list2. toArray()); |
。
3)关于您的新要求(首先检查包含的字符串数组长度),您可以编写一个对转换的列表执行相等检查的通用助手方法:
1 2 3 4 5
| <T, U> boolean equal(List<T> list1, List<T> list2, Function<T, U> mapper) {
List<U> first = list1.stream().map(mapper).collect(toList());
List<U> second = list2.stream().map(mapper).collect(toList());
return first.equals(second);
} |
那么解决方案可能是:
1 2
| return equal (list1, list2, s -> s. length)
&& equal (list1, list2, Arrays::asList ); |
。
- #2更简单,但也创建了每个列表的副本…
- @霍尔格是的,事实上,第一个解决方案也包括复制列表。但所包含的字符串数组不会复制到这两个解决方案中的任何一个,只复制对它们的引用。
- 我稍微编辑了一下这个问题,如果你想检查一下…我觉得迪佩奎尔不会那么做,是吗?
- @Hyde deepEquals检查长度。在javadoc中:如果两个数组引用都为空,或者它们引用的数组包含相同数量的元素,并且两个数组中所有对应的元素对都非常相等,则认为这两个数组引用完全相等。
- @是的,但我的意思是,它可能不会首先检查所有数组的长度,然后才开始检查它们的内容。它假定在检查第二个数组的长度之前检查第一个遇到的数组的内容。
- @源代码是可访问的,为什么不检查自己呢?一些旧的open-jdk版本。在我看来,这必须是公认的答案,尤其是因为2。
如果列表是随机访问列表(因此对get的调用是快速的,通常是恒定的时间),则可以使用流,从而导致:
1 2
| //checks for null and size before
boolean same = IntStream. range(0, list1. size()). allMatch(i -> Arrays. equals(list1. get(i ), list2. get(i ))); |
号
但是,您可以将一些不属于的实现(如LinkedList)作为参数提供。在这种情况下,最好的方法是显式使用迭代器。比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| boolean compare (List <String[]> list1, List <String[]> list2 ) {
//checks for null and size
Iterator <String[]> iteList1 = list1. iterator();
Iterator <String[]> iteList2 = list2. iterator();
while(iteList1. hasNext()) {
if(!Arrays. equals(iteList1. next(), iteList2. next())) {
return false;
}
}
return true;
} |
- 如果第二个列表包含更多元素,则第一个解决方案将返回true。第二个解决方案对于不同大小的大列表效率很低,因为它只会在遍历后返回false。
- @Jaroslawpawlak第一个解决方案是假设已经完成了对空引用和大小的检查(只是替换了for循环)。我编辑了第二个解决方案。
- 很公平。在这种情况下,我们可以简化return true;的第二个解决方案中的最终返回语句。
- @Jaroslawpawlak是的,在这种情况下我们甚至可以省略&& iteList2.hasNext()。
- 我稍微编辑了一下这个问题,如果你想检查一下…
您可以使用迭代器在一个列表上进行流式处理,并与另一个列表的每个元素进行比较:
1 2 3
| Iterator <String[]> it = list1. iterator();
boolean match = list1. size() == list2. size() &&
list2. stream(). allMatch(a -> Arrays. equals(a, it. next())); |
在第一个列表中使用迭代器而不是get(index)方法更好,因为列表是否为RandomAccess并不重要。
注意:这只适用于顺序流。使用并行流将导致错误的结果。
编辑:根据上次编辑的问题,最好提前检查每对数组的长度,我想只要稍微修改一下我以前的代码就可以做到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Iterator <String[]> itLength = list1. iterator();
Iterator <String[]> itContents = list1. iterator();
boolean match =
list1. size() == list2. size()
&&
list2. stream()
. allMatch(a -> {
String[] s = itLength. next();
return s == null ? a == null :
a == null ? s == null :
a. length == s. length;
})
&&
list2. stream()
. allMatch(a -> Arrays. equals(a, itContents. next())); |
。
这里我使用了两个迭代器,并对list2进行了两次流式处理,但是在检查第一对数组的内容之前,我看不到其他方法来检查所有长度。检查长度是空安全的,而检查内容则委托给Arrays.equals(array1, array2)方法。
- 我警告说,如果有人打电话给.parallelStream(),这将产生意想不到的结果。
- 是的,我知道。但我已经想象有人复制粘贴这个,然后说"嘿,我会尝试使用一个并行的流来加快速度"。
- 您可以进一步将其简化为:list2.stream() .allMatch(a -> Arrays.equals(a, it.next()))。