Listenverarbeitung unter Bildern verstanden --java8 stream / javaslang- Zusätzlicher Artikel!
Es war wie immer zu lang, also habe ich es getrennt.
Lass uns grob gehen!
Es gibt drei Definitionen von "Reduzieren" im Java8-Stream:
Ich werde die beiden anderen vorstellen.
Das erste ist das.
Optional<T> reduce(BinaryOperator<T> accumulator);
Optional<T> reduce( ((T, T) -> T) f); //Vereinfachung
T reduce(T t, ((T, T) -> T) f); //Repost der ersten reduzieren
Der Unterschied besteht darin, dass es kein "T t" gibt und die Rückgabe in "Optional" verpackt ist.
Sie können sehen, warum dies passiert, indem Sie sich das Bild ansehen.
Diejenigen, die das erste "T t" haben
Diejenigen, die kein "T t" haben
Der Unterschied zwischen der Angabe des Anfangswertes selbst und der Verwendung des ersten als Anfangswert ist der Unterschied in der Anwesenheit oder Abwesenheit von "T t".
Sie können also sehen, warum die Rückgabe "optional" ist, indem Sie den Fall einer leeren Liste betrachten.
Wenn die Liste leer ist, kann "T t" das Endergebnis sein
Nichts passiert, wenn es kein "T t" gibt und die Liste leer ist
Dies liegt daran, dass eine Rückgabe nicht vorbereitet werden kann, wenn kein Anfangswert vorhanden ist und die Liste leer ist.
Ich werde den verbleibenden vorstellen.
Das zweite ist das.
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
U reduce(U u, ((U, T) -> U) f, ((U, U) -> U) g); //Vereinfachung
U reduce(U u, ((U, T) -> U) f); //Vergessen Sie vorerst das dritte Argument
T reduce(T t, ((T, T) -> T) f); //Repost der ersten Reduktion
Wenn Sie das mysteriöse dritte Argument vergessen, ist es dem ersten "Reduzieren" sehr ähnlich.
Dies bedeutet, dass der Anfangswert, auch wenn er sich vom Listentyp unterscheidet, gefaltet werden kann. In anderen Sprachen ist dieses Formular der Standard.
Wie bei den letzten drei Argumenten berücksichtigt der Java8-Stream die parallele Ausführung. Daher ist eine Methode erforderlich, um die Ergebnisse der geteilten Ausführung zusammenzuführen. Um ehrlich zu sein, möchte ich, dass Sie dies verbergen.
(Dies ist wirklich ein "Bild", weil ich die interne Verarbeitung nicht richtig gelesen habe)Es mag einen gewissen Widerstand gegen das 3-Argument geben, aber wenn Sie denken, dass das 3-Argument Rauschen ist, ist es dasselbe wie in anderen Sprachen, daher ist es gut, es zu lernen, ohne es zu vermeiden.
Wenn Sie die Summe von "[10, 30, 45, 15]" wissen möchten, sollten Sie "Summe" verwenden. Das ist richtig.
"Reduzieren" ist nur dann wertvoll, wenn der Anfangstyp unterschiedlich ist. Wenn Sie für den Anfangswert einen anderen Typ verwenden können, können Sie dies tatsächlich tun.
Sie können beispielsweise schnell schreiben, ob die Liste von "(" und ")" mit "Reduzieren" korrekt geschlossen wurde.
resolve(asList('(', '(', ')', ')')) // true
resolve(asList('(', '(', ')')) // false
resolve(asList('(', ')', ')', '(')) // false
private static boolean resolve(List<Character> cs) {
return cs.stream()
.reduce(
Optional.of(0),
(acc, c) -> c == '('
? acc.map(n -> n + 1)
: acc.map(n -> n - 1).filter(n -> n >= 0),
(acc1, acc2) -> acc1
.flatMap(v1 -> acc2.map(v2 -> v1 + v2))
).equals(Optional.of(0));
}
Setzen Sie den Anfangswert auf "Optional (0)" und während der Faltung ".map (n + 1)" für "(" Optional (n) "für", ".map für" Optional (n) "für") " Auf (n -1) `setzen. Wenn es jedoch unter "Optional (0)" fällt, ist es "leer". Sobald es "leer" wird, wird es niemals zu "Optional (0)" zurückkehren, selbst wenn Sie "empty.map (n + 1)" ausführen.
Wenn es am Ende nach dem Falten "Optional (0)" ist, bedeutet dies, dass die Anzahl von "(" und ")" gleich ist und es nie zu viel ")" gab.
Das dritte Argument, das zwei "Optional" zusammenführt, kann hinzugefügt werden, indem man nach innen schaut, wenn beide "Optional (n)" sind. Wenn beide ~ ~ sind, ist das flatMap
. Es fühlt sich gut an, wenn dies bald herauskommt.
U reduce(U u, ((U, T) -> U) f, ((U, U) -> U) g); //Erneut veröffentlichen
Lassen Sie uns lernen, während wir einen Blick auf die Form werfen.
Da Reduzieren auf die Reihenfolge und Richtung der Berechnung achten muss, werde ich ein kurzes Beispiel geben.
Die beiden haben das gleiche Ergebnis.
Stream.of(1, 2, 3).reduce((acc, n) -> acc + n).orElse(0) // 6
Stream.of(1, 2, 3).reduce(0, (acc, n) -> acc + n) // 6
Die beiden Ergebnisse sind jedoch unterschiedlich.
Stream.of("1", "ok", "15:10").reduce((acc, s) -> acc + " | " + s).orElse("") // 1 | ok | 15:10
Stream.of("1", "ok", "15:10").reduce("", (acc, s) -> acc + " | " + s) // | 1 | ok | 15:10
Sie können den Unterschied verstehen, indem Sie selbst ein Bild zeichnen.
Gut zu erinnern.
Nachdem wir nun "Reduzieren" angesprochen haben, werfen wir einen Blick auf Javaslangs "Reduzieren".
List.of("1", "ok", "15:10").reduceRightOption((s, acc) -> acc + " | " + s).getOrElse(""); // 15:10 | ok | 1
Je nach Sprache und Bibliothek ist auch "Reduzieren" verfügbar, das von rechts ausgeklappt wird. (Beachten Sie, dass dies nicht (acc, s) ->
ist, sondern (s, acc) ->
)
Selbst wenn Sie dies verwenden, unterscheidet sich das Ergebnis häufig von dem von links. Seien Sie also vorsichtig.
Dies ist das Ende von "Reduzieren"!
Einführung von takeWhile
, das bei Listenoperationen nützlich ist. Dies wird mit Javaslang eingeführt.
(Ich wusste nicht, dass der Java8-Stream kein "takeWhile" hat ...)
Lassen Sie uns die Zeile "label: millisec" des ersten Themas in der Reihenfolge vom frühesten nur dort setzen, wo es weniger als 30 ist.
lines
.filter(line -> line.contains(":"))
.map(line -> Integer.valueOf(line.split(":")[1]))
.sorted()
.takeWhile(n -> n < 30); // [10, 15]
takeWhile
ist wie" von Anfang an nur dann aufnehmen, wenn bestimmte Bedingungen erfüllt sind ".
Ich benutze es oft mit dropWhile
, also ist es gut, sich auch daran zu erinnern.
Zum Schluss werde ich noch einen vorstellen, "zip", den ich unerwartet oft benutze. Dies wird mit Javaslang eingeführt.
Es ist, als würde man denselben Teil von zwei Listen koppeln. Es ist möglicherweise einfacher zu verstehen, wenn Sie glauben, dass es sich wie ein Reißverschluss anfühlt.
List.of(1, 2, 3)
.zip(List.of('a', 'b', 'c')) // [(1, a), (2, b), (3, c)]
("Zip" kann nur verwendet werden, wenn es einen Typ gibt, der verschiedene Typen wie "Tupel " koppeln kann.)
Anders als wenn Sie "A" und "B" koppeln möchten, wenn Sie beispielsweise die Größe jeder Lücke in der Liste "Ganzzahl" wissen möchten, versuchen Sie "zip", indem Sie dieselbe Liste um eins verschieben. Da ist ein Weg.
List<Integer> times = List.of(10, 15, 35, 60); //Dies[15 - 10, 35 - 15, 60 - 35]Möchte
times
.zip(times.subSequence(1))
.map(t -> t._2 - t._1); // [5, 20, 25]
Verwenden Sie "Reduzieren", wenn Sie von Anfang an über die Verarbeitung nachdenken? Sie mögen denken, aber wenn Sie ein Bild machen, ist es völlig anders.
(Reduzieren
berechnet nicht mit dem Nachbarn, sondern berechnet die Summe (oder etwas) bis zu diesem Punkt nacheinander, sodass die Lückenberechnung nicht einfach durchgeführt werden kann.)
Wenn Sie in einer Protokolldatei "zip" gut filtern und zuordnen, "zip", den Verarbeitungszeitunterschied für jede Zeile verwenden und "umgekehrte Sortierung und takeWhile" versuchen, werden die Zeilen, die 500 ms oder mehr dauern, in der Reihenfolge ihrer Langsamkeit angezeigt. Du kannst es schaffen.
Es wird definitiv Gespräche wie "Ich kenne die Vorzüge nicht" und "Ist es nicht in Ordnung für?" Geben, aber natürlich gibt es Vorzüge, die die Lernkosten wert sind.
Es gibt einige, aber ich werde kurz drei auflisten.
Zum Beispiel gibt es eine solche Funktion von "String-> Integer".
private static Integer toIntAndTwice(String s) {
return Integer.valueOf(s) * 2;
}
Um ehrlich zu sein, schreiben Sie den Code, der diese Funktion auf "wenn es mehrere" Strings "gibt," wenn es höchstens einen "String" gibt "und" wenn es einen "String" gibt, der möglicherweise fehlerhaft ist "anwendet. Werden.
Beispiel "Liste"
List<Integer> result = List.empty();
List<String> org = ...; // List.of(1, 2, 3) or empty
for (String x : org) {
result.append(toIntAndTwice(x));
}
return result;
Option
Beispiel
Option<Integer> result;
Option<String> org = ...; // Option.of(1) or empty
if (org.isEmpty()) {
result = Option.none();
} else {
result = Option.of(toIntAndTwice(org.get()));
}
return result;
try
Beispiel
Integer result;
String org = ...; // "1" or "x"
try {
result = toIntAndTwice(org);
} catch (Throwable t) {
result = null;
}
return result;
Sie müssen einen völlig anderen Code schreiben, um in einer bestimmten Situation "toIntAndTwice" auf "String" anzuwenden.
Wenn Sie dies mit map
schreiben, wird es so sein.
Beispiel "Liste"
List<String> org = ...;
List<Integer> mapped = org.map(JSMain::toIntAndTwice);
return mapped;
Option
Beispiel
Option<String> org = ...;
Option<Integer> mapped = org.map(JSMain::toIntAndTwice);
return mapped;
Try
Beispiel
Try<String> org = ...;
Try<Integer> mapped = org.map(JSMain::toIntAndTwice);
return mapped;
Es sieht genauso aus! Dies liegt daran, dass "die Regeln von" Liste "und" Option "in einer bestimmten Situation" und "was Sie tatsächlich tun möchten (" toIntAndTwice ")" getrennt sind und erstere von der Sprache gefolgt wird.
Übrigens, wenn der Code bisher ähnlich ist, habe ich das Gefühl, dass er allgemeiner gemacht werden kann, oder? Da "List" und "Option" von Javaslang von "Value" erben, können Sie dies auch tun.
Wenn Sie einen solchen Wert
private static Value<Integer> mapAnyType(Value<String> org) {
return org.map(JSMain::toIntAndTwice);
}
Es funktioniert, ob das Argument "Liste" oder "Option" ist!
Beispiel "Liste"
Value<Integer> m1 = mapAnyType(List.of("1", "2", "3")); // List(2, 4, 6)
Option
Beispiel
Value<Integer> m2 = mapAnyType(Option.none()); // None
Try
Beispiel
Value<Integer> m3 = mapAnyType(Try.of(() -> "x")); // Failure(java.lang.NumberFormatException: For input string: "x")
In diesem Fall kann derselbe Code unterschiedliche Situationen behandeln. Bereiten Sie beispielsweise einen "Stornierungsprozess für Zahlungsoptionen", eine Funktion zum gleichzeitigen Abbrechen ("Liste") und eine Funktion zum Abbrechen vor, wenn Sie ("Option") haben. ")" Und "Die Funktion zum Abbrechen, weil Sie sie haben sollten ( Try
) "kann sofort realisiert werden.
Ich denke, der größte Vorteil ist die Trennung von "Situation" und "Verarbeitung".
Legacy-Code, den jeder liebt. Das ist üblich.
//Initialisieren
result = 0;
flag = false;
for (i ...) {
result = ...;
//Ist ~ ~ fertig?
if (i < ...) {
result = ...;
flag = true;
}
//Wenn ~ ~ dann ende
if (flag) {
return result;
}
//Dann Schleife mit ~ ~
for (j ...) {
result = ...;
//Wenn ~ ~ dann ende
if (j < ...) {
return result;
}
}
//Initialisieren
flag = false;
}
//Rückkehr
return result;
Das Rückgabeergebnis in diesem Code ist dasselbe wie der Text, aber der Inhalt ist völlig anders. (Vielleicht weiß ich es gar nicht.)
Da die Zeilen einen Kontext enthalten, können Sie nicht nur diese Zeile kopieren und einfügen, und es scheint, dass Sie Blöcke mit Kommentaren erstellen. In Wirklichkeit ist dies jedoch nur ein großer Block.
Wenn dies wie der obige Code aussieht, arbeiten alle drei Zeilen des Arguments unabhängig voneinander, und es gibt keine in Bearbeitung befindlichen Variablen im Methodenbereich, die nicht "return" sein sollten.
(Da es ein ;
gibt, ist dieser Code eine Zeile. Daher kann es keinen Lückenzustand geben.)
return cs.stream()
.reduce(
Optional.of(0),
(acc, c) -> c == '(' ? acc.map(n -> n + 1) : acc.map(n -> n - 1).filter(n -> n >= 0),
(acc1, acc2) -> acc1.flatMap(v1 -> acc2.map(v2 -> v1 + v2))
).equals(Optional.of(0));
Ich denke, dass dies überwiegend wiederverwendbarer und von höherer Qualität ist. (Wenn Sie dies mit gemeinsam genutztem Code tun, sollten Sie natürlich etwas vorsichtiger sein. Das zweite Argument sollte richtig benannt und der Testcode leicht geschrieben sein. Das dritte Argument sollte "ReduceUtil :: mergeOptional" sein. Effektives Java Ich denke, die 3. Ausgabe hat das auch gesagt.)
Ich werde als nächstes auf die Details eingehen, aber je mehr Sie die Idee selbst kennen, desto besser wird sie auf den ersten Blick, es sei denn, es handelt sich um eine spezielle Sprache.
Wenn Sie Java8-Stream lernen und zu Ruby wechseln, können Sie die Liste sofort bearbeiten. Selbst wenn Sie mit Java8 noch nicht vertraut sind und mit Python vertraut sind, können Sie Streams erstellen.
Wie in den Last-Minute-Vorteilen erwähnt, verfügen die meisten Sprachen über "Map, Filter, Reduce".
Wenn Sie eine Sprache beibehalten müssen, die Sie bei der Arbeit nicht so schnell kennen, oder wenn Sie einige Änderungen an den von Ihnen aufgenommenen Werkzeugen vornehmen möchten, ist es in Ordnung, wenn Sie die Wörter kennen und Bilder verarbeiten. Am Ende werde ich mit einer Zusammenfassung abschließen, wie dieselbe Verarbeitung in der Sprache durchgeführt wird, die Sie häufig hören.
(Der Teil mit (*)
fühlt sich so an, als könnten Sie etwas Ähnliches tun, wenn Sie es gut verwenden.)
lang | map | filter | reduce(zero / left) reduce(head / left) reduce(zero / right) reduce(head / right) |
take while | zip |
---|---|---|---|---|---|
java | map | filter | reduce reduce - - |
- | - |
groovy | collect | findAll | inject inject - - |
takeWhile | transpose (*) |
scala | map | filter | reduceLeft / reduceLeftOption foldLeft reduceRight / reduceRightOption foldRight |
takeWhile | zip |
python | map | filter | reduce - - - |
itertools.takewhile | zip |
php | array_map | array_filter | array_reduce array_reduce - - |
- | array_map (*) |
ruby | map / collect | select | reduce / inject reduce / inject - - |
take_while | zip |
js | map | filter | reduce reduce reduceRight reduceRight |
- | - |
haskell | map | filter | foldl foldl1 foldr foldr1 |
takeWhile | zip |
Wenn Sie sich an die Wörter um "Map / Collect", "Filter", "Reduce / Fold / Inject" erinnern, funktionieren die meisten Sprachen.
"Map vs Collect" und "Reduce vs Inject" können interessant sein.
Wenn jemand so weit gelesen hat, danke.
Es war gut, dass der Artikel an dem Tag pünktlich war, an dem ich ihn deklarierte. Das ist es.
Recommended Posts