如何用现代Java比较数组列表的相等性?


70

我有两个数组列表。

如何在不使用外部库的情况下轻松地将它们与Java 8及其功能的相等性进行比较?我正在寻找一种比这种强力代码(未经测试的代码,可能包含错别字等)更好的(更高级别,更短,更有效)的解决方案:

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可以提供的更好的方法,请随时提一下。

编辑:在查看评论并查看此问题如何变得中等热点之后,我认为更好”应包括在检查数组内容之前首先检查所有数组的长度,因为如果有内部的话,它有可能更快地找到不等式数组很长。


2
如果仅使用List <List <String >>而不是List <String []>,则可以执行list1.equals(list2)。实际的比较(在幕后)仍然是蛮力的。
Marthursson '16

3
该代码有效,快速,易于阅读/理解和维护……为什么要改变它?为什么短一些更好?
法尔科

1
请记住,较短的解决方案可能也会比较慢。
法拉普

3
什么是“更好”?快点?易于维护?使用更少的内存?
MartinSchröder16年

@MartinSchröder我对此问题做了一些澄清。
hyde

Answers:


31

1)基于Java 8流的解决方案:

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+):

return Arrays.deepEquals(list1.toArray(), list2.toArray());

3)关于您的新要求(首先检查包含的String数组的长度),您可以编写一个通用的辅助方法,对转换后的列表进行相等性检查:

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

那么解决方案可能是:

return equal(list1, list2, s -> s.length)
    && equal(list1, list2, Arrays::asList);

5
#2更简单,但也可以创建每个列表的副本…
Holger

2
@Holger是的,实际上,第一个解决方案包括复制列表。但是在两个解决方案中的任何一个中都不会复制所包含的String数组,仅会复制对它们的引用。
Dragan Bozanovic

我稍微编辑了一下问题,如果您想检查一下……我认为deepEquals不会这样做,对吗?
海德

2
@hydedeepEquals会检查长度。从javadoc中获取:如果两个数组引用都为null,或者如果它们引用包含相同数量元素的数组,并且两个数组中所有对应的元素对都非常相等,则认为两个数组引用非常相等。
SpaceTrucker '16

@SpaceTrucker是的,但是我的意思是,它可能不首先检查所有数组的长度,等等,只有在开始检查它们的内容之后。它会假设它先检查遇到的第一个数组的内容,然后再检查第二个数组的长度。
海德

50

for至少循环可以streamified,导致:

return (list1.size()==list2.size() &&
        IntStream.range(0, list1.size())
                 .allMatch(i -> Arrays.equals(list1.get(i), list2.get(i)));

12
非常好的解决方案。值得补充的是,对于非RandomAccess列表而言,它效率低下。
Jaroslaw Pawlak

10
@cat,在一般情况下,无法比O(n)更快地比较n个元素的两个集合。
ymbirtt '16

17
@ymbirtt:实际上,有一种方法,但是它要求您编写自己的集合或扩展现有集合并进行修改。只需将哈希码保留在集合上即可。每次添加项目时,都要相应地调整哈希值(例如乘以质数并添加项目的哈希码)。然后,您要做的就是比较两个集合的哈希,即O(1)。由于可能会发生冲突,因此您可以选择仅在散列匹配时进行完全比较,因此它是O(n),但在其余时间要快得多。另外,删除或编辑集合中的项目可能很复杂。
Darrel Hoffman

1
zip对于此类问题而言,它更为自然,这是许多不同语言中众所周知的习语-如hahn的回答所述。更不用说在某些情况下的性能优势(如@JaroslawPawlak在顶部评论中提到的)。
BartoszKP '16

8
@DarrelHoffman为了进行精确比较,如果哈希匹配,则必须比较每个元素。
user253751 '16

31

使用https://stackoverflow.com/a/23529010/755183的zip(源自lambda b93)函数,代码可能类似于:

boolean match = a.size() == b.size() && 
                zip(a.stream(), b.stream(), Arrays::deepEquals).
                allMatch(equal -> equal)

更新

为了先检查数组的大小然后再检查内容,这可能是要考虑的解决方案

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

15

可以使用流,如果名单是随机访问列表(这样的呼叫get速度快-通常是固定时间),导致:

//checks for null and size before
boolean same = IntStream.range(0, list1.size()).allMatch(i -> Arrays.equals(list1.get(i), list2.get(i)));

但是,您可以提供一些未实现的实现作为参数(例如LinkedLists)。在这种情况下,最好的方法是显式使用迭代器。就像是:

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。
Jaroslaw Pawlak

@JaroslawPawlak第一个解决方案是假定对空引用和大小的检查已经完成(只需替换for循环)。我为第二种解决方案进行了编辑。
亚历克西斯C.16年

1
很公平。在这种情况下,我们可以将第二个解决方案中的最终退货声明简化为return true;:)
Jaroslaw Pawlak,

@JaroslawPawlak是的,我们甚至可以忽略&& iteList2.hasNext()这种情况
Alexis C.

6

您可以使用一个迭代器流式处理一个列表并与另一个列表的每个元素进行比较:

Iterator<String[]> it = list1.iterator();
boolean match = list1.size() == list2.size() &&
                list2.stream().allMatch(a -> Arrays.equals(a, it.next()));

get(index)在第一个列表上使用迭代器代替方法会更好,因为列表是否存在都RandomAccess没有关系。

注意:这仅适用于顺序流。使用并行流将导致错误的结果。


编辑:根据问题的最后编辑,这表明最好事先检查每对数组的长度,我认为可以对我之前的代码稍作修改即可实现:

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()
Alexis C.

是的我知道。但我已经想象有人复制粘贴此内容并说“嘿,我将尝试使用并行流来加快速度”。
Alexis C.

1
您可以将其进一步简化为:list2.stream() .allMatch(a -> Arrays.equals(a, it.next()))
Dragan Bozanovic
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.