Thực hiện các danh sách lười biếng, tốt nhất là bằng ngôn ngữ mà bạn không biết rõ [đã đóng]


21

Đây là một bài tập hay để trở nên thông thạo ngôn ngữ lập trình mà bạn muốn học, nhưng chỉ biết mày mò một cách nhẹ nhàng. Điều này liên quan đến việc làm việc với các đối tượng, sử dụng hoặc mô phỏng các lần đóng và kéo dài hệ thống loại.

Nhiệm vụ của bạn là viết mã để quản lý các danh sách lười biếng, sau đó sử dụng nó để thực hiện thuật toán này để tạo các số Fibonacci:

Mẫu mã có trong Haskell

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

Kết quả:

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

Việc thực hiện danh sách lười biếng của bạn phải đáp ứng các nguyên tắc sau:

  • Nút List là một trong ba điều sau:
    • Nil - Danh sách trống.
      []
    • Nhược điểm - Một mục duy nhất, được ghép nối với Danh sách các mục còn lại:
      1 : [2,3,4,5]
      ( :là toán tử khuyết điểm trong Haskell)
    • Thunk - Một tính toán hoãn lại tạo ra một nút List khi cần thiết.
  • Nó hỗ trợ các hoạt động sau:
    • nil - Xây dựng một danh sách trống.
    • Nhược điểm - Xây dựng một tế bào khuyết điểm.
    • thunk - Xây dựng Thunk, được cung cấp một hàm không có đối số và trả về Nil hoặc Cons.
    • lực lượng - Cho một nút Danh sách:
      • Nếu đó là Nil hoặc Cons, chỉ cần trả lại.
      • Nếu đó là Thunk, hãy gọi chức năng của nó để nhận Nil hoặc Cons. Thay thunk bằng Nil hoặc Cons đó và trả lại.
        Lưu ý: Thay thế thunk bằng giá trị bắt buộc của nó là một phần quan trọng trong định nghĩa "lười biếng" . Nếu bước này bị bỏ qua, thuật toán Fibonacci ở trên sẽ quá chậm.
    • trống rỗng - Xem nếu một nút Danh sách là Nil (sau khi buộc nó).
    • head (hay còn gọi là "xe hơi") - Nhận mục đầu tiên của danh sách (hoặc ném phù hợp nếu đó là Nil).
    • đuôi (còn gọi là "cdr") - Nhận các phần tử sau phần đầu của danh sách (hoặc ném phù hợp nếu nó không).
    • zipWith - Đưa ra một hàm nhị phân (ví dụ (+)) và hai danh sách (có thể là vô hạn), áp dụng hàm cho các mục tương ứng của danh sách. Thí dụ:
      zipWith (+) [1,2,3] [1,1,10] == [2,3,13]
    • mất - Cho một số N và một danh sách (có thể là vô hạn), lấy N mục đầu tiên của danh sách.
    • in - In tất cả các mục trong một danh sách. Điều này sẽ làm việc tăng dần khi được đưa ra một danh sách dài hoặc vô hạn.
  • fibssử dụng chính nó trong định nghĩa riêng của nó. Thiết lập đệ quy lười biếng là một mẹo khó khăn; bạn sẽ cần phải làm một cái gì đó như thế này:

    • Phân bổ một thunk cho fibs. Để nó trong một trạng thái giả bây giờ.
    • Xác định hàm thunk, phụ thuộc vào tham chiếu đến fibs.
    • Cập nhật thunk với chức năng của nó.

    Bạn có thể muốn ẩn hệ thống ống nước này bằng cách xác định hàm fixgọi hàm trả về Danh sách với giá trị trả về của chính nó. Cân nhắc ngủ trưa ngắn để ý tưởng này có thể được thiết lập.

  • Không đa hình (khả năng làm việc với danh sách của bất kỳ loại mặt hàng nào) là không bắt buộc, nhưng hãy xem liệu bạn có thể tìm ra cách để làm điều đó thành ngữ trong ngôn ngữ của bạn không.

  • Đừng lo lắng về việc quản lý bộ nhớ. Ngay cả các ngôn ngữ có bộ sưu tập rác cũng có xu hướng mang theo các vật thể bạn sẽ không bao giờ sử dụng lại (ví dụ: trên ngăn xếp cuộc gọi), vì vậy đừng ngạc nhiên nếu chương trình của bạn bị rò rỉ bộ nhớ trong khi duyệt qua danh sách vô hạn.

Vui lòng đi chệch một chút so với các hướng dẫn này để phù hợp với các chi tiết của ngôn ngữ của bạn hoặc để khám phá các phương pháp tiếp cận thay thế cho danh sách lười biếng.

Quy tắc:

  • Chọn một ngôn ngữ bạn không biết rõ. Tôi không thể "yêu cầu" cái này, do đó thẻ "hệ thống danh dự". Tuy nhiên, cử tri có thể kiểm tra lịch sử của bạn để xem những ngôn ngữ bạn đã đăng.
  • Đừng sử dụng hỗ trợ danh sách lười biếng tích hợp sẵn trong ngôn ngữ của bạn để làm mọi thứ. Đăng một cái gì đó đáng kể hoặc ít nhất là thú vị.

    • Haskell là khá nhiều ra. Đó là, trừ khi bạn làm một cái gì đó như thế này:

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

      Lưu ý: Đánh giá không nghiêm ngặt của Haskell không vượt quá giới hạn, nhưng việc thực hiện danh sách lười biếng của bạn không nên lấy khả năng của nó trực tiếp từ đó. Trên thực tế, thật thú vị khi thấy một giải pháp hiệu quả, hoàn toàn chức năng không đòi hỏi sự lười biếng.

    • Con trăn:

      • Đừng sử dụng itertools.
      • Máy phát điện vẫn ổn, nhưng bạn sử dụng chúng, bạn sẽ phải tìm cách ghi nhớ các giá trị bắt buộc.

Hành vi nên làm gì khi gọi zipWithhai danh sách có độ dài khác nhau?
balpha

@balpha: Tôi đã chọn hành vi của Haskells: Nếu một trong hai danh sách là con số không, hãy trả về con số không.
FUZxxl

@balpha: Trong Haskell, zipWith dừng khi một trong hai danh sách hết hàng. Do đó , zipWith (+) [1,2,3,4,5] [0,0,0] == [1,2,3]. Tuy nhiên, điều này không quan trọng đối với thuật toán Fibonacci ở trên, vì cả hai đối số cho zipWith đều là danh sách vô hạn.
Joey Adams

Thử thách này có một bất ngờ tiềm ẩn trong đó: bạn cần phải làm một điều gì đó đặc biệt để thực hiện fibschính xác, vì nó phụ thuộc vào chính nó. Tôi cập nhật câu hỏi để giải thích về đệ quy lười biếng. FUZxxl đã tìm ra nó bởi anh ấy / cô ấy / cô ấy.
Joey Adams

Bạn có ý nghĩa gì khi "làm việc tăng dần" khi bạn in một danh sách lớn?
Lowjacker

Câu trả lời:


6

PostScript

Tôi đã từng chơi với PostScript trước đây , nhưng tôi không nói là tôi biết nó đặc biệt rõ (thực tế, tôi đoán là bạn có thể đếm số người trên thế giới thực sự biết PostScript bằng một tay).

Tôi đã đi chệch khỏi thông số kỹ thuật của bạn rằng chức năng được sử dụng để tạo ra một thunk được phép trả lại một thunk khác; forcesẽ tiếp tục đánh giá cho đến khi kết quả là a nilhoặc a cons.

Các danh sách được thực hiện dưới dạng từ điển:

<< /type /nil >>

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

<< /type /thunk
   /func evaluationFunction >>

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

Các mã sau. Lưu ý rằng chúng tôi ghi đè một số toán tử dựng sẵn (đặc biệt print; tôi chưa kiểm tra nếu có nhiều hơn); trong sử dụng thực tế, điều này sẽ phải được theo dõi cho. Tất nhiên sẽ không có sử dụng trong thế giới thực, vì vậy điều đó tốt.

Các ý kiến ​​trước khi các thủ tục được đọc là

% before2 before1 before0  <| procedure |>  after1 after0

tức là hiển thị nội dung ngăn xếp dự kiến ​​trước cuộc gọi và kết quả nội dung ngăn xếp sau cuộc gọi. Các ý kiến ​​trong các thủ tục hiển thị nội dung của ngăn xếp sau khi dòng cụ thể đã được thực thi.

% 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

Tải phần này vào Ghostscript, bỏ qua trang được hiển thị - chúng tôi chỉ làm việc với trình thông dịch. Đây là thuật toán 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>

Hai chức năng thú vị khác:

% 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

Bắt đầu đếm ở mức 5, nhân từng phần tử của danh sách kết quả với 3 và hiển thị mười giá trị đầu tiên:

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

Về đa hình: Mặc dù PostScript được gõ mạnh, nó cho phép các loại tùy ý làm giá trị từ điển, vì vậy bạn có thể ném vào bất cứ thứ gì bạn thích:

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>

Lưu ý rằng lỗi loại, ví dụ như từ việc cố gắng thêm chuỗi vào số, sẽ chỉ xảy ra tại thời điểm đánh giá:

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

Kinh ngạc. (Làm thế nào) để forceghi nhớ các giá trị trả về?
Joey Adams

@JoeyAdams: Nó thực sự. Sau khi đánh giá một thunk, copytoán tử sao chép nội dung của phiên bản được đánh giá thành bản gốc, ghi đè /typevà có thể đặt các giá trị khác. Sau khi đánh giá đệ quy cho đến khi chúng tôi có nilhoặc cons, nó cũng (thông qua undef) loại bỏ /funcvà, nếu có thể , /data. Bước cuối cùng không thực sự cần thiết ( /func/datasẽ bị bỏ qua), nhưng bỏ qua bước này sẽ làm rò rỉ thêm bộ nhớ :)
balpha

6

C

Tôi là người mới bắt đầu hoàn toàn bằng C, mã này thực sự là thứ thực sự đầu tiên tôi mã hóa trong C. Nó biên dịch mà không có bất kỳ cảnh báo nào và chạy tốt trên hệ thống của tôi.

Làm thế nào để xây dựng

Đầu tiên, lấy tarball từ máy chủ của tôi . Nó bao gồm một maketệp tạo tệp , vì vậy chỉ cần chạy để xây dựng nó và sau đó make runchạy nó. Chương trình sau đó in ra một danh sách 93 số đầu tiên. (Sau số 94, số nguyên 64 bit không dấu) tràn ra)

Giải trình

Lõi chương trình là tập tin lazy-list.c. Trong tệp tiêu đề tương ứng, tôi xác định một cấu trúc list, đó là danh sách lười biếng của chúng tôi. Nó trông như thế này:

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

Các thành viên kindlà một loại thẻ. Nó đánh dấu, cho dù chúng tôi đã kiểm tra lại danh sách end ( NIL), một ô đã được đánh giá ( CONS) hoặc thunk ( THUNK). Sau đó, có một liên minh. Nó là

  • hoặc một ô đã được đánh giá có giá trị và đuôi
  • hoặc một thunk, có con trỏ hàm và cấu trúc, có thể chứa một số đối số cho hàm, nếu cần.

Nội dung của liên minh được khẳng định bằng thẻ. Nếu thẻ là NIL, nội dung của liên minh là không xác định.

Bằng cách xác định các hàm trợ giúp được đề cập trong đặc tả ở trên, người ta thường có thể trừu tượng hóa định nghĩa danh sách từ việc sử dụng nó, ví dụ. bạn có thể chỉ cần gọi nil()để có được một danh sách trống thay vì tự tạo một danh sách.

Ba chức năng thú vị nhất là zipWith, takefibonaccis. Nhưng tôi không muốn giải thích take, vì nó rất giống với zipWith. Tất cả các chức năng, hoạt động lười biếng, có ba thành phần:

  • Một bọc, mà tạo ra một thunk
  • Một công nhân, thực hiện các tính toán cho một ô
  • Một cấu trúc, giữ các đối số

Trong trường hợp zipWith, đây là zipWith, __zipWith__zipArgs. Tôi chỉ hiển thị chúng ở đây mà không có bất kỳ lời giải thích nào thêm, có chức năng nên khá rõ ràng:

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

Các chức năng thú vị khác là fibonaccis(). Vấn đề là, chúng ta cần truyền một con trỏ của ô thứ nhất và thứ hai đến thunk của ô thứ ba, nhưng để tạo ra các ô đó, chúng ta cũng cần một con trỏ đến thunk. Để giải quyết vấn đề đó, tôi đã điền con trỏ vào thunk NULLtrước và thay đổi nó thành thunk, sau khi nó được tạo. Đây là phần nghe:

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;

Cải tiến có thể

  • Giải pháp của tôi không sử dụng đa hình. Mặc dù có thể, kỹ năng C của tôi không đủ để biết cách sử dụng nó. Thay vào đó, tôi đã sử dụng một loại content_t, người ta có thể thay đổi thành bất cứ điều gì phù hợp.
  • Người ta có thể trích xuất thunk ra khỏi định nghĩa của danh sách và chỉ sử dụng nó một cách trừu tượng, nhưng làm như vậy sẽ làm cho mã phức tạp hơn.
  • Người ta có thể cải thiện các phần trong mã của tôi không tốt C.

Trình tốt đẹp, đặc biệt là một C hẹn giờ đầu tiên. Về đa hình, nếu bạn sẵn sàng phân bổ tất cả nội dung của mình trên heap, bạn có thể sử dụng void*làm loại content_t.
Casey

@Casey: Cảm ơn bạn rất nhiều. Tôi đã nghĩ đến việc sử dụng void*quá, nhưng tôi nghĩ rằng nó sẽ vượt qua hệ thống loại quá xa. Không thể sử dụng các mẫu?
FUZxxl

C không có mẫu, đó là C ++, nhưng vâng, bạn có thể sử dụng các mẫu C ++ để làm cho nó chung chung.
Casey

Tôi không biết cách sử dụng chúng. Nhưng tôi đoán, chỉ là C bị hạn chế về mặt hệ thống. - Tôi thậm chí không thể mã hóa chương trình này mà không sử dụng void*và bạn bè.
FUZxxl

1
Thành viên kindlà một loại thẻ. Bạn có thể gọi nó tag, vì đó là một thuật ngữ được chấp nhận khá nhiều cho khái niệm này (ví dụ: công đoàn được gắn thẻ , máy quay không có thẻ Spinless . Mặt khác, "loại" có ý nghĩa khác trong một Bối cảnh Haskell: loại của một loại. IntCó loại *, []có loại * -> *(,)có loại * -> * -> *.
Joey Adams

5

C ++

Đây là điều lớn nhất tôi từng viết trong C ++. Tôi thường sử dụng Objective-C.

Nó đa hình nhưng nó không miễn phí bất cứ thứ gì.

mainHàm của tôi (và addhàm to ZipWith) cuối cùng trông như thế này:

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

Điều này mang lại

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

Các lớp học hoạt động như thế này:

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

Nguồn đầy đủ: tại đây . Đó là một mớ hỗn độn, chủ yếu là vì nó nằm trong một tệp lớn.

Chỉnh sửa: thay đổi liên kết (cái cũ đã chết).


3
Công việc tuyệt vời và cảm ơn vì đã sử dụng "throw a fit" theo nghĩa đen :-) Tôi không phải là chuyên gia về C ++, nhưng cách C ++ - y để thực hiện Thunk có thể là sử dụng một đối tượng chức năng (còn gọi là "functor") (đó là là, quá tải ()toán tử) và sử dụng tính kế thừa để tránh phải sử dụng void*. Xem ở đây cho một ví dụ tầm thường của việc đó.
Joey Adams

Các liên kết nguồn đầy đủ đã chết bây giờ. Bạn có thể tải lại nó? gist.github.com là một nơi tốt để đặt nó.
Joey Adams

@JoeyAdams: xong.
bến tàu

4

Con trăn

Không sử dụng trình tạo để thực hiện danh sách, chỉ để thực hiện __iter__phương thức sử dụng với 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)

Danh sách Fibonacci được tạo như thế này:

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

1
Thật là đẹp Dòng yêu thích của tôi là self.__class__ = node.__class__. Lưu ý rằng điều này đánh vào một ngoại lệ Chưa được thực hiện khi nó lên tới 2971215073 (dài), đây rõ ràng là một đối số không hợp lệ đối với int .__ add__. Để hỗ trợ các số nguyên lớn, hãy làmfib.append(fib.zip_with(lambda a,b: a+b, fib.tail()))
Joey Adams

1
Tại sao bạn không thể nối thêm cho đến trống hoặc thunk?
PyRulez

4

Hồng ngọc

Chương trình Ruby đầu tiên của tôi. Chúng tôi biểu diễn tất cả các nút dưới dạng mảng, trong đó độ dài mảng xác định kiểu:

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

Mã này sau đó khá đơn giản, với một hack để thiết lập lại chức năng thunk để thiết lập sợi đệ quy.

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

Bạn có thể sử dụng [...]thay vì Array[...].
Lowjacker

3

Google đi

Một ngôn ngữ tương đối mới và tôi đã học nó bằng cách CTRL+Fsử dụng 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()))
}

Vấn đề đã được khắc phục, bằng cách xử lý thunk-trong-a-thunks. Tuy nhiên, có vẻ như trình biên dịch trực tuyến không thể lấy 40 phần tử, có thể là do bộ nhớ. Tôi sẽ kiểm tra nó trên Linux của tôi sau.

[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

Tôi đã kiểm tra mã với trình biên dịch trực tuyến , vì tôi không thể cài đặt Go trên Windows một cách dễ dàng.


Điều này là khá tốt đẹp và đơn giản. Tuy nhiên, thay vì 3 bools, bạn có thể sử dụng một thẻ có giá trị có thể là các hằng số được tạo bởi trình tạo iotahằng. Xem một ví dụ trong Đặc tả ngôn ngữ lập trình Gocâu trả lời trên StackOverflow .
Joey Adams

FibsChức năng của bạn không hoạt động vì Go sử dụng đánh giá nghiêm ngặt và tự Fibsđệ quy mà không có điều kiện kết thúc. Fibs0/ Fibs1sử dụng cách tiếp cận trình tạo đơn giản thay vì thuật toán được mô tả trong bài viết của tôi, vì vậy nó không đáp ứng "yêu cầu". Tôi đã cập nhật bài viết của mình để giải thích về đệ quy lười biếng, cần thiết để thực hiện fibs = 0 : 1 : zipWith (+) fibs (tail fibs) .
Joey Adams

Cons(0, Cons(1, ZipWith(Plus, Thunk(Fibs), Tail(Thunk(Fibs))))), nó hết bộ nhớ
Ming-Tang

Tôi đã thử Cons(0, Cons(1, Thunk(func() List { return ZipWith(Plus, Thunk(Fibs), Thunk(func() List { return Tail(Fibs()) })) })))và tôi nhận được lỗi địa chỉ bộ nhớ không hợp lệ
Ming-Tang

1
Vì bạn vẫn đang học Go: Bạn có thể tạo một số mã thanh lịch hơn nhiều so với cách sử dụng giao diện cho Danh sách và các loại riêng biệt cho Thunks, v.v.
cthom06

3

Pha lê

Mặc dù theo dõi kho GitHub, tôi chưa bao giờ thực sự sử dụng Crystal cho đến bây giờ. Crystal là một biến thể Ruby được gõ tĩnh với suy luận kiểu đầy đủ. Mặc dù đã có câu trả lời của Ruby, việc gõ tĩnh của Crystal đã khiến tôi sử dụng đa hình, thay vì một mảng, để biểu diễn các nút. Bởi vì Crystal không cho phép sửa đổi self, tôi đã tạo ra một lớp bao bọc, được đặt tên Node, nó sẽ bọc mọi thứ khác và quản lý các thunks.

Cùng với các lớp, tôi tạo ra các chức năng xây dựng lnil, consthunk. Trước đây, tôi chưa bao giờ sử dụng Ruby cho hơn 20 tập lệnh, vì vậy, các công cụ chặn đã khiến tôi mất khá nhiều thời gian.

Tôi dựa vào fibchức năng tắt câu trả lời 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

Tôi đã bẻ cong các quy tắc một chút vì chưa có giải pháp .NET ở đây - hay nói chung là giải pháp OOP ngoại trừ giải pháp trong Python sử dụng tính kế thừa, nhưng nó đủ khác với giải pháp của tôi để làm cho cả hai thú vị (đặc biệt là Python cho phép sửa đổi selfthể hiện, làm cho việc thực hiện thunk trở nên đơn giản).

Vì vậy, đây là C # . Tiết lộ đầy đủ: Tôi không ở đâu gần người mới bắt đầu sử dụng C # nhưng tôi đã không tiếp xúc với ngôn ngữ này trong một thời gian vì hiện tại tôi không sử dụng nó cho công việc.

Các điểm nổi bật:

  • Tất cả các lớp ( Nil, Cons, Thunk) có nguồn gốc từ một lớp cơ sở trừu tượng chung, List.

  • Các Thunklớp học sử dụng Envelope-Letter mẫu. Điều này về cơ bản mô phỏng việc self.__class__ = node.__class__gán trong nguồn Python, vì thistham chiếu không thể được sửa đổi trong C #.

  • IsEmpty, HeadTaillà tài sản.

  • Tất cả các chức năng phù hợp được thực hiện đệ quy và lười biếng (ngoại trừ Print, không thể lười biếng) bằng cách trả về thunks. Ví dụ: đây là Nil<T>.ZipWith:

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

    Khoan và đây là 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));
        });
    }
    

    Thật không may, C # không có nhiều công văn, nếu không tôi cũng có thể thoát khỏi iftuyên bố. Than ôi, không có xúc xắc.

Bây giờ, tôi không thực sự hài lòng với việc thực hiện của mình. Tôi hạnh phúc cho đến nay vì tất cả những điều trên là hoàn toàn đơn giản. Nhưng . Tôi cảm thấy rằng định nghĩa Fiblà phức tạp không cần thiết vì tôi cần phải bọc các đối số thành thunks để làm cho nó hoạt động:

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

(Ở đây, List.Cons, List.ThunkList.ZipWithlà giấy gói thuận tiện.)

Tôi muốn hiểu tại sao định nghĩa dễ dàng hơn nhiều sau đây không hoạt động:

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

đưa ra một định nghĩa phù hợp Concat, tất nhiên. Đây thực chất là những gì mã Python làm - nhưng nó không hoạt động (= ném vừa vặn).

/ EDIT: Joey đã chỉ ra lỗ hổng rõ ràng trong giải pháp này. Tuy nhiên, thay thế dòng thứ hai bằng một thunk cũng gây ra lỗi (Mono segfaults; Tôi nghi ngờ một lỗi tràn ngăn xếp mà Mono không xử lý tốt):

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

Mã nguồn đầy đủ có thể được tìm thấy như một ý chính trên GitHub .


"Thật không may, C # không có nhiều công văn" - bạn có thể nhận được hiệu ứng bằng cách sử dụng các sự kiện, mặc dù điều đó khá khó khăn.
Peter Taylor

Điều mỉa mai về đánh giá lười biếng là nó đòi hỏi nhà nước phải thực hiện. fib.ZipWithfib.Tailsử dụng cái cũ fib, vẫn còn [0,1]và không thay đổi. Do đó, bạn nhận được [0,1,1](tôi nghĩ) và Takechức năng của bạn không cho phép bạn lấy từ null ( mặc dù vậy, Haskell thực hiện). Hãy thử gói giá trị của dòng thứ hai trong một thunk, vì vậy nó sẽ đề cập đến cái mới fibhơn là cái cũ.
Joey Adams

@Peter Có; bạn cũng có thể sử dụng mẫu Khách truy cập để triển khai nhiều công văn nhưng tôi muốn giải pháp đơn giản.
Konrad Rudolph

@Joey Duh. Bây giờ rõ ràng là rõ ràng. Tuy nhiên, giải pháp thunk vẫn không hoạt động (xem câu trả lời cập nhật) nhưng hiện tại tôi quá bận để điều tra.
Konrad Rudolph

2

Pico

đối với bản ghi, giải pháp này sử dụng bản dịch lực trì hoãn của sơ đồ như được định nghĩa trong srfi-45 . và xây dựng danh sách lười biếng trên đó.

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

}

cái nhìn đầu ra như thế này: (nhưng tùy thuộc vào cách tpico. được vá nó có thể có dấu ngoặc kép hơn trong nó display. thường in chuỗi với dấu ngoặc kép có nghĩa là tất cả sự xuất hiện của [, ,, ]sẽ có dấu ngoặc kép quanh họ như "[".)

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

do các giới hạn của kiểu dữ liệu số nguyên trong tpico, điều này không thành công khi tính toán số Fibonacci thứ 45 (hoặc bù thứ 46).

lưu ý rằng tpico 2.0pl11 bị hỏng trong đó begin(a,b)(thường được viết là {a;b}) và ifhàm không được đệ quy đuôi. không đề cập đến việc tôi mất 5 năm để tìm ra lý do tại sao beginđuôi không được đệ quy. cũng tại thời điểm đó tôi đã viết bản dịch của srfi-45 tại Pico. Hóa ra beginlà đang chờ đợi giá trị của btrước khi quay lại khi không cần phải đợi. và một khi tôi đã nhận được rằng tôi cũng có thể sửa chữa ifvì nó có cùng một vấn đề. và có một lỗi khác khiến cho hàm tạo mức meta makekhông hoạt động.

Pico cho phép điều khiển hàm nếu các đối số của nó được đánh giá trước khi hàm được gọi hoặc chỉ được đóng gói dưới dạng thunks. Đối với mã này, tôi có thể dấu chấm lửng qua các số lẻ của cuộc gọi theo chức năng .

Pico không có loại suy luận. Tôi đã nghĩ về điều này trong một thời gian nhưng tôi đã gặp phải một vấn đề do sự kỳ lạ của cuộc gọi theo chức năng . tôi đã đưa ra tuyên bố rằng các loại phải mã hóa sự tồn tại của các tên biến bị ràng buộc . nhưng tôi chủ yếu nghĩ về cách thích ứng suy luận kiểu Hindley-Milner với một tập hợp con của Pico mà không bị đột biến. ý tưởng chính là trình kiểm tra kiểu trả về nhiều lược đồ có thể nếu có nhiều hơn một ràng buộc có thểkiểm tra kiểu thành công nếu có ít nhất một lược đồ kiểu có thể . một lược đồ có thể là một lược đồ không có xung đột gán.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.