Ich bin mir bewusst, dass Linksfalte linksgerichtete Bäume und Rechtsfalte rechtsgerichtete Bäume hervorbringt, aber wenn ich nach einer Falte greife, bin ich manchmal in kopfschmerzauslösenden Gedanken versunken und versuche festzustellen, welche Art von Falte Ist angemessen. Normalerweise löse ich das gesamte Problem ab und gehe die Implementierung der Faltfunktion durch, die für mein Problem gilt.
Was ich also wissen möchte ist:
- Welche Faustregeln gelten für die Entscheidung, ob nach links oder rechts gefaltet werden soll?
- Wie kann ich angesichts des Problems, mit dem ich konfrontiert bin, schnell entscheiden, welche Art von Falte ich verwenden möchte?
In Scala by Example (PDF) gibt es ein Beispiel für die Verwendung einer Falte zum Schreiben einer Funktion namens Abflachen, die eine Liste von Elementlisten zu einer einzigen Liste zusammenfasst. In diesem Fall ist eine rechte Falte die richtige Wahl (angesichts der Art und Weise, wie die Listen verkettet sind), aber ich musste ein wenig darüber nachdenken, um zu dieser Schlussfolgerung zu gelangen.
Da das Falten eine so häufige Aktion in der (funktionalen) Programmierung ist, möchte ich in der Lage sein, solche Entscheidungen schnell und sicher zu treffen. Also ... irgendwelche Tipps?
Antworten:
Sie können eine Falte in eine Infix-Operator-Notation übertragen (dazwischen schreiben):
Dieses Beispiel falten mit der Akkumulatorfunktion
x
also gleich
Jetzt müssen Sie nur noch über die Assoziativität Ihres Operators nachdenken (indem Sie Klammern setzen!).
Wenn Sie einen linksassoziativen Operator haben, setzen Sie die Klammern wie folgt
Hier verwenden Sie eine linke Falte . Beispiel (Pseudocode im Hashkell-Stil)
Wenn Ihr Operator rechtsassoziativ ist ( rechte Falte ), werden die Klammern wie folgt gesetzt:
Beispiel: Cons-Operator
Im Allgemeinen sind arithmetische Operatoren (die meisten Operatoren) linksassoziativ und daher
foldl
weiter verbreitet. In den anderen Fällen ist die Infixnotation + Klammern sehr nützlich.quelle
foldl1
undfoldr1
in Haskell (foldl
undfoldr
nehmen Sie einen externen Anfangswert), und Haskells "Nachteile" werden(:)
nicht genannt(::)
, aber ansonsten ist dies korrekt. Vielleicht möchten Sie hinzufügen, dass Haskell zusätzlich einfoldl'
/ bereitstellt,foldl1'
das strenge Varianten vonfoldl
/ sindfoldl1
, da faule Arithmetik nicht immer wünschenswert ist.Olin Shivers differenzierte sie mit den Worten "foldl ist der grundlegende Listeniterator" und "foldr ist der grundlegende Listenrekursionsoperator". Wenn Sie sich ansehen, wie Foldl funktioniert:
Sie können sehen, wie der Akkumulator (wie in einer rekursiven Iteration) erstellt wird. Im Gegensatz dazu fährt foldr fort:
Hier können Sie die Durchquerung des Basisfalls 4 sehen und von dort aus das Ergebnis aufbauen.
Ich setze also eine Faustregel auf: Wenn es wie eine Listeniteration aussieht, die einfach in schwanzrekursiver Form zu schreiben wäre, ist foldl der richtige Weg.
Aber dies wird wahrscheinlich am deutlichsten an der Assoziativität der von Ihnen verwendeten Operatoren ersichtlich. Wenn sie linksassoziativ sind, verwenden Sie foldl. Wenn sie rechtsassoziativ sind, verwenden Sie foldr.
quelle
Andere Poster haben gute Antworten gegeben und ich werde nicht wiederholen, was sie bereits gesagt haben. Da Sie in Ihrer Frage ein Scala-Beispiel angegeben haben, gebe ich ein Scala-spezifisches Beispiel. Wie Tricks bereits sagte, muss ein
foldRight
Stapelrahmen beibehaltenn-1
werden, wobein
die Länge Ihrer Liste und dies leicht zu einem Stapelüberlauf führen kann - und nicht einmal die Schwanzrekursion könnte Sie davor bewahren.A
List(1,2,3).foldRight(0)(_ + _)
würde sich reduzieren auf:während
List(1,2,3).foldLeft(0)(_ + _)
würde sich reduzieren auf:die iterativ berechnet werden kann, wie bei der Implementierung von
List
.In einer streng bewerteten Sprache wie Scala
foldRight
kann a den Stapel für große Listen leicht in die Luft jagen, während a diesfoldLeft
nicht tut.Beispiel:
Meine Faustregel lautet daher: Für Operatoren, die keine bestimmte Assoziativität haben, verwenden Sie immer
foldLeft
, zumindest in Scala. Andernfalls gehen Sie mit anderen Ratschlägen in den Antworten;).quelle
Es ist auch erwähnenswert (und mir ist klar, dass dies ein wenig offensichtlich ist), dass im Fall eines kommutativen Operators die beiden ziemlich gleichwertig sind. In dieser Situation könnte ein Foldl die bessere Wahl sein:
foldl:
(((1 + 2) + 3) + 4)
kann jede Operation berechnen und den akkumulierten Wert übertragenfoldr: Muss
(1 + (2 + (3 + 4)))
ein Stapelrahmen für1 + ?
und2 + ?
vor der Berechnung geöffnet werden3 + 4
, dann muss er zurückgehen und die Berechnung für jeden durchführen.Ich bin nicht genug Experte für funktionale Sprachen oder Compiler-Optimierungen, um zu sagen, ob dies tatsächlich einen Unterschied macht, aber es scheint auf jeden Fall sauberer, ein Foldl mit kommutativen Operatoren zu verwenden.
quelle
foldr
kann sie sehr wohl effizienter sein alsfoldl
und erfordert keine zusätzlichen Stapelrahmen.