2014-04-02 9 views
26

W jakich okolicznościach trzeci argument do "zmniejszenia" wywoływany w strumieniach Java 8?Cel trzeciego argumentu funkcji "zmniejszania" w programowaniu funkcjonalnym Java 8

Poniższy kod próbuje przejść przez listę łańcuchów i dodać wartości punktów kodowych pierwszego znaku każdego z nich. Wartość zwrócona przez ostateczną wartość lambda nigdy nie wydaje się być użyta, a jeśli wstawisz println, to nigdy nie wydaje się być wywoływana. Dokumentacja opisuje go jako „sumator”, ale nie mogę znaleźć więcej szczegółów ...

int result = 
    data.stream().reduce(0, (total,s) -> total + s.codePointAt(0), (a,b) -> 1000000); 

Odpowiedz

17

Mówisz o this function?

reduce <U> U reduce(U identity, 
      BiFunction<U,? super T,U> accumulator, 
      BinaryOperator<U> combiner) 

Przeprowadza redukcji na elementach tego strumienia za pomocą dostarczonego tożsamości, gromadzenia i łączących funkcje. Jest to odpowiednik:

U result = identity; 
for (T element : this stream) 
    result = accumulator.apply(result, element) 
return result; 

, ale nie jest ograniczony do wykonywania sekwencyjnego. Wartość tożsamości musi być tożsamością dla funkcji łączenia. Ten oznacza, że ​​dla wszystkich u, sumator (tożsamość, u) jest równy u. Dodatkowo funkcja łączenia musi być zgodna z funkcją akumulowania ; dla wszystkich u i t, musi posiadać:

combiner.apply(u, accumulator.apply(identity, t)) == 
    accumulator.apply(u, t) 

To jest operacja terminalu.

API Uwaga: wiele redukcji za pomocą tego formularza można przedstawić w bardziej wyraźny sposób niż przy użyciu mapy i operacji zmniejszania. Funkcja akumulacji działa jako połączony program odwzorowujący i akumulator, który może być czasem bardziej wydajny niż oddzielne odwzorowywanie i redukowanie, na przykład , ponieważ znajomość poprzednio zmniejszonej wartości pozwala uniknąć obliczeń o pewnej wartości. Typ Parametry: U - Typ wyniku Parametry: tożsamość - wartość identyfikacyjna akumulatora funkcji sumatora - asocjacyjna, nie zakłócająca, bezpaństwowa funkcja włączenia dodatkowego elementu do sumatora wyników - asocjacyjnego, -interfering, bezpaństwowcem funkcja łączenia dwóch wartości, które muszą być zgodne z funkcją akumulatora Zwraca: wynik redukcji Zobacz także: zmniejszenia (BinaryOperator) zmniejszenia (obiekt, BinaryOperator)

zakładam jego celem jest umożliwienie równoległego obliczania, a więc domyślam się, że jest on stosowany tylko wtedy, gdy redukcja jest równa pe powstały równolegle. Jeśli jest wykonywany sekwencyjnie, nie ma potrzeby używania combiner. Nie wiem tego na pewno - po prostu zgaduję na podstawie komentarza do dokumentu "[...] nie jest przymuszany do wykonania sekwencyjnie" oraz wielu innych wzmianek o "równoległej realizacji" w komentarzach.

+0

Ów funkcji rzeczywiście. Ciekawa myśl o paralelności - wypróbuje to ... –

+2

To by się wydawało. Wstawienie ".parallel()" powoduje zwrócenie 1000000. –

+0

@Garth fajne, nie mogę się doczekać, aby wypróbować to sam! –

3

Prosty kod test, aby potwierdzić korzystanie z sumatora:

String[] strArray = {"abc", "mno", "xyz"}; 
List<String> strList = Arrays.asList(strArray); 

System.out.println("stream test"); 
int streamResult = strList.stream().reduce(
     0, 
     (total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); }, 
     (a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "]"); return 1000000;} 
    ); 
System.out.println("streamResult: " + streamResult); 

System.out.println("parallelStream test"); 
int parallelStreamResult = strList.parallelStream().reduce(
     0, 
     (total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); }, 
     (a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "]"); return 1000000;} 
    ); 
System.out.println("parallelStreamResult: " + parallelStreamResult); 

System.out.println("parallelStream test2"); 
int parallelStreamResult2 = strList.parallelStream().reduce(
     0, 
     (total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); }, 
     (a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "] a+b[" + (a+b) + "]"); return a+b;} 
    ); 
System.out.println("parallelStreamResult2: " + parallelStreamResult2); 

wyjściowa:

stream test 
accumulator: total[0] s[abc] s.codePointAt(0)[97] 
accumulator: total[97] s[mno] s.codePointAt(0)[109] 
accumulator: total[206] s[xyz] s.codePointAt(0)[120] 
streamResult: 326 
parallelStream test 
accumulator: total[0] s[mno] s.codePointAt(0)[109] 
accumulator: total[0] s[abc] s.codePointAt(0)[97] 
accumulator: total[0] s[xyz] s.codePointAt(0)[120] 
combiner: a[109] b[120] 
combiner: a[97] b[1000000] 
parallelStreamResult: 1000000 
parallelStream test2 
accumulator: total[0] s[mno] s.codePointAt(0)[109] 
accumulator: total[0] s[xyz] s.codePointAt(0)[120] 
accumulator: total[0] s[abc] s.codePointAt(0)[97] 
combiner: a[109] b[120] a+b[229] 
combiner: a[97] b[229] a+b[326] 
parallelStreamResult2: 326 
6

myślę Reduction operations ustęp z java.util.stream zbiorczym może odpowiedzieć na to pytanie.Podam najważniejszą część tutaj:


W bardziej ogólnej postaci, operacja zmniejszenia na elementach typu <T> uzyskując wynik typu <U> wymaga trzech parametrów:

<U> U reduce(U identity, 
       BiFunction<U, ? super T, U> accumulator, 
       BinaryOperator<U> combiner); 

Tutaj element tożsamości jest zarówno początkową wartością początkową dla redukcji, jak i domyślnym wynikiem, jeśli nie ma żadnych elementów wejściowych. Funkcja akumulatora przyjmuje częściowy wynik i następny element i generuje nowy wynik cząstkowy. Funkcja łączenia łączy dwa częściowe wyniki, dając nowy częściowy wynik. (Kombinator jest konieczny przy równoległych redukcjach, gdzie dane wejściowe są podzielone, częściowa kumulacja obliczona dla każdej partycji, a następnie częściowe wyniki są łączone w celu uzyskania końcowego wyniku). funkcja łączenia. Oznacza to, że dla wszystkich u, combiner.apply(identity, u) jest równa u. Dodatkowo funkcja łączenia musi być asocjacyjna i musi być zgodna z funkcją akumulatora: dla wszystkich u i t, combiner.apply(u, accumulator.apply(identity, t)) musi być equals() do accumulator.apply(u, t).

Formularz trzyargumentowy jest uogólnieniem dwuargumentowej formy, obejmującym etap mapowania do etapu akumulacji. Mogliśmy ponownie rzucić prostą sumą-of-ciężarami przykład stosując bardziej ogólną postać następująco:

int sumOfWeights = widgets.stream() 
          .reduce(0, 
            (sum, b) -> sum + b.getWeight()) 
            Integer::sum); 

choć wyraźne zmniejszenie map-forma jest bardziej czytelny, a zatem powinny być zazwyczaj preferowane. Uogólniona forma jest przewidziana dla przypadków, w których można zoptymalizować znaczną pracę przez połączenie mapowania i zredukowania do jednej funkcji.


Innymi słowy, o ile mi zrozumieć, forma trzy argument jest przydatna w dwóch przypadkach:

  1. Gdy równoległe sprawy egzekucyjne.
  2. Gdy można osiągnąć znaczącą optymalizację wydajności, łącząc etapy mapowania i akumulacji. W przeciwnym razie można użyć prostszej i czytelnej, jawnej formy zmniejszania liczby map.

zrozumiały sposób wspomniano poprzednio w tym samym dokumencie:

int sumOfWeights = widgets.parallelStream() 
     .filter(b -> b.getColor() == RED) 
     .mapToInt(b -> b.getWeight()) 
     .sum();