Java SE 8 ha coppie o tuple?


185

Sto giocando con pigre operazioni funzionali in Java SE 8, e voglio mapun indice isu una coppia / tupla (i, value[i]), quindi filterbasato sul secondo value[i]elemento, e infine output solo gli indici.

Devo ancora soffrire questo: qual è l'equivalente della coppia C ++ <L, R> in Java? nell'audace nuova era di lambda e ruscelli?

Aggiornamento: ho presentato un esempio piuttosto semplificato, che ha una soluzione ordinata offerta da @dkatzel in una delle risposte di seguito. Tuttavia, non si generalizza. Pertanto, vorrei aggiungere un esempio più generale:

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))
    );
  }

}

Ciò fornisce un output errato[0, 0, 0] che corrisponde ai conteggi per le tre colonne che sono tutte false. Ciò di cui ho bisogno sono gli indici di queste tre colonne. L'output corretto dovrebbe essere [0, 2, 4]. Come posso ottenere questo risultato?


2
C'è già AbstractMap.SimpleImmutableEntry<K,V>da anni ... Ma comunque, invece di mappatura iper (i, value[i])solo per il filtraggio da parte value[i]e la mappatura di nuovo a i: perché non solo filtro, value[i]in primo luogo, senza la mappatura?
Holger,

@Holger Devo sapere quali indici di un array contengono valori che corrispondono a un criterio. Non posso farlo senza preservare inel flusso. Ho anche bisogno value[i]di criteri. Ecco perché ne ho bisogno(i, value[i])
negromante il

1
@necromancer Giusto, funziona solo se è economico ottenere il valore dall'indice, come un array, una raccolta ad accesso casuale o una funzione economica. Immagino che il problema sia che volevi presentare un caso d'uso semplificato, ma è stato semplificato eccessivamente e quindi ceduto a un caso speciale.
Stuart Marks,

1
@necromancer Ho modificato un po 'l'ultimo paragrafo per chiarire la domanda che penso tu stia facendo. È giusto? Inoltre, questa è una domanda su un grafico diretto (non aciclico)? (Non che importi molto.) Infine, dovrebbe essere l'output desiderato [0, 2, 4]?
Stuart Marks,

1
Credo che la soluzione giusta per risolvere questo problema sia avere una futura versione Java che supporti le tuple come tipo di ritorno (come un caso speciale di Object) e che le espressioni lambda possano usare tale tupla direttamente per i suoi parametri.
Thorbjørn Ravn Andersen,

Risposte:


206

AGGIORNAMENTO: Questa risposta è in risposta alla domanda originale, Java SE 8 ha coppie o tuple? (E implicitamente, in caso contrario, perché no?) L'OP ha aggiornato la domanda con un esempio più completo, ma sembra che possa essere risolto senza utilizzare alcun tipo di struttura Pair. [Nota dall'OP: ecco l'altra risposta corretta .]


La risposta breve è no. Devi arrotolare il tuo o portare una delle numerose librerie che lo implementano.

Avere una Pairlezione in Java SE è stato proposto e respinto almeno una volta. Vedi questo thread di discussione su una delle mailing list di OpenJDK. I compromessi non sono evidenti. Da un lato, ci sono molte implementazioni di Pair in altre librerie e nel codice dell'applicazione. Ciò dimostra una necessità e l'aggiunta di tale classe a Java SE aumenterà il riutilizzo e la condivisione. D'altra parte, avere una classe Pair aumenta la tentazione di creare strutture di dati complicati da coppie e raccolte senza creare i tipi e le astrazioni necessari. (Questa è una parafrasi del messaggio di Kevin Bourillion da quel thread.)

Consiglio a tutti di leggere l'intero thread di posta elettronica. È straordinariamente penetrante e non ha flamage. È abbastanza convincente. All'inizio ho pensato "Sì, ci dovrebbe essere una classe Pair in Java SE" ma quando il thread ha raggiunto la fine avevo cambiato idea.

Si noti tuttavia che JavaFX ha la classe javafx.util.Pair . Le API di JavaFX si sono evolute separatamente dalle API di Java SE.

Come si può vedere dalla domanda collegata Qual è l'equivalente della coppia C ++ in Java? c'è un ampio spazio di progettazione che circonda quella che è apparentemente un'API così semplice. Gli oggetti dovrebbero essere immutabili? Dovrebbero essere serializzabili? Dovrebbero essere comparabili? La classe dovrebbe essere definitiva o no? I due elementi devono essere ordinati? Dovrebbe essere un'interfaccia o una classe? Perché fermarsi a coppie? Perché non triple, quadruple o N-tuple?

E ovviamente c'è l'inevitabile denominazione in moto per gli elementi:

  • (a, b)
  • (primo secondo)
  • (sinistra destra)
  • (auto, cdr)
  • (foo, bar)
  • eccetera.

Un grosso problema che è stato appena menzionato è il rapporto tra coppie e primitivi. Se si dispone di un (int x, int y)dato che rappresenta un punto nello spazio 2D, rappresentandolo come si Pair<Integer, Integer>consumano tre oggetti anziché due parole a 32 bit. Inoltre, questi oggetti devono risiedere nell'heap e dovranno sostenere il sovraccarico GC.

Sembrerebbe chiaro che, come i flussi, sarebbe essenziale che esistessero specializzazioni primitive per le coppie. Vogliamo vedere:

Pair
ObjIntPair
ObjLongPair
ObjDoublePair
IntObjPair
IntIntPair
IntLongPair
IntDoublePair
LongObjPair
LongIntPair
LongLongPair
LongDoublePair
DoubleObjPair
DoubleIntPair
DoubleLongPair
DoubleDoublePair

Anche un IntIntPairrichiederebbe comunque un oggetto sull'heap.

Questi, ovviamente, ricordano la proliferazione di interfacce funzionali nel java.util.functionpacchetto in Java SE 8. Se non si desidera un'API gonfiata, quali si tralasciano? Potresti anche sostenere che questo non è abbastanza e che Booleandovrebbero essere aggiunte anche specializzazioni per, diciamo ,.

La mia sensazione è che se Java avesse aggiunto una classe Pair molto tempo fa, sarebbe stato semplice, o anche semplicistico, e non avrebbe soddisfatto molti dei casi d'uso che stiamo immaginando ora. Considera che se Pair fosse stato aggiunto nel time frame JDK 1.0, probabilmente sarebbe stato mutevole! (Guarda java.util.Date.) Le persone ne sarebbero state contente? La mia ipotesi è che se ci fosse una classe Pair in Java, sarebbe una specie di non-davvero-utile e tutti continuerebbero a rotolare per soddisfare le loro esigenze, ci sarebbero varie implementazioni Pair e Tuple in librerie esterne, e la gente continuerebbe a discutere / discutere su come risolvere la classe Pair di Java. In altre parole, un po 'nello stesso posto in cui ci troviamo oggi.

Nel frattempo, è in corso un lavoro per affrontare il problema fondamentale, che è un migliore supporto nella JVM (e infine nel linguaggio Java) per i tipi di valore . Vedi questo documento sullo stato dei valori . Questo è un lavoro preliminare, speculativo, e copre solo questioni dal punto di vista della JVM, ma ha già una buona dose di pensiero. Naturalmente non ci sono garanzie che questo possa entrare in Java 9, o mai entrare da nessuna parte, ma mostra l'attuale direzione di pensiero su questo argomento.


3
@necromancer I metodi di fabbrica con le primitive non aiutano Pair<T,U>. Poiché i generici devono essere di tipo di riferimento. Eventuali primitive verranno inscatolate quando vengono archiviate. Per conservare le primitive hai davvero bisogno di una classe diversa.
Stuart Marks,

3
@necromancer E sì, a posteriori, i costruttori primitivi in ​​scatola non avrebbero dovuto essere pubblici e valueOfavrebbero dovuto essere l'unico modo per ottenere un'istanza in scatola. Ma quelli sono presenti lì da Java 1.0 e probabilmente non vale la pena provare a cambiare a questo punto.
Stuart Marks,

3
Ovviamente, ci dovrebbe essere solo un pubblico Pairo una Tupleclasse con un metodo factory che crei le classi di specializzazione necessarie (con memoria ottimizzata) in modo trasparente in background. Alla fine, i lambda fanno esattamente questo: possono catturare un numero arbitrario di variabili di tipo arbitrario. E ora immagina un supporto linguistico che consenta di creare la classe tupla appropriata in fase di runtime innescata da invokedynamicun'istruzione ...
Holger,

3
@Holger Qualcosa del genere potrebbe funzionare se si adeguasse i tipi di valore alla JVM esistente, ma la proposta di Tipi di valore (ora "Progetto Valhalla" ) è molto più radicale. In particolare, i suoi tipi di valore non sarebbero necessariamente allocati in heap. Inoltre, a differenza degli oggetti oggi e dei primitivi oggi, i valori non avrebbero identità.
Stuart Marks,

2
@Stuart Marks: Ciò non interferirebbe in quanto il tipo che ho descritto potrebbe essere il tipo "boxed" per un tale tipo di valore. Con una invokedynamicfabbrica basata simile alla creazione lambda, tale modifica successiva non sarebbe un problema. A proposito, anche i lambda non hanno identità. Come affermato esplicitamente, l'identità che potresti percepire oggi è un artefatto dell'attuale implementazione.
Holger,

46

Puoi dare un'occhiata a queste classi integrate:


3
Questa è la risposta corretta, per quanto riguarda la funzionalità integrata per le coppie. Si noti che SimpleImmutableEntrygarantisce solo che i riferimenti memorizzati nel Entrynon cambiano, non che i campi del collegamento keye degli valueoggetti (o quelli degli oggetti a cui si collegano) non cambiano.
Luke Hutchison,

22

Purtroppo, Java 8 non ha introdotto coppie o tuple. Puoi sempre usare org.apache.commons.lang3.tuple ovviamente (che personalmente uso in combinazione con Java 8) oppure puoi creare i tuoi wrapper. Oppure usa Maps. O cose del genere, come spiegato nella risposta accettata alla domanda a cui ti sei collegato.


AGGIORNAMENTO: JDK 14 sta introducendo i record come funzionalità di anteprima. Queste non sono tuple, ma possono essere utilizzate per salvare molti degli stessi problemi. Nel tuo esempio specifico dall'alto, potrebbe essere simile a questo:

public class Jdk14Example {
    record CountForIndex(int index, long count) {}

    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()
                        .mapToObj(i -> {
                            long count = IntStream.range(0, directed_acyclic_graph[i].length)
                                            .filter(j -> directed_acyclic_graph[j][i])
                                            .count();
                            return new CountForIndex(i, count);
                        }
                        )
                        .filter(n -> n.count == 0)
                        .collect(() -> new ArrayList<CountForIndex>(), (c, e) -> c.add(e), (c1, c2) -> c1.addAll(c2))
        );
    }
}

Quando compilato ed eseguito con JDK 14 (al momento della scrittura, questa è una build con accesso anticipato) utilizzando il --enable-previewflag, si ottiene il seguente risultato:

[CountForIndex[index=0, count=0], CountForIndex[index=2, count=0], CountForIndex[index=4, count=0]]

In realtà una delle risposte di @StuartMarks mi ha permesso di risolverlo senza tuple, ma dal momento che non sembra generalizzare probabilmente alla fine ne avrò bisogno.
Negromante,

@necromancer Sì, è un'ottima risposta. La libreria apache può ancora tornare utile a volte, ma tutto dipende dal design del linguaggio Javas. Fondamentalmente, le tuple dovrebbero essere primitive (o simili) per funzionare come fanno in altre lingue.
Blalasaadri,

1
Nel caso in cui non l'avessi notato, la risposta includeva questo link estremamente informativo: cr.openjdk.java.net/~jrose/values/values-0.html sulla necessità e le prospettive di tali primitivi tra cui le tuple.
Negromante,

17

Sembra che l'esempio completo possa essere risolto senza l'uso di alcun tipo di struttura Pair. La chiave è filtrare gli indici di colonna, con il predicato che controlla l'intera colonna, invece di mappare gli indici di colonna sul numero di falsevoci in quella colonna.

Il codice che fa questo è qui:

    System.out.println(
        IntStream.range(0, acyclic_graph.length)
            .filter(i -> IntStream.range(0, acyclic_graph.length)
                                  .noneMatch(j -> acyclic_graph[j][i]))
            .boxed()
            .collect(toList()));

Ciò si traduce in un output del [0, 2, 4]quale penso sia il risultato corretto richiesto dall'OP.

Notare anche l' boxed()operazione che racchiude i intvalori in Integeroggetti. Ciò consente di utilizzare il toList()raccoglitore preesistente invece di dover scrivere le funzioni del raccoglitore che eseguono il pugilato.


1
+1 asso nella manica :) Questo non generalizza ancora, giusto? Questo è stato l'aspetto più sostanziale della domanda perché mi aspetto di affrontare altre situazioni in cui uno schema come questo non funzionerà (ad esempio colonne con non più di 3 valori true). Di conseguenza, accetterò l'altra tua risposta come corretta, ma indicherò anche questa! Grazie mille :)
Negromante il

Questo è corretto ma accetta l'altra risposta dallo stesso utente. (vedi osservazioni di cui sopra e altrove.)
negromante

1
@necromancer Esatto, questa tecnica non è del tutto generale nei casi in cui si desidera l'indice, ma l'elemento dati non può essere recuperato o calcolato utilizzando l'indice. (Almeno non facilmente.) Ad esempio, considera un problema in cui stai leggendo righe di testo da una connessione di rete e vuoi trovare il numero di riga dell'ennesima riga che corrisponde a un modello. Il modo più semplice è mappare ogni linea in una coppia o in una struttura dati composita per numerare le linee. C'è probabilmente un modo bizzarro ed efficace per farlo senza una nuova struttura di dati.
Stuart Marks,

@StuartMarks, Una coppia è <T, U>. una tripla <T, U, V>. ecc. Il tuo esempio è un elenco, non una coppia.
Pacerier,

7

Vavr (precedentemente chiamato Javaslang) ( http://www.vavr.io ) fornisce anche tuple (fino a una dimensione di 8). Ecco il javadoc: https://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/Tuple.html .

Questo è un semplice esempio:

Tuple2<Integer, String> entry = Tuple.of(1, "A");

Integer key = entry._1;
String value = entry._2;

Perché lo stesso JDK non abbia avuto un semplice tipo di tuple fino ad ora è un mistero per me. Scrivere lezioni di wrapper sembra essere un business quotidiano.


Alcune versioni di Vavr utilizzavano tiri subdoli sotto il cofano. Fai attenzione a non usarli.
Thorbjørn Ravn Andersen,

7

Da Java 9, è possibile creare istanze di Map.Entrypiù facile rispetto a prima:

Entry<Integer, String> pair = Map.entry(1, "a");

Map.entryrestituisce un non modificabile Entrye vieta i null.


6

Dato che ti preoccupi solo degli indici, non è necessario mappare affatto le tuple. Perché non scrivere semplicemente un filtro che utilizza gli elementi di ricerca nell'array?

     int[] value =  ...


IntStream.range(0, value.length)
            .filter(i -> value[i] > 30)  //or whatever filter you want
            .forEach(i -> System.out.println(i));

+1 per un'ottima soluzione pratica. Tuttavia, non sono sicuro che si generalizzi alla mia situazione in cui sto generando i valori al volo. Ho posto la mia domanda come un array per offrire un semplice caso a cui pensare e hai trovato una soluzione eccellente.
Negromante

5

Sì.

Map.Entrypuò essere usato come a Pair.

Sfortunatamente non aiuta con i flussi Java 8 poiché il problema è che anche se lambdas può accettare più argomenti, il linguaggio Java consente solo di restituire un singolo valore (oggetto o tipo primitivo). Ciò implica che ogni volta che si dispone di un flusso si finisce con il passare un singolo oggetto dall'operazione precedente. Questa è una mancanza nel linguaggio Java, perché se fossero supportati più valori di ritorno E i flussi li supportassero potremmo avere compiti non banali molto più belli fatti dai flussi.

Fino ad allora, c'è solo un piccolo uso.

EDIT 2018-02-12: Mentre stavo lavorando a un progetto, ho scritto una classe di supporto che aiuta a gestire il caso speciale di avere un identificatore all'inizio dello stream che è necessario in un secondo momento, ma la parte intermedia dello stream non lo conosce. Fino a quando non arrivo a rilasciarlo da solo, è disponibile su IdValue.java con un test unitario su IdValueTest.java


2

Le collezioni Eclipse hanno Paire tutte le combinazioni di coppie primitive / oggetto (per tutte e otto le primitive).

La Tuplesfactory può creare istanze di Pair, e la PrimitiveTuplesfactory può essere usata per creare tutte le combinazioni di coppie primitive / oggetto.

Li abbiamo aggiunti prima del rilascio di Java 8. Sono stati utili per implementare Iteratori chiave / valore per le nostre mappe primitive, che supportiamo anche in tutte le combinazioni primitive / oggetti.

Se sei disposto ad aggiungere l'overhead aggiuntivo della libreria, puoi utilizzare la soluzione accettata di Stuart e raccogliere i risultati in una primitiva IntListper evitare il pugilato. Abbiamo aggiunto nuovi metodi in Eclipse Collections 9.0 per consentire la Int/Long/Doublecreazione di raccolte da Int/Long/DoubleStream.

IntList list = IntLists.mutable.withAll(intStream);

Nota: sono un committer per le raccolte Eclipse.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.