In der Stream reduce-Methode muss die Identität immer 0 für Summe und 1 für Multiplikation sein?

7

Ich fahre mit Java 8 fort.

Ich habe ein interessantes Verhalten gefunden:

zeigt Codebeispiel:

%Vor%

und Modellklasse:

%Vor%

12 + 32 + 10 + 18 = 72
Für sequentiellen Stream gibt dieser Code immer 73 (72 + 1) zurück, aber für parallel gibt es immer 76 (72 + 4 * 1) zurück. 4 - Stream Elemente zählen.

Als ich dieses Ergebnis sah, dachte ich, dass es seltsam ist, dass der parallele Stream und die sequentiellen Streams unterschiedliche Ergebnisse liefern.

Habe ich irgendwo einen Vertrag verloren?

P.S.

für mich 73 ist das erwartete Ergebnis, aber 76 - nicht.

    
gstackoverflow 30.09.2015, 12:50
quelle

5 Antworten

17

Der Identitätswert ist ein Wert, z. B. x op identity = x . Dies ist ein Konzept, das nicht nur auf Java Stream s zutrifft, siehe zum Beispiel auf Wikipedia .

Es listet einige Beispiele von Identitätselementen auf, von denen einige direkt in Java-Code ausgedrückt werden können, z. B.

  • reduce("", String::concat)
  • reduce(true, (a,b) -> a&&b)
  • reduce(false, (a,b) -> a||b)
  • reduce(Collections.emptySet(), (a,b)->{ Set<X> s=new HashSet<>(a); s.addAll(b); return s; })
  • reduce(Double.POSITIVE_INFINITY, Math::min)
  • reduce(Double.NEGATIVE_INFINITY, Math::max)

Es sollte klar sein, dass der Ausdruck x + y == x für beliebige x nur erfüllt werden kann, wenn y==0 , also 0 das Identitätselement für die Addition ist. In ähnlicher Weise ist 1 das Identitätselement für die Multiplikation.

Komplexere Beispiele sind

  • Einen Strom von Prädikaten reduzieren

    %Vor%
  • Einen Strom von Funktionen reduzieren

    %Vor%
Holger 30.09.2015, 13:23
quelle
5

Ja, Sie brechen den Vertrag der Combiner-Funktion. Die Identität, die das erste Element von reduce ist, muss combiner(identity, u) == u erfüllen. Zitieren des Javadoc von Stream.reduce :

  

Der Identitätswert muss eine Identität für die Kombinatorfunktion sein. Dies bedeutet, dass für alle u , combiner(identity, u) gleich u ist.

Allerdings führt Ihre Combiner-Funktion eine Addition durch, und 1 ist nicht das Identitätselement für die Addition; 0 ist.

  • Ändere die verwendete Identität zu 0 und du wirst keine Überraschung haben: Das Ergebnis ist 72 für die beiden Optionen.

  • Zu Ihrer eigenen Unterhaltung, ändern Sie Ihre Combiner-Funktion, um eine Multiplikation durchzuführen (die Identität bleibt 1) und Sie werden auch das gleiche Ergebnis für beide Optionen bemerken.

Lassen Sie uns ein Beispiel erstellen, in dem die Identität weder 0 noch 1 ist. Geben Sie unter Berücksichtigung Ihrer eigenen Domänenklasse Folgendes an:

%Vor%

Dies wird den Strom von Person auf den längsten Personennamen reduzieren.

    
Tunaki 30.09.2015 12:55
quelle
3

Die JavaDoc-Dokumentation für Stream.reduce gibt ausdrücklich an, dass

  

Der Identity-Wert muss eine Identität für die Combiner-Funktion sein

1 ist kein Identitätswert für den Additionsoperator, weshalb Sie unerwartete Ergebnisse erhalten. Wenn Sie 0 verwenden (welcher ist der Identitätswert des Additionsoperators), erhalten Sie das gleiche Ergebnis aus seriellen und parallelen Streams.

    
Ian Roberts 30.09.2015 12:54
quelle
1

Zusätzlich zu den exzellenten Antworten, die vorher gepostet wurden, sollte erwähnt werden, dass Sie, wenn Sie mit etwas anderem als Null beginnen möchten, einfach den anfänglichen Summanden aus der Stream-Operation entfernen können:

%Vor%

Das gleiche gilt für andere Reduktionsoperationen. Wenn Sie zum Beispiel das Produkt mit 2 berechnen möchten, anstatt es mit .reduce(2, (a, b) -> a*b) falsch zu machen, können Sie .reduce(1, (a, b) -> a*b)*2 ausführen. Finden Sie einfach die wahre Identität für Ihre Operation, verschieben Sie die "falsche Identität" nach außen und Sie erhalten das korrekte Ergebnis sowohl für den sequentiellen als auch für den parallelen Fall.

Beachten Sie schließlich, dass Sie Ihr Problem auf effizientere Weise lösen können:

%Vor%

oder alternativ

%Vor%

Hier wird die Summierung ohne Boxen bei jedem Zwischenschritt durchgeführt, daher kann es viel schneller sein.

    
Tagir Valeev 01.10.2015 03:33
quelle
0

Deine Frage hat wirklich zwei Teile. Warum bekommst du 76, wenn du 73 verwendest, indem du sequentiell verwendest. Und was die Identität, soweit Multiplikation und Addition geht für Reduce.

Das Beantworten letzterer wird helfen, den ersten Teil zu beantworten. Die Identität ist ein mathematisches Konzept, ich werde versuchen, für diese Nicht-Mathe-Geeks in einfachen Begriffen zu bleiben. Die Identität ist der Wert, der auf sich selbst angewendet wird und denselben Wert zurückgibt.

Die additive Identität ist 0. Wenn wir annehmen würden, dass a eine Zahl ist, gibt die Identitätseigenschaft von Zahlen an, dass a und seine Identität geben a zurück. (Grundsätzlich a + 0 = a ). Die multiplikative Identität sagt b multipliziert mit seiner Identität, die 1) sich immer selbst zurückgibt, b .

Die java reduce-Methode verwendet die Identität etwas variabler. Indem wir uns die Fähigkeit zu sagen geben, möchten wir die Operationen Addition und Multiplikation mit einem zusätzlichen Schritt durchführen, wenn wir uns dazu entscheiden. Wenn Sie Ihr Beispiel nehmen und die Identität in 0 ändern, erhalten Sie 72.

%Vor%

Dies summiert einfach die Alter zusammen und gibt diesen Wert zurück. Ändere es auf 100, dann gibst du 172 zurück. Aber wenn du parallel fährst, warum erhält dein Ergebnis 76 und in meinem Beispiel 472? Wenn Sie einen Stream verwenden, werden die Ergebnisse anstelle von einzelnen Elementen als eine Menge betrachtet. Per JavaDocs für Streams:

  

Streams erleichtern die parallele Ausführung, indem sie die Berechnung als eine Pipeline aggregierter Operationen neu definieren und nicht als imperative Operationen für jedes einzelne Element.

Warum ist die Behandlung von Sätzen wichtig? Wenn Sie den Standard-Stream (nicht: parallel oder parallelStream) verwenden, ist das, was Sie in Ihrem Beispiel tun, die Summe und die Behandlung, die eine einzelne Zahl ist. Also erhältst du 73, und wenn du die Identität auf 100 änderst, würde ich 172 bekommen. Aber wieso benutzt du parallel, bekommst du 76? oder in meinem Beispiel 472? Weil Java nun die Menge in kleinere (einzelne) Elemente aufteilt, addiert man seine Identität (die du als 1 angegeben hast), summierst das und summierst das Ergebnis dann zu den restlichen Elementen, die die gleiche Operation ausgeführt haben.

Wenn Sie beabsichtigen, 1 zum Ergebnis hinzuzufügen, ist es sicherer, Tagirs Vorschlag zu folgen und nach der Rückkehr des Streams 1 zum Ende hinzuzufügen.

    
chris m 22.12.2017 02:36
quelle

Tags und Links