Estoy jugando con operaciones funcionales perezosas en Java SE 8, y quiero map
un índice i
a un par / tupla (i, value[i])
, luego filter
basado en el segundo value[i]
elemento y finalmente generar solo los índices.
Debo seguir sufriendo esto: ¿Cuál es el equivalente del par C ++ <L, R> en Java? en la audaz nueva era de lambdas y arroyos?
Actualización: presenté un ejemplo bastante simplificado, que tiene una solución ordenada ofrecida por @dkatzel en una de las respuestas a continuación. Sin embargo, no se generaliza. Por lo tanto, permítanme agregar un ejemplo más general:
package com.example.test;
import java.util.ArrayList;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
boolean [][] directed_acyclic_graph = new boolean[][]{
{false, true, false, true, false, true},
{false, false, false, true, false, true},
{false, false, false, true, false, true},
{false, false, false, false, false, true},
{false, false, false, false, false, true},
{false, false, false, false, false, false}
};
System.out.println(
IntStream.range(0, directed_acyclic_graph.length)
.parallel()
.mapToLong(i -> IntStream.range(0, directed_acyclic_graph[i].length)
.filter(j -> directed_acyclic_graph[j][i])
.count()
)
.filter(n -> n == 0)
.collect(() -> new ArrayList<Long>(), (c, e) -> c.add(e), (c1, c2) -> c1.addAll(c2))
);
}
}
Esto da una salida incorrecta[0, 0, 0]
que corresponde a los recuentos de las tres columnas que son todas false
. Lo que necesito son los índices de estas tres columnas. La salida correcta debería ser [0, 2, 4]
. ¿Cómo puedo obtener este resultado?
fuente
AbstractMap.SimpleImmutableEntry<K,V>
desde hace años ... Pero de todos modos, en lugar de la cartografíai
a(i, value[i])
simplemente para filtrar porvalue[i]
ida y vuelta a la cartografíai
: ¿por qué no filtrovalue[i]
en el primer lugar, sin la asignación?i
en la secuencia. También necesitovalue[i]
los criterios. Por eso lo necesito(i, value[i])
[0, 2, 4]
?Respuestas:
ACTUALIZACIÓN: Esta respuesta es en respuesta a la pregunta original, ¿Java SE 8 tiene pares o tuplas? (E implícitamente, si no, ¿por qué no?) El OP ha actualizado la pregunta con un ejemplo más completo, pero parece que se puede resolver sin usar ningún tipo de estructura de pares. [Nota de OP: aquí está la otra respuesta correcta .]
La respuesta corta es no. Debe rodar el suyo o traer una de las varias bibliotecas que lo implementan.
Se
Pair
propuso y rechazó tener una clase en Java SE al menos una vez. Vea este hilo de discusión en una de las listas de correo de OpenJDK. Las compensaciones no son obvias. Por un lado, hay muchas implementaciones de pares en otras bibliotecas y en el código de la aplicación. Eso demuestra una necesidad, y agregar tal clase a Java SE aumentará la reutilización y el uso compartido. Por otro lado, tener una clase Pair aumenta la tentación de crear estructuras de datos complicadas a partir de pares y colecciones sin crear los tipos y abstracciones necesarios. (Esa es una paráfrasis del mensaje de Kevin Bourillion de ese hilo).Recomiendo a todos que lean todo el hilo de correo electrónico. Es notablemente perspicaz y no tiene daño. Es bastante convincente. Cuando comenzó, pensé: "Sí, debería haber una clase Pair en Java SE", pero cuando el hilo llegó a su fin, había cambiado de opinión.
Sin embargo, tenga en cuenta que JavaFX tiene la clase javafx.util.Pair . Las API de JavaFX evolucionaron por separado de las API de Java SE.
Como se puede ver en la pregunta vinculada ¿ Cuál es el equivalente del par C ++ en Java? Hay un espacio de diseño bastante grande que rodea lo que aparentemente es una API tan simple. ¿Deberían los objetos ser inmutables? ¿Deberían ser serializables? ¿Deberían ser comparables? ¿La clase debería ser final o no? ¿Deberían ordenarse los dos elementos? ¿Debería ser una interfaz o una clase? ¿Por qué parar en parejas? ¿Por qué no triples, cuádruples o tuplas N?
Y, por supuesto, existe el inevitable cambio de nombres para los elementos:
Un gran problema que apenas se ha mencionado es la relación de los pares con los primitivos. Si tiene un
(int x, int y)
dato que representa un punto en el espacio 2D, representarlo ya quePair<Integer, Integer>
consume tres objetos en lugar de dos palabras de 32 bits. Además, estos objetos deben residir en el montón e incurrirán en gastos generales de GC.Parecería claro que, al igual que Streams, sería esencial que hubiera especializaciones primitivas para pares. ¿Queremos ver:
Incluso un
IntIntPair
todavía requeriría un objeto en el montón.Estos, por supuesto, recuerdan la proliferación de interfaces funcionales en el
java.util.function
paquete en Java SE 8. Si no desea una API hinchada, ¿cuáles dejaría de lado? También podría argumentar que esto no es suficiente, y que las especializaciones para, por ejemplo, tambiénBoolean
deberían agregarse.Mi sensación es que si Java hubiera agregado una clase Pair hace mucho tiempo, habría sido simple, o incluso simplista, y no habría satisfecho muchos de los casos de uso que estamos imaginando ahora. Tenga en cuenta que si se hubiera agregado Par en el marco de tiempo JDK 1.0, ¡probablemente habría sido mutable! (Mire java.util.Date.) ¿La gente habría estado contenta con eso? Supongo que si hubiera una clase Pair en Java, sería un poco no muy útil y todos seguirían implementando los suyos para satisfacer sus necesidades, habría varias implementaciones de Pair y Tuple en bibliotecas externas, y la gente todavía estaría discutiendo / discutiendo sobre cómo arreglar la clase Pair de Java. En otras palabras, más o menos en el mismo lugar en el que estamos hoy.
Mientras tanto, se está trabajando para abordar el problema fundamental, que es un mejor soporte en la JVM (y eventualmente en el lenguaje Java) para los tipos de valor . Ver este documento Estado de los valores . Este es un trabajo preliminar y especulativo, y cubre solo temas desde la perspectiva de JVM, pero ya tiene una buena cantidad de pensamiento detrás de él. Por supuesto, no hay garantías de que esto entrará en Java 9, o nunca entrará en ningún lado, pero sí muestra la dirección actual de pensar sobre este tema.
fuente
Pair<T,U>
. Dado que los genéricos deben ser de tipo de referencia. Cualquier primitiva se encuadrará cuando se almacene. Para almacenar primitivas realmente necesitas una clase diferente.valueOf
deberían haber sido la única forma de obtener una instancia en caja. Pero esos han estado allí desde Java 1.0 y probablemente no valga la pena intentar cambiar en este momento.Pair
oTuple
clase con un método de fábrica que cree las clases de especialización necesarias (con almacenamiento optimizado) de forma transparente en segundo plano. Al final, las lambdas hacen exactamente eso: pueden capturar un número arbitrario de variables de tipo arbitrario. Y ahora imagina un soporte de idioma que permite crear la clase de tupla apropiada en tiempo de ejecución activada por unainvokedynamic
instrucción ...invokedynamic
fábrica con base similar a la creación lambda, tal modificación posterior no sería un problema. Por cierto, las lambdas tampoco tienen identidad. Como se indicó explícitamente, la identidad que puede percibir hoy es un artefacto de la implementación actual.Puede echar un vistazo a estas clases integradas:
AbstractMap.SimpleEntry
AbstractMap.SimpleImmutableEntry
fuente
SimpleImmutableEntry
solo garantiza que las referencias almacenadas en elEntry
no cambien, no que los campos de los objetos vinculadoskey
yvalue
(o los de los objetos a los que se vinculan) no cambien.Lamentablemente, Java 8 no introdujo pares o tuplas. Por supuesto, siempre puede usar org.apache.commons.lang3.tuple (que personalmente uso en combinación con Java 8) o puede crear sus propios contenedores. O usa Mapas. O cosas así, como se explica en la respuesta aceptada a esa pregunta a la que se vinculó.
ACTUALIZACIÓN: JDK 14 está introduciendo registros como una función de vista previa. Estas no son tuplas, pero se pueden usar para salvar muchos de los mismos problemas. En su ejemplo específico de arriba, eso podría verse más o menos así:
Cuando se compila y ejecuta con JDK 14 (en el momento de la escritura, esta es una compilación de acceso temprano) usando el
--enable-preview
indicador, obtiene el siguiente resultado:fuente
Parece que el ejemplo completo se puede resolver sin el uso de ningún tipo de estructura de pares. La clave es filtrar en los índices de la columna, con el predicado comprobando la columna completa, en lugar de asignar los índices de la columna al número de
false
entradas en esa columna.El código que hace esto está aquí:
Esto da como resultado una salida de la
[0, 2, 4]
cual creo que es el resultado correcto solicitado por el OP.También tenga en cuenta la
boxed()
operación que encajona losint
valores enInteger
objetos. Esto le permite a uno usar eltoList()
recopilador preexistente en lugar de tener que escribir las funciones del recopilador que hacen el propio boxeo.fuente
true
). Por consiguiente, aceptaré su otra respuesta como correcta, ¡pero también señalaré esta! Muchas gracias :)Vavr (anteriormente llamado Javaslang) ( http://www.vavr.io ) también proporciona tuplas (hasta un tamaño de 8). Aquí está el javadoc: https://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/Tuple.html .
Este es un ejemplo simple:
Por qué JDK en sí no vino con un tipo simple de tuplas hasta ahora es un misterio para mí. Escribir clases de envoltura parece ser un asunto de todos los días.
fuente
Desde Java 9, puede crear instancias
Map.Entry
más fáciles que antes:Map.entry
devuelve un no modificableEntry
y prohíbe los nulos.fuente
Como solo te interesan los índices, no es necesario que asignes tuplas en absoluto. ¿Por qué no simplemente escribir un filtro que usa los elementos de búsqueda en su matriz?
fuente
Si.
Map.Entry
se puede usar como aPair
.Desafortunadamente, no ayuda con las secuencias Java 8, ya que el problema es que, aunque las lambdas pueden tomar múltiples argumentos, el lenguaje Java solo permite devolver un único valor (objeto o tipo primitivo). Esto implica que cada vez que tiene una secuencia termina pasando un solo objeto de la operación anterior. Esto es una falta en el lenguaje Java, porque si se admitieran múltiples valores de retorno Y los flujos los admitieran, podríamos tener tareas no triviales mucho más agradables realizadas por los flujos.
Hasta entonces, solo hay poco uso.
EDITAR 12-02-2018: Mientras trabajaba en un proyecto, escribí una clase auxiliar que ayuda a manejar el caso especial de tener un identificador más temprano en la secuencia que necesita en un momento posterior, pero la parte de la secuencia intermedia no lo sabe. Hasta que pueda lanzarlo solo, está disponible en IdValue.java con una prueba unitaria en IdValueTest.java
fuente
Eclipse Collections tiene
Pair
y todas las combinaciones de pares primitivos / objeto (para las ocho primitivas).La
Tuples
fábrica puede crear instancias dePair
, y laPrimitiveTuples
fábrica se puede usar para crear todas las combinaciones de pares primitivos / objeto.Agregamos estos antes de que se lanzara Java 8. Fueron útiles para implementar Iteradores clave / valor para nuestros mapas primitivos, que también admitimos en todas las combinaciones primitivas / objeto.
Si está dispuesto a agregar la sobrecarga adicional de la biblioteca, puede usar la solución aceptada de Stuart y recopilar los resultados en una primitiva
IntList
para evitar el boxeo. Agregamos nuevos métodos en Eclipse Collections 9.0 para permitirInt/Long/Double
que se creen colecciones desdeInt/Long/Double
Streams.Nota: Soy un committer para Eclipse Collections.
fuente