2014-09-26 6 views
7

Sono nuovo di Java 8 e al momento non riesco a cogliere pienamente gli Stream, è possibile riempire un array utilizzando le operazioni funzionali di Stream? Questo è un codice di esempio di come lo farei con uno standard per ciclo:Riempimento di un array multidimensionale utilizzando un flusso

public static void testForLoop(){ 
    String[][] array = new String[3][3]; 
    for (int x = 0; x < array.length; x++){ 
     for (int y = 0; y < array[x].length; y++){ 
      array[x][y] = String.format("%c%c", letter(x), letter(y)); 
     } 
    }    
} 

public static char letter(int i){ 
    return letters.charAt(i); 
} 

Se è possibile come lo farei con Stream? Se è possibile, è conveniente (prestazioni e leggibilità)?

+0

terribilmente rilevante, ma penso che si intende 'array [x] .length' per il ciclo interno. –

+0

Sì, ho sicuramente fatto –

+0

Basta usare un ciclo for standard. Il tuo codice è semplice e chiaramente ovvio per il lettore. Per quanto elegante possa apparire una soluzione di streaming, non vedo che aggiunga nulla qui. –

risposta

12

Ecco una soluzione che produce la matrice anziché modificare una variabile definita in precedenza:

String[][] array = 
    IntStream.range(0, 3) 
      .mapToObj(x -> IntStream.range(0, 3) 
            .mapToObj(y -> String.format("%c%c", letter(x), letter(y))) 
            .toArray(String[]::new)) 
      .toArray(String[][]::new); 

Se si desidera utilizzare flussi paralleli allora è molto importante per evitare effetti collaterali come la modificazione di una variabile (array o oggetto). Potrebbe portare a condizioni di competizione o altri problemi di concorrenza.Puoi leggere ulteriori informazioni a riguardo in java.util.stream package documentation - vedere Non interferenza, Comportamenti stateless e Sezioni di effetti sezioni.

+0

In realtà questo sembra un po 'più convulso della soluzione finale che ho trovato, dovrei provare quale è più veloce però. –

+0

Giusto - è meno leggibile rispetto ad altre soluzioni ma ha il Vantaggio che non modifica una variabile esterna ciò che è generalmente molto importante quando si desidera utilizzare flussi paralleli - si pensi alle condizioni di gara –

+0

Puoi leggere ulteriori informazioni al riguardo nel [java.util.stream] (https: // docs .oracle.com/javase/8/docs/api/java/util/stream/package-summary.html) documentazione del pacchetto (* Non-interference *, * Stateless behavior * e * Side-effects * section) –

2

Ci sono un paio di modi per farlo.

Un modo è con un paio annidato IntStreams negli indici di riga e di colonna:

String[][] testStream() { 
    String[][] array = new String[3][3]; 
    IntStream.range(0, array.length).forEach(x -> 
     IntStream.range(0, array[x].length).forEach(y -> 
      array[x][y] = String.format("%c%c", letter(x), letter(y)))); 
    return array; 
} 

altro modo che sembra promettente è quello di utilizzare Array.setAll invece di flussi. Questo è ottimo per generare valori per un array monodimensionale: si fornisce una funzione che mappa dall'indice dell'array al valore che si desidera assegnare nell'array. Ad esempio, è possibile eseguire questa operazione:

String[] sa = new String[17]; 
Arrays.setAll(sa, i -> letter(i)); 

Sfortunatamente è meno conveniente per gli array multidimensionali. Il metodo setAll che accetta un lambda che restituisce un valore assegnato alla posizione dell'array in quell'indice. Se hai creato un array multidimensionale, le dimensioni superiori sono già inizializzate con matrici dimensionali inferiori. Non si desidera assegnarli, ma si desidera il comportamento di ciclo implicito di setAll.

Con questo in mente, è possibile utilizzare setAll per inizializzare l'array multidimensionale come questo:

static String[][] testArraySetAll() { 
    String[][] array = new String[3][3]; 
    Arrays.setAll(array, x -> { 
     Arrays.setAll(array[x], y -> String.format("%c%c", letter(x), letter(y))); 
     return array[x]; 
    }); 
    return array; 
} 

L'interno setAll è ragionevolmente bello, ma quello esterno deve avere una lambda dichiarazione che chiama la interno setAll e quindi restituisce l'array corrente. Non troppo carino.

Non è chiaro per me che entrambi questi approcci siano meglio dei tipici cicli for nidificati.

4

Il modo migliore è una combinazione dei due approcci di Stuart Marks’ answer.

IntStream.range(0, array.length).forEach(x -> Arrays.setAll(
    array[x], y -> String.format("%c%c", letter(x), letter(y)))); 

Il ragionamento conduce alla soluzione è che “riempire un array multi-dimensionale” in Java significa “iterazione di matrice esterno (s)” seguita da “riempire una matrice unidimensionale” come String[][] è solo un array di elementi String[] in Java. Per impostare i loro elementi è necessario scorrere su tutti gli elementi String[] e poiché è necessario l'indice per calcolare il valore finale, non è possibile utilizzare Arrays.stream(array).forEach(…). Quindi per l'array esterno che itera sugli indici è appropriato.

Per gli array interni la ricerca è la soluzione migliore per la modifica di una matrice (monodimensionale). Qui, Arrays.setAll(…,…) è appropriato.

0

Dopo aver lavorato e la sperimentazione in tutto questo è la migliore opzione sono venuto con:

IntStream.range(0, array.length).forEach(x -> Arrays.setAll(array[x], y -> builder.build2Dobject(x, y))); 

(Nel caso specifico ho proposto che sarebbe stato:

IntStream.range(0, array.length).forEach(x -> Arrays.setAll(array[x], y -> String.format("%c%c", letter(x), letter(y))); 

per un array 3D è semplicemente:

IntStream.range(0, array.length).forEach(x -> IntStream.range(0, array[x].length).forEach(y -> Arrays.setAll(array[x][y], z -> builder.build3Dobject(x, y, z)))); 

questo è il codice che permette al programma di scegliere l'opzione più veloce:

public static void fill2DArray(Object[][] array, Object2DBuilderReturn builder){ 
    int totalLength = array.length * array[0].length; 
    if (totalLength < 200){ 
     for(int x = 0; x < array.length; x++){ 
      for (int y = 0; y < array[x].length; y++){ 
       array[x][y] = builder.build2Dobject(x, y); 
      } 
     } 
    } else if (totalLength >= 200 && totalLength < 1000){ 
     IntStream.range(0, array.length).forEach(x -> Arrays.setAll(array[x], y -> builder.build2Dobject(x, y))); 
    } else { 
     IntStream.range(0, array.length).forEach(x -> Arrays.setAll(array[x], y -> builder.build2Dobject(x, y))); 
    } 
} 

l'interfaccia funzionale:

@FunctionalInterface 
public interface Object2DBuilderReturn<T> { 
    public T build2Dobject(int a, int b); 
} 
Non
Problemi correlati