2014-09-26 4 views
7

Ich bin neu in Java 8 und derzeit nicht in der Lage, Streams vollständig zu erfassen, ist es möglich, ein Array mit den Stream-Funktionen zu füllen? Dies ist ein Beispiel-Code, wie ich es mit einem Standard-for-Schleife tun würde:Füllen eines mehrdimensionalen Arrays mit einem Stream

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

Wenn es möglich ist, wie würde ich es tun mit Strom? Wenn es möglich ist, ist es bequem (Leistung und Lesbarkeit)?

+0

Nicht besonders relevant, aber ich denke, Sie 'Array gemeint [x] .length' für die innere Schleife. –

+0

Ja, ich tat definitiv –

+0

Verwenden Sie einfach eine Standard-For-Schleife. Ihr Code ist einfach und klar für den Leser. So elegant eine Stream-Lösung auch aussehen mag, ich sehe hier nichts. –

Antwort

12

Hier haben Sie eine Lösung, die das Array statt Modifizieren einer zuvor definierten Variablen erzeugt:

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

Wenn Sie dann parallele Ströme verwendet werden soll, es ist sehr wichtig, Nebenwirkungen wie Änderungen einer Variablen zu vermeiden (array oder Objekt). Es kann zu Race Conditions oder anderen Nebenläufigkeitsproblemen führen.Sie können mehr darüber in java.util.stream package documentation lesen - siehe Nicht-Interferenz, Staatenloses Verhalten und Nebenwirkungen Abschnitte.

+0

Eigentlich scheint dies ein bisschen mehr Konvolut als die endgültige Lösung, die ich fand, ich sollte testen, welche schneller ist. –

+0

Richtig - es ist weniger lesbar als andere Lösungen, aber es hat die Vorteil, dass es keine externe Variable verändert, was im Allgemeinen sehr wichtig ist, wenn Sie parallele Streams verwenden wollen - denken Sie an Race Conditions. –

+0

Sie können mehr darüber im [java.util.stream] (https: // docs .oracle.com/javase/8/docs/api/java/util/stream/package-summary.html) Paketdokumentation (* Nicht-Interferenz *, * Staatenloses Verhalten * und * Nebenwirkungen * Sektionen). –

2

Es gibt mehrere Möglichkeiten, dies zu tun.

Eine Möglichkeit ist, mit ein paar IntStreams über die Zeilen- und Spaltenindizes verschachtelt:

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

Ein anderer Weg, das ist scheint vielversprechend Array.setAll statt Ströme zu verwenden. Dies ist ideal zum Generieren von Werten für ein eindimensionales Array: Sie stellen eine Funktion bereit, die vom Arrayindex auf den Wert zuordnet, der im Array zugewiesen werden soll. Zum Beispiel könnten Sie dies tun:

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

Leider ist es weniger praktisch für mehrdimensionale Arrays. Die setAll-Methode, die ein Lambda akzeptiert, das einen Wert zurückgibt, der der Array-Position an diesem Index zugewiesen ist. Wenn Sie ein mehrdimensionales Array erstellt haben, werden die höheren Dimensionen bereits mit niederdimensionalen Arrays initialisiert. Sie möchten ihnen nicht zuweisen, aber Sie möchten das implizite Schleifenverhalten von setAll.

Vor diesem Hintergrund können Sie setAll verwenden, um die multidimensionalen Array wie folgt zu initialisieren:

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

Die innere setAll ist recht nett, aber die äußere eine Aussage Lambda haben muss, die die innere setAll ruft und gibt dann das aktuelle Array zurück. Nicht zu hübsch.

Es ist mir nicht klar, dass einer dieser Ansätze besser als die typischen geschachtelten for-Schleifen ist.

4

Der beste Weg ist eine Kombination der beiden Ansätze von Stuart Marks’ answer.

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

Die Überlegung zu der Lösung führt, ist, dass in Java „ein mehrdimensionales Array Füllen“ bedeutet, „über die äußere Array Iterieren (s)“, gefolgt von „ein eindimensionales Array Füllen“, wie String[][] ist nur ein Array von String[] Elementen in Java. Um ihre Elemente zu setzen, müssen Sie über alle String[] Elemente iterieren und da Sie den Index benötigen, um den endgültigen Wert zu berechnen, können Sie Arrays.stream(array).forEach(…) nicht verwenden. Für das äußere Array ist das Iterieren über die Indizes angemessen.

Für die inneren Arrays ist die Suche nach der besten Lösung zum Ändern eines (eindimensionalen) Array. Hier ist Arrays.setAll(…,…) angebracht.

0

Nach der Arbeit und Tests, um dies ist die beste Option, komme ich mit:

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

(In dem speziellen Fall, den ich vorgeschlagen, sei:

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

für einen 3D-Array ist es einfach:

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

dies ist der Code, der das Programm die schnellste Option wählen können:

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

die funktionale Schnittstelle:

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