如何将数组列表的相等性与现代 Java 进行比较?

Posted

技术标签:

【中文标题】如何将数组列表的相等性与现代 Java 进行比较?【英文标题】:How to compare equality of lists of arrays with modern Java? 【发布时间】:2016-05-26 07:11:27 【问题描述】:

我有两个数组列表。

如何在不使用外部库的情况下轻松比较这些与 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 所能提供的更好的方式,请随时提及。

编辑:在查看 cmets 并看到这个问题如何变得适度热门之后,我认为 更好”应该包括首先检查长度所有数组,在检查数组内容之前,因为如果内部数组很长,则有可能更快地发现不等式。

【问题讨论】:

如果您只是使用 List> 而不是 List,您可以使用 list1.equals(list2)。但是,实际比较(在幕后)仍然是蛮力。 代码有效,快速且易于阅读/理解和维护...为什么要更改它?为什么越短越好? 请记住,较短的解决方案也可能会更慢。 什么是“更好”?快点?更容易维护?使用更少的内存? @MartinSchröder 我稍微澄清了这个问题。 【参考方案1】:

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)关于您的新要求(首先检查包含的字符串数组长度),您可以编写一个通用帮助方法来检查转换后的列表是否相等:

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

【讨论】:

#2 更简单,但也为每个列表创建一个副本…… @Holger 没错,实际上第一个解决方案也涉及复制列表。但是在这两种解决方案中都不会复制包含的 String 数组,只会复制对它们的引用。 我稍微编辑了这个问题,如果你想检查一下……我认为 deepEquals 不会这样做,是吗? @hyde deepEquals 会检查长度。来自javadoc:如果两个数组引用都为空,或者如果它们引用包含相同数量的元素的数组并且两个数组中所有对应的元素对都是完全平等。 @SpaceTrucker 是的,但我的意思是,它可能不会首先检查 all 数组等的长度,然后才开始检查它们的内容。它假设它在检查第二个数组的长度之前检查第一个遇到的数组的内容。【参考方案2】:

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

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

在第一个列表上使用迭代器而不是 get(index) 方法更好,因为列表是否为 RandomAccess 并不重要。

注意:这只适用于 sequential 流。使用并行流会导致错误的结果。


编辑:根据上次编辑的问题,这表明最好提前检查每对数组的长度,我认为可以通过对我之前的稍作修改来实现代码:

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 两次,但在检查第一对数组的内容之前,我没有其他方法可以检查所有长度。检查长度是 null 安全的,而检查内容则委托给 Arrays.equals(array1, array2) 方法。

【讨论】:

我会警告说,如果有人打电话给.parallelStream(),这会产生意想不到的结果。 是的,我知道。但我已经想象有人复制粘贴这个并说“嘿,我会尝试使用并行流加速这个”。 你可以进一步简化为:list2.stream() .allMatch(a -&gt; Arrays.equals(a, it.next())).【参考方案3】:

使用来自https://***.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);

【讨论】:

【参考方案4】:

如果列表是随机访问列表,您可以使用流(因此对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。 @JaroslawPawlak 第一个解决方案是假设空引用和大小的检查已经完成(只是替换 for 循环)。我为第二个解决方案进行了编辑。 很公平。在这种情况下,我们可以将第二个解决方案中的最终返回语句简化为return true; :) @JaroslawPawlak 是的,我们甚至可以省略条件中的&amp;&amp; iteList2.hasNext()【参考方案5】:

for 循环至少可以流化,导致:

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

【讨论】:

相当不错的解决方案。值得补充的是,它对于非 RandomAccess 列表效率低下。 @cat,在一般情况下,没有办法比 O(n) 更快地比较两个 n 个元素的集合。 @ymbirtt:实际上,有一种方法,但它需要您编写自己的集合或扩展现有集合并对其进行修改。只需在集合上保留一个哈希码。每次添加一个项目时,相应地调整哈希(例如,乘以一个素数并添加项目的哈希码)。然后你所要做的就是比较 2 个集合的哈希值,即 O(1)。由于存在冲突的可能性,您可以选择仅在哈希匹配时进行完整比较,因此它会是 O(n) ,但其余时间要快得多。此外,删除或编辑集合中的项目可能很复杂。 zip 更自然地解决此类问题,并且它是许多不同语言的众所周知的习语 - 如 hahn's answer 所示。更不用说在某些情况下的性能优势(正如@JaroslawPawlak 在顶部评论中提到的那样)。 @DarrelHoffman 为了准确比较,如果哈希匹配,您必须比较每个元素。

以上是关于如何将数组列表的相等性与现代 Java 进行比较?的主要内容,如果未能解决你的问题,请参考以下文章

java 中如何比较两个数组对象的内容是不是相等?急

判断两个数组是不是相等

如何将数组列表分成相等的部分?

Java比较两个数组里的元素是否相等

将字符串数组与变量Java进行比较[重复]

java如何判断两个二维对象数组相等,让后打印一句话。