Was ist der beste Weg, um eine Falte vorzeitig zu beenden? Stellen Sie sich als vereinfachtes Beispiel vor, ich möchte die Zahlen in einem zusammenfassen Iterable
, aber wenn ich auf etwas stoße, das ich nicht erwarte (z. B. eine ungerade Zahl), möchte ich möglicherweise beenden. Dies ist eine erste Annäherung
def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
nums.foldLeft (Some(0): Option[Int]) {
case (Some(s), n) if n % 2 == 0 => Some(s + n)
case _ => None
}
}
Diese Lösung ist jedoch ziemlich hässlich (wie in, wenn ich eine .foreach und eine Rückkehr gemacht hätte - es wäre viel sauberer und klarer) und am schlimmsten ist, dass sie die gesamte Iterierbarkeit durchläuft, selbst wenn sie auf eine ungerade Zahl stößt .
Was wäre also der beste Weg, um eine solche Falte zu schreiben, die früh endet? Soll ich das einfach rekursiv schreiben oder gibt es einen akzeptierteren Weg?
scala
functional-programming
Heptisch
quelle
quelle
Antworten:
Meine erste Wahl wäre normalerweise die Rekursion. Es ist nur mäßig weniger kompakt, möglicherweise schneller (sicherlich nicht langsamer) und kann bei vorzeitiger Beendigung die Logik klarer machen. In diesem Fall benötigen Sie verschachtelte Defs, was etwas umständlich ist:
Meine zweite Wahl wäre zu verwenden
return
, da es alles andere intakt hält und Sie nur die Falte in eine einwickeln müssen,def
damit Sie etwas zurückgeben können - in diesem Fall haben Sie bereits eine Methode, also:Dies ist in diesem speziellen Fall viel kompakter als die Rekursion (obwohl wir mit der Rekursion besonders unglücklich waren, da wir eine iterable / iterator-Transformation durchführen mussten). Der nervöse Kontrollfluss ist etwas zu vermeiden, wenn alles andere gleich ist, aber hier ist es nicht. Kein Schaden bei der Verwendung in Fällen, in denen es wertvoll ist.
Wenn ich dies oft tun würde und es irgendwo in der Mitte einer Methode haben wollte (also konnte ich nicht einfach return verwenden), würde ich wahrscheinlich die Ausnahmebehandlung verwenden, um einen nicht lokalen Kontrollfluss zu generieren. Das ist schließlich das, was es kann, und die Fehlerbehandlung ist nicht das einzige Mal, dass es nützlich ist. Der einzige Trick besteht darin, zu vermeiden, dass eine Stapelverfolgung generiert wird (was sehr langsam ist), und das ist einfach, da die Eigenschaft
NoStackTrace
und ihre untergeordnete Eigenschaft diesControlThrowable
bereits für Sie tun. Scala verwendet dies bereits intern (tatsächlich implementiert es so die Rückgabe aus dem Inneren der Falte!). Lassen Sie uns unsere eigenen machen (kann nicht verschachtelt werden, obwohl man das beheben könnte):Hier ist natürlich die Verwendung
return
besser, aber beachten Sie, dass Sieshortcut
überall platzieren können, nicht nur eine ganze Methode einwickeln.Als nächstes müsste ich fold erneut implementieren (entweder ich selbst oder eine Bibliothek finden, die dies tut), damit dies eine vorzeitige Beendigung signalisieren kann. Die zwei natürlichen Wege, dies zu tun, bestehen darin, den Wert nicht zu verbreiten, sondern
Option
den Wert zu enthalten, wobeiNone
die Beendigung bedeutet; oder um eine zweite Anzeigefunktion zu verwenden, die den Abschluss signalisiert. Die von Kim Stebel gezeigte faule Falte von Scalaz deckt bereits den ersten Fall ab, daher zeige ich den zweiten (mit einer veränderlichen Implementierung):(Ob Sie die Kündigung durch Rekursion, Rückkehr, Faulheit usw. implementieren, liegt bei Ihnen.)
Ich denke, das deckt die wichtigsten vernünftigen Varianten ab; Es gibt auch einige andere Optionen, aber ich bin mir nicht sicher, warum man sie in diesem Fall verwenden würde. (
Iterator
selbst würde gut funktionieren, wenn es eine hättefindOrPrevious
, aber es funktioniert nicht, und die zusätzliche Arbeit, die erforderlich ist, um dies von Hand zu tun, macht es zu einer dummen Option, hier zu verwenden.)quelle
foldOrFail
ist genau das, was ich hatte , kam mit , wenn es um die Frage nachzudenken. Kein Grund, in der IMO-Implementierung keinen veränderlichen Iterator und keine while-Schleife zu verwenden, wenn alles gut gekapselt ist. Die Verwendungiterator
zusammen mit der Rekursion macht keinen Sinn.sumEvenNumbers
oder Fold istop
return
(dh es kehrt aus innerste expliziter Methode Sie es in finden), aber nach , dass es sollte nicht sehr lange dauern. Die Regel ist ziemlich klar unddef
verrät, wo sich die Einschlussmethode befindet.B
nicht gemacht,Option[B]
weil es sich dann wie ein Fold verhält, bei dem der Rückgabetyp dem Typ des Nullakkumulators entspricht . Ersetzen Sie einfach alle Optionsrückgaben durch b. und pas in None als Null. Immerhin wollte die Frage eine Falte, die vorzeitig enden kann, anstatt zu scheitern.Das von Ihnen beschriebene Szenario (Beenden eines unerwünschten Zustands) scheint ein guter Anwendungsfall für die
takeWhile
Methode zu sein. Es ist im Wesentlichenfilter
, sollte aber mit der Begegnung mit einem Element enden, das die Bedingung nicht erfüllt.Beispielsweise:
Dies funktioniert auch für
Iterator
s /Iterable
s einwandfrei. Die Lösung, die ich für Ihre "Summe aus geraden Zahlen, aber ungeraden Zahlen" vorschlage, lautet:Und nur um zu beweisen, dass es nicht Ihre Zeit verschwendet, wenn es eine ungerade Zahl erreicht ...
quelle
Mit der faulen Version von foldRight in Scalaz können Sie in einem funktionalen Stil tun, was Sie wollen. Eine ausführlichere Erklärung finden Sie in diesem Blogbeitrag . Während diese Lösung a verwendet
Stream
, können SieIterable
einStream
effizient in ein mit umwandelniterable.toStream
.Dies wird nur gedruckt
Dies zeigt deutlich, dass die anonyme Funktion nur zweimal aufgerufen wird (dh bis sie auf die ungerade Zahl trifft). Das liegt an der Definition von foldr, dessen Unterschrift (im Fall von
Stream
) istdef foldr[B](b: B)(f: (Int, => B) => B)(implicit r: scalaz.Foldable[Stream]): B
. Beachten Sie, dass die anonyme Funktion einen by name-Parameter als zweites Argument verwendet und daher nicht ausgewertet werden muss.Übrigens können Sie dies immer noch mit der Pattern Matching-Lösung des OP schreiben, aber ich finde if / else und map eleganter.
quelle
println
vorif
-else
Ausdruck setzen?toStream
, sodass diese Antwort allgemeiner ist, als es zunächst erscheint.Nun, Scala erlaubt nicht lokale Rückgaben. Es gibt unterschiedliche Meinungen darüber, ob dies ein guter Stil ist oder nicht.
BEARBEITEN:
In diesem speziellen Fall können Sie, wie von @Arjan vorgeschlagen, auch Folgendes tun:
quelle
Some(0): Option[Int]
kannst du einfach schreibenOption(0)
.Katzen haben eine Methode namens foldM , die nicht kurzgeschlossen (für
Vector
,List
,Stream
, ...).Es funktioniert wie folgt:
Sobald eines der Elemente in der Sammlung nicht gerade ist, wird es zurückgegeben.
quelle
@ Rex Kerr Ihre Antwort hat mir geholfen, aber ich musste sie optimieren, um entweder zu verwenden
quelle
Sie können versuchen, eine temporäre Variable und takeWhile zu verwenden. Hier ist eine Version.
Das
evenSum
sollteSome(20)
in diesem Fall sein.quelle
Sie können
foldM
von Katzen lib verwenden (wie von @Didac vorgeschlagen), aber ich schlage vor, zu verwenden,Either
anstatt,Option
wenn Sie tatsächliche Summe heraus erhalten möchten.bifoldMap
wird verwendet, um das Ergebnis aus zu extrahierenEither
.Beispiele:
quelle
(acc + n).asRight
stattEither.right(acc + n)
aber trotzdem)Sie können eine ausgewählte Ausnahme auslösen, wenn Sie auf Ihr Beendigungskriterium stoßen, und diese im aufrufenden Code behandeln.
quelle
Eine schönere Lösung wäre die Verwendung von span:
... aber es durchläuft die Liste zweimal, wenn alle Zahlen gerade sind
quelle
Nur aus "akademischen" Gründen (:
Dauert zweimal als es sollte, aber es ist ein schöner Einzeiler. Wenn "Schließen" nicht gefunden wird, wird es zurückgegeben
Ein anderer (besserer) ist dieser:
quelle