Terapkan daftar malas, lebih disukai dalam bahasa yang Anda tidak tahu [tertutup]


21

Ini adalah latihan yang bagus untuk menjadi lebih fasih dalam bahasa pemrograman yang ingin Anda pelajari, tetapi hanya sedikit mengutak-atiknya. Ini melibatkan bekerja dengan objek, menggunakan atau mensimulasikan penutupan, dan meregangkan sistem tipe.

Tugas Anda adalah menulis kode untuk mengelola daftar malas, lalu menggunakannya untuk mengimplementasikan algoritma ini untuk menghasilkan angka Fibonacci:

Sampel kode ada di Haskell

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

Hasil:

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

Implementasi daftar malas Anda harus memenuhi pedoman ini:

  • Node Daftar adalah salah satu dari tiga hal:
    • Nihil - Daftar kosong.
      []
    • Kontra - Satu item, dipasangkan dengan Daftar item yang tersisa:
      1 : [2,3,4,5]
      ( :adalah operator kontra di Haskell)
    • Thunk - Perhitungan tangguhan yang menghasilkan node Daftar saat diperlukan.
  • Ini mendukung operasi berikut:
    • nil - Buat daftar kosong.
    • kontra - Membangun sel kontra.
    • thunk - Construct a Thunk, diberi fungsi yang tidak membutuhkan argumen dan mengembalikan Nil atau Kontra.
    • force - Diberikan simpul Daftar:
      • Jika Nil atau Kontra, kembalikan saja.
      • Jika itu Thunk, panggil fungsinya untuk mendapatkan Nil atau Kontra. Ganti thunk dengan Nil atau Kontra itu, dan kembalikan.
        Catatan: Mengganti thunk dengan nilai paksa adalah bagian penting dari definisi "malas" . Jika langkah ini dilewati, algoritma Fibonacci di atas akan terlalu lambat.
    • kosong - Lihat apakah simpul Daftar adalah Nil (setelah memaksanya).
    • head (aka "car") - Dapatkan item pertama dari daftar (atau cocokkan jika nihil).
    • tail (alias "cdr") - Dapatkan elemen setelah kepala daftar (atau melempar jika itu nol).
    • zipWith - Diberikan fungsi biner (misalnya (+)) dan dua (mungkin tak terbatas) daftar, menerapkan fungsi tersebut ke item yang sesuai dari daftar. Contoh:
      zipWith (+) [1,2,3] [1,1,10] == [2,3,13]
    • take - Diberikan nomor N dan daftar (mungkin tak terbatas), ambil item N pertama dari daftar.
    • print - Cetak semua item dalam daftar. Ini harus bekerja secara bertahap ketika diberi daftar panjang atau tidak terbatas.
  • fibsmenggunakan dirinya sendiri dalam definisi sendiri. Menyiapkan rekursi malas agak rumit; Anda harus melakukan sesuatu seperti ini:

    • Alokasikan thunk untuk fibs. Biarkan dalam kondisi dummy untuk saat ini.
    • Tentukan fungsi thunk, yang tergantung pada referensi ke fibs.
    • Perbarui thunk dengan fungsinya.

    Anda mungkin ingin menyembunyikan pipa ledeng ini dengan mendefinisikan fungsi fixyang memanggil fungsi Daftar-kembali dengan nilai baliknya sendiri. Pertimbangkan untuk tidur sebentar agar ide ini bisa masuk.

  • Polimorfisme (kemampuan untuk bekerja dengan daftar jenis barang apa pun) tidak diperlukan, tetapi lihat apakah Anda dapat menemukan cara untuk melakukannya yang idiomatis dalam bahasa Anda.

  • Jangan khawatir tentang manajemen memori. Bahkan bahasa dengan pengumpulan sampah memiliki kecenderungan untuk membawa benda-benda yang tidak akan pernah Anda gunakan lagi (misalnya pada tumpukan panggilan), jadi jangan heran jika program Anda membocorkan memori saat melintasi daftar yang tak terbatas.

Jangan ragu untuk menyimpang sedikit dari pedoman ini untuk mengakomodasi rincian bahasa Anda, atau untuk mengeksplorasi pendekatan alternatif untuk daftar malas.

Aturan:

  • Pilih bahasa yang tidak Anda kenal dengan baik. Saya tidak bisa "meminta" ini, karena itu tag "sistem kehormatan". Namun, pemilih dapat memeriksa riwayat Anda untuk melihat bahasa apa yang Anda posting.
  • Jangan gunakan dukungan daftar malas bawaan bahasa Anda untuk melakukan semuanya. Posting sesuatu yang substansial atau setidaknya menarik.

    • Haskell cukup banyak keluar. Yaitu, kecuali Anda melakukan sesuatu seperti ini:

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

      Catatan: Evaluasi Haskell yang tidak ketat tidak terlarang, tetapi implementasi daftar malas Anda tidak seharusnya menurunkan kemampuannya langsung dari sana. Bahkan, akan menarik untuk melihat solusi yang efisien, murni fungsional yang tidak memerlukan kemalasan.

    • Python:

      • Jangan gunakan itertools.
      • Generator baik-baik saja, tetapi Anda menggunakannya, Anda harus menemukan beberapa cara untuk menghafal nilai yang dipaksakan.

Apa yang seharusnya terjadi ketika memanggil zipWithdua daftar dengan panjang yang berbeda?
balpha

@balpha: Saya memilih perilaku Haskells: Jika salah satu dari daftar adalah nol, kembalikan nil.
FUZxxl

@balpha: Di Haskell, zipWith berhenti ketika salah satu daftar kehabisan item. Karenanya zipWith (+) [1,2,3,4,5] [0,0,0] == [1,2,3],. Namun, ini tidak masalah untuk algoritma Fibonacci di atas, karena kedua argumen untuk zipWith adalah daftar tanpa batas.
Joey Adams

Tantangan ini memiliki kejutan tersembunyi di dalamnya: Anda perlu melakukan sesuatu yang khusus untuk diterapkan fibsdengan benar, karena itu tergantung pada dirinya sendiri. Saya memperbarui pertanyaan untuk menguraikan rekursi malas. FUZxxl menemukan jawabannya sendiri.
Joey Adams

Apa yang Anda maksud dengan "bekerja secara bertahap" ketika Anda mencetak daftar besar?
Lowjacker

Jawaban:


6

Nota bene

Saya sudah pernah bermain dengan PostScript sebelumnya , tetapi saya tidak akan mengatakan bahwa saya mengetahuinya dengan sangat baik (pada kenyataannya, tebakan saya adalah Anda dapat menghitung jumlah orang di dunia yang benar-benar tahu PostScript menggunakan satu tangan).

Saya menyimpang dari spek Anda bahwa fungsi yang digunakan untuk membuat thunk diizinkan untuk mengembalikan thunk lain; forceakan terus mengevaluasi sampai hasilnya adalah a nilatau a cons.

Daftar diimplementasikan sebagai kamus:

<< /type /nil >>

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

<< /type /thunk
   /func evaluationFunction >>

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

Kode berikut. Perhatikan bahwa kami menimpa beberapa operator builtin (khususnya print; Saya belum memeriksa jika ada lebih banyak); dalam penggunaan dunia nyata, ini harus diperhatikan. Tentu saja tidak akan ada penggunaan dunia nyata, jadi itu tidak masalah.

Komentar sebelum prosedur harus dibaca sebagai

% before2 before1 before0  <| procedure |>  after1 after0

yaitu menunjukkan isi tumpukan yang diharapkan sebelum panggilan dan isi tumpukan yang dihasilkan setelah panggilan. Komentar dalam prosedur menunjukkan konten tumpukan setelah baris tertentu dieksekusi.

% 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

Muat ini ke Ghostscript, abaikan halaman yang ditampilkan - kami hanya bekerja dengan penerjemah. Inilah algoritma 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>

Dua fungsi menarik tambahan:

% 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

Mulai menghitung pada 5, kalikan setiap elemen dari daftar yang dihasilkan dengan 3, dan tampilkan sepuluh nilai pertama:

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

Mengenai polimorfisme: Meskipun PostScript diketik dengan kuat, ini memungkinkan jenis yang sewenang-wenang sebagai nilai kamus, sehingga Anda dapat memasukkan apa pun yang Anda suka:

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>

Perhatikan bahwa kesalahan ketik, misalnya dari mencoba menambahkan string ke angka, hanya akan terjadi pada waktu evaluasi:

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

Luar biasa. (Bagaimana) apakah forcememoize mengembalikan nilai?
Joey Adams

@ JoeyAdams: Memang. Setelah mengevaluasi suatu kesalahan, copyoperator menyalin konten dari versi yang dievaluasi ke dalam dokumen asli, menimpa /typedan mungkin menetapkan nilai-nilai lain. Setelah mengevaluasi secara rekursif sampai kami memiliki nilatau cons, itu juga (melalui undef) menghapus /funcdan, jika berlaku /data,. Langkah terakhir tidak sepenuhnya diperlukan ( /funcdan /datahanya akan diabaikan), tetapi meninggalkan langkah ini akan membocorkan lebih banyak memori :)
balpha

6

C

Saya seorang pemula total dalam C, kode ini sebenarnya adalah hal nyata pertama yang saya kodekan dalam C. Ini mengkompilasi tanpa peringatan dan berjalan dengan baik pada sistem saya.

Cara membangun

Pertama, dapatkan tarball dari server saya . Ini termasuk makefile, jadi jalankan saja makeuntuk membangunnya dan kemudian make runjalankan. Program kemudian mencetak daftar dari 93 angka fibonacci pertama. (Setelah angka 94, bilangan bulat 64 bit yang tidak ditandatangani)

Penjelasan

Inti program adalah file lazy-list.c. Dalam file header yang sesuai, saya mendefinisikan struct list, itu adalah daftar malas kami. Ini terlihat seperti ini:

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

Anggota kindsemacam tag. Itu menandai, apakah kita mengisi ulang daftar akhir ( NIL), sel yang sudah dievaluasi ( CONS) atau thunk ( THUNK). Kemudian, ada yang mengikuti persatuan. ini

  • baik sel yang sudah dievaluasi dengan nilai dan ekor
  • atau thunk, menampilkan function-pointer dan struct, yang mungkin berisi beberapa argumen untuk fungsi tersebut, jika diperlukan.

Konten serikat dinyatakan oleh tag. Jika tagnya adalah NIL, konten serikat tidak ditentukan.

Dengan mendefinisikan fungsi pembantu yang disebutkan dalam spesifikasi di atas, orang biasanya dapat mengabstraksi definisi daftar dari penggunaannya, misalnya. Anda cukup menelepon nil()untuk mendapatkan daftar kosong alih-alih membuatnya sendiri.

Tiga fungsi yang paling menarik adalah zipWith, takedan fibonaccis. Tetapi saya tidak ingin menjelaskan take, karena sangat mirip zipWith. Semua fungsi, yang beroperasi dengan malas, memiliki tiga komponen:

  • Pembungkus, yang menciptakan thunk
  • Seorang pekerja, yang melakukan perhitungan untuk satu sel
  • Sebuah struct, yang menyimpan argumen

Dalam hal zipWithini zipWith, __zipWithdan __zipArgs. Saya hanya menunjukkannya di sini tanpa penjelasan lebih lanjut, fungsinya harus cukup jelas:

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

Fungsi menarik lainnya adalah fibonaccis(). Masalahnya adalah, kita perlu melewatkan pointer dari sel pertama dan kedua ke thunk ketiga, tetapi untuk membuat sel-sel itu kita juga perlu pointer ke thunk. Untuk mengatasi masalah itu, saya mengisi pointer ke thunk dengan yang NULLpertama dan mengubahnya menjadi thunk, setelah itu dibuat. Berikut adalah pendengarannya:

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;

Kemungkinan peningkatan

  • Solusi saya tidak menggunakan polimorfisme. Meskipun mungkin, keterampilan C saya tidak cukup untuk tahu bagaimana menggunakannya. Sebagai gantinya, saya menggunakan jenis content_t, yang dapat diubah ke apa pun yang cocok.
  • Seseorang dapat mengekstraksi thunk keluar dari definisi daftar dan menggunakannya hanya secara abstrak, tetapi melakukan hal itu akan membuat kode lebih rumit.
  • Seseorang dapat memperbaiki bagian-bagian dari kode saya yang tidak baik C.

Pengajuan yang bagus, terutama untuk menjadi C-timer pertama. Mengenai polimorfisme, jika Anda bersedia mengalokasikan semua konten Anda di heap, Anda dapat menggunakan void*sebagai tipe content_t.
Casey

@Casey: Terima kasih banyak. Saya berpikir untuk menggunakan void*juga, tapi saya pikir itu akan menghindari sistem tipe terlalu jauh. Apakah itu tidak mungkin menggunakan templat?
FUZxxl

C tidak memiliki templat, yaitu C ++, tapi ya Anda bisa menggunakan templat C ++ untuk membuatnya menjadi umum.
Casey

Saya tidak tahu cara menggunakannya. Tapi saya kira, hanya saja C agak terbatas dalam hal sistem hurufnya. - Saya bahkan tidak bisa membuat kode program ini tanpa menggunakan void*dan teman-teman.
FUZxxl

1
“Anggota The kindadalah jenis tag.” Anda hanya bisa menyebutnya tag, karena itu istilah yang cukup diterima untuk konsep (misalnya tagged union , bertulang tagless G-mesin . Di sisi lain, "semacam" memiliki arti yang berbeda dalam Konteks Haskell: tipe dari suatu tipe. IntMemiliki jenis *, []jenis * -> *, dan (,)jenis * -> * -> *.
Joey Adams

5

C ++

Ini adalah hal terbesar yang pernah saya tulis dalam C ++. Saya biasanya menggunakan Objective-C.

Ini polimorfik tetapi tidak pernah membebaskan apa pun.

mainFungsi saya (dan addfungsi untuk ZipWith) akhirnya terlihat seperti ini:

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

Ini memberi

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

Kelas bekerja seperti ini:

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

Sumber lengkap: di sini . Ini berantakan, terutama karena itu dalam satu file besar.

Sunting: mengubah tautan (yang lama sudah mati).


3
Kerja luar biasa, dan terima kasih telah menerima "lempar pas" secara harfiah :-) Saya sama sekali bukan pakar C ++, tetapi cara yang lebih C ++ - y untuk mengimplementasikan Thunk mungkin dengan menggunakan objek fungsi (alias "functor") (yang adalah, membebani ()operator), dan menggunakan warisan untuk menghindari keharusan menggunakan void*. Lihat di sini untuk contoh sepele dari melakukan itu.
Joey Adams

Tautan sumber lengkap sudah mati sekarang. Bisakah Anda mengunggahnya kembali? gist.github.com adalah tempat yang bagus untuk meletakkannya.
Joey Adams

@ JoeyAdams: selesai.
marinus

4

Python

Tidak menggunakan generator untuk mengimplementasikan daftar, hanya untuk mengimplementasikan __iter__metode yang digunakan 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)

Daftar Fibonacci dibuat seperti ini:

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

1
Ini indah. Baris favorit saya adalah self.__class__ = node.__class__. Perhatikan bahwa ini mencapai pengecualian NotImplemented ketika mencapai hingga 2971215073 (panjang), yang tampaknya merupakan argumen yang tidak valid untuk int .__ add__. Untuk mendukung bilangan bulat besar, lakukanfib.append(fib.zip_with(lambda a,b: a+b, fib.tail()))
Joey Adams

1
Mengapa Anda tidak dapat menambahkannya ke tempat kosong atau terpukul?
PyRulez

4

Rubi

Program Ruby pertama saya. Kami mewakili semua node sebagai array, di mana panjang array menentukan jenisnya:

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

Kode ini kemudian cukup mudah, dengan hack untuk mengatur ulang fungsi thunk untuk mengatur rekursif.

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

Anda bisa menggunakan [...]bukan Array[...].
Lowjacker

3

Google Go

Bahasa yang relatif baru, dan saya mempelajarinya dengan CTRL+Fmenggunakan 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()))
}

Masalahnya telah diperbaiki, dengan berurusan dengan thunk-in-a-thunks. Namun, tampaknya kompiler online tidak dapat mengambil 40 elemen, mungkin karena memori. Saya akan mengujinya di Linux saya nanti.

[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

Saya menguji kode dengan kompiler online , karena saya tidak dapat menginstal Go di Windows dengan mudah.


Ini cukup bagus dan sederhana. Namun, alih-alih 3 bools, Anda bisa menggunakan tag tunggal yang nilainya mungkin adalah konstanta yang dihasilkan oleh iotagenerator konstan. Lihat contoh di Spesifikasi Bahasa Pemrograman Go , dan jawaban di StackOverflow .
Joey Adams

Anda Fibsfungsi tidak bekerja karena Go menggunakan evaluasi yang ketat, dan Fibsrecurses pada dirinya sendiri tanpa kondisi terminating. Fibs0Saya Fibs1menggunakan pendekatan generator sederhana daripada algoritma yang dijelaskan dalam posting saya, sehingga tidak memenuhi "persyaratan". Saya memperbarui posting saya untuk menguraikan rekursi malas, yang diperlukan untuk mengimplementasikan fibs = 0 : 1 : zipWith (+) fibs (tail fibs) .
Joey Adams

Cons(0, Cons(1, ZipWith(Plus, Thunk(Fibs), Tail(Thunk(Fibs))))), itu hilang dari ingatan
Ming-Tang

Saya mencoba Cons(0, Cons(1, Thunk(func() List { return ZipWith(Plus, Thunk(Fibs), Thunk(func() List { return Tail(Fibs()) })) })))dan saya mendapatkan kesalahan alamat memori yang tidak valid
Ming-Tang

1
Karena Anda masih mempelajari Go: Anda dapat membuat beberapa kode yang jauh lebih elegan daripada ini menggunakan antarmuka untuk Daftar dan tipe terpisah untuk Thunks, dll.
cthom06

3

Kristal

Meskipun mengikuti repositori GitHub, saya belum pernah menggunakan Crystal sampai sekarang. Crystal adalah varian Ruby yang diketik secara statis dengan inferensi tipe penuh. Meskipun sudah ada jawaban Ruby, pengetikan statis Crystal membuat saya menggunakan polimorfisme, dan bukan array, untuk mewakili node. Karena Crystal tidak mengizinkan modifikasi self, saya membuat kelas pembungkus, bernama Node, yang akan membungkus segala sesuatu yang lain dan mengelola pemukul.

Seiring dengan kelas, saya menciptakan fungsi konstruktor lnil, consdan thunk. Saya juga tidak pernah menggunakan Ruby lebih dari 20 script sebelumnya, jadi hal-hal yang menghalangi saya cukup sedikit.

Saya mendasarkan fibfungsi dari jawaban Go .

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

Saya membengkokkan aturan sedikit karena belum ada solusi .NET di sini - atau lebih umum solusi OOP kecuali untuk yang di Python yang menggunakan warisan, tapi itu cukup berbeda dari solusi saya untuk membuat keduanya menarik (khususnya sejak Python memungkinkan untuk memodifikasi selfinstance, membuat implementasi thunk secara langsung).

Jadi ini C # . Pengungkapan penuh: Saya tidak berada di dekat seorang pemula di C # tetapi saya belum menyentuh bahasa dalam beberapa saat karena saya saat ini tidak menggunakannya di tempat kerja.

Poin yang menonjol:

  • Semua kelas ( Nil, Cons, Thunk) berasal dari kelas dasar umum abstrak, List.

  • The Thunkkelas menggunakan Amplop-Surat pola. Ini pada dasarnya mengemulasi self.__class__ = node.__class__tugas dalam sumber Python, karena thisreferensi tidak dapat dimodifikasi dalam C #.

  • IsEmpty, Headdan Tailsifat.

  • Semua fungsi yang sesuai diimplementasikan secara rekursif dan malas (kecuali untuk Print, yang tidak bisa malas) dengan mengembalikan thunks. Sebagai contoh, ini adalah Nil<T>.ZipWith:

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

    ... dan ini adalah 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));
        });
    }
    

    Sayangnya, C # tidak memiliki banyak pengiriman, kalau tidak saya juga bisa menghilangkan ifpernyataan itu. Sayangnya, tidak ada dadu.

Sekarang, saya tidak begitu senang dengan implementasi saya. Saya senang sejauh ini karena semua hal di atas benar-benar mudah. Tapi . Saya merasa bahwa definisi Fibrumit tidak perlu karena saya perlu membungkus argumen menjadi thunks untuk membuatnya bekerja:

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

(Di sini, List.Cons, List.Thunkdan List.ZipWithadalah kemudahan pembungkus.)

Saya ingin memahami mengapa definisi yang jauh lebih mudah berikut tidak berfungsi:

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

diberikan definisi yang tepat Concat, tentu saja. Ini pada dasarnya adalah apa yang dilakukan kode Python - tetapi tidak berfungsi (= melempar kecocokan).

/ EDIT: Joey telah menunjukkan kelemahan yang jelas dalam solusi ini. Namun, mengganti baris kedua dengan thunk juga menghasilkan kesalahan (Mono segfaults; Saya curiga stack overflow yang tidak ditangani dengan baik oleh Mono):

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

Kode sumber lengkap dapat ditemukan sebagai inti di GitHub .


"Sayangnya, C # tidak memiliki banyak pengiriman" - Anda bisa mendapatkan efek menggunakan peristiwa, meskipun itu agak gila.
Peter Taylor

Hal yang ironis tentang evaluasi malas adalah bahwa hal itu membutuhkan negara untuk diimplementasikan. fib.ZipWithdan fib.Tailgunakan yang lama fib, yang tetap [0,1]dan tidak berubah. Dengan demikian, Anda mendapatkan [0,1,1](saya pikir), dan Takefungsi Anda tidak membiarkan Anda mengambil dari nol ( take Haskell tidak, meskipun). Cobalah membungkus nilai baris kedua dalam sebuah pukulan, jadi itu akan merujuk pada yang baru fibdaripada yang lama.
Joey Adams

@ Peter Ya; Anda juga dapat menggunakan pola Pengunjung untuk menerapkan beberapa pengiriman tetapi saya ingin solusi tetap sederhana.
Konrad Rudolph

@ Joey Duh. Jelas sangat menyilaukan sekarang. Namun, solusi thunk masih tidak berfungsi (lihat jawaban yang diperbarui) tetapi saya terlalu sibuk sekarang untuk menyelidiki.
Konrad Rudolph

2

Pico

sebagai catatan, solusi ini menggunakan terjemahan gaya tunda skema sebagaimana didefinisikan dalam srfi-45 . dan membangun daftar malas di atas itu.

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

}

output terlihat seperti ini: (tapi tergantung pada bagaimana tpico. ditambal mungkin memiliki tanda kutip lebih ganda di dalamnya display. biasanya mencetak string dengan tanda kutip yaitu semua penampilan dari [, ,, ]akan memiliki kutipan di sekitar mereka seperti "[".)

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

karena batas-batas dari tipe data integer dalam tpico ini gagal menghitung angka Fibonacci 45 (atau offset ke-46).

perhatikan bahwa tpico 2.0pl11 rusak dalam hal itu begin(a,b)(yang biasanya ditulis sebagai {a;b}) dan iffungsinya tidak rekursif ekor. belum lagi bahwa saya butuh 5 tahun untuk mencari tahu mengapa begintidak ekor rekursif. juga pada waktu itu saya menulis terjemahan srfi-45 di Pico. ternyata yang beginmenunggu nilainya bsebelum kembali ketika tidak perlu menunggu. dan begitu saya mendapatkannya saya juga bisa memperbaiki ifkarena memiliki masalah yang sama. dan ada kesalahan lain ini yang membuat konstruktor meta level maketidak beroperasi.

Pico memungkinkan kontrol fungsi jika argumennya dievaluasi sebelum fungsi dipanggil atau hanya dikemas sebagai thunks. untuk kode ini, saya dapat ellipsis atas keanehan panggilan dengan fungsi .

Pico tidak memiliki tipe inferensi. Saya berpikir tentang ini untuk sementara waktu tetapi saya berlari ke masalah karena keanehan panggilan dengan fungsi . saya datang dengan pernyataan bahwa jenis harus menyandikan keberadaan nama variabel terikat . tetapi saya terutama berpikir tentang bagaimana menyesuaikan inferensi tipe Hindley-Milner ke subset dari Pico tanpa mutasi. ide utamanya adalah bahwa pemeriksa tipe mengembalikan beberapa skema yang mungkin jika ada lebih dari satu kemungkinan pengikatan dan tipe check berhasil jika setidaknya ada satu skema tipe yang mungkin . skema yang mungkin adalah skema yang tidak ada jenis konflik tugas.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.