Как сравнить равенство списков массивов с современной Java?
У меня есть два списка массивов.
Как легко сравнить равенство их с 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 предлагает еще лучший способ, который предлагает whaterver Java 8, не стесняйтесь также упоминать об этом.
Изменить: После просмотра комментариев и просмотра того, как этот вопрос стал умеренно горячим, я думаю, что "лучше" должно включать в себя первую проверку длины всех массивов, прежде чем проверять содержимое массива, потому что у этого есть возможность найти неравенство намного быстрее, если внутренние массивы длинны.
Ответы
Ответ 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) Что касается вашего нового требования (сначала проверить длину массива 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);
Ответ 2
Цикл for
, по крайней мере, может быть потоковым, что приводит к:
return (list1.size()==list2.size() &&
IntStream.range(0, list1.size())
.allMatch(i -> Arrays.equals(list1.get(i), list2.get(i)));
Ответ 3
используя zip
(который исходит из lambda b93) из fooobar.com/questions/42732/..., код может выглядеть так:
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;
}
Ответ 5
Вы можете передавать по одному списку и сравнивать с каждым элементом другого с помощью итератора:
Iterator<String[]> it = list1.iterator();
boolean match = list1.size() == list2.size() &&
list2.stream().allMatch(a -> Arrays.equals(a, it.next()));
Использование итератора вместо метода get(index)
в первом списке лучше, потому что не имеет значения, является ли список RandomAccess
или нет.
Примечание: это работает только с последовательным потоком. Использование параллельного потока приведет к неправильным результатам.
EDIT: согласно последнему праву вопроса, который указывает, что было бы лучше проверить длину каждой пары массивов заранее, я думаю, что это может быть достигнуто с небольшой модификацией моего предыдущего кода:
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)
.