2014-09-26 19 views
7

Jestem nowicjuszem w Javie 8 i obecnie nie mogę w pełni uchwycić strumieni, czy można wypełnić tablicę za pomocą operacji funkcjonalnych Stream? To jest przykładowy kod, w jaki sposób zrobiłbym to ze standardową pętlą:Wypełnianie wielowymiarowej tablicy za pomocą strumienia

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

Jeśli to możliwe, w jaki sposób mogę to zrobić za pomocą Stream? Jeśli to możliwe, czy jest to wygodne (wydajność i czytelność)?

+0

Nie strasznie istotne, ale myślę, że oznaczało 'tablica [x] .length' dla pętli wewnętrznej. –

+0

Tak, zdecydowanie zrobiłem –

+0

Wystarczy użyć standardowej pętli for. Twój kod jest prosty i czytelny dla czytelnika. Wygląda tak elegancko, jak rozwiązanie strumieniowe, ale nie widzę, żeby coś tu dodawało. –

Odpowiedz

12

Tutaj masz rozwiązanie, które produkuje tablicy zamiast modyfikowania wcześniej zdefiniowanej zmiennej:

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

Jeśli chcesz użyć równoległych strumieni to jest to bardzo ważne, aby uniknąć skutków ubocznych, takich jak modyfikacje zmiennej (array lub obiekt). Może to prowadzić do warunków wyścigu lub innych problemów związanych z współbieżnością.Możesz przeczytać więcej na ten temat w java.util.stream package documentation - patrz Brak zakłóceń, Zachowania bezstanowe i Efekty uboczne przekroje.

+0

W rzeczywistości wydaje mi się to nieco bardziej skomplikowane niż ostateczne rozwiązanie, które znalazłem, powinienem przetestować, który z nich jest szybszy.) –

+0

Dobrze - jest mniej czytelny niż inne rozwiązania, ale ma Zaletą jest to, że nie modyfikuje ona zmiennej zewnętrznej, co jest na ogół bardzo ważne, gdy chcesz korzystać z równoległych strumieni - pomyśl o warunkach wyścigu. –

+0

Możesz przeczytać więcej na ten temat w [java.util.stream] (https: // docs .oracle.com/javase/8/docs/api/java/util/stream/package-summary.html) dokumentacja pakietu (* Brak zakłóceń *, * Zachowania bezstanowe * i * Efekty uboczne * sekcje) –

2

Istnieje kilka sposobów, aby to zrobić.

Jednym z nich jest z parą zagnieżdżony IntStreams przez indeksy wierszy i kolumn:

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

Innym sposobem, który wydaje się być obiecujące jest stosowanie Array.setAll zamiast strumieni. Jest to doskonałe do generowania wartości dla jednowymiarowej tablicy: udostępniasz funkcję, która odwzorowuje indeks tablicy na wartość, którą chcesz przypisać w tablicy. Na przykład można to zrobić:

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

Niestety jest to mniej wygodne dla wielowymiarowych macierzy. Metoda setAll, która pobiera lambda, która zwraca wartość przypisaną do położenia tablicy w tym indeksie. Jeśli masz tablicę wielowymiarową, wyższe wymiary są już zainicjowane za pomocą macierzy niższych wymiarów. Nie chcesz przypisać do nich, ale chcesz ukryte zachowanie pętli setAll.

Mając to na uwadze, można użyć setAll aby zainicjować tablicę wielowymiarową takiego:

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

Wewnętrzna setAll jest dość ładne, ale zewnętrzna jeden musi mieć lambda oświadczenie, że wywołuje wewnętrzną setAll a następnie zwraca bieżącą tablicę. Nie za ładne.

Nie jest dla mnie jasne, że jedno z tych podejść jest lepsze niż typowe zagnieżdżone pętle for.

4

Najlepszy sposób to połączenie dwóch podejść z Stuart Marks’ answer.

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

Rozumowanie prowadzące do rozwiązania jest to, że „napełniania wielowymiarową macierz” w Java oznacza „kolejno po zewnętrznej matrycy (a)”, a następnie „wypełnienie jednowymiarową tablicę” jako String[][] jest po prostu tablica elementów String[] w Javie. Aby ustawić ich elementy, należy powtórzyć wszystkie elementy String[], a ponieważ potrzebny jest indeks do obliczenia wartości końcowej, nie można użyć wartości Arrays.stream(array).forEach(…). Dlatego też zewnętrzna macierzowa iteracja nad wskaźnikami jest właściwa.

Dla tablic wewnętrznych wyszukiwanie jest najlepszym rozwiązaniem do modyfikacji (jednowymiarowej) tablicy. Tutaj jest odpowiednie ustawienie Arrays.setAll(…,…).

0

Po pracy i testowanie wokół to jest najlepszym rozwiązaniem Przyjechałem z:

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

(w konkretnym przypadku I proponowanego byłoby:

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

dla 3d tablicy to po prostu:

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

ten kod pozwala programowi wybrać najszybszą opcję:

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

interfejs funkcjonalny:

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