Почему Stream.allMatch () возвращает true для пустого потока?

84

У нас с коллегой возникла ошибка, связанная с нашим предположением, что вызов пустого потока allMatch()вернется false.

if (myItems.allMatch(i -> i.isValid()) { 
    //do something
}

Конечно, это отчасти наша вина, если мы предполагаем, а не читаем документацию. Но я не понимаю, почему allMatch()возвращается поведение по умолчанию для пустого потока true. Что послужило причиной этого? Подобно anyMatch()операции (которая, наоборот, возвращает false), эта операция используется императивным способом, который отходит от монады и, вероятно, используется в ifоператоре. Учитывая эти факты, есть ли причина, по которой для большинства применений желательно использовать по allMatch()умолчанию trueпустой поток?

tmn
источник
4
Это немного странно. Мы ожидаем, что если allMatchвернет true, то так и должно быть anyMatch. Вдобавок для пустого корпуса, allMatch(...) == noneMatch(...)что тоже странно.
Radiodef
6
Википедия говорит, что это конвенция: en.wikipedia.org/wiki/Universal_quantification#The_empty_set
Alex - GlassEditor.com
Небольшое отступление о синтаксисе: вместо того, чтобы писать свой предикат как i -> i.isValid(), вы можете написать Foo::isValid(где Foo, конечно, какой класс вы транслируете)
МэттПутнам,
2
«Эта операция используется в императивном порядке, что отходит от монады» - я сомневаюсь, что это влияет на какие-либо решения.
user253751

Ответы:

117

Это известно как пустая правда . Все члены пустой коллекции удовлетворяют вашему условию; в конце концов, можете ли вы указать на то, что нет?

Точно так же anyMatchвозвращается false, потому что вы не можете найти элемент своей коллекции, который соответствует условию. Многих это сбивает с толку, но оказывается, что это наиболее полезный и последовательный способ определить «все» и «все» для пустых множеств.

user2357112 поддерживает Монику
источник
3
Боже, ненавижу логическую логику. Думаю, я понял, о чем вы говорите. Отсутствие негативов - это позитив, но отсутствие позитивов - не негатив.
tmn
13
@ThomasN. Таким же образом, значение продукта пустого набора чисел равно 1сумме пустого набора чисел 0. Это нейтральные элементы для умножения / сложения. В случае с логическими значениями это у вас есть, True and x = xи, False or x = xследовательно, если вы обобщаете andи orна последовательности (вот что allи anyесть), вы в конечном итоге получаете Trueи Falseдля пустого случая, то есть они соответствующие нейтральные элементы.
Бакури
9
@ThomasN. anyMatchтесты на отсутствие положительных результатов, allMatchтесты на отсутствие отрицательных.
user253751
3
Обратите внимание, что таким образом вы можете делать приятные вещи, например, закон Де Моргана: stream.allMatch (предикат) совпадает с! Stream.anyMatch (predicate.negate ()). Точно так же! Stream.allMatch (predicate.negate ()) совпадает с stream.anyMatch (предикат).
Hans
1
@PatrickBard: Вы можете сказать: «Можете ли вы указать на тот, который делает?», И это полностью верно , но это означает, что все члены коллекции не удовлетворяют условию. Все члены коллекции удовлетворяют условию, а все члены коллекции не удовлетворяют условию. Это разные утверждения от «неверно, что все члены коллекции удовлетворяют условию». Как я уже сказал, это сбивает с толку.
user2357112 поддерживает Монику
10

Вот еще один способ подумать об этом:

allMatch()является &&то , что sum()является+

Рассмотрим следующие логические утверждения:

IntStream.of(1, 2).sum() + 3 == IntStream.of(1, 2, 3).sum()
IntStream.of(1).sum() + 2 == IntStream.of(1, 2).sum()

Это имеет смысл, потому что sum()это просто обобщение +. Однако что произойдет, если вы удалите еще один элемент?

IntStream.of().sum() + 1 == IntStream.of(1).sum()

Мы видим, что имеет смысл определять IntStream.of().sum()сумму пустой последовательности чисел определенным образом. Это дает нам «элемент идентичности» суммирования или значение, которое при добавлении к чему-либо не имеет никакого эффекта (0 ).

Мы можем применить ту же логику к Booleanалгебре.

Stream.of(true, true).allMatch(it -> it) == Stream.of(true).allMatch(it -> it) && true

В более общем смысле:

stream.concat(Stream.of(thing)).allMatch(it -> it) == stream.allMatch(it -> it) && thing

Если stream = Stream.of()тогда это правило все равно нужно применять. Мы можем использовать "элемент идентичности" &&, чтобы решить эту проблему. true && thing == thing, так Stream.of().allMatch(it -> it) == true.

Ганс
источник
6

Когда я вызываю list.allMatch(или его аналоги на других языках), я хочу определить, не соответствуют ли какие-либо элементы listпредикату. Если элементов нет, ни один из них может не соответствовать. Моя следующая логика будет выбирать элементы и ожидать, что они соответствуют предикату. Для пустого списка я не выберу никаких элементов, и логика останется верной.

Что делать, если allMatchвозвращается falseпустой список?

Моя прямолинейная логика не удалась:

 if (!myList.allMatch(predicate)) {
   throw new InvalidDataException("Some of the items failed to match!");
 }
 for (Item item : myList) { ... }

Мне нужно не забыть заменить чек на !myList.empty() && !myList.allMatch().

Короче говоря, allMatchвозврат trueдля пустого списка не только логически обоснован, но и находится на правильном пути выполнения, требуя меньшего количества проверок.

9000
источник
ты наверное имел ввидуif (!allMatch)
assylias
3

Похоже, в основе его лежит математическая индукция. В информатике применение этого могло бы быть базовым случаем рекурсивного алгоритма.

Если поток пуст, говорят, что количественное определение выполнено без содержания и всегда верно. Документы Oracle: потоковые операции и конвейеры

Ключевым моментом здесь является то, что оно «удовлетворено пусто», что по своей природе несколько вводит в заблуждение. В Википедии есть достойное обсуждение этого вопроса.

В чистой математике бессмысленно истинные утверждения, как правило, не представляют интереса сами по себе, но они часто возникают как базовый случай доказательств с помощью математической индукции. Википедия: Пустая правда

Натан
источник
1

Хотя на этот вопрос уже неоднократно давались правильные ответы, я хочу предложить более математический подход.

Для этого я хочу рассматривать поток как Set (в математическом смысле). потом

emptyStream.allMatch(x-> p(x))

соответствует введите описание изображения здесьпока

emtpyStream.anyMatch(x -> p(x))

соответствует введите описание изображения здесь.

То, что вторая часть ложна, совершенно очевидно, поскольку в пустом наборе нет элементов. Первый вариант немного сложнее. Вы можете принять это как истину по определению или изучить другие ответы на некоторые из причин, почему так должно быть.

Примером, иллюстрирующим это различие, являются утверждения типа «Все люди, живущие на Марсе, имеют 3 ноги» (верно) и «На Марсе живет человек с 3 ногами» (ложь).

Schlaagi
источник