Quel est l'équivalent de la grande foldLeft
de Scala dans Java 8?
J'ai été tenté de penser que c'était reduce
, mais réduire doit renvoyer quelque chose de type identique à ce qu'il réduit.
Exemple:
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();
}
}
Le problème dans le code ci-dessus est le acc
umulator: new StringBuilder("")
Ainsi, quelqu'un pourrait-il m'indiquer l'équivalent approprié de foldLeft
/réparer mon code?
Mettre à jour:
Voici la tentative initiale pour obtenir votre code corrigé:
public static String concatenate(List<Character> chars) {
return chars
.stream()
.reduce(new StringBuilder(),
StringBuilder::append,
StringBuilder::append).toString();
}
Il utilise la méthode suivante reduction :
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
Cela peut sembler déroutant, mais si vous examinez les javadocs, une explication intéressante peut vous aider à saisir rapidement les détails. La réduction est équivalente au code suivant:
U result = identity;
for (T element : this stream)
result = accumulator.apply(result, element)
return result;
Pour une explication plus détaillée, veuillez vérifier cette source .
Cette utilisation n’est pas correcte car elle enfreint le contrat de réduction qui stipule que l’accumulateur doit être une fonction sans état associative, non interférant, permettant d’incorporer un élément supplémentaire dans un résultat. En d'autres termes, puisque l'identité est modifiable, le résultat sera cassé en cas d'exécution parallèle.
Comme indiqué dans les commentaires ci-dessous, une option correcte utilise la réduction comme suit:
return chars.stream().collect(
StringBuilder::new,
StringBuilder::append,
StringBuilder::append).toString();
Le fournisseur StringBuilder::new
sera utilisé pour créer des conteneurs réutilisables qui seront combinés ultérieurement.
Il n'y a pas d'équivalent de foldLeft
dans l'API Stream de Java 8. Comme d'autres l'ont noté, reduce(identity, accumulator, combiner)
s'approche, mais ce n'est pas équivalent à foldLeft
car il nécessite que le type résultant B
se combine avec lui-même et soit associatif (en d'autres termes, ressemble à un monoïde), propriété que tous les types n'ont pas.
Il existe également une demande d’amélioration à cet effet: add Stream.foldLeft () terminal operation
Pour voir pourquoi la réduction ne fonctionne pas, considérons le code suivant, dans lequel vous avez l'intention d'exécuter une série d'opérations arithmétiques commençant par un nombre donné:
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
Si vous avez essayé d'écrire reduce(2, fun, combine)
, quelle fonction de combinateur pourriez-vous utiliser pour combiner deux nombres? Ajouter clairement les deux chiffres ne résout pas le problème. De plus, la valeur 2
n'est clairement pas un élément identity.
Notez qu'aucune opération nécessitant une exécution séquentielle ne peut être exprimée en reduce
. foldLeft
est en réalité plus générique que reduce
: vous pouvez implémenter reduce
avec foldLeft
mais vous ne pouvez pas implémenter foldLeft
avec reduce
.
La méthode que vous recherchez est Java.util.Stream.reduce
, en particulier la surcharge avec trois paramètres, identité, accumulateur et fonction binaire. C'est l'équivalent correct de la variable foldLeft
de Scala.
Cependant, vous êtes pas autorisé à utiliser la variable reduce
de Java de cette manière, et également la variable foldLeft
de Scala. Utilisez collect
à la place.
D'autres sont corrects il n'y a pas d'équivalent cependant. Voici un util qui vient près-
<U, T> U foldLeft(Collection<T> sequence, U identity, BiFunction<U, ? super T, U> accumulator) {
U result = identity;
for (T element : sequence)
result = accumulator.apply(result, element);
return result;
}
votre cas en utilisant la méthode ci-dessus ressemblerait
public String concatenate(List<Character> chars) {
return foldLeft(chars, new StringBuilder(""), StringBuilder::append).toString();
}
Ou sans la méthode lambda ref sugar,
public String concatenate(List<Character> chars) {
return foldLeft(chars, new StringBuilder(""), (stringBuilder, character) -> stringBuilder.append(character)).toString();
}