2016-12-20 13 views
11

Co jest odpowiednikiem wspaniałego foldLeft Scali w Java 8?Równoważnik składanki Scala w Javie 8

Kusiło mnie, aby pomyśleć, że to był reduce, ale zmniejszenie musi zwrócić coś identycznego do tego, na co się zmniejsza.

Przykład:

import java.util.List; 

public class Foo { 

    // this method works pretty well 
    public int sum(List<Integer> numbers) { 
     return numbers.stream() 
         .reduce(0, (acc, n) -> (acc + n)); 
    } 

    // this method makes the file not compile 
    public String concatenate(List<Character> chars) { 
     return chars.stream() 
        .reduce(new StringBuilder(""), (acc, c) -> acc.append(c)).toString(); 
    } 
} 

Problem w powyższym kodzie jest acc umulator: new StringBuilder("")

Zatem ktoś może wskazać mi właściwą równowartość foldLeft/naprawić mojego kodu?

+2

FYI: Nazwa tego języka to "Scala", a nie "SCALA". (Wydaje mi się, że istnieje inny język o nazwie "SCALA", który prawdopodobnie nie jest tym, który masz na myśli.) –

+0

Powiązany http://stackoverflow.com/questions/30736587/builder-pattern-with-a-java-8-stream – Tunaki

Odpowiedz

6

Aktualizacja:

Oto wstępna próba, aby uzyskać kod stałe:

public static String concatenate(List<Character> chars) { 
     return chars 
       .stream() 
       .reduce(new StringBuilder(), 
           StringBuilder::append, 
           StringBuilder::append).toString(); 
    } 

Wykorzystuje następujące reduce method:

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

To może wydawać się dziwne, ale jeśli spojrzeć w javadocs jest ładne wyjaśnienie, które może pomóc ci szybko zrozumieć szczegóły. Redukcja jest równoznaczne z następującym kodem:

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

na bardziej dogłębne wyjaśnienie w sprawdź this source.

To użycie nie jest poprawne, ponieważ narusza umowę ograniczającą, która stwierdza, że ​​akumulator powinien być asocjacyjną, nie zakłócającą, bezpaństwową funkcją włączenia dodatkowego elementu do wyniku. Innymi słowy, ponieważ tożsamość jest zmienna, wynik zostanie przerwany w przypadku równoległego wykonania.

Jak zauważył w komentarzach poniżej prawidłowej opcji jest za pomocą redukcji w następujący sposób:

return chars.stream().collect(
    StringBuilder::new, 
    StringBuilder::append, 
    StringBuilder::append).toString(); 

Oferent StringBuilder::new będą wykorzystywane do tworzenia pojemniki wielokrotnego użytku, które zostaną później połączone.

+6

Tak samo, jak w przypadku innej odpowiedzi: * Nie używaj 'zmniejszania' w ten sposób. Funkcje * nie * mogą modyfikować swoje parametry. Poprawne użycie to '.collect (StringBuilder :: new, StringBuilder :: append, StringBuilder :: append)'. Zobacz [Mutable reduction] (https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#MutableReduction). – Holger

+0

@Holger: dzięki, to prawda. Odpowiedź jest zaktualizowana. –

+3

Tu nie chodzi o efektywność, chodzi o poprawność. Używanie 'reduce' w ten sposób narusza umowę i musi być uznane za zepsute, nawet jeśli w pewnych okolicznościach może to zrobić. Przede wszystkim z pewnością przerwie to przy korzystaniu ze strumienia równoległego. – Holger

7

Metoda, której szukasz, to java.util.Stream.reduce, szczególnie przeciążenie z trzema parametrami, tożsamością, akumulatorem i funkcją binarną. To jest właściwy odpowiednik Scala's foldLeft.

jednak jesteś nie wolno używać Javy reduce w ten sposób, a także nie Scala foldLeft o to chodzi. Zamiast tego użyj collect.

+3

Podczas gdy podoba mi się twoja odpowiedź, "nie masz pozwolenia" wydaje się trochę źle. Czy możesz to powtórzyć? –

+2

Byłby to błąd typu, gdyby system typu Java był wystarczająco ekspresyjny, aby wyrazić to ograniczenie. Ale tak nie jest, a ograniczenie jest wspomniane tylko w JavaDocs. JavaDocs mówią, jakie rodzaje obiektów można przekazywać, a obiekty, które przechodzi OP, nie spełniają tych ograniczeń, nie wolno im wywoływać 'zmniejszenia'. Jak inaczej to określasz? –

+2

Cóż, nie ma żadnych ograniczeń co do typu, tylko w odniesieniu do sposobu korzystania z obiektów. Jeśli użyjesz funkcji akumulatora i kombajnu, takich jak '(a, b) -> nowy StringBuilder(). Append (a) .append (b)', byłby to legalny użytek, choć niezbyt wydajny, w porównaniu do 'zbierania ' rozwiązanie. – Holger

7

Nie ma odpowiednika foldLeft w Java Stream API. Jak inni zauważyli, reduce(identity, accumulator, combiner) jest blisko, ale nie jest to równoważne z foldLeft, ponieważ wymaga, aby wynikowy typ B łączył się z samym sobą i był asocjacyjny (w innych terminach, podobny do monoidu), właściwość, która nie ma każdego typu.

Jest też prośba wzmocnienie tego: add Stream.foldLeft() terminal operation

Aby zobaczyć dlaczego zmniejszyć nie zadziała, należy rozważyć następujący kod, którym zamierza wykonywać szereg operacji arytmetycznych zaczynając od danej liczby:

val arithOps = List(('+', 1), ('*', 4), ('-', 2), ('/', 5)) 
val fun: (Int, (Char, Int)) => Int = { 
    case (x, ('+', y)) => x + y 
    case (x, ('-', y)) => x - y 
    case (x, ('*', y)) => x * y 
    case (x, ('/', y)) => x/y 
} 
val number = 2 
arithOps.foldLeft(number)(fun) // ((2 + 1) * 4 - 2)/5 

Jeśli próbowałeś napisać reduce(2, fun, combine), jaką funkcję łączenia mógłbyś przejść, która łączy dwie liczby? Dodanie dwóch liczb wyraźnie nie rozwiązuje problemu. Ponadto, wartość 2 wyraźnie nie jest elementem.

Należy zauważyć, że żadna operacja wymagająca wykonania sekwencyjnego nie może być wyrażona w postaci reduce. foldLeft jest w rzeczywistości bardziej ogólny niż reduce: można zaimplementować reduce z foldLeft, ale nie można zaimplementować foldLeft z reduce.