Mit Lazy-Seq ohne den Stack zu blasen: Ist es möglich Faulheit mit Tail Recursion zu kombinieren?

8

Um Clojure zu lernen, löse ich die Probleme bei 4Urlaub . Ich schneide mir gerade die Zähne bei der Frage 164 , wo Sie (teilweise) die Sprache a DFA akzeptiert. Eine interessante Bedingung ist, dass die Sprache unendlich sein kann, also muss die Lösung faul sein (in diesem Fall die Testfälle für die Lösung (take 2000 ... .

Ich habe eine Lösung, die auf meiner Maschine funktioniert, aber wenn ich sie auf der Website einreiche, bläst sie den Stack (wenn ich die Anzahl der akzeptablen Strings von 2000 auf 20000 erhöhe, blase ich den Stack auch lokal, also ist es ein Mangel an meiner Lösung).

Meine Lösung [1] ist:

%Vor%

akzeptiert das DFA

%Vor%

und gibt die Sprache zurück, die DFA akzeptiert ( #{a ab abc} ). Bei der Ermittlung der ersten 2000 akzeptierten DFA-Zeichenfolgen

%Vor%

es bläst den Stapel. Natürlich sollte ich die Lösung so umstrukturieren, dass sie rekursiv ist, aber ich sehe nicht, wie das möglich ist. Insbesondere sehe ich nicht, wie es überhaupt möglich ist, Faulheit mit Schwanz-Rekursivität zu kombinieren (über recur oder trampoline ). Die Funktion lazy-seq erstellt einen Abschluss, sodass die Verwendung von recur in lazy-seq den Abschluss als Rekursionspunkt verwendet. Bei der Verwendung von lazy-seq in recur wird immer lazy-seq ausgewertet, weil recur einen Funktionsaufruf ausgibt, der seine Argumente auswerten muss.

Wenn ich trampoline verwende, sehe ich nicht, wie ich iterativ eine Liste konstruieren kann, deren Elemente lazy evaluiert werden können. Wie ich es benutzt habe und es benutzt sehe, kann trampoline nur dann einen Wert zurückgeben, wenn es fertig ist (d. H. Eine der Trampolining-Funktionen gibt keine Funktion zurück).

Andere Lösungen gelten als außerhalb des Geltungsbereichs

Ich betrachte eine andere Art der Lösung dieses Problems, die nicht in den Rahmen dieser Frage fällt. Ich arbeite gerade an einer Lösung, die iterate verwendet, wobei jeder Schritt nur die Zeichenfolgen berechnet, die der "nächste Schritt" (folgende Übergänge vom aktuellen Status) akzeptiert, so dass er überhaupt nicht rekurriert. Sie verfolgen dann nur die aktuellen Zustände und die Strings, die Sie in diesen Zustand gebracht haben (die Präfixe für die nächsten Zustände). In diesem Fall ist es schwierig, zu erkennen, wann ein DFA, der eine endliche Sprache akzeptiert, keine Ergebnisse mehr zurückgibt. Ich habe noch kein richtiges Stop-Kriterium für das take-while um das iterate entwickelt, aber ich bin mir ziemlich sicher, dass ich es schaffen werde, dass diese Lösung funktioniert. Für diese Frage interessiert mich die grundsätzliche Frage: Können Faulheit und Schwanzrekursivität kombiniert werden oder ist das grundsätzlich unmöglich?

[1] Beachten Sie, dass es auf der Seite einige Einschränkungen gibt, wie zB def und defn nicht verwenden zu können, was einige Besonderheiten meines Codes erklären könnte.

    
Confusion 15.07.2012, 20:35
quelle

2 Antworten

2

Das Problem ist, dass Sie etwas bauen, das wie folgt aussieht:

%Vor%

Was im Prinzip gut ist, aber die Elemente auf der rechten Seite dieser Liste werden für eine lange Zeit nicht realisiert, und bis Sie sie realisieren, haben sie einen riesigen Stapel von faulen Sequenzen, die es sein müssen gezwungen, um ein einzelnes Element zu erhalten.

Wenn Ihnen ein Spoiler nichts ausmacht, können Sie meine Lösung unter Ссылка sehen. Ich mache zwei Dinge anders als du, und beide sind wichtig:

  1. Zuerst die Breite des Baums durchqueren. Sie wollen nicht in einer Schleife von q0 auf q0 "hängen bleiben", wenn das ein nicht akzeptierender Zustand ist. Es scheint, dass dies kein Problem für den speziellen Testfall ist, in dem Sie aufgrund der Reihenfolge, in der die Übergänge an Sie übergeben werden, ein Problem haben, aber der nächste Testfall hat danach dieses Merkmal.
  2. Verwenden von doall , um eine Sequenz zu erzwingen, die ich träge aufbaue. Da ich weiß, dass viele concat s einen sehr großen Stack erstellen, und ich weiß auch, dass die Sequenz niemals unendlich sein wird, erzwinge ich die ganze Sache, während ich sie erstelle, um die Layerung von lazy Sequenzen zu verhindern, die den Stack überlaufen lässt.

Bearbeiten: Im Allgemeinen können Sie keine Lazy-Sequenzen mit Tail-Rekursion kombinieren. Sie können eine Funktion verwenden, die beide verwendet, die möglicherweise wiederkehrend ist, wenn vor dem Hinzufügen eines einzelnen Elements mehr Arbeit zu erledigen ist, und die bei einem neuen Element immer wiederkehren, aber meistens haben sie entgegengesetzte Ziele und versuchen zu kombinieren sie werden unvorsichtig nur zu Schmerzen führen und keine besonderen Verbesserungen.

    
amalloy 16.07.2012, 18:16
quelle
6

Wenn Sie lazy-seq verwenden, führen Sie einfach einen normalen Funktionsaufruf aus, anstatt recur zu verwenden. Die Faulheit vermeidet den rekursiven Stackverbrauch, für den sonst recur verwendet wird.

Zum Beispiel eine vereinfachte Version von repeat :

%Vor%     
Alex Taggart 16.07.2012 01:14
quelle