Dies ist eine schöne Übung, um die Programmiersprache, die Sie lernen wollten, fließender zu beherrschen, aber nur leicht herumgebastelt haben. Dies beinhaltet das Arbeiten mit Objekten, das Verwenden oder Simulieren von Verschlüssen und das Dehnen des Typsystems.
Ihre Aufgabe ist es, Code zum Verwalten von Lazy Lists zu schreiben und dann diesen Algorithmus zum Generieren von Fibonacci-Zahlen zu implementieren:
Codebeispiele sind in Haskell
let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
in take 40 fibs
Ergebnis:
[0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,5702887,9227465,14930352,24157817,39088169,63245986]
Ihre Lazy-List-Implementierung sollte diese Richtlinien erfüllen:
- Ein Listenknoten ist eines von drei Dingen:
- Nil - Leere Liste.
[]
- Nachteile - Ein einzelnes Element, gepaart mit einer Liste der verbleibenden Elemente:
1 : [2,3,4,5]
(:
ist der Nachteile-Operator in Haskell) - Thunk - Eine verzögerte Berechnung, die bei Bedarf einen List-Knoten erzeugt.
- Nil - Leere Liste.
- Es unterstützt die folgenden Operationen:
- nil - Erstellt eine leere Liste.
- cons - Konstruiert eine Cons-Zelle.
- thunk - Konstruiert einen Thunk mit einer Funktion, die keine Argumente akzeptiert und ein Nil oder Cons zurückgibt.
- force - Bei gegebenem Listenknoten:
- Wenn es sich um einen Nullwert oder einen Nachteil handelt, geben Sie ihn einfach zurück.
- Wenn es ein Thunk ist, rufen Sie seine Funktion auf, um eine Null oder Nachteile zu erhalten. Ersetzen Sie den Thunk durch diesen Nil oder Cons und geben Sie ihn zurück.
Hinweis: Das Ersetzen des Thunks durch seinen erzwungenen Wert ist ein wichtiger Bestandteil der Definition von "faul" . Wenn dieser Schritt übersprungen wird, ist der obige Fibonacci-Algorithmus zu langsam.
- leer - Überprüfen Sie, ob ein List-Knoten Null ist (nachdem er erzwungen wurde).
- head (aka "car") - Liefert den ersten Eintrag einer Liste (oder wirft einen Fit, wenn es Nil ist).
- tail (aka "cdr") - Liefert die Elemente nach dem Kopf einer Liste (oder wirft einen Fit, wenn es Nil ist).
- zipWith - Wenden Sie die Funktion bei einer Binärfunktion (z. B.
(+)
) und zwei (möglicherweise unendlichen) Listen auf die entsprechenden Elemente der Listen an. Beispiel:
zipWith (+) [1,2,3] [1,1,10] == [2,3,13]
- take - Nimm die ersten N Elemente der Liste, wenn du eine Nummer N und eine (möglicherweise unendliche) Liste hast.
- Drucken - Alle Elemente in einer Liste drucken. Dies sollte schrittweise funktionieren, wenn eine lange oder unendliche Liste angegeben wird.
fibs
verwendet sich in seiner eigenen Definition. Das Einrichten einer faulen Rekursion ist etwas schwierig. Sie müssen so etwas tun:- Vergeben Sie einen Thunk für
fibs
. Lassen Sie es vorerst in einem Dummy-Zustand. - Definieren Sie die Thunk-Funktion, die von einem Verweis auf abhängt
fibs
. - Aktualisieren Sie den Thunk mit seiner Funktion.
Sie können dieses Installationsprogramm ausblenden, indem Sie eine Funktion definieren,
fix
die eine Listenrückgabefunktion mit einem eigenen Rückgabewert aufruft. Erwägen Sie, ein kurzes Nickerchen zu machen, damit diese Idee greifbar wird.- Vergeben Sie einen Thunk für
Polymorphismus (die Fähigkeit, mit Listen jeglicher Art von Gegenständen zu arbeiten) ist nicht erforderlich, aber versuchen Sie, einen Weg zu finden, der in Ihrer Sprache idiomatisch ist.
- Sorgen Sie sich nicht um die Speicherverwaltung. Sogar Sprachen mit Garbage Collection neigen dazu, Objekte mit sich herumzutragen, die Sie nie wieder verwenden werden (z. B. auf dem Aufrufstapel). Seien Sie also nicht überrascht, wenn Ihr Programm beim Durchlaufen einer unendlichen Liste Speicherplatz verliert.
Sie können leicht von diesen Richtlinien abweichen, um die Besonderheiten Ihrer Sprache zu berücksichtigen oder alternative Ansätze für faule Listen zu untersuchen.
Regeln:
- Wählen Sie eine Sprache, die Sie nicht gut kennen. Ich kann dies nicht "fordern", daher das Tag "honor-system". Die Wähler können jedoch Ihren Verlauf überprüfen, um festzustellen, in welchen Sprachen Sie Beiträge verfasst haben.
Verwenden Sie nicht die in Ihrer Sprache integrierte Lazy-List-Unterstützung, um alles zu erledigen. Schreibe etwas Wesentliches oder zumindest Interessantes.
Haskell ist ziemlich out. Das heißt, es sei denn, Sie machen so etwas:
data List a = IORef (ListNode a) data ListNode a = Nil | Cons !a !(List a) | Thunk !(IO (ListNode a))
Hinweis: Die nicht strenge Bewertung von Haskell ist nicht uneingeschränkt zulässig, aber Ihre Implementierung einer verzögerten Liste sollte ihre Funktionalität nicht direkt von dort ableiten. In der Tat wäre es interessant, eine effiziente, rein funktionale Lösung zu sehen, die keine Faulheit erfordert.
Python:
- Verwenden Sie keine itertools.
- Generatoren sind in Ordnung, aber wenn Sie sie verwenden, müssen Sie einen Weg finden, um erzwungene Werte zu speichern.
quelle
zipWith
von zwei unterschiedlich langen Listen verhalten ?zipWith (+) [1,2,3,4,5] [0,0,0] == [1,2,3]
. Für den obigen Fibonacci-Algorithmus spielt dies jedoch keine Rolle, da beide Argumente für zipWith unendliche Listen sind.fibs
richtig zu implementieren , da es von sich selbst abhängt. Ich habe die Frage aktualisiert, um auf die faulen Rekursionen einzugehen. FUZxxl hat es von ihm / ihr / sich selbst herausgefunden.Antworten:
Nachsatz
Ich habe schon früher mit PostScript gespielt , aber ich würde nicht sagen, dass ich es besonders gut kenne (ich vermute sogar, dass Sie die Anzahl der Menschen auf der Welt, die PostScript wirklich kennen, mit einer Hand zählen können).
Ich habe von Ihrer Spezifikation abgewichen, weil die Funktion, mit der ein Thunk erstellt wird, einen anderen Thunk zurückgeben darf.
force
wird weiter auswerten, bis das Ergebnis anil
oder a istcons
.Die Listen sind als Wörterbücher implementiert:
Der Code folgt. Beachten Sie, dass wir einige eingebaute Operatoren überschreiben (insbesondere
print
, ich habe nicht geprüft, ob es mehr gibt). In der Praxis müsste darauf geachtet werden. Natürlich wird es keine reale Welt geben, also ist das in Ordnung.Die Kommentare vor den Prozeduren sind als zu lesen
dh den erwarteten Stack-Inhalt vor dem Aufruf und den resultierenden Stack-Inhalt nach dem Aufruf anzeigen. Die Kommentare innerhalb der Prozeduren zeigen den Inhalt des Stapels, nachdem die bestimmte Zeile ausgeführt wurde.
Laden Sie dies in Ghostscript und ignorieren Sie die angezeigte Seite - wir arbeiten nur mit dem Interpreter. Hier ist der Fibonacci-Algorithmus:
Zwei weitere interessante Funktionen:
Beginnen Sie mit der Zählung bei 5, multiplizieren Sie jedes Element der resultierenden Liste mit 3 und zeigen Sie die ersten zehn Werte an:
In Bezug auf Polymorphismus: Obwohl PostScript stark typisiert ist, können beliebige Typen als Wörterbuchwerte verwendet werden, sodass Sie alles eingeben können, was Sie möchten:
Beachten Sie, dass Tippfehler, z. B. beim Versuch, Zeichenfolgen zu Zahlen hinzuzufügen, nur zur Bewertungszeit auftreten:
quelle
force
zurückgegebenen Werte?copy
kopiert der Bediener den Inhalt der bewerteten Version in das Original und überschreibt/type
und setzt möglicherweise andere Werte. Nach rekursiver Auswertung, bis wir einnil
oder habencons
, wird auch (viaundef
) entfernt/func
und gegebenenfalls/data
. Der letzte Schritt ist nicht unbedingt notwendig (/func
und/data
würde einfach ignoriert), aber wenn dieser SchrittC
Ich bin ein absoluter Anfänger in C, dieser Code ist tatsächlich das erste, was ich in C codiert habe. Er wird ohne Warnungen kompiliert und läuft einwandfrei auf meinem System.
Wie zu bauen
Holen Sie sich zuerst den Tarball von meinem Server . Es enthält ein Makefile. Führen Sie es einfach aus
make
, um es zu erstellen und dannmake run
auszuführen. Das Programm druckt dann eine Liste der ersten 93 Fibonacci-Zahlen. (Nach Nummer 94 läuft eine 64-Bit-Ganzzahl ohne Vorzeichen über.)Erläuterung
Der Kern des Programms ist die Datei
lazy-list.c
. In der entsprechenden Header-Datei definiere ich eine Strukturlist
, die unsere Lazy List ist. Es sieht aus wie das:Das Mitglied
kind
ist eine Art Tag. Es markiert, ob wir das Listenende (NIL
), eine bereits ausgewertete Zelle (CONS
) oder einen Thunk (THUNK
) aufgeladen haben . Dann folgt eine Vereinigung. Es istDer Inhalt der Union wird durch das Tag bestätigt. Wenn das Tag ist
NIL
, ist der Inhalt der Union undefiniert.Durch die Definition der in der obigen Spezifikation erwähnten Hilfsfunktionen kann man normalerweise die Listendefinition von ihrer Verwendung abstrahieren, z. Sie können einfach anrufen
nil()
, um eine leere Liste zu erhalten, anstatt selbst eine zu erstellen.Die drei interessantesten Funktionen sind
zipWith
,take
undfibonaccis
. Aber ich möchte es nicht erklärentake
, da es sehr ähnlich istzipWith
. Alle Funktionen, die träge arbeiten, haben drei Komponenten:Im Falle
zipWith
, diese sindzipWith
,__zipWith
und__zipArgs
. Ich zeige sie hier nur ohne weitere Erklärung, da sollte die Funktion ganz klar sein:Die andere interessante Funktion ist
fibonaccis()
. Das Problem ist, dass wir einen Zeiger der ersten und zweiten Zelle an den Thunk der dritten übergeben müssen, aber um diese Zellen zu erstellen, benötigen wir auch einen Zeiger auf den Thunk. Um dieses Problem zu lösen, füllte ich den Zeiger auf den Thunk mit einemNULL
ersten und änderte ihn in den Thunk, nachdem er erstellt wurde. Hier ist das Zuhören:Mögliche Verbesserungen
content_t
, den man nach Belieben ändern kann.quelle
void*
als Typ verwendencontent_t
.void*
zu verwenden, aber ich dachte, das würde das Typsystem zu weit umgehen. Ist das nicht mit Vorlagen möglich?void*
und Freunde zu benutzen .kind
ist so etwas wie ein Tag." Man könnte es auch so nennentag
, da dies ein ziemlich akzeptierter Begriff für das Konzept ist (z. B. " Union" , " Spineless Tagless G-machine") . Andererseits hat "kind" in a eine andere Bedeutung Haskell Kontext: Der Typ eines TypsInt
hat Art*
,[]
hat Art* -> *
und(,)
hat Art* -> * -> *
.C ++
Dies ist die größte Sache, die ich jemals in C ++ geschrieben habe. Normalerweise benutze ich Objective-C.
Es ist polymorph, aber es befreit nie etwas.
Meine
main
Funktion (und dieadd
Funktion zuZipWith
) sah dann so aus:Das gibt
Die Klassen arbeiten wie folgt:
Vollständige Quelle: hier . Es ist ein Chaos, vor allem, weil es in einer großen Datei ist.
Bearbeiten: Link geändert (der alte war tot).
quelle
()
Operator) und verwenden Sie die Vererbung, um die Verwendung zu vermeidenvoid*
. Hier finden Sie ein einfaches Beispiel dafür.Python
Verwendet keine Generatoren, um die Liste zu implementieren, sondern nur, um die
__iter__
Methode für die Verwendung mit zu implementierenfor
.Die Fibonacci-Liste wird folgendermaßen erstellt:
quelle
self.__class__ = node.__class__
. Beachten Sie, dass dies auf eine NotImplemented-Ausnahme stößt, wenn sie 2971215073 (long) erreicht. Dies ist anscheinend ein ungültiges Argument für int .__ add__. Um große ganze Zahlen zu unterstützen, gehen Sie wiefib.append(fib.zip_with(lambda a,b: a+b, fib.tail()))
Rubin
Mein erstes Ruby-Programm. Wir stellen alle Knoten als Arrays dar, wobei die Arraylänge den Typ bestimmt:
Der Code ist dann ziemlich einfach, mit einem Hack zum Zurücksetzen der Thunk-Funktion zum Einrichten der rekursiven Fib.
quelle
[...]
anstelle von verwendenArray[...]
.Google Go
Eine relativ neue Sprache, und ich habe sie gelernt, indem ich
CTRL+F
die Spec .Das Problem wurde durch den Umgang mit Thunk-in-a-Thunks behoben. Es scheint jedoch, dass der Online-Compiler 40 Elemente nicht aufnehmen kann, möglicherweise aufgrund des Speichers. Ich werde es später auf meinem Linux testen.
Ich habe den Code mit dem Online-Compiler getestet , da ich Go nicht einfach unter Windows installieren kann.
quelle
iota
Konstanten sind. Sehen Sie ein Beispiel in der Go Programming Language Specification , und eine Antwort auf Stackoverflow .Fibs
Funktion funktioniert nicht, da Go eine strenge Bewertung verwendet und sichFibs
selbst ohne eine Beendigungsbedingung wiederholt.Fibs0
/Fibs1
verwendet einen einfachen Generatoransatz anstelle des in meinem Beitrag beschriebenen Algorithmus, sodass die "Anforderungen" nicht erfüllt werden. Ich habe meinen Beitrag aktualisiert, um die für die Implementierung erforderliche verzögerte Rekursion zu erläuternfibs = 0 : 1 : zipWith (+) fibs (tail fibs)
.Cons(0, Cons(1, ZipWith(Plus, Thunk(Fibs), Tail(Thunk(Fibs)))))
, es geht aus dem GedächtnisCons(0, Cons(1, Thunk(func() List { return ZipWith(Plus, Thunk(Fibs), Thunk(func() List { return Tail(Fibs()) })) })))
und ich bekomme einen ungültigen SpeicheradressenfehlerKristall
Obwohl ich dem GitHub-Repository gefolgt bin, habe ich Crystal bisher noch nie benutzt . Crystal ist eine statisch typisierte Ruby-Variante mit vollständiger Typinferenz. Obwohl es bereits eine Ruby-Antwort gibt, führte mich die statische Typisierung von Crystal dazu, Polymorphismus anstelle eines Arrays zu verwenden, um die Knoten darzustellen. Da Crystal das Ändern von nicht zulässt
self
, habe ich eine Wrapper-Klasse namens erstelltNode
, die alles andere umschließt und die Thunks verwaltet.Zusammen mit den Klassen habe ich die Konstruktorfunktionen erstellt
lnil
,cons
undthunk
. Ich habe Ruby auch noch nie für ein Skript mit mehr als 20 Zeilen verwendet.Ich habe die
fib
Funktion von der Go-Antwort abhängig gemacht .quelle
Ich habe die Regeln ein wenig geändert, weil es hier noch keine .NET-Lösung gibt - oder allgemeiner eine OOP-Lösung mit Ausnahme derjenigen in Python, die Vererbung verwendet, aber sie unterscheidet sich genug von meiner Lösung, um beides interessant zu machen (insbesondere seit Python) erlaubt das zu modifizieren
self
Instanz, wodurch die Thunk-Implementierung unkompliziert wird.Das ist es also C # . Vollständige Offenlegung: Ich bin noch lange kein Anfänger in C #, aber ich habe die Sprache eine Weile nicht mehr angerührt, da ich momentan keine Verwendung dafür im Job habe.
Die wichtigsten Punkte:
Alle Klassen (
Nil
,Cons
,Thunk
) leiten sich von einer gemeinsamen abstrakte Basisklasse,List
.Die
Thunk
Klasse verwendet das Envelope-Letter- Muster. Dies emuliert im Wesentlichen dieself.__class__ = node.__class__
Zuweisung in der Python-Quelle, da diethis
Referenz in C # nicht geändert werden kann.IsEmpty
,Head
UndTail
sind Eigenschaften.Alle entsprechenden Funktionen werden rekursiv und
Print
träge implementiert (mit Ausnahme derjenigen, die nicht träge sein können), indem Thunks zurückgegeben werden. Zum Beispiel ist diesNil<T>.ZipWith
:… Und das ist
Cons<T>.ZipWith
:Leider hat C # keinen Mehrfachversand, sonst könnte ich die
if
Aussage auch loswerden . Ach, keine Würfel.Jetzt bin ich mit meiner Implementierung nicht wirklich zufrieden. Ich bin soweit glücklich, weil alles oben Genannte völlig unkompliziert ist. Aber . Ich
Fib
halte die Definition von für unnötig kompliziert, da ich die Argumente in Thunks einwickeln muss, damit es funktioniert:(Hier
List.Cons
,,List.Thunk
undList.ZipWith
sind Convenience - Wrapper.)Ich würde gerne verstehen, warum die folgende viel einfachere Definition nicht funktioniert:
gegeben eine angemessene Definition von
Concat
von natürlich. Dies ist im Wesentlichen das, was der Python-Code tut - aber es funktioniert nicht (= einen Fit auslösen)./ EDIT: Joey hat auf den offensichtlichen Mangel dieser Lösung hingewiesen. Das Ersetzen der zweiten Zeile durch einen Thunk führt jedoch auch zu einem Fehler (Mono-Segfaults; ich vermute einen Stapelüberlauf, den Mono nicht gut handhabt):
Der vollständige Quellcode ist auf GitHub zu finden .
quelle
fib.ZipWith
undfib.Tail
benutze das altefib
, das bleibt[0,1]
und sich nicht ändert. Sie erhalten also[0,1,1]
(glaube ich), und IhreTake
Funktion lässt Sie nicht von null nehmen (Haskells take tut es jedoch). Versuchen Sie, den Wert der zweiten Zeile in einen Thunk zu setzen, damit er sich auf den neuenfib
und nicht auf den alten Wert bezieht .Pico
Für die Aufzeichnung verwendet diese Lösung eine Übersetzung der Verzögerungskraft des Schemas, wie in srfi-45 definiert . und baut darauf faule Listen auf.
die ausgabe sieht so aus: (aber je nachdem wie
tpico
. gepatcht ist es doppelte Anführungszeichen in es haben könntedisplay
. Normalerweise druckt Strings mit Anführungszeichen dh alle Erscheinungen[
,,
,]
würde um sie herum haben Anführungszeichen wie"["
.)Aufgrund der Grenzen des Integer-Datentyps in tpico schlägt dies bei der Berechnung der 45. (oder 46. versetzten) Fibonacci-Zahl fehl.
Beachten Sie, dass tpico 2.0pl11 darin nicht funktioniert
begin(a,b)
(was üblicherweise geschrieben wird als{a;b}
if
nicht funktioniert ) und die Funktion nicht rekursiv ist. Ganz zu schweigen davon, dass ich 5 Jahre gebraucht habe, um herauszufinden, warum derbegin
Schwanz nicht rekursiv war. damals schrieb ich auch die übersetzung von srfi-45 in pico. Es stellte sich heraus, dassbegin
es auf den Wert von wartete,b
bevor es zurückkehrte, wenn es nicht warten musste. und als ich das bekam, konnte ich es auch beheben,if
da es das gleiche Problem hatte. und es gab diesen anderen Fehler, der den Meta-Level-Konstruktor gemacht hatmake
funktionsunfähig machte.Mit Pico kann eine Funktion steuern, ob ihre Argumente ausgewertet werden, bevor die Funktion aufgerufen oder nur als Thunks verpackt wird. Für diesen Code kann ich die Seltsamkeiten des Aufrufs nach Funktion auslassen .
Pico hat keine Typinferenz. Ich habe eine Weile darüber nachgedacht, aber ich bin auf ein Problem gestoßen, das auf die Merkwürdigkeiten des Aufrufs nach Funktion zurückzuführen ist . Ich fand die Aussage, dass Typen die Existenz gebundener Variablennamen codieren müssen . aber ich dachte hauptsächlich darüber nach, wie man die Hindley-Milner-Inferenz an eine Teilmenge von Pico ohne Mutation anpasst. Die Hauptidee war, dass der Typprüfer mehrere mögliche Schemata zurückgibt, wenn mehr als eine mögliche Bindung vorhanden ist, und die Typprüfung erfolgreich ist, wenn mindestens ein mögliches Typschema vorhanden ist . Ein mögliches Schema ist eines, bei dem keine Typzuweisungskonflikte auftreten.
quelle