¿Por qué Stream.allMatch () devuelve verdadero para una secuencia vacía?


84

Mi colega y yo tuvimos un error que se debió a nuestra suposición de que allMatch()regresaría una llamada de flujo vacía false.

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

Por supuesto, es culpa nuestra por asumir y no leer la documentación. Pero lo que no entiendo es por qué allMatch()regresa el comportamiento predeterminado para una secuencia vacía true. ¿Cuál fue el motivo de esto? Al igual que el anyMatch()(que al contrario devuelve falso), esta operación se usa de manera imperativa que sale de la mónada y probablemente se usa en una ifdeclaración. Teniendo en cuenta estos hechos, ¿hay alguna razón por la que tener la allMatch()opción predeterminada trueen un flujo vacío sea deseable para la mayoría de los usos?


4
Eso es un poco raro. Esperaríamos que si allMatchdevuelve verdadero, también debería hacerlo anyMatch. Además, para el caso vacío, allMatch(...) == noneMatch(...)que también es extraño.
Radiodef


Solo un comentario rápido sobre la sintaxis: en lugar de escribir su predicado como i -> i.isValid(), puede escribir Foo::isValid(dónde Fooestá la clase que está transmitiendo, por supuesto)
MattPutnam

2
"Esta operación se usa de una manera imperativa que sale de la mónada". Dudo que esto influya en cualquier decisión.
user253751

Respuestas:


115

Esto se conoce como verdad vacía . Todos los miembros de una colección vacía satisfacen su condición; después de todo, ¿puedes señalar uno que no lo haga?

Del mismo modo, anyMatchdevuelve false, porque no puede encontrar un elemento de su colección que coincida con la condición. Esto es confuso para mucha gente, pero resulta ser la forma más útil y consistente de definir "cualquiera" y "todos" para conjuntos vacíos.


3
Caray, odio la lógica booleana. Creo que tengo la idea de lo que estás diciendo. La ausencia de negativos es positiva, pero la ausencia de positivos no es negativa.
tmn

13
@ThomasN. De la misma manera, el valor del producto de un conjunto vacío de números es 1mientras que la suma de un conjunto vacío de números es 0. Son los elementos neutrales para la multiplicación / suma. En el caso de booleanos usted tiene que True and x = xy False or x = xpor lo tanto, si se generaliza andy ora secuencias (que es lo ally anyson) se termina con Truey Falsepara el caso vacío, es decir, los respectivos elementos neutros.
Bakuriu

9
@ThomasN. anyMatchpruebas de ausencia de positivos, allMatchpruebas de ausencia de negativos.
user253751

3
Tenga en cuenta que de esta manera puede hacer cosas agradables como la ley de De Morgan: stream.allMatch (predicado) es lo mismo que! Stream.anyMatch (predicate.negate ()). De manera similar,! Stream.allMatch (predicate.negate ()) es lo mismo que stream.anyMatch (predicado).
Hans

1
@PatrickBard: Puedes decir "¿puedes señalar uno que sí?", Y eso es completamente válido , pero lo que significa es que todos los miembros de la colección no cumplen la condición. Todos los miembros de la colección satisfacen la condición y todos los miembros de la colección no cumplen la condición. Estas son declaraciones diferentes de "es falso que todos los miembros de la colección cumplan la condición". Como dije, es confuso.
user2357112 apoya a Monica

10

Aquí hay otra forma de pensar en esto:

allMatch() Es para && lo que sum()es a+

Considere las siguientes declaraciones lógicas:

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

Esto tiene sentido porque sum()es solo una generalización de +. Sin embargo, ¿qué sucede cuando eliminas un elemento más?

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

Podemos ver que tiene sentido definir IntStream.of().sum(), o la suma de una secuencia vacía de números, de una manera particular. Esto nos da el "elemento de identidad" de la suma, o el valor que, cuando se agrega a algo, no tiene efecto ( 0).

Podemos aplicar la misma lógica al Booleanálgebra.

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

Más genéricamente:

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

Si stream = Stream.of()entonces esta regla aún debe aplicarse. Podemos usar el "elemento de identidad" de && para resolver esto. true && thing == thing, entonces Stream.of().allMatch(it -> it) == true.


6

Cuando llamo list.allMatch(o sus análogos en otros idiomas), quiero detectar si algún elemento listno coincide con el predicado. Si no hay elementos, es posible que ninguno no coincida. Mi siguiente lógica elegiría elementos y esperaría que coincidieran con el predicado. Para una lista vacía, no elegiré ningún elemento y la lógica seguirá siendo sólida.

¿Qué pasa si se allMatchdevuelve falsepor una lista vacía?

Mi lógica sencilla fallaría:

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

Necesitaré recordar reemplazar el cheque por !myList.empty() && !myList.allMatch().

En resumen, allMatchregresar truepor una lista vacía no solo es lógicamente sólido, sino que también se encuentra en el camino feliz de la ejecución, que requiere menos verificaciones.


probablemente quisiste decirif (!allMatch)
Assylias

3

Parece que la base es la inducción matemática. Para la informática, una aplicación de esto podría ser un caso base de un algoritmo recursivo.

Si el flujo está vacío, se dice que la cuantificación se satisface de forma vacía y siempre es cierta. Oracle Docs: flujo de operaciones y canalizaciones

La clave aquí es que está "vacuosamente satisfecho", lo que, por naturaleza, es algo engañoso. Wikipedia tiene una discusión decente al respecto.

En matemática pura, los enunciados vacuosamente verdaderos no son generalmente de interés por sí mismos, pero con frecuencia surgen como el caso base de las demostraciones por inducción matemática. Wikipedia: Vacuous Truth


1

Si bien esta pregunta ya se ha respondido correctamente multiplicado por veces, quiero introducir un enfoque más matemático.

Para eso quiero considerar la corriente como un Conjunto (en el sentido matemático). Entonces

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

corresponde a ingrese la descripción de la imagen aquíwhile

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

corresponde a ingrese la descripción de la imagen aquí.

Que la segunda parte sea falsa es bastante obvio ya que no hay elementos en el conjunto vacío. El primero es un poco más complicado. Puede aceptar que sea cierto por definición o buscar en las otras respuestas algunas de las razones por las que debería ser así.

Un ejemplo para ilustrar esta diferencia son proposiciones como "Todos los humanos que viven en Marte tienen 3 patas" (verdadero) y "Hay un ser humano viviendo en Marte con 3 patas" (falso).

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.