2016-12-20 3 views
11

Что такое эквивалент Scala's great foldLeft в Java 8?Эквивалент foldLeft Scala в Java 8

У меня возникло искушение подумать, что это было reduce, но у сокращения должно быть что-то похожее на то, что он уменьшает.

Пример:

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

Проблема в приведенном выше коде является acc umulator: new StringBuilder("")

Таким образом, кто-то может мне точку в правильном эквивалент foldLeft/исправить мой код?

+2

FYI: Название языка «Scala», а не «SCALA». (Я считаю, что существует другой язык под названием «SCALA», который, вероятно, не тот, который вы имеете в виду.) –

+0

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

ответ

6

Update:

Вот первая попытка получить код исправлено:

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

Он использует следующие reduce method:

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

Это может показаться странным, но если вы посмотрите в javadocs есть хорошее объяснение, которое может помочь вам быстро понять детали. Снижение эквивалентно следующий код:

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

Для более подробного описания работы, пожалуйста, проверьте this source.

Это использование неверно, поскольку оно нарушает договор сокращения, в котором говорится, что аккумулятор должен быть ассоциативной, неинтерферирующей функцией без учета состояния для включения дополнительного элемента в результат. Другими словами, поскольку идентификатор изменчив, результат будет нарушен в случае параллельного выполнения.

Как указано в комментариях ниже правильный вариант используется сокращение следующим образом:

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

Поставщик StringBuilder::new будет использоваться для создания многоразовых контейнеров, которые позже будут объединены.

+6

То же, что и с другим ответом: * Не используйте 'сокращение' таким образом. Функции * не * позволяют изменять их параметры. Правильное использование: '.collect (StringBuilder :: new, StringBuilder :: append, StringBuilder :: append)'. См. [Mutable reduction] (https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#MutableReduction). – Holger

+0

@ Хольгер: спасибо, это правда. Ответ обновляется. –

+3

Речь идет не об эффективности, а о правильности. Использование 'reduce' таким образом нарушает договор и должно считаться сломанным, даже если оно может делать предполагаемое в определенных обстоятельствах. Прежде всего, он будет прерываться наверняка при использовании параллельного потока. – Holger

7

Метод, который вы ищете, это java.util.Stream.reduce, в частности перегрузка с тремя параметрами, идентификацией, аккумулятором и двоичной функцией. Это правильный эквивалент Scala's foldLeft.

Однако вы не разрешено использовать в Java reduce таким образом, а также не в Scala foldLeft по этому вопросу. Вместо этого используйте collect.

+3

Хотя мне нравится ваш ответ, «вам не разрешено» кажется немного неправильным. Вы можете перефразировать это? –

+2

Это была бы ошибка типа, если бы система типов Java была достаточно выразительной, чтобы выразить это ограничение. Но это не так, ограничение ограничивается только в JavaDocs. JavaDocs говорит, какие типы объектов вам разрешены, а объекты, которые проходят OP, не удовлетворяют этим ограничениям, ergo ей не разрешено называть 'reduce'. Как еще вы это сформулировали? –

+2

Ну, нет ограничений на тип, только на то, как вы используете объекты. Если вы используете функции аккумулятора и объединителя, такие как '(a, b) -> новый StringBuilder(). Append (a) .append (b)', это будет законное использование, хотя и не очень эффективное, по сравнению с 'collect' решение. – Holger

7

В API потока Java 8 нет эквивалента foldLeft. Как отмечают другие, reduce(identity, accumulator, combiner) подходит к концу, но он не эквивалентен foldLeft, потому что для этого требуется получить тип B, чтобы объединиться с ним и быть ассоциативным (другими словами, быть моноидальным), свойство, которое не каждый тип имеет.

Существует также запрос на повышение для этого: add Stream.foldLeft() terminal operation

Чтобы понять, почему снижения не будет работать, рассмотрим следующий код, в котором вы собираетесь выполнить ряд арифметических операций, начиная с заданного числа:

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 

Если вы попытались написать reduce(2, fun, combine), какую функцию объединителя вы могли бы передать, которая объединяет два числа? Добавление двух чисел вместе явно не решает проблему. Кроме того, значение 2 явно не является идентификатором .

Обратите внимание, что никакая операция, требующая последовательного выполнения, может быть выражена в терминах reduce. foldLeft на самом деле более общий, чем reduce: вы можете реализовать reduce с foldLeft, но вы не можете реализовать foldLeft с reduce.

Смежные вопросы