Implementa liste pigre, preferibilmente in una lingua che non conosci bene [chiuso]


21

Questo è un buon esercizio per diventare più fluente in quel linguaggio di programmazione che intendevi imparare, ma hai solo armeggiato con leggerezza. Ciò implica lavorare con oggetti, usare o simulare chiusure e allungare il sistema di tipi.

Il tuo compito è scrivere codice per gestire liste pigre, quindi utilizzarlo per implementare questo algoritmo per generare numeri di Fibonacci:

Gli esempi di codice sono in Haskell

let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
 in take 40 fibs

Risultato:

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

L'implementazione dell'elenco pigro dovrebbe soddisfare queste linee guida:

  • Un nodo Elenco è una delle tre cose:
    • Zero: elenco vuoto.
      []
    • Contro - Un singolo oggetto, associato a un elenco degli elementi rimanenti:
      1 : [2,3,4,5]
      ( :è l'operatore contro in Haskell)
    • Thunk - Un calcolo differito che produce un nodo Elenco quando necessario.
  • Supporta le seguenti operazioni:
    • zero: crea un elenco vuoto.
    • contro - Costruisci una cella contro.
    • thunk - Costruisci un Thunk, data una funzione che non accetta argomenti e restituisce uno zero o un contro.
    • force - Dato un nodo Elenco:
      • Se è uno zero o contro, semplicemente restituirlo.
      • Se è un Thunk, chiama la sua funzione per ottenere uno zero o contro. Sostituisci il thunk con quel Nil o Contro e restituiscilo.
        Nota: la sostituzione del thunk con il suo valore forzato è una parte importante della definizione di "pigro" . Se questo passaggio viene saltato, l'algoritmo di Fibonacci sopra sarà troppo lento.
    • vuoto - Verifica se un nodo Elenco è zero (dopo averlo forzato).
    • head (alias "car"): ottieni il primo elemento di un elenco (o lancia un attacco se è zero).
    • tail (aka "cdr") - Ottieni gli elementi dopo la testa di una lista (o lancia un adattamento se è zero).
    • zipWith - Data una funzione binaria (es. (+)) e due (possibilmente infiniti) elenchi, applica la funzione agli elementi corrispondenti degli elenchi. Esempio:
      zipWith (+) [1,2,3] [1,1,10] == [2,3,13]
    • take - Dato un numero N e un elenco (possibilmente infinito), prendi i primi N elementi dell'elenco.
    • stampa - Stampa tutti gli elementi in un elenco. Questo dovrebbe funzionare in modo incrementale quando viene fornito un elenco lungo o infinito.
  • fibssi usa nella sua stessa definizione. Impostare una ricorsione pigra è un po 'complicato; dovrai fare qualcosa del genere:

    • Allocare un thunk per fibs. Lascialo in uno stato fittizio per ora.
    • Definire la funzione thunk, che dipende da un riferimento a fibs.
    • Aggiorna il thunk con la sua funzione.

    È possibile nascondere questo impianto idraulico definendo una funzione fixche chiama una funzione di ritorno in elenco con il proprio valore di ritorno. Considera di fare un breve pisolino in modo che questa idea possa entrare.

  • Il polimorfismo (la capacità di lavorare con elenchi di qualsiasi tipo di oggetto) non è richiesto, ma vedi se riesci a trovare un modo per farlo che sia idiomatico nella tua lingua.

  • Non preoccuparti della gestione della memoria. Anche le lingue con garbage collection hanno la tendenza a portare in giro oggetti che non userete mai più (ad es. Nello stack di chiamate), quindi non stupitevi se il vostro programma perde memoria mentre attraversa un elenco infinito.

Sentiti libero di deviare leggermente da queste linee guida per soddisfare i dettagli della tua lingua o per esplorare approcci alternativi alle liste pigre.

Regole:

  • Scegli una lingua che non conosci bene. Non posso "richiedere" questo, quindi il tag "sistema d'onore". Tuttavia, gli elettori possono controllare la tua cronologia per vedere in quali lingue hai pubblicato.
  • Non utilizzare il supporto dell'elenco pigro incorporato nella tua lingua per fare tutto. Pubblica qualcosa di sostanziale o almeno interessante.

    • Haskell è praticamente fuori. Cioè, a meno che tu non faccia qualcosa del genere:

      data List a = IORef (ListNode a)
      data ListNode a = Nil | Cons !a !(List a) | Thunk !(IO (ListNode a))
      

      Nota: la valutazione non rigorosa di Haskell non è off-limits, ma l'implementazione dell'elenco pigro non dovrebbe derivare la sua capacità direttamente da lì. In effetti, sarebbe interessante vedere una soluzione efficiente, puramente funzionale che non richiede pigrizia.

    • Pitone:

      • Non usare itertools.
      • I generatori vanno bene, ma li usi, dovrai trovare un modo per memorizzare valori forzati.

Quale dovrebbe essere il comportamento quando si chiama zipWithdue elenchi di diverse lunghezze?
balpha,

@balpha: ho scelto il comportamento di Haskells: se una delle liste è nulla, restituisci zero.
FUZxxl

@balpha: in Haskell, zipWith si interrompe quando uno dei due elenchi si esaurisce. Quindi, zipWith (+) [1,2,3,4,5] [0,0,0] == [1,2,3]. Tuttavia, questo non ha importanza per l'algoritmo di Fibonacci sopra, poiché entrambi gli argomenti di zipWith sono liste infinite.
Joey Adams,

Questa sfida ha avuto una sorpresa nascosta: devi fare qualcosa di speciale per implementarla fibscorrettamente, poiché dipende da se stessa. Ho aggiornato la domanda per elaborare la ricorsione pigra. FUZxxl l'ha capito da solo.
Joey Adams,

Cosa intendi per "lavorare in modo incrementale" quando stampi un grande elenco?
Lowjacker,

Risposte:


6

PostScript

Ho già giocato con PostScript , ma non direi che lo conosco particolarmente bene (in effetti, la mia ipotesi è che puoi contare il numero di persone al mondo che conoscono davvero PostScript con una sola mano).

Ho deviato dalle tue specifiche in quanto la funzione utilizzata per creare un thunk può restituire un altro thunk; forcecontinuerà a valutare fino a quando il risultato è a nilo a cons.

Le liste sono implementate come dizionari:

<< /type /nil >>

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

<< /type /thunk
   /func evaluationFunction >>

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

Il codice segue. Si noti che stiamo sovrascrivendo alcuni operatori integrati (in particolare print; non ho verificato se ce ne sono altri); nell'uso del mondo reale, questo dovrebbe essere guardato fuori per. Ovviamente non ci sarà alcun uso nel mondo reale, quindi va bene.

I commenti prima delle procedure devono essere letti come

% before2 before1 before0  <| procedure |>  after1 after0

cioè mostrando il contenuto dello stack previsto prima della chiamata e il contenuto dello stack risultante dopo la chiamata. I commenti all'interno delle procedure mostrano il contenuto dello stack dopo l'esecuzione della riga specifica.

% 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

Carica questo in Ghostscript, ignorando la pagina visualizzata: stiamo solo lavorando con l'interprete. Ecco l'algoritmo di Fibonacci:

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

Due ulteriori funzioni interessanti:

% 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

Inizia a contare da 5, moltiplica ogni elemento dell'elenco risultante per 3 e visualizza i primi dieci valori:

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

Per quanto riguarda il polimorfismo: anche se PostScript è fortemente tipizzato, consente tipi arbitrari come valori di dizionario, quindi puoi inserire qualsiasi cosa ti piaccia:

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>

Si noti che gli errori di tipo, ad es. Dal tentativo di aggiungere stringhe ai numeri, si verificano solo al momento della valutazione:

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

Sorprendente. (Come) forcememorizza i valori restituiti?
Joey Adams,

@JoeyAdams: Lo fa davvero. Dopo aver valutato un thunk, l' copyoperatore copia il contenuto della versione valutata nell'originale, sovrascrivendo /typeed eventualmente impostando altri valori. Dopo ricorsivamente valutazione finché non avremo una nilo cons, anche (via undef) rimuove /funce, se del caso, /data. L'ultimo passaggio non è strettamente necessario ( /funce /dataverrebbe semplicemente ignorato), ma lasciare questo passaggio perderebbe ancora più memoria :)
balpha

6

C

Sono un principiante assoluto in C, questo codice è in realtà la prima cosa reale che ho codificato in C. Si compila senza alcun avviso e funziona bene sul mio sistema.

Come costruire

Per prima cosa, scarica il tarball dal mio server . Include un makefile, quindi corri makeper crearlo e poimake run eseguirlo. Il programma stampa quindi un elenco dei primi 93 numeri di fibonacci. (Dopo il numero 94, trabocca un numero intero a 64 bit senza segno)

Spiegazione

Il nucleo dei programmi è il file lazy-list.c. Nel file di intestazione corrispondente, definisco una struttura list, che è la nostra lista pigra. Sembra così:

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;
};

Il membro kindè una specie di tag. Indica, sia che abbiamo ricontrollato le liste end ( NIL), una cella che è già valutata ( CONS) o un thunk ( THUNK). Quindi, segue un'unione. È

  • una cella già valutata con un valore e una coda
  • o un thunk, con un puntatore a funzione e uno struct, che può contenere alcuni argomenti alla funzione, se necessario.

Il contenuto dell'unione è affermato dal tag. Se il tag èNIL , il contenuto dell'unione non è definito.

Definendo le funzioni di supporto menzionate nella specifica sopra, si può in genere estrarre la definizione delle liste dal suo utilizzo, ad es. puoi semplicemente chiamare nil()per ottenere un elenco vuoto invece di crearne uno da solo.

Le tre funzioni più interessanti sono zipWith, takee fibonaccis. Ma non voglio spiegare take, dal momento che è molto simile a zipWith. Tutte le funzioni, che operano pigramente, hanno tre componenti:

  • Un wrapper, che crea un thunk
  • Un lavoratore che esegue i calcoli per una cella
  • Una struttura che mantiene gli argomenti

In caso di zipWith, questi sono zipWith, __zipWithe __zipArgs. Li mostro qui senza ulteriori spiegazioni, la funzione dovrebbe essere abbastanza chiara:

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);
}

L'altra funzione interessante è fibonaccis(). Il problema è che dobbiamo passare un puntatore della prima e seconda cella al thunk della terza, ma per creare quelle celle abbiamo anche bisogno di un puntatore al thunk. Per risolvere il problema, ho riempito il puntatore del thunk con un NULLprimo e l'ho cambiato nel thunk, dopo che è stato creato. Ecco l'ascolto:

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;

Possibili miglioramenti

  • La mia soluzione non usa il polimorfismo. Sebbene possibilmente possibile, le mie abilità in C non sono sufficienti per sapere come usarlo. Invece, ho usato un tipo content_t, che si può cambiare in qualunque forma.
  • Si potrebbe estrarre il thunk dalla definizione dell'elenco e usarlo solo in modo astratto, ma farlo renderebbe il codice più complicato.
  • Si potrebbero migliorare le parti del mio codice che non sono buone C.

Bella presentazione, soprattutto per essere un primo timer C. Per quanto riguarda il polimorfismo, se sei disposto a allocare tutti i tuoi contenuti nell'heap, puoi usarlo void*come tipo di content_t.
Casey,

@ Casey: grazie mille. Ho pensato di usare void*anche io, ma ho pensato che avrebbe evitato troppo il sistema dei tipi. Non è possibile usare i template?
FUZxxl,

C non ha modelli, ovvero C ++, ma sì, potresti usare i modelli C ++ per renderlo generico.
Casey,

Non so come usarli. Ma immagino, è solo che C è un po 'limitato in termini di sistema di tipi. - Non ero nemmeno in grado di codificare questo programma senza usare void*e gli amici.
FUZxxl

1
"Il membro kindè una specie di tag." Potresti semplicemente chiamarlo tag, in quanto è un termine abbastanza accettato per il concetto (ad esempio unione con tag , G-machine Spinless Tagless . D'altra parte, "tipo" ha un significato diverso in un . Haskell contesto: il tipo di un tipo Intè tipo *, []ha genere * -> *, e (,)ha genere * -> * -> *.
Joey Adams

5

C ++

Questa è la cosa più grande che abbia mai scritto in C ++. Normalmente uso Objective-C.

È polimorfico ma non libera mai nulla.

La mia mainfunzione (e la addfunzione di ZipWith) è finita così:

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;
}

Questo da

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

Le lezioni funzionano così:

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

Fonte completa: qui . È un casino, principalmente perché è in un unico file di grandi dimensioni.

Modifica: cambiato il collegamento (quello vecchio era morto).


3
Ottimo lavoro, e grazie per aver preso letteralmente "un adattamento" :-) Non sono affatto un esperto di C ++, ma un modo più C ++ per implementare Thunk potrebbe essere quello di utilizzare un oggetto funzione (aka "functor") (che è, sovraccaricare l' ()operatore) e utilizzare l'ereditarietà per evitare di doverlo usare void*. Vedi qui per un banale esempio di farlo.
Joey Adams,

Il collegamento completo alla fonte è morto ora. Potresti ricaricarlo? gist.github.com è un buon posto per dirlo.
Joey Adams,

@JoeyAdams: fatto.
Marin

4

Pitone

Non utilizza generatori per implementare l'elenco, solo per implementare il __iter__metodo da utilizzare con 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)

L'elenco di Fibonacci è creato in questo modo:

>>> from lazylist import *
>>> fib = Node(0, Node(1, NullNode()))
>>> fib.append(fib.zip_with(lambda a, b: a + b, fib.tail()))
>>> 

1
Questo è bellissimo. La mia linea preferita è self.__class__ = node.__class__. Si noti che ciò genera un'eccezione NotImplemented quando arriva fino a 2971215073 (long), che apparentemente è un argomento non valido per int .__ add__. Per supportare i numeri interi grandi, faifib.append(fib.zip_with(lambda a,b: a+b, fib.tail()))
Joey Adams,

1
Perché non puoi aggiungere vuoto o thunk?
PyRulez,

4

Rubino

Il mio primo programma Ruby. Rappresentiamo tutti i nodi come array, in cui la lunghezza dell'array determina il tipo:

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

Il codice è quindi piuttosto semplice, con un hack per reimpostare la funzione thunk per impostare la fibra ricorsiva.

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

Puoi usare [...]invece di Array[...].
Lowjacker,

3

Google Go

Un linguaggio relativamente nuovo, e l'ho imparato CTRL+Finging the 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()))
}

Il problema è stato risolto, trattando il thunk-into-a-thunk. Tuttavia, sembra che il compilatore online non possa accettare 40 elementi, forse a causa della memoria. Lo proverò sul mio Linux più tardi.

[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

Ho testato il codice con il compilatore online , perché non riesco a installare Go su Windows facilmente.


Questo è abbastanza carino e semplice. Tuttavia, anziché 3 bool, è possibile utilizzare un singolo tag i cui valori possibili sono costanti generate dal generatore di iotacostanti. Vedi un esempio nella specifica del linguaggio di programmazione Go e una risposta su StackOverflow .
Joey Adams,

La tua Fibsfunzione non funziona perché Go utilizza una valutazione rigorosa e si Fibsricorre su se stesso senza una condizione di chiusura. Fibs0/ Fibs1utilizza un semplice approccio generatore piuttosto che l'algoritmo descritto nel mio post, quindi non soddisfa i "requisiti". Ho aggiornato il mio post per elaborare la ricorsione pigra, che è necessaria per implementare fibs = 0 : 1 : zipWith (+) fibs (tail fibs) .
Joey Adams,

Cons(0, Cons(1, ZipWith(Plus, Thunk(Fibs), Tail(Thunk(Fibs))))), si esaurisce la memoria
Ming-Tang

Ho provato Cons(0, Cons(1, Thunk(func() List { return ZipWith(Plus, Thunk(Fibs), Thunk(func() List { return Tail(Fibs()) })) })))e ho ricevuto un errore di indirizzo di memoria non valido
Ming-Tang

1
Dato che stai ancora imparando Go: puoi creare un codice molto più elegante di questo usando interfacce per gli elenchi e tipi separati per Thunks, ecc.
cthom06,

3

Cristallo

Nonostante abbia seguito il repository GitHub, non ho mai usato Crystal fino ad ora. Crystal è una variante Ruby tipicamente statica con inferenza di tipo completo. Anche se c'è già una risposta di Ruby, la tipizzazione statica di Crystal mi ha portato a usare il polimorfismo, piuttosto che un array, per rappresentare i nodi. Poiché Crystal non consente la modifica di self, ho creato una classe wrapper, denominata Node, che avvolgeva tutto il resto e gestiva i thunk.

Insieme con le classi, ho creato le funzioni di costruzione lnil, conse thunk. Non ho mai usato Ruby per più di una sceneggiatura di 20 righe prima d'ora, quindi la roba del blocco mi ha buttato via un po '.

Ho basato la fibfunzione sulla risposta Vai .

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

2

Ho piegato un po 'le regole perché non c'è ancora una soluzione .NET qui - o più in generale una soluzione OOP ad eccezione di quella in Python che utilizza l'ereditarietà, ma è abbastanza diversa dalla mia soluzione da rendere entrambe interessanti (in particolare da Python consente di modificare l' selfistanza, rendendo semplice l'implementazione thunk).

Quindi questo è C # . Divulgazione completa: non sono in nessun posto vicino a un principiante in C # ma non tocco la lingua da un po 'di tempo poiché al momento non ne ho più bisogno sul posto di lavoro.

I punti salienti:

  • Tutte le classi ( Nil, Cons, Thunk) derivano da una classe base astratta comune List.

  • La Thunkclasse utilizza il modello Envelope-Letter . Questo essenzialmente emula l' self.__class__ = node.__class__assegnazione nel sorgente Python, poiché il thisriferimento non può essere modificato in C #.

  • IsEmpty, HeadE Tailsono proprietà.

  • Tutte le funzioni appropriate sono implementate in modo ricorsivo e pigramente (tranne Printche, che non può essere pigro) restituendo thunk. Ad esempio, questo è Nil<T>.ZipWith:

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

    ... e questo è 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));
        });
    }
    

    Sfortunatamente, C # non ha spedizioni multiple, altrimenti potrei anche liberarmi della ifdichiarazione. Ahimè, niente dadi.

Ora, non sono davvero contento della mia implementazione. Sono felice finora perché tutto quanto sopra è totalmente semplice. Ma . Sento che la definizione di Fibè inutilmente complicata poiché devo avvolgere gli argomenti in thunk per farlo funzionare:

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

(Qui, List.Cons, List.Thunke List.ZipWithsono involucri di convenienza.)

Vorrei capire perché la seguente definizione molto più semplice non funziona:

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

data una definizione appropriata di Concat, ovviamente. Questo è essenzialmente ciò che fa il codice Python - ma non funziona (= lancio di un adattamento).

/ EDIT: Joey ha sottolineato l'evidente difetto di questa soluzione. Tuttavia, la sostituzione della seconda riga con un thunk produce anche un errore (Mono segfaults; sospetto un overflow dello stack che Mono non gestisce bene):

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

Il codice sorgente completo può essere trovato come sintesi su GitHub .


"Sfortunatamente, C # non ha un invio multiplo" - puoi ottenere l'effetto usando gli eventi, anche se è piuttosto confuso.
Peter Taylor,

L'aspetto ironico della valutazione pigra è che richiede uno stato da implementare. fib.ZipWithe fib.Tailusa il vecchio fib, che rimane [0,1]e non cambia. Quindi, ottieni [0,1,1](penso) e il tuoTake funzione non ti consente di prendere da null (comunque il take di Haskell ). Prova a racchiudere il valore della seconda riga in un thunk, quindi farà riferimento al nuovo fibanziché al vecchio.
Joey Adams,

@Peter Sì; puoi anche utilizzare il modello Visitatore per implementare la spedizione multipla, ma volevo che la soluzione rimanesse semplice.
Konrad Rudolph,

@Joey Duh. È accecantemente ovvio ora. Tuttavia, la soluzione thunk non funziona ancora (vedi la risposta aggiornata) ma ora sono troppo occupato per indagare.
Konrad Rudolph,

2

Pico

per la cronaca, questa soluzione utilizza una traduzione della forza di ritardo dello schema come definito in srfi-45 . e costruisce elenchi pigri su di esso.

{ 
` 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))

}

gli sguardi di output come questo: (ma a seconda di come tpico. è patchato potrebbe avere più doppi apici in esso display. stampe normalmente le stringhe con le virgolette vale a dire tutte le apparenze di [, ,, ]avrebbe citazioni intorno a loro come "[".)

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

a causa dei limiti del tipo di dati intero in tpico, questo non riesce a calcolare il 45 ° (o 46 ° offset) numero di Fibonacci.

si noti che tpico 2.0pl11 è interrotto in quello begin(a,b)(che è comunemente scritto come {a;b}) e la iffunzione non è ricorsiva di coda. per non parlare del fatto che mi ci sono voluti 5 anni per capire perché beginnon fosse ricorsivo alla coda. anche a quel tempo ho scritto la traduzione di srfi-45 in Pico. si è scoperto che beginera in attesa del valore di bprima di tornare quando non aveva bisogno di aspettare. e una volta ifcapito che sono stato anche in grado di risolvere in quanto aveva lo stesso problema. e c'è stato questo altro errore che ha reso makeinoperante il costruttore del meta livello .

Pico consente a una funzione di controllare se i suoi argomenti vengono valutati prima che la funzione venga chiamata o semplicemente impacchettata come thunk. per questo codice, posso usare i puntini di sospensione sulle stranezze della chiamata per funzione .

Pico non ha deduzioni di tipo. ci ho pensato per un po 'ma ho riscontrato un problema a causa delle stranezze della chiamata per funzione . ho trovato l'affermazione che i tipi devono codificare l'esistenza di nomi di variabili associati . ma pensavo principalmente a come adattare l'inferenza di tipo Hindley-Milner a un sottoinsieme di Pico senza mutazione. l'idea principale era che il controllo del tipo restituisce più schemi possibili se ci sono più di un possibile legame e il controllo del tipo ha esito positivo se esiste almeno un possibile schema di tipi . uno schema possibile è quello in cui nessun conflitto di assegnazione dei tipi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.