Чому Stream.allMatch () повертає true для порожнього потоку?


84

Ми з колегою мали помилку, яка була зумовлена ​​припущенням, що allMatch()повернеться порожній потік false.

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

Звичайно, це свого роду наша вина, якщо ми припускаємо і не читаємо документацію. Але я не розумію, чому allMatch()повертається поведінка за замовчуванням для порожнього потоку true. Що було причиною цього? Подібно до anyMatch()(яке протилежним чином повертає false), ця операція використовується імперативно, відступаючи від монади, і, ймовірно, використана в ifоператорі. Беручи до уваги ці факти, чи є якась причина, чому allMatch()встановлення значення trueза замовчуванням на порожній потік є бажаним для більшості використання?


4
Це трохи дивно. Ми очікували б, що якщо allMatchповерне істину, то це повинно бути anyMatch. До того ж для порожньої справи, allMatch(...) == noneMatch(...)що теж дивно.
Radiodef

6
Вікіпедія каже, що це конвенція: en.wikipedia.org/wiki/Universal_quantification#The_empty_set
Alex - GlassEditor.com

Просто трохи осторонь про синтаксис: замість того, щоб писати свій предикат як i -> i.isValid(), ви можете писати Foo::isValid(де, Fooзвичайно, будь-який клас, який ви транслюєте)
MattPutnam

2
"Ця операція використовується імперативно, що відходить від монади" - я сумніваюся, що ці фактори приймають будь-які рішення.
user253751

Відповіді:


115

Це відомо як пуста істина . Усі члени порожньої колекції задовольняють ваші умови; зрештою, чи можете ви вказати на той, який цього не робить?

Подібним чином anyMatchповертається false, оскільки ви не можете знайти елемент своєї колекції, який би відповідав умові. Це бентежить багатьох людей, але це виявляється найбільш корисним і послідовним способом визначення "будь-якого" та "всього" для порожніх наборів.


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 (предикат).
Ганс,

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для порожнього списку є не тільки логічно обґрунтованим, але й лежить на щасливому шляху виконання, вимагаючи меншої кількості перевірок.


ви, мабуть, мали на увазіif (!allMatch)
assylias

3

Схоже, основою цього є математична індукція. Для інформатики застосування цього може бути базовим випадком рекурсивного алгоритму.

Якщо потік порожній, кількісне визначення вважається вакуумним і завжди відповідає дійсності. Документи Oracle: Потокові операції та конвеєри

Ключовим тут є те, що воно "безглуздо задоволено", що, за своєю природою, дещо вводить в оману. Вікіпедія веде пристойну дискусію з цього приводу.

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


1

Незважаючи на те, що на це питання вже було дано правильну відповідь помножено разів, я хочу застосувати більш математичний підхід.

Для цього я хочу розглядати потік як набір (у математичному сенсі). Тоді

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

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

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

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

Те, що друга частина хибна, цілком очевидно, оскільки в порожньому наборі немає елементів. Перший трохи складніший. Ви можете або прийняти це як істину за визначенням, або розглянути інші відповіді з деяких причин, чому так повинно бути.

Прикладом для пояснення цієї різниці є пропозиції на кшталт "Усі люди, що живуть на Марсі, мають 3 ноги" (правда) та "На Марсі живе людина з 3 ногами" (помилково)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.