잘 모르는 언어로 게으른 목록을 구현하십시오.


21

이것은 당신이 배우고 자하는 프로그래밍 언어에 더 유창하게되기위한 좋은 연습입니다. 여기에는 객체 작업, 클로저 사용 또는 시뮬레이션, 유형 시스템 확장이 포함됩니다.

당신의 임무는 게으른 목록을 관리하는 코드를 작성하고 피보나치 수를 생성하기 위해이 알고리즘을 구현하는 데 사용하는 것입니다.

코드 샘플은 Haskell에 있습니다

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

결과:

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

지연 목록 구현은 다음 지침을 충족해야합니다.

  • 목록 노드는 다음 세 가지 중 하나입니다.
    • 무기명-빈 목록.
      []
    • 단점-나머지 항목의 목록과 쌍을 이루는 단일 항목 :
      1 : [2,3,4,5]
      ( :Haskell의 단점 연산자 임)
    • 썽크-필요할 때 목록 노드를 생성하는 지연된 계산.
  • 다음 작업을 지원합니다.
    • nil-빈리스트를 작성합니다.
    • cons-죄수 셀을 구성합니다.
    • thunk-인수를 취하지 않고 Nil 또는 Cons를 리턴하는 함수가 주어지면 Thunk를 구성하십시오.
    • force-주어진 목록 노드 :
      • Nil 또는 Cons 인 경우 간단히 반품하십시오.
      • Thunk 인 경우 함수를 호출하여 Nil 또는 Cons를 얻습니다. 썽크를 해당 Nil 또는 Cons로 교체하고 반환하십시오.
        참고 : 썽크를 강제 값으로 바꾸는 것은 "lazy"정의의 중요한 부분입니다 . 이 단계를 건너 뛰면 위의 피보나치 알고리즘이 너무 느려집니다.
    • empty-List 노드가 Nil인지 확인합니다 (강제 한 후).
    • head (일명 "car")-목록의 첫 번째 항목을 가져옵니다 (또는 Nil 인 경우 맞추기).
    • tail (일명 "cdr")-목록의 머리 다음에 요소를 가져 오거나 Nil 인 경우 맞춤을 던집니다.
    • zipWith-이진 함수 (예 :) (+)와 두 개 (무한대) 목록이 주어지면 해당 함수를 목록의 해당 항목에 적용합니다. 예:
      zipWith (+) [1,2,3] [1,1,10] == [2,3,13]
    • take-숫자 N과 (무한한) 목록이 주어지면 목록의 첫 N 항목을 가져옵니다.
    • print-목록에있는 모든 항목을 인쇄합니다. 길거나 무한한 목록이 주어지면 점진적으로 작동합니다.
  • fibs자체 정의에서 자체를 사용합니다. 게으른 재귀를 설정하는 것은 약간 까다 롭습니다. 다음과 같은 작업을 수행해야합니다.

    • 에 썽크를 할당하십시오 fibs. 지금은 더미 상태로 두십시오.
    • 에 대한 참조에 따라 썽크 함수를 정의하십시오 fibs.
    • 기능으로 썽크를 업데이트하십시오.

    fix자체 반환 값으로 List-returning 함수를 호출하는 함수 를 정의하여이 배관을 숨길 수 있습니다 . 이 아이디어를 설정할 수 있도록 짧은 낮잠을 고려하십시오.

  • 다형성 (모든 유형의 항목 목록으로 작업하는 기능)은 필요하지 않지만 언어에서 관용적 인 방법을 찾을 수 있는지 확인하십시오.

  • 메모리 관리에 대해 걱정하지 마십시오. 가비지 콜렉션이있는 언어조차도 다시는 사용하지 않는 객체 (예 : 호출 스택)를 가지고 다니는 경향이 있으므로, 무한 목록을 순회하면서 프로그램이 메모리를 누출하더라도 놀라지 마십시오.

언어의 특정 사항을 수용하거나 게으른 목록에 대한 다른 접근 방식을 탐색하려면이 지침에서 약간 벗어나십시오.

규칙 :

  • 잘 모르는 언어를 선택하십시오. 이것을 "필수"할 수 없으므로 "명예 시스템"태그입니다. 그러나 유권자들은 귀하의 기록을 확인하여 귀하가 게시 한 언어를 확인할 수 있습니다.
  • 언어의 내장 지연 목록 지원을 사용하여 모든 것을 수행하지 마십시오. 실질적이거나 흥미로운 것을 게시하십시오.

    • 하스켈은 꽤 나왔습니다. 즉, 다음과 같이하지 않으면 :

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

      참고 : Haskell의 엄격하지 않은 평가는 제한이 없지만 지연 목록 구현은 직접 기능을 파생해서는 안됩니다. 실제로 게으름이 필요하지 않은 효율적이고 순수하게 기능적인 솔루션을 보는 것이 흥미로울 것입니다.

    • 파이썬 :

      • itertools를 사용하지 마십시오.
      • 제너레이터는 훌륭하지만,이를 사용하면 강제 값을 메모 할 방법을 찾아야합니다.

zipWith길이가 다른 두 목록을 호출 할 때 동작은 무엇입니까 ?
balpha

@ balpha : Haskells 동작을 선택했습니다. 목록 중 하나가 nil이면 nil을 반환하십시오.
FUZxxl

@balpha : Haskell에서 zipWith는 목록 중 하나라도 부족하면 중지됩니다. 따라서 zipWith (+) [1,2,3,4,5] [0,0,0] == [1,2,3]. 그러나 zipWith에 대한 두 인수가 모두 무한한 목록이므로 위의 피보나치 알고리즘에는 문제가되지 않습니다.
Joey Adams

이 도전에는 숨겨진 놀라움이 fibs있습니다. 자체 구현에 따라 올바르게 구현하기 위해 특별한 작업을 수행 해야합니다. 게으른 재귀에 대해 자세히 설명하기 위해 질문을 업데이트했습니다. FUZxxl 은 그 / 그녀 / 자신이 알아 냈습니다.
Joey Adams

큰 목록을 인쇄 할 때 "증분 적으로 작업"한다는 것은 무엇을 의미합니까?
Lowjacker

답변:


6

추신

나는 전에 PostScript를 가지고 놀았 지만 나는 그것을 잘 알고 있다고 말하지 않을 것입니다 (사실, 내 생각에 한 손으로 PostScript를 실제로 아는 사람들의 수를 셀 수 있습니다).

썽크를 만드는 데 사용되는 함수가 다른 썽크를 반환 할 수 있다는 점에서 귀하의 사양을 벗어났습니다. force결과가 a nil또는 a 가 될 때까지 계속 평가 합니다 cons.

목록은 사전으로 구현됩니다.

<< /type /nil >>

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

<< /type /thunk
   /func evaluationFunction >>

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

코드는 다음과 같습니다. 우리는 일부 내장 연산자를 덮어 쓰고 있습니다 (특히 print; 더 있는지 확인하지 않았습니다). 실제 사용에서는이 점을주의해야합니다. 물론 실제 사용은 없으므로 괜찮습니다.

절차 이전의 의견은 다음과 같이 읽습니다.

% before2 before1 before0  <| procedure |>  after1 after0

즉, 호출 전의 예상 스택 내용과 호출 후의 스택 내용을 표시합니다. 프로 시저 내의 주석은 특정 행이 실행 된 후 스택의 내용을 보여줍니다.

% 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

표시된 페이지를 무시하고 이것을 Ghostscript에로드하십시오. 우리는 인터프리터 만 사용하고 있습니다. 피보나치 알고리즘은 다음과 같습니다.

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

두 가지 흥미로운 기능 :

% 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

5부터 계산을 시작하고 결과 목록의 각 요소에 3을 곱한 후 처음 10 개의 값을 표시하십시오.

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

다형성에 관하여 : PostScript는 강력하게 형식화되어 있어도 임의의 형식을 사전 값으로 허용하므로 원하는 것을 넣을 수 있습니다.

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>

숫자에 문자열을 추가하는 등의 유형 오류는 평가시에만 발생합니다.

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

놀랄 만한. (어떻게) force반환 값을 메모합니까?
Joey Adams

@JoeyAdams : 실제로 그렇습니다. 썽크를 평가 한 후 copy작업자는 평가 된 버전의 내용을 원본에 복사하여 덮어 쓰기 /type하고 다른 값을 설정합니다. 후 반복적으로 우리가있을 때까지 평가 nil또는 cons(통해서도 그것을 undef제거합니다) /func하고, 해당되는 경우 /data. 마지막 단계는 꼭 필요한 것은 ( /func그리고 /data단지 무시 될 것이다)하지만,이 단계 알아두면 더 많은 메모리 : 누출 것
balpha

6

기음

나는 C의 초보자입니다.이 코드는 실제로 C로 코딩 한 첫 번째 것입니다. 경고없이 컴파일되고 시스템에서 정상적으로 실행됩니다.

구축하는 방법

먼저 내 서버 에서 tarball을 가져옵니다 . makefile이 포함되어 있으므로 make빌드 make run하여 실행하십시오. 그런 다음 프로그램은 처음 93 개의 피보나치 수 목록을 인쇄합니다. (번호 94 이후 부호없는 64 비트 정수 오버플로)

설명

프로그램 핵심은 파일 lazy-list.c입니다. 해당 헤더 파일에서 struct를 정의 list합니다. 게으른 목록입니다. 다음과 같이 보입니다 :

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

멤버 kind는 일종의 태그입니다. 목록의 끝을 예약했는지 ( NIL), 이미 평가 된 셀 ( CONS) 또는 썽크 ( THUNK)를 표시합니다. 그런 다음 노조가 따릅니다. 그것은

  • 값과 꼬리가있는 이미 평가 된 셀
  • 또는 함수 포인터와 구조체를 특징으로하는 썽크, 필요한 경우 함수에 대한 일부 인수를 포함 할 수 있습니다.

공용체의 내용은 태그에 의해 주장됩니다. 태그가NIL 인 경우 공용체의 내용이 정의되지 않습니다.

상기 명세서에서 언급 된 헬퍼 함수를 ​​정의함으로써, 예를 들어 목록 정의를 그것의 사용법으로부터 추상화 할 수있다. nil()혼자서 만드는 대신 빈 목록을 얻기 위해 전화 를 걸 수 있습니다 .

세 가지 가장 흥미로운 기능은 zipWith, take하고 fibonaccis. 그러나 나는 take그것과 매우 유사하기 때문에 설명하고 싶지 않습니다 zipWith. 느리게 작동하는 모든 기능에는 세 가지 구성 요소가 있습니다.

  • 썽크를 만드는 래퍼
  • 한 셀에 대한 계산을 수행하는 작업자
  • 인수를 유지하는 구조체

의 경우 zipWith,이은 zipWith, __zipWith__zipArgs. 추가 설명없이 여기에 표시하면 기능이 명확해야합니다.

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

다른 흥미로운 기능은 fibonaccis()입니다. 문제는 첫 번째와 두 번째 셀의 포인터를 세 번째의 썽크에 전달해야한다는 것입니다. 그러나 이러한 셀을 만들려면 썽크에 대한 포인터도 필요합니다. 이 문제를 해결하기 위해 썽크에 대한 포인터를 먼저 채워서 썽 크로 NULL변경했습니다. 청취는 다음과 같습니다.

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 기술로는 사용법을 알기에 충분하지 않습니다. 대신, 나는 type을 사용했는데 content_t, 맞는 것으로 바꿀 수 있습니다.
  • 목록의 정의에서 썽크를 추출하여 추상적으로 만 사용할 수 있지만 그렇게하면 코드가 더 복잡해집니다.
  • C가 아닌 코드 부분을 개선 할 수 있습니다.

특히 C 초보자 인 경우 제출이 좋습니다. 다형성과 관련하여 모든 콘텐츠를 힙에 할당하려는 경우 void*의 유형으로 사용할 수 있습니다 content_t.
Casey

@Casey : 대단히 감사합니다. 나는 void*너무 사용 하려고 생각했지만 타입 시스템을 너무 멀리 버릴 것이라고 생각했습니다. 템플릿을 사용하여 불가능합니까?
FUZxxl

C에는 템플릿이 없으며 C ++이지만 C ++ 템플릿을 사용하여 일반으로 만들 수 있습니다.
Casey

사용 방법을 모르겠습니다. 그러나 C가 typesystem이라는 측면에서 제한적이라고 생각합니다. - void*친구와 친구 없이이 프로그램을 코딩 할 수 없었습니다 .
FUZxxl

1
"멤버 kind는 일종의 태그입니다." tag개념에 대해 꽤 통용되는 용어 이므로 태그 라고 부를 수 있습니다 (예 : 태그 조합 , Spineless Tagless G-machine . 반면에 "종류"는 다른 의미로 사용됩니다. Haskell 문맥 : 유형의 유형 Int은 친절하고 *, []친절 * -> *하며, (,)친절 * -> * -> *하다
Joey Adams

5

C ++

이것은 내가 C ++로 작성한 것 중 가장 큰 것입니다. 나는 보통 Objective-C를 사용합니다.

다형성이지만 아무것도 해방시키지 않습니다.

main기능 (및 add기능 ZipWith)은 다음과 같이 보입니다.

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

이것은 준다

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

수업은 다음과 같이 작동합니다.

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

전체 소스 : 여기 . 그것은 하나의 큰 파일에 있기 때문에 엉망입니다.

편집 : 링크를 변경했습니다 (이전 링크는 죽었습니다).


3
훌륭한 작품, 그리고 문자 그대로 "맞춤을 던져"주셔서 감사합니다 :-) 나는 결코 C ++ 전문가는 아니지만 Thunk를 구현하는 더 C ++-y 방법은 함수 객체 (일명 "펑")를 (즉, 는 ()연산자를 오버로드하고 상속을 사용하여 피할 필요가 없습니다 void*. 그렇게하는 사소한 예는 여기참조하십시오 .
Joey Adams

전체 소스 링크가 현재 종료되었습니다. 다시 올릴 수 있습니까? gist.github.com 은 그것을 넣기에 좋은 곳입니다.
Joey Adams

@JoeyAdams : 끝났습니다.
marinus

4

파이썬

생성기를 사용하여 목록을 구현하지 않고로 사용할 __iter__메소드 를 구현하기 만합니다 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)

피보나치 목록은 다음과 같이 생성됩니다.

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

1
이것은 아름답다. 내가 가장 좋아하는 라인은 self.__class__ = node.__class__입니다. 최대 2971215073 (길이)에 도달하면 NotImplemented 예외가 발생하며 int .__ add__에 대한 잘못된 인수 인 것 같습니다. 큰 정수를 지원하려면fib.append(fib.zip_with(lambda a,b: a+b, fib.tail()))
Joey Adams

1
왜 비어 있거나 썽크에 추가 할 수 없습니까?
PyRulez

4

루비

나의 첫번째 루비 프로그램. 우리는 모든 노드를 배열로 나타내며, 배열 길이는 유형을 결정합니다.

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

그런 다음 코드는 매우 간단하며 썽크 기능을 재설정하여 재귀 fib를 설정합니다.

def nil_()
  return Array[]
end

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

def thunk(f)
  return Array[f]
end

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

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

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

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

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

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

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

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

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

print(take(40, fibs))

[...]대신 사용할 수 있습니다 Array[...].
Lowjacker

3

구글 고

비교적 새로운 언어이며 Spec을 사용CTRL+F 하여 학습했습니다 .

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

문제는 썽크를 처리하여 해결되었습니다. 그러나 온라인 컴파일러는 메모리 때문에 40 개의 요소를 사용할 수없는 것 같습니다. 나중에 Linux에서 테스트하겠습니다.

[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

Windows에 Go를 쉽게 설치할 수 없기 때문에 온라인 컴파일러로 코드를 테스트했습니다 .


이것은 꽤 좋고 간단합니다. 그러나 3 개의 부울 대신 iota상수 생성기에 의해 생성 된 상수가 가능한 단일 태그를 사용할 수 있습니다 . Go 프로그래밍 언어 사양의 예제StackOverflow에 대한 답변을 참조하십시오 .
Joey Adams

귀하의 Fibs이동 엄격한 평가를 사용하고 있기 때문에 기능이 작동하지 않습니다 Fibs종결 조건없이 자체에 재귀. Fibs0/ Fibs1내 게시물에 설명 된 알고리즘 대신 간단한 생성기 접근 방식을 사용하므로 "요구 사항"을 충족하지 않습니다. 나는 게으른 재귀에 대해 자세히 설명하기 위해 게시물을 업데이트했다 fibs = 0 : 1 : zipWith (+) fibs (tail fibs) .
Joey Adams

Cons(0, Cons(1, ZipWith(Plus, Thunk(Fibs), Tail(Thunk(Fibs))))), 메모리 부족
Ming-Tang

나는 시도했다 Cons(0, Cons(1, Thunk(func() List { return ZipWith(Plus, Thunk(Fibs), Thunk(func() List { return Tail(Fibs()) })) }))) 유효하지 않은 메모리 주소 오류가 발생했습니다
Ming-Tang

1
여전히 Go를 배우고 있기 때문에 List에 대한 인터페이스와 Thunk에 대한 별도의 유형 등을 사용하여 이것보다 훨씬 더 우아한 코드를 만들 수 있습니다.
Tunks

3

결정

GitHub 리포지토리를 따르더라도 지금까지 Crystal을 사용한 적이 없습니다 . Crystal은 정식 유추 된 정적 인 형식의 Ruby 변형입니다. 이미 Ruby 답변이 있지만 Crystal의 정적 타이핑을 사용하면 배열 대신 다형성을 사용하여 노드를 나타 냈습니다. Crystal은의 수정을 허용하지 않기 때문에 다른 모든 것을 래핑하고 썽크를 관리 self하는 래퍼 클래스 ()를 만들었습니다 Node.

클래스와 함께, 내가 만든 생성자 함수 lnil, consthunk . 전에도 20 줄 이상의 스크립트에 루비를 사용한 적이 없었기 때문에 블록 관련 요소가 많이 나왔습니다.

Go 답변 에서 fib기능을 기반으로했습니다. .

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

여기에 .NET 솔루션이 없기 때문에 규칙을 약간 구부 렸습니다. 또는 상속을 사용하는 Python의 솔루션을 제외하고는 일반적으로 OOP 솔루션이지만, 특히 Python 이후로 두 가지를 흥미롭게 만드는 솔루션과는 다릅니다. 수정할 수 있습니다 self 인스턴스 썽크 구현을 간단하게 만듭니다).

그래서 이것은 C # 입니다. 전체 공개 : 나는 C #의 초보자 근처에 없지만 현재 직장에서 사용하지 않기 때문에 한동안 언어를 만지지 않았습니다.

두드러진 점 :

  • 모든 클래스는 ( Nil, Cons, Thunk), 공통 추상 기본 클래스에서 파생List .

  • Thunk클래스는 Envelope-Letter 패턴을 사용합니다 . C # self.__class__ = node.__class__에서는 this참조를 수정할 수 없으므로 기본적으로 Python 소스 의 할당을 에뮬레이트합니다 .

  • IsEmpty, HeadTail속성입니다.

  • 모든 적절한 함수는 Print썽크를 반환하여 재귀 적으로 및 게으르게 구현됩니다 (제외 할 수없는 제외 ). 예를 들면 다음과 같습니다 Nil<T>.ZipWith.

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

    … 그리고 이것은 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));
        });
    }
    

    불행히도 C #에는 다중 디스패치가 없으므로 그렇지 않으면 if명령문을 제거 할 수 있습니다 . 아아, 주사위는 없어.

이제 구현에 만족하지 않습니다. 위의 모든 것이 완전히 간단하기 때문에 지금까지 행복합니다. 그러나 . 나는 Fib인수를 썽 크로 감싸서 작동해야하기 때문에 정의 가 불필요하게 복잡 하다고 생각합니다 .

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

(여기서, List.Cons, List.ThunkList.ZipWith 편의 랩퍼이다.)

다음과 같은 훨씬 쉬운 정의가 작동하지 않는 이유를 이해하고 싶습니다.

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

적절한 정의가 주어진다면 Concat물론 . 이것은 본질적으로 파이썬 코드가하는 일이지만 작동하지 않습니다 (= 맞춤).

/ 편집 : Joey는이 솔루션의 명백한 결함을 지적했습니다. 그러나 두 번째 줄을 썽 크로 바꾸면 오류가 발생합니다 (Mono segfaults; Mono가 제대로 처리하지 못하는 스택 오버플로가 의심됩니다).

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

전체 소스 코드는 GitHub에서 요점으로 찾을 수 있습니다 .


"불행히도 C #에는 다중 디스패치가 없습니다"-다소 해킹이 되더라도 이벤트를 사용하여 효과를 얻을 수 있습니다.
피터 테일러

게으른 평가에 대한 아이러니 한 것은 구현하기 위해 상태가 필요하다는 것입니다. fib.ZipWithfib.Tail이전 사용 fib남아있는, [0,1]변경을하지 않습니다. 따라서 [0,1,1](제 생각에) 얻을 수 있으며 Take함수에서 null을 사용할 수 없습니다 (Haskell의 테이크 는 수행). 두 번째 줄의 rvalue를 썽 크로 감싸서 fib오래된 것이 아니라 새로운 것을 참조하십시오 .
Joey Adams

@ 피터 예; 또한 방문자 패턴을 사용하여 다중 디스패치를 ​​구현할 수 있지만 솔루션이 단순하게 유지되기를 원했습니다.
Konrad Rudolph 2016 년

@Joey Duh. 지금은 맹목적으로 분명합니다. 그러나 썽크 솔루션은 여전히 ​​작동하지 않지만 (업데이트 답변 참조) 지금 조사하기에는 너무 바쁩니다.
Konrad Rudolph 2016 년

2

피코

기록을 위해이 솔루션은 srfi-45에 정의 된대로 체계의 지연 력 변환을 사용합니다 . 그리고 그 위에 게으른 목록을 작성합니다.

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

}

이 같은 출력 외모 : (하지만이 방법에 따라 tpico. 그것이 더 큰 따옴표가있을 수 있습니다 패치 display. 일반적으로 따옴표 문자열을 인쇄, 즉 모든 출연 [, ,, ]등의 주변 시세를 할 것이다 "[".)

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

tpico에서 정수 데이터 유형의 한계로 인해 45 번째 (또는 46 번째 오프셋) 피보나치 수를 계산할 때 실패합니다.

tpico 2.0pl11은 begin(a,b)(일반적으로로 작성 됨 {a;b}) 에서 깨지고 if함수는 꼬리 재귀가 아닙니다. begin꼬리 재귀가 아닌 이유 를 알아내는 데 5 년이 걸렸다는 것은 말할 것도 없습니다. 또한 그 당시 나는 Pico에서 srfi-45의 번역을 썼습니다. 기다릴 필요가 없을 때 돌아 오기 전에 begin값을 기다리는 것으로 밝혀졌습니다 b. 그리고 일단 내가 그것을 얻었을 때도 if같은 문제가 있었기 때문에 고칠 수있었습니다 . 메타 수준 생성자를 make작동 불능으로 만드는 다른 오류가있었습니다 .

Pico는 함수가 호출되거나 썽 크로 패키지되기 전에 인수가 평가되는지 여부를 함수가 제어 할 수있게합니다. 이 코드의 경우 함수 별 호출의 이상에 대해 줄임표를 사용할 수 있습니다 .

Pico에는 형식 유추가 없습니다. 나는 이것에 대해 잠시 동안 생각했지만 함수에 의한 호출의 이상 으로 인해 문제가 발생했습니다 . 나는 타입이 바인딩 된 변수 이름의 존재를 인코딩해야 한다는 진술을 생각해 냈습니다 . 그러나 나는 주로 Hindley-Milner 유형의 추론을 돌연변이없이 Pico의 하위 집합에 적용하는 방법을 생각하고있었습니다. 주요 아이디어는 하나 이상의 가능한 바인딩이있는 경우 형식 검사기가 가능한 여러 스키마를 반환 하고 하나 이상의 가능한 형식 체계가있는 경우 형식 검사가 성공한다는 것입니다 . 가능한 체계는 형식 할당 충돌이없는 체계입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.