Implementiere Lazy Lists, vorzugsweise in einer Sprache, die du nicht gut kennst. [Closed]

21

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.
  • 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.
  • fibsverwendet 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, fixdie eine Listenrückgabefunktion mit einem eigenen Rückgabewert aufruft. Erwägen Sie, ein kurzes Nickerchen zu machen, damit diese Idee greifbar wird.

  • 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.
Joey Adams
quelle
Wie soll sich der Aufruf zipWithvon zwei unterschiedlich langen Listen verhalten ?
balpha
@balpha: Ich habe Haskells Verhalten gewählt: Wenn eine der Listen Null ist, kehre Null zurück.
FUZxxl
@balpha: In Haskell stoppt zipWith, wenn in einer der Listen keine Elemente mehr vorhanden sind. Daher 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.
Joey Adams
Diese Herausforderung hatte eine versteckte Überraschung: Sie müssen etwas Besonderes tun, um fibsrichtig 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.
Joey Adams
Was meinen Sie mit "inkrementell arbeiten", wenn Sie eine große Liste drucken?
Lowjacker

Antworten:

6

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. forcewird weiter auswerten, bis das Ergebnis a niloder a ist cons.

Die Listen sind als Wörterbücher implementiert:

<< /type /nil >>

<< /type /cons
   /head someValue
   /tail someList >>

<< /type /thunk
   /func evaluationFunction >>

<< /type /dataThunk
   /func evaluationFunction
   /data someValueToBePassedToTheFunction >>

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

% before2 before1 before0  <| procedure |>  after1 after0

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.

% Helper procedure that creates a dictionary with the top two elements as keys
% and the next two elements as values.
%
% value1 value2 key1 key2  <| _twodict |>  << /key1 /value1 /key2 /value2 >>
/_twodict {
    << 5 1 roll    % << value1 value2 key1 key2
    4 2 roll       % << key1 key2 value1 value2
    3 2 roll       % << key1 value1 value2 key2
    exch >>
} def

/nil {
    << /type /nil >>
} def

% item list  <| cons |>  consCell
/cons {
    /head /tail _twodict
    dup /type /cons put
} def

% constructs a thunk from the function, which will be called with no
% arguments to produce the actual list node. It is legal for the function
% to return another thunk.
%
% func  <| thunk |>  lazyList
/thunk {
    /thunk /func /type _twodict
} def

% A dataThunk is like a regular thunk, except that there's an additional
% data object that will be passed to the evaluation function
%
% dataObject func  <| dataThunk |>  lazyList
/dataThunk {
    /data /func _twodict
    dup /type /dataThunk put 
} def

% lazyList  <| force |>  consOrNil
/force {
    dup /type get dup
    /thunk eq
    {
        pop
        dup /func get exec exch copy
        force
        dup /func undef
    }
    {
        /dataThunk eq
        {
            dup dup /data get exch
            /func get exec exch copy
            force
            dup dup /func undef /data undef
        } if
    } ifelse
} def

/empty {
    force
    /type get
    /nil eq
} def

/head {
    force /head get
} def

/tail {
    force /tail get
} def

/print {
    dup empty not
    {
        dup
        head ==
        tail
        print    
    }
    {
        pop
    } ifelse
} def

% sourceList n  <| take |>  resultingList
/take {
    /source /n _twodict
    {
        dup /source get exch    % source data
        /n get 1 sub dup        % source n-1 n-1
        -1 eq
        {
            pop pop nil
        }
        {                       % source n-1
            exch                % n-1 source
            dup head            % n-1 source head
            3 1 roll            % head n-1 source
            tail
            exch take           % head rest
            cons
        } ifelse
    }
    dataThunk
} def

% sourceList1 sourceList2 func  <| zipWith |>  resultList
/zipWith {
    3 1 roll
    2 array astore                  % func [L1 L2] 
    /func /sources _twodict
    {
        dup /sources get aload pop  % data L1 L2
        2 copy empty exch empty or
        {
            pop pop pop nil
        }
        {
            dup head exch tail      % data L1 H2 T2
            3 2 roll
            dup head exch tail      % data H2 T2 H1 T1
            exch                    % data H2 T2 T1 H1
            4 3 roll                % data T2 T1 H1 H2
            5 4 roll /func get      % T2 T1 H1 H2 func
            dup 4 1 roll            % T2 T1 func H1 H2 func
            exec                    % T2 T1 func NEWHEAD
            4 2 roll                % func NEWHEAD T2 T1
            exch 4 3 roll           % NEWHEAD T1 T2 func 
            zipWith cons
        } ifelse
    }
    dataThunk
} def

Laden Sie dies in Ghostscript und ignorieren Sie die angezeigte Seite - wir arbeiten nur mit dem Interpreter. Hier ist der Fibonacci-Algorithmus:

[balpha@localhost lazylist]$ gs lazylist.ps 
GPL Ghostscript 8.71 (2010-02-10)
Copyright (C) 2010 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
GS> /fibs 0 1 { fibs fibs tail { add } zipWith } thunk cons cons def
GS> fibs 40 take print
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
GS>

Zwei weitere interessante Funktionen:

% creates an infinite list that starts with the given value, incrementing
% by one for each additional element
%
% startValue  <| count |>  lazyList
/count {
    {
        dup
        1 add count
        cons
    }
    dataThunk
} def    

% apply the given function to each element of the source list, creating
% a (lazy) list that contains the corresponding results
%
% sourceList function  <| map |> resultList
/map {
    /source /func _twodict
    {
        dup /func get exch
        /source get                 % func source
        dup empty not
        {
            dup head                % func source head
            2 index                 % func source head func
            exec 3 1 roll           % newHead func source
            tail exch map cons
        }
        {
            pop pop nil
        } ifelse
    }
    dataThunk
} def

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:

GS> 5 count { 3 mul } map 10 take print
15
18
21
24
27
30
33
36
39
42

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:

GS> 1337 [ 42 3.14 ] << /key /value >> (Hello world) 3 count
GS<5> cons cons cons cons 10 take print
1337
[42 3.14]
-dict-
(Hello world)
3
4
5
6
7
8
GS>

Beachten Sie, dass Tippfehler, z. B. beim Versuch, Zeichenfolgen zu Zahlen hinzuzufügen, nur zur Bewertungszeit auftreten:

GS> (some string) (another string) nil cons cons
GS<1> 13 27 nil cons cons
GS<2> { add } zipWith      % no error yet
GS<1> print
Error: /typecheck in --add--
balpha
quelle
Tolle. (Wie) merkt man sich die forcezurückgegebenen Werte?
Joey Adams
@ JoeyAdams: Es tut in der Tat. Nach der Bewertung eines Thunks copykopiert der Bediener den Inhalt der bewerteten Version in das Original und überschreibt /typeund setzt möglicherweise andere Werte. Nach rekursiver Auswertung, bis wir ein niloder habencons , wird auch (via undef) entfernt /funcund gegebenenfalls /data. Der letzte Schritt ist nicht unbedingt notwendig ( /funcund /datawürde einfach ignoriert), aber wenn dieser Schritt
weggelassen wird
6

C

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 dann make runauszufü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 Struktur list, die unsere Lazy List ist. Es sieht aus wie das:

enum cell_kind {
  NIL,
  CONS,
  THUNK
};

typedef enum cell_kind cell_kind;

typedef long int content_t;

struct list {
  cell_kind kind;
  union {
    struct {
      content_t* head;
      struct list* tail;
    } cons;
    struct {
      struct list* (*thunk)(void*);
      /* If you want to give arguments to the thunk, put them in here */
      void* args;
    } thunk;
  } content;
};

Das Mitglied kindist 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 ist

  • entweder eine bereits bewertete Zelle mit einem Wert und einem Ende
  • oder ein Thunk mit einem Funktionszeiger und einer Struktur, der bei Bedarf einige Argumente für die Funktion enthalten kann.

Der 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 , takeund fibonaccis. Aber ich möchte es nicht erklären take, da es sehr ähnlich ist zipWith. Alle Funktionen, die träge arbeiten, haben drei Komponenten:

  • Ein Wrapper, der einen Strich macht
  • Ein Arbeiter, der die Berechnungen für eine Zelle durchführt
  • Eine Struktur, die die Argumente behält

Im Falle zipWith, diese sind zipWith, __zipWithund__zipArgs . Ich zeige sie hier nur ohne weitere Erklärung, da sollte die Funktion ganz klar sein:

struct __zipArgs {
  content_t* (*f)(content_t*,content_t*);
  list* listA;
  list* listB;
};

static list* __zipWith(void* args_) {
  struct __zipArgs* args = args_;
  list* listA = args->listA;
  list* listB = args->listB;
  list* listC;

  content_t* (*f)(content_t*,content_t*) = args->f;
  content_t* headA = head(listA);
  content_t* headB = head(listB);
  content_t* headC;

  if (NULL == headA || NULL == headB) {
    free(args);
    return nil();
  } else {
    headC = f(headA, headB);
    args->listA = tail(listA);
    args->listB = tail(listB);
    listC = thunk(__zipWith,args);
    return cons(headC,listC);
  }
}

list* zipWith(content_t* (*f)(content_t*,content_t*),list* listA, list* listB) {
  struct __zipArgs* args = malloc(sizeof(struct __zipArgs));
  args->f = f;
  args->listA = listA;
  args->listB = listB;
  return thunk(__zipWith,args);
}

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 einem NULLersten und änderte ihn in den Thunk, nachdem er erstellt wurde. Hier ist das Zuhören:

static content_t* __add(content_t* a,content_t* b) {
  content_t* result = malloc(sizeof(content_t));
  *result = *a + *b;
  return result;
}

list* fibonaccis() {
  static content_t one_ = 1;
  static content_t zero_ = 0;
  list* one  = cons(&one_,NULL);
  list* two  = cons(&zero_,one);
  list* core = zipWith(__add,one,two);
  one->content.cons.tail = core;
  return two;

Mögliche Verbesserungen

  • Meine Lösung verwendet keinen Polymorphismus. Obwohl möglich, reichen meine C-Kenntnisse nicht aus, um damit umgehen zu können. Stattdessen habe ich einen Typ verwendet content_t, den man nach Belieben ändern kann.
  • Man könnte den Thunk aus der Definition der Liste extrahieren und ihn nur abstrakt verwenden, aber dies würde den Code komplizierter machen.
  • Man könnte die Teile meines Codes verbessern, die nicht gut C sind.
FUZxxl
quelle
Gute Vorlage, vor allem, wenn man ein C-Neuling ist. Wenn Sie in Bezug auf Polymorphismus bereit sind, all Ihre Inhalte auf dem Heap zuzuweisen, können Sie void*als Typ verwenden content_t.
Casey
@Casey: Vielen Dank. Ich dachte darüber nach, auch void*zu verwenden, aber ich dachte, das würde das Typsystem zu weit umgehen. Ist das nicht mit Vorlagen möglich?
FUZxxl
C hat keine Vorlagen, das ist C ++, aber Sie können C ++ - Vorlagen verwenden, um es generisch zu machen.
Casey
Ich weiß nicht, wie ich sie benutzen soll. Aber ich denke, es ist nur so, dass C in Bezug auf sein Typensystem irgendwie begrenzt ist. - Ich konnte dieses Programm nicht einmal programmieren, ohne void*und Freunde zu benutzen .
FUZxxl
1
"Das Mitglied kindist so etwas wie ein Tag." Man könnte es auch so nennen tag, 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 Typs Inthat Art *, []hat Art * -> *und (,)hat Art * -> * -> *.
Joey Adams
5

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 mainFunktion (und die addFunktion zu ZipWith) sah dann so aus:

int add(int a, int b) {return a + b;}

int main(int argc, char **argv) {
    int numFib = 15; // amount of fibonacci numbers we'll print
    if (argc == 2) {
        numFib = atoi(argv[1]);
    }

    // list that starts off 1, 1...
    LazyList<int> fibo = LazyList<int>(new Cons<int>(1,
                     new LazyList<int>(new Cons<int>(1))));
    // zip the list with its own tail
    LazyList<int> *fiboZip = LazyList<int>::ZipWith(add, &fibo, fibo.Tail());
    // connect the begin list to the zipped list
    fibo.Tail() -> ConnectToList(fiboZip);

    // print fibonacci numbers
    int *fibonums = fibo.Take(numFib);    
    for (int i=0; i<numFib; i++) cout << fibonums[i] << " ";

    cout<<endl;

    return 0;
}

Das gibt

 ./lazylist-fibo 20
 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 

Die Klassen arbeiten wie folgt:

make a thunk:    LazyList<T>(new Thunk<T>( function, *args )) 
make empty list: LazyList<T>(new Nil<T>())
make cons:       LazyList<T>(new Cons<T>( car, *cdr ))

list empty:      list.Empty()
list's head:     list.Head()
list's tail:     list.Tail()
zipWith:         LazyList<T>::ZipWith(function, a, b)
take:            list.Take(n)
print:           list.Print()

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).

Marinus
quelle
3
Ausgezeichnete Arbeit, und danke, dass Sie "throw a fit" wörtlich genommen haben :-) Ich bin keineswegs ein C ++ - Experte, aber ein besserer Weg, Thunk zu implementieren, könnte darin bestehen, ein Funktionsobjekt (aka "functor") (das) zu verwenden ist, überladen Sie den ()Operator) und verwenden Sie die Vererbung, um die Verwendung zu vermeiden void*. Hier finden Sie ein einfaches Beispiel dafür.
Joey Adams
Der vollständige Quelllink ist jetzt nicht mehr verfügbar. Könnten Sie es erneut hochladen? gist.github.com ist ein guter Ort, um es auszudrücken .
Joey Adams
@ JoeyAdams: fertig.
Marinus
4

Python

Verwendet keine Generatoren, um die Liste zu implementieren, sondern nur, um die __iter__Methode für die Verwendung mit zu implementieren for.

class Node(object):
    def __init__(self, head, tail):
        self.__head__ = head
        self.__tail__ = tail

    def force(self):
        return self

    def empty(self):
        return False

    def head(self):
        return self.__head__

    def tail(self):
        return self.__tail__

    def zip_with(self, func, other):
        def gen_func():
            if other.empty():
                return other
            return Node(func(self.head(), other.head()), self.tail().zip_with(func, other.tail()))
        return Thunk(gen_func)

    def __iter__(self):
        while not self.empty():
            yield self.head()
            self = self.tail()

    def append(self, other):
        while not self.tail().empty():
            self = self.tail()
        self.__tail__ = other

    def take(self, n):
        if n == 0:
            return NullNode()
        else:
            return Node(self.__head__, self.__tail__.take(n - 1))

    def _print(self):
        for item in self:
            print item

class NullNode(Node):
    def __init__(self):
        pass

    def empty(self):
        return True

    def head(self):
        raise TypeError("cannot get head of nil")

    def tail(self):
        raise TypeError("cannot get tail of nil")

    def zip_with(self, func, other):
        return self

    def append(self, other):
        raise TypeError("cannot append to nil")

    def take(self, n):
        return self

class Thunk(Node):
    def __init__(self, func):
        self.func = func

    def force(self):
        node = self.func()
        self.__class__ = node.__class__
        if not node.empty():
            self.__head__ = node.head()
            self.__tail__ = node.tail()
        return self

    def empty(self):
        return self.force().empty()

    def head(self):
        return self.force().head()

    def tail(self):
        return self.force().tail()

    def take(self, n):
        return self.force().take(n)

Die Fibonacci-Liste wird folgendermaßen erstellt:

>>> from lazylist import *
>>> fib = Node(0, Node(1, NullNode()))
>>> fib.append(fib.zip_with(lambda a, b: a + b, fib.tail()))
>>> 
Lowjacker
quelle
1
Das ist schön. Meine Lieblingslinie ist 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 wie fib.append(fib.zip_with(lambda a,b: a+b, fib.tail()))
Joey Adams
1
Warum können Sie nicht an leere oder Thunk anhängen?
PyRulez
4

Rubin

Mein erstes Ruby-Programm. Wir stellen alle Knoten als Arrays dar, wobei die Arraylänge den Typ bestimmt:

0: empty list
1: thunk (call the single element to get the cons cell)
2: cons cell (1st is head, 2nd is tail)

Der Code ist dann ziemlich einfach, mit einem Hack zum Zurücksetzen der Thunk-Funktion zum Einrichten der rekursiven Fib.

def nil_()
  return Array[]
end

def cons(a, b)
  return Array[a, b]
end

def thunk(f)
  return Array[f]
end

def force(x)
  if x.size == 1
    r = x[0].call
    if r.size == 2
      x[0] = r[0]
      x.push(r[1])
    else
      x.pop()
    end
  end
end

def empty(x)
  force(x)
  return x.size == 0
end

def head(x)
  force(x)
  return x[0]
end

def tail(x)
  force(x)
  return x[1]
end

def zipWith(f, a, b)
  return thunk(lambda {
    if empty(a) or empty(b)
      return nil_()
    else
      return cons(f.call(head(a), head(b)), zipWith(f, tail(a), tail(b)))
    end
  })
end

def take(n, x)
  if n == 0
    return nil_()
  else
    return cons(head(x), take(n - 1, tail(x)))
  end
end

def print(x)
  while not empty(x)
    puts x[0]
    x = x[1]
  end
end

def add(x, y)
  return x + y
end

T=thunk(nil)  # dummy thunk function
fibs=cons(0, cons(1, T))
T[0]=zipWith(method(:add), fibs, tail(fibs))[0]  # overwrite thunk function

print(take(40, fibs))
Keith Randall
quelle
Sie können [...]anstelle von verwenden Array[...].
Lowjacker
3

Google Go

Eine relativ neue Sprache, und ich habe sie gelernt, indem ich CTRL+Fdie Spec .

package main
import "fmt"

type List struct {
  isNil, isCons, isThunk bool
  head *interface { }
  tail *List
  thunk (func() List)
}

func Nil() List {
  return List { true, false, false, nil, nil, Nil }
}

func Cons(a interface { }, b List) List {
  return List { false, true, false, &a, &b, Nil }
}

func Thunk(f(func() List)) List {
  return List { false, false, true, nil, nil, f }
}

func Force(x List) List {
  if x.isNil { return Nil()
  } else if x.isCons { return Cons(*x.head, *x.tail) }
  return Force(x.thunk())
}

func Empty(x List) bool {
  return Force(x).isNil;
}

func Head(x List) interface { } {
  y := Force(x)
  if y.isNil { panic("No head for empty lists.") }
  return *y.head
}

func Tail(x List) List {
  y := Force(x)
  if y.isNil { panic("No tail for empty lists.") }
  return *y.tail
}

func Take(n int, x List) List {
  if (n == 0) { return Nil() }
  return Thunk(func() List {
    y := Force(x)
    return Cons(*y.head, Take(n - 1, *y.tail))
  })
}

func Wrap(x List) List {
  return Thunk(func() List {
    return x
  })
}

func ZipWith(f(func(interface { }, interface { }) interface { }), a List, b List) List {
  return Thunk(func() List {
    x, y := Force(a), Force(b)
    if x.isNil || y.isNil {
      return Nil()
    }
    return Cons(f(*x.head, *y.head), ZipWith(f, *x.tail, *y.tail))
  });
}

func FromArray(a []interface { }) List {
  l := Nil()
  for i := len(a) - 1; i > -1; i -- {
    l = Cons(a[i], l)
  }
  return l
}

func Print(x List) {
  fmt.Print("[")
  Print1(x)
  fmt.Print("]")
}

func Print1(x List) {
  y := Force(x)
  if y.isCons {
    fmt.Print(Head(y))
    z := Force(Tail(y))
    if z.isCons { fmt.Print(", ") }
    Print1(z)
  }
}

func Plus(a interface { }, b interface { }) interface { } {
  return a.(int) + b.(int)
}

func Fibs() List {

  return Thunk(func() List {
    return Cons(0, Cons(1, Thunk(func() List {
      return ZipWith(Plus, Thunk(Fibs), Tail(Thunk(Fibs)))
    })))
  })
}

func Fibs0() List {
  // alternative method, working
  return Cons(0, Cons(1, Fibs1(0, 1)))
}

func Fibs1(a int, b int) List {
  c := a + b
  return Cons(c, Thunk(func() List { return Fibs1(b, c) }))
}

func CountUp(x int, k int) List {
  return Cons(x, Thunk(func() List {
    return CountUp(x + k, k)
  }))
}

func main() {
  //a := []interface{} { 0, 1, 2, 3 }
  //l, s := FromArray(a), FromArray(a)
  Print(Take(40, Fibs()))
}

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.

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368runtime: address space conflict: map() = 
throw: runtime: address space conflict

panic during panic

Ich habe den Code mit dem Online-Compiler getestet , da ich Go nicht einfach unter Windows installieren kann.

Ming-Tang
quelle
Das ist ziemlich nett und einfach. Anstelle von 3 Bools können Sie jedoch auch ein einzelnes Tag verwenden, dessen mögliche Werte vom Konstantengenerator generierte iotaKonstanten sind. Sehen Sie ein Beispiel in der Go Programming Language Specification , und eine Antwort auf Stackoverflow .
Joey Adams
Ihre FibsFunktion funktioniert nicht, da Go eine strenge Bewertung verwendet und sich Fibsselbst ohne eine Beendigungsbedingung wiederholt. Fibs0/ Fibs1verwendet 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äutern fibs = 0 : 1 : zipWith (+) fibs (tail fibs) .
Joey Adams
Cons(0, Cons(1, ZipWith(Plus, Thunk(Fibs), Tail(Thunk(Fibs))))), es geht aus dem Gedächtnis
Ming-Tang
Ich habe versucht, Cons(0, Cons(1, Thunk(func() List { return ZipWith(Plus, Thunk(Fibs), Thunk(func() List { return Tail(Fibs()) })) })))und ich bekomme einen ungültigen Speicheradressenfehler
Ming-Tang
1
Da Sie noch lernen, gehen Sie: Sie können viel eleganter Code als dies mit Schnittstellen für die Listen und separate Typen für Thunks usw. machen
cthom06
3

Kristall

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 erstellt Node, die alles andere umschließt und die Thunks verwaltet.

Zusammen mit den Klassen habe ich die Konstruktorfunktionen erstellt lnil , consund thunk. Ich habe Ruby auch noch nie für ein Skript mit mehr als 20 Zeilen verwendet.

Ich habe die fibFunktion von der Go-Antwort abhängig gemacht .

class InvalidNodeException < Exception
end

abstract class LazyValue
end

class LNil < LazyValue
    def empty?
        true
    end

    def force!
        self
    end

    def head
        raise InvalidNodeException.new "cannot get head of LNil"
    end

    def tail
        raise InvalidNodeException.new "cannot get tail of Nil"
    end

    def take(n)
        Node.new self
    end
end

class Cons < LazyValue
    def initialize(@car, @cdr)
    end

    def empty?
        false
    end

    def force!
        @cdr.force!
        self
    end

    def head
        @car
    end

    def tail
        @cdr
    end

    def take(n)
        Node.new n > 0 ? Cons.new @car, @cdr.take n-1 : LNil.new
    end
end

class Thunk < LazyValue
    def initialize(&@func : (-> Node))
    end

    def empty?
        raise Exception.new "should not be here!"
    end

    def force!
        @func.call()
    end

    def head
        self.force!.head
    end

    def tail
        self.force!.tail
    end

    def take(n)
        self.force!.take n
    end
end

class Node
    def initialize(@value = LNil.new)
    end

    def empty?
        self.force!
        @value.empty?
    end

    def force!
        @value = @value.force!
        self
    end

    def head
        self.force!
        @value.head
    end

    def tail
        self.force!
        @value.tail
    end

    def take(n)
        self.force!
        return @value.take n
    end

    def print
        cur = self
        while !cur.empty?
            puts cur.head
            cur = cur.tail
        end
    end
end

def lnil
    Node.new LNil.new
end

def cons(x, r)
    Node.new Cons.new x, r
end

def thunk(&f : (-> Node))
    Node.new Thunk.new &f
end

def inf(st=0)
    # a helper to make an infinite list
    f = ->() { lnil }
    f = ->() { st += 1; cons st, thunk &f }
    thunk { cons st, thunk &f }
end

def zipwith(a, b, &f : Int32, Int32 -> Int32)
    thunk { a.empty? || b.empty? ? lnil :
            cons f.call(a.head, b.head), zipwith a.tail, b.tail, &f }
end

def fibs
    # based on the Go answer
    fibs2 = ->(a : Int32, b : Int32) { lnil }
    fibs2 = ->(a : Int32, b : Int32) { cons a+b, thunk { fibs2.call b, a+b } }
    cons 0, cons 1, thunk { fibs2.call 0, 1 }
end

fibs.take(40).print
zipwith(inf, (cons 1, cons 2, cons 3, lnil), &->(a : Int32, b : Int32){ a+b }).print
kirbyfan64sos
quelle
2

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 ThunkKlasse verwendet das Envelope-Letter- Muster. Dies emuliert im Wesentlichen die self.__class__ = node.__class__Zuweisung in der Python-Quelle, da die thisReferenz in C # nicht geändert werden kann.

  • IsEmpty, HeadUnd Tailsind Eigenschaften.

  • Alle entsprechenden Funktionen werden rekursiv und Printträge implementiert (mit Ausnahme derjenigen, die nicht träge sein können), indem Thunks zurückgegeben werden. Zum Beispiel ist dies Nil<T>.ZipWith:

    public override List<T> ZipWith(Func<T, T, T> func, List<T> other) {
        return Nil();
    }
    

    … Und das ist Cons<T>.ZipWith:

    public override List<T> ZipWith(Func<T, T, T> func, List<T> other) {
        return Thunk(() => {
            if (other.IsEmpty)
                return Nil();
    
            return Cons(func(Head, other.Head), Tail.ZipWith(func, other.Tail));
        });
    }
    

    Leider hat C # keinen Mehrfachversand, sonst könnte ich die ifAussage 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 Fibhalte die Definition von für unnötig kompliziert, da ich die Argumente in Thunks einwickeln muss, damit es funktioniert:

List<int> fib = null;
fib = List.Cons(0, List.Cons(1,
    List.ZipWith(
        (a, b) => a + b,
        List.Thunk(() => fib),
        List.Thunk(() => fib.Tail))));

(Hier List.Cons,, List.ThunkundList.ZipWith sind Convenience - Wrapper.)

Ich würde gerne verstehen, warum die folgende viel einfachere Definition nicht funktioniert:

List<int> fib = List.Cons(0, List.Cons(1, List.Nil<int>()));
fib = fib.Concat(fib.ZipWith((a, b) => a + b, fib.Tail));

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):

fib = List.Thunk(() => fib.Concat(fib.ZipWith((a, b) => a + b, fib.Tail)));

Der vollständige Quellcode ist auf GitHub zu finden .

Konrad Rudolph
quelle
"Leider hat C # keinen Mehrfachversand" - Sie können den Effekt mithilfe von Ereignissen erzielen, auch wenn das etwas hackig ist.
Peter Taylor
Das Ironische an fauler Evaluierung ist, dass der Zustand zur Implementierung erforderlich ist. fib.ZipWithund fib.Tailbenutze das alte fib, das bleibt [0,1]und sich nicht ändert. Sie erhalten also [0,1,1](glaube ich), und Ihre TakeFunktion 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 neuen fibund nicht auf den alten Wert bezieht .
Joey Adams
@ Peter Ja; Sie können das Besuchermuster auch zum Implementieren von Mehrfachversand verwenden, aber ich wollte, dass die Lösung einfach bleibt.
Konrad Rudolph
@ Joey Duh. Es ist jetzt blindlings offensichtlich. Die Thunk-Lösung funktioniert jedoch immer noch nicht (siehe aktualisierte Antwort), aber ich bin zu beschäftigt, um dies zu untersuchen.
Konrad Rudolph
2

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.

{ 
` scheme's srfi-45 begins here `

  _lazy_::"lazy";
  _eager_::"eager";

  lazy(exp())::[[_lazy_, exp]];
  eager(exp)::[[_eager_, exp]];
  delay(exp())::lazy(eager(exp()));

  force(promise)::
    { content:promise[1];
      if(content[1]~_eager_,
        content[2],
        if(content[1]~_lazy_,
          { promise_:content[2]();
            content:promise[1];
            if(content[1]~_lazy_, 
             { content_:promise_[1];
               content[1]:=content_[1];
               content[2]:=content_[2];
               promise_[1]:=content });
            force(promise) })) };

` scheme's srfi-45 ends here `

nil:delay([]);
is_nil(s):size(force(s))=0;
cons(a(),b()):delay([a(),b()]);
head(s):force(s)[1];
tail(s):force(s)[2];

zipWith(f,a,b):
  lazy(if(is_nil(a)|is_nil(b),
         nil,
         cons(f(head(a),head(b)), zipWith(f,tail(a),tail(b)))));

fibs:void;
fibs:=cons(0, cons(1, zipWith(+,fibs,tail(fibs))));

take(c,s):
  lazy(if((c=0)|(is_nil(s)),
         nil,
         cons(head(s),take(c-1,tail(s)))));

print(s):
  { comma(s):
      if(is_nil(s),
        void,
        { display(head(s));
          if(!(is_nil(tail(s))), display(","));
          comma(tail(s)) });
    display("[");
    comma(s);
    display("]");
    void };

print(take(40,fibs))

}

die ausgabe sieht so aus: (aber je nachdem wie tpico . gepatcht ist es doppelte Anführungszeichen in es haben könnte display. Normalerweise druckt Strings mit Anführungszeichen dh alle Erscheinungen [, ,, ]würde um sie herum haben Anführungszeichen wie "[".)

[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]<void>

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 der beginSchwanz nicht rekursiv war. damals schrieb ich auch die übersetzung von srfi-45 in pico. Es stellte sich heraus, dass begines auf den Wert von wartete, bbevor es zurückkehrte, wenn es nicht warten musste. und als ich das bekam, konnte ich es auch beheben, ifda 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.

Dan D.
quelle