遅延リストを、できればよく知らない言語で実装してください[非公開]


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]

遅延リストの実装は、次のガイドラインを満たす必要があります。

  • リストノードは、次の3つのいずれかです。
    • Nil-空のリスト。
      []
    • 短所-単一のアイテム、残りの項目のリストとペア:
      1 : [2,3,4,5]
      : Haskellの短所演算子です)
    • サンク-必要なときにListノードを生成する遅延計算。
  • 次の操作をサポートしています。
    • nil-空のリストを作成します。
    • cons-コンスセルを構築します。
    • thunk-引数を取らず、NilまたはConsを返す関数を指定して、Thunkを構築します。
    • force-リストノードが与えられた場合:
      • NilまたはConsの場合は、単にそれを返します。
      • サンクの場合、その関数を呼び出してNilまたはConsを取得します。サンクをそのNilまたはConsに置き換えて返します。
        注:サンクを強制値に置き換えることは、「遅延」の定義の重要な部分です。このステップをスキップすると、上記のフィボナッチアルゴリズムは非常に遅くなります。
    • empty-ListノードがNilかどうかを確認します(強制した後)。
    • head(別名「車」)-リストの最初の項目を取得します(またはNilの場合は適合をスローします)。
    • tail(別名「cdr」)-リストの先頭の後の要素を取得します(または、Nilの場合は適合をスローします)。
    • zipWith-バイナリ関数(例(+))と2つの(おそらく無限の)リストが与えられた場合、リストの対応するアイテムに関数を適用します。例:
      zipWith (+) [1,2,3] [1,1,10] == [2,3,13]
    • take-数Nと(場合によっては無限の)リストを指定して、リストの最初のN個のアイテムを取得します。
    • print-リスト内のすべてのアイテムを印刷します。これは、長いリストまたは無限のリストが指定された場合に徐々に機能するはずです。
  • fibs独自の定義で自身を使用します。遅延再帰を設定するのは少し厄介です。次のようなことをする必要があります。

    • にサンクを割り当てfibsます。今のところ、ダミーの状態のままにしておきます。
    • への参照に依存するサンク関数を定義します fibsます。
    • サンクをその機能で更新します。

    fix独自の戻り値を使用してリストを返す関数を呼び出す関数を定義することにより、この配管を非表示にすることができます。このアイデアが着手できるように、短い昼寝を検討してください。

  • ポリモーフィズム(任意のタイプのアイテムのリストを操作する機能)は必要ありませんが、言語で慣用的な方法を見つけることができるかどうかを確認してください。

  • メモリ管理について心配する必要はありません。ガベージコレクションを使用する言語でさえ、使用しない(呼び出しスタックなど)ことのないオブジェクトを持ち歩く傾向があるため、無限リストを走査している間にプログラムがメモリをリークしても驚かないでください。

これらのガイドラインからわずかに逸脱して、言語の詳細に対応したり、遅延リストへの代替アプローチを検討してください。

ルール:

  • よく知らない言語を選んでください。これを「要求」することはできません。したがって、「honor-system」タグです。ただし、投票者は履歴をチェックして、投稿した言語を確認できます。
  • 言語の組み込みの遅延リストサポートを使用してすべてを実行しないでください。実質的または少なくとも興味深いものを投稿してください。

    • Haskellはほとんど出ています。つまり、次のようなことをしない限り:

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

      注:Haskellの非厳密な評価は立ち入り禁止ではありませんが、レイジーリストの実装はそこから直接その機能を引き出すべきではありません。実際、怠lazを必要としない効率的で純粋に機能的なソリューションを見るのは興味深いでしょう。

    • Python:

      • itertoolsを使用しないでください。
      • ジェネレータは問題ありませんが、ジェネレータを使用する場合、強制された値をメモする方法を見つける必要があります。

zipWith長さの異なる2つのリストを呼び出すときの動作はどうなりますか?
balpha

@balpha:Haskellsの動作を選択しました:リストのいずれかがnilの場合、nilを返します。
FUZxxl

@balpha:Haskellでは、いずれかのリストのアイテムがなくなると、zipWithは停止します。したがって、zipWith (+) [1,2,3,4,5] [0,0,0] == [1,2,3]。ただし、上記のフィボナッチアルゴリズムでは、zipWithの両方の引数が無限リストであるため、これは重要ではありません。
ジョーイアダムス

この課題には隠された驚きがありました。fibs正しく実装するには、それ自体に依存するため、特別なことをする必要があります。遅延再帰について詳しく説明するために質問を更新しました。 FUZxxl彼/彼女/自身によってそれを理解しました。
ジョーイアダムス

大きなリストを印刷するときに「段階的に作業する」とはどういう意味ですか?
ロージャッカー

回答:


6

PostScript

以前に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>

2つの追加の興味深い機能:

% 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戻り値をメモしますか?
ジョーイアダムス

@JoeyAdams:確かにそうです。サンクを評価した後、copyオペレーターは、評価されたバージョンの内容をオリジナルにコピーし、上書きし/type、場合によっては他の値を設定します。nilor が見つかるまで再帰的に評価した後cons、(を介してundef)を削除/funcし、該当する場合は/data。最後のステップは厳密には必要ではない(/func/data:)単に無視されるだろう)が、このステップを除外することも、より多くのメモリリークが発生しました
balpha

6

C

私は完全にCの初心者です。このコードは実際にはCでコーディングした最初の本物です。警告なしでコンパイルされ、システムで正常に実行されます。

作り方

まず、サーバーからtarballを取得します。メイクファイルが含まれているので、実行makeしてビルドしてからmake runから実行するだけです。プログラムは、最初の93個のフィボナッチ数のリストを出力します。(番号94の後、符号なし64ビット整数がオーバーフローします)

説明

プログラムの中核はファイルですlazy-list.c。対応するヘッダーファイルで、list遅延リストであるstructを定義します。次のようになります。

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()リストを作成するのではなく、単に空のリストを取得するために呼び出すことができます。

最も興味深い3つの関数はzipWithtakefibonaccisです。しかし、私は説明したくないtake、それは非常によく似ているので、zipWith。遅延動作するすべての関数には、3つのコンポーネントがあります。

  • サンクを作成するラッパー
  • 1つのセルの計算を実行するワーカー
  • 引数を保持する構造体

以下の場合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()です。問題は、1番目と2番目のセルのポインターを3番目のサンクに渡す必要があることですが、これらのセルを作成するには、サンクへのポインターも必要です。この問題を解決するために、サンクへのポインタを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を初めて使用する人にとっては素晴らしい投稿です。ポリモーフィズムに関して、すべてのコンテンツをヒープに割り当てたい場合はvoid*、のタイプとして使用できますcontent_t
ケーシー

@ケーシー:どうもありがとうございます。私も使用することを考えましたがvoid*、それは型システムを遠ざけすぎると思いました。テンプレートを使用することはできませんか?
FUZxxl

Cにはテンプレートがありません、つまりC ++ですが、はい、C ++テンプレートを使用して汎用にすることができます。
ケーシー

それらの使用方法がわかりません。しかし、私は、Cが型システムの点で制限されているということだけだと思います。-使用せずにこのプログラムをコーディングすることさえできませんでしたvoid*友人や友人を。
-FUZxxl

1
「メンバーkindは一種のタグです。」これtagは、コンセプトのかなり受け入れられている用語なので、単に呼び出すことができます(たとえば、タグ付きユニオンSpineless Tagless G-machine。一方、「種類」は、 。Haskellのコンテキスト:タイプの種類は Int一種た*[]種類あり* -> *、そして(,)種類がある* -> * -> *
ジョーイ・アダムス

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

完全なソース:ここ。主に1つの大きなファイルにあるため、混乱します。

編集:リンクを変更しました(古いリンクは無効でした)。


3
すばらしい仕事であり、文字通り「適合」をしてくれてありがとう:-)私は決してC ++の専門家ではありませんが、Thunkを実装するためのよりC ++-yの方法は、関数オブジェクト(別名「ファンクター」)を使用することですは、()演算子をオーバーロードします)、継承を使用してを使用する必要がないようにしますvoid*。 それを行う簡単な例については、こちらご覧ください
ジョーイアダムス

完全なソースリンクは現在無効です。再アップロードしてもらえますか? gist.github.comはそれを置くのに適した場所です。
ジョーイアダムス

@JoeyAdams:できました。
マリヌス

4

Python

リストを実装するのにジェネレーターを使用せず、で使用する__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()))
ジョーイアダムス

1
空またはサンクに追加できないのはなぜですか?
PyRulez 14

4

ルビー

私の最初のRubyプログラム。すべてのノードを配列として表します。配列の長さによってタイプが決まります。

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[...]
ロージャッカー

3

Google Go

比較的新しい言語でありCTRL+FSpecを使用して学習しました。

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

GoをWindowsに簡単にインストールできないため、オンラインコンパイラでコードをテストしました。


これはとても簡単で簡単です。ただし、3つのブール値の代わりに、可能な値がiota定数ジェネレーターによって生成された定数である単一のタグを使用できます。Goプログラミング言語の仕様の例StackOverflowの回答を参照してください。
ジョーイアダムス

あなたFibsGoは厳格な評価を使用し、ための機能が動作しませんFibs終了条件なしで自身に再帰。 Fibs0/ Fibs1は、私の投稿で説明されているアルゴリズムではなく、単純なジェネレーターアプローチを使用しているため、「要件」を満たしていません。投稿を更新して、を実装するために必要な遅延再帰について詳しく説明しましたfibs = 0 : 1 : zipWith (+) fibs (tail fibs)
ジョーイアダムス

Cons(0, Cons(1, ZipWith(Plus, Thunk(Fibs), Tail(Thunk(Fibs)))))、それはメモリの消灯
明唐

私が試したCons(0, Cons(1, Thunk(func() List { return ZipWith(Plus, Thunk(Fibs), Thunk(func() List { return Tail(Fibs()) })) })))と私は、無効なメモリアドレスエラーを取得
明唐

1
あなたはまだ移動を学んでいるので:あなたはなどサンクのためのリストと別のタイプのためのインターフェイスを使用して、これよりも、いくつかのより多くのエレガントなコードを作ることができます
cthom06

3

結晶

GitHubリポジトリをフォローしていますが、これまでCrystalを実際に使用したことはありません。Crystalは、完全に型推論された静的型付きRubyバリアントです。すでにRubyの答えがありますが、Crystalの静的型付けにより、ノードではなく配列ではなくポリモーフィズムを使用するようになりました。Crystalは次の変更を許可しないためselfNode、他のすべてをラップしてサンクを管理するという名前のラッパークラスを作成しました。

クラスに加えて、私はコンストラクタ関数を作成しlnilcons、とthunk。私も、これまで20行以上のスクリプトにRubyを使用したことは一度もないので、ブロックのようなものは私をかなり捨てました。

私はベース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#の初心者には程遠いですが、仕事で現在使用していないので、しばらくの間言語に触れていません。

顕著なポイント:

  • すべてのクラスは(NilConsThunk)、一般的な抽象基本クラスから派生しますList

  • このThunkクラスはEnvelope-Letterパターンを使用します。参照はC#で変更できないself.__class__ = node.__class__ため、これは基本的にPythonソースの割り当てをエミュレートしthisます。

  • IsEmptyHeadおよびTailプロパティです。

  • 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.ConsList.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もちろん、の適切な定義が与えられます。これは基本的にPythonコードが行うことですが、機能していません(=適合をスローします)。

/編集:Joeyはこのソリューションの明らかな欠陥を指摘しました。ただし、2行目をサンクに置き換えるとエラーも発生します(Monoのセグメンテーション違反。Monoでうまく処理できないスタックオーバーフローが疑われます)。

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

完全なソースコードはGitHubの要点として見つけることができます。


「残念ながら、C#には複数のディスパッチがありません」-イベントを使用して効果を得ることができますが、それはかなりハックです。
ピーターテイラー

遅延評価の皮肉な点は、実装に状態が必要なことです。 fib.ZipWithそしてfib.Tail古い使用fibのままである、[0,1]と変わりませんが。したがって、あなたは[0,1,1](私は思う)を取得し、あなたのTake関数はnullから取ることができません(しかしHaskellのtakeはします)。2行目の右辺値をサンクでラップしてみてくださいfib。これにより、古い行ではなく新しい行が参照されます。
ジョーイアダムス

@ピーターはい。また、Visitorパターンを使用して複数のディスパッチを実装することもできますが、ソリューションはシンプルに保つ必要がありました。
コンラッドルドルフ

@Joey Duh。今では目がくらむほど明らかです。ただし、サンクソリューションはまだ機能しません(更新された回答を参照)が、私は今忙しくて調査できません。
コンラッドルドルフ

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年かかりました。またその時、私はピコでsrfi-45の翻訳を書きました。待つ必要がなかったときに戻る前にbegin値を待っていたことが判明しましたb。そしてif、同じ問題が発生したため、修正することもできました。メタレベルのコンストラクターがmake動作不能になった他のエラーがありました。

Picoは、関数が呼び出される前に引数が評価されるか、単にサンクとしてパッケージ化されるかどうかを関数で制御します。このコードでは、functionによる呼び出しの奇妙さを省略できます

Picoには型の推論はありません。私はしばらくこれについて考えましたが、関数による呼び出しの奇妙さのために問題に遭遇しました。型はバインドされた変数名の存在をエンコードする必要があるというステートメントを思い付きました。しかし、私は主にHindley-Milner型推論を突然変異のないPicoのサブセットに適応させる方法を考えていました。主なアイデアは、そのあったが、結合可能つ以上である場合型チェッカー戻り複数の可能なスキーム及び少なくとも一つの可能な種類の方式が存在する場合に型チェックが成功しました。可能なスキームは、型の割り当てが競合しないものです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.