Ein Kollege frage mich, wie ich in Java 8 am besten eine laufende Summe eines Streams ermittle. Nun haben die Java-Götter kein scan() wie in Scala oder Haskell spendiert, und so war das beste, was mir einfiel:
AtomicInteger accu = new AtomicInteger(0);
List<Integer> list = Stream.of(1, 2, 3, 4, 5)
.map(i -> i + accu.getAndUpdate(j -> i + j))
.collect(Collectors.toList()); //1,3,6,10,15
Gibt es einen weniger “hackeligen” Weg, das zu erreichen?
Streams sind Javas functional programming Feature.
Einer der Grundpfeiler von FP ist, dass die Elemente einer Liste keinerlei Bezug zueinander haben. Das ist beispielweise wichtig, um die Streamsverarbeitung parallelisieren zu können.
In deinem Beispiel ist aber die Reihenfolge der Elemente in der Eingangsliste (was eben genau eine Beziehung dieser Elemente untereinander darstellt) wichtig, weil sonst die Ergebnisliste falsach ist.
Ergo: FP (und damit Streams) ist nicht der beste Ansatz dieses Problem zu lösen…
Wenn man also schon mit der Wahl des Werkzeugs nicht ganz auf der Linie liegt kann man es auch richtig mißbrauchen (normaler Weise ist die Verwendung von .forEach() ein sicheres Zeichen, dass Streams nicht optimal ist):
List<Integer> runningSum = new ArrayList<>();
List<Integer> list = Stream.of(1, 2, 3, 4, 5)
.forEach(i -> runningSum .add(i+ runningSum.stream().mapToInt(j->j).sum() ));
Provokante Frage meinerseits:
Warum sollte Haskel die einzige Programmiersprache sein, die keine Konstrukte enthält, die der „reinen Lehre“ des der Sprache zu Grunde liegenden Pradigmas widersprechen?
Sicher.
Aber da habe ich dann dieses Schlagwort: premature optimization…
Sicher könnte theoretisch auch Haskell auch nicht so FP-mäßige Sachen enthalten (praktisch eher weniger, da musst du erst mal an Menschen wie Edward Kmett vorbei), aber in dem Fall ist es eben nicht die einzige Sprache:
Nicht direkt. Premature optimization ist, wenn man systematisch durch den Code geht und jedes for(int i=0;i<n;i++) durch for(int i=0;i<n;++i) ersetzt, oder jedes int y = x * 3; durch int y = x+x+x;. Wenn es um die asymptotische Komplexität geht, ist das nicht „premature“ und nicht „optimization“, sondern „don’t implement crap due to laziness“ oder so.
Aber wenn man weiß, dass die Eingabe „klein“ ist (d.h. nur wenige hundert Elemente enthält), macht es wohl schlicht keinen Unterschied, und dann sind das nicht die 3%, um die es geht, und man sollte die einfachste Lösung verwenden.
Trotzdem, in bezug auf die Frage, ob es sinnvoll ist, einen (prefix) scan parallel zu berechnen, verweise ich mal auf Vector Models for Data-Parallel Computing von Guy Blelloch.
Spoiler zur Antwort
Ja
Das wirft aber tatsächlich die Frage auf, was für ein stream das denn ist. Man kann zwar prinzipiell rausfinden, ob der stream „ordered“ ist oder nicht, aber tatsächlich wäre die Implementierung auf einem Array vieeel einfacher, wenn man es „gut“ (d.h. effizient parallel) machen wollte. Einen „normalen, kleinen stream“ denke ich, dass die Lösung aus dem Eröffnungsbeitrag die mit dem besten Verhältnis zwischen Kompaktheit, Performance und Lesbarkeit hat.
Stream<Integer> s = Stream.of(1,2,3,4,5);
final List<Integer> chkSums = new ArrayList<>();
s.forEach(new Consumer<Integer>() {
int sum = 0;
public void accept(Integer t) {
sum += t;
chkSums.add(sum);
};
});