How to compare equality of lists of arrays with modern Java?

JavaArraysListJava 8Equals

Java Problem Overview


I have two lists of arrays.

How do I easily compare equality of these with Java 8 and its features, without using external libraries? I am looking for a "better" (higher-level, shorter, more efficient) solution than brute-force code like this (untested code, may contain typos etc, not the point of the question):

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

Or, if there isn't any nicer way, that's a valid answer too.

Bonus: If Java 9 offers an even better way what whaterver Java 8 can offer, feel free to mention it as well.

Edit: After looking at the comments, and seeing how this question has become moderately hot, I think the "better" should include first checking lengths of all arrays, before checking array contents, because that has potential to find inequality much quicker, if inner arrays are long.

Java Solutions


Solution 1 - Java

The for loop at least can be streamified, leading to:

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

Solution 2 - Java

using zip (which originates from lambda b93) function from https://stackoverflow.com/a/23529010/755183, code could look like:

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

update

in order to check size of arrays first and then content this could be a solution to consider

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

Solution 3 - Java

1) Solution based on Java 8 streams:

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) Much simpler solution (works in Java 5+):

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

3) Regarding your new requirement (to check the contained String arrays length first), you could write a generic helper method that does equality check for transformed lists:

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

Then the solution could be:

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

Solution 4 - Java

You could use a stream if the lists are random access lists (so that a call to get is fast - generally constant time) leading to:

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

However, you might give as parameters some implementations that are not (such as LinkedLists). In this case, the best way is to use the iterator explicitly. Something like:

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

Solution 5 - Java

You could stream over one list and compare to each element of the other by using an iterator:

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

Using an iterator instead of the get(index) method on the first list is better because it doesn't matter whether the list is RandomAccess or not.

Note: this only works with a sequential stream. Using a parallel stream will lead to wrong results.


EDIT: As per the question last edit, which indicates it would be better to check the length of every pair of arrays in advance, I think it could be achieved with a slight modification to my previous code:

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

Here I'm using two iterators and am streaming list2 twice, but I see no other way to check all lengths before checking the contents of the first pair of arrays. Check for lengths is null-safe, while check for contents is delegated to the Arrays.equals(array1, array2) method.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionhydeView Question on Stackoverflow
Solution 1 - JavakhelwoodView Answer on Stackoverflow
Solution 2 - JavahahnView Answer on Stackoverflow
Solution 3 - JavaDragan BozanovicView Answer on Stackoverflow
Solution 4 - JavaAlexis C.View Answer on Stackoverflow
Solution 5 - JavafpsView Answer on Stackoverflow