Реалізуйте ліниві списки, бажано, мовою, яку ви не знаєте добре [закрито]


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]

Ваша реалізація списку лінивих повинна відповідати цим рекомендаціям:

  • Вузол списку - це одна з трьох речей:
    • Nil - Порожній список.
      []
    • Мінуси - один елемент, сполучений зі списком решти елементів:
      1 : [2,3,4,5]
      ( :є оператором мінусів у Haskell)
    • Thunk - відкладене обчислення, яке створює вузол List при необхідності.
  • Він підтримує такі операції:
    • nil - побудова порожнього списку.
    • мінуси - Побудова клітинки проти.
    • thunk - Побудуйте Thunk, задавши функцію, яка не бере аргументів і повертає Nil або мінуси.
    • сила - Дано вузол списку:
      • Якщо це ніль або мінуси, просто поверніть його.
      • Якщо це Thunk, виклик його функції, щоб отримати нуль або мінуси. Замініть тулуб на той Ніл або Мінуси і поверніть його.
        Примітка: Заміна грона примусовим значенням - важлива частина визначення поняття "ледачий" . Якщо цей крок буде пропущений, алгоритм Фібоначчі вище буде надто повільним.
    • порожній - подивіться, чи вузол списку є Nil (після вимушення).
    • head (він же "машина") - Отримайте перший пункт списку (або киньте форму, якщо це Nil).
    • хвіст (він же "cdr") - Отримайте елементи після заголовка списку (або киньте форму, якщо це Nil).
    • zipWith - З огляду на бінарну функцію (наприклад (+)) та два (можливо, нескінченні) списки, застосуйте цю функцію до відповідних елементів списків. Приклад:
      zipWith (+) [1,2,3] [1,1,10] == [2,3,13]
    • take - З огляду на число N та (можливо, нескінченний) список, візьміть перші N елементів списку.
    • друк - друк усіх елементів у списку. Це повинно працювати поступово, якщо їм надано довгий або нескінченний список.
  • fibsвикористовує себе у власному визначенні. Налаштування ледачої рекурсії - це складність; вам потрібно зробити щось подібне:

    • Виділіть грудку для fibs. Залиште це поки в манекеновому стані.
    • Визначте функцію грона, яка залежить від посилання на fibs.
    • Оновіть грудку за допомогою її функції.

    Ви можете заховати цю сантехніку, визначивши функцію, fixяка викликає функцію повернення списку зі своїм власним значенням повернення. Розгляньте короткий сон, щоб ця ідея могла втілитися.

  • Поліморфізм (вміння працювати зі списками будь-якого виду предметів) не потрібен, але подивіться, чи зможете ви знайти мову, яка ідіоматична для вашої мови.

  • Не турбуйтеся про управління пам'яттю. Навіть мови зі збиранням сміття мають тенденцію переносити об’єкти, які ви ніколи більше не використовуватимете (наприклад, у стеку дзвінків), тому не дивуйтеся, якщо ваша програма просочує пам'ять під час проходження нескінченного списку.

Не соромтесь трохи відхилятися від цих вказівок, щоб вмістити деталі вашої мови або вивчити альтернативні підходи до лінивих списків.

Правила:

  • Оберіть мову, яку ви не добре знаєте. Я не можу "вимагати" цього, звідси і тег "система честі". Однак виборці можуть перевірити вашу історію, щоб побачити, на яких мовах ви публікували публікації.
  • Не використовуйте вбудовану підтримку лінивого списку вашої мови, щоб зробити все. Опублікуйте щось істотне або принаймні цікаве.

    • Haskell майже не виходить. Тобто, якщо ви не зробите щось подібне:

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

      Примітка. Неточна оцінка Haskell не є обмеженою, але реалізація вашого лінивого списку не повинна виводити його звідси безпосередньо. Насправді, було б цікаво побачити ефективне, суто функціональне рішення, яке не потребує ліні.

    • Пітон:

      • Не використовуйте itertools.
      • Генератори в порядку, але ви їх використовуєте, вам доведеться знайти спосіб запам’ятовування вимушених значень.

Якою має бути поведінка при дзвінках zipWithу два списки різної довжини?
Бальфа

@balpha: Я вибрав поведінку Haskells: Якщо будь-який із списків є нульовим, поверніть нуль.
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 , але я б не сказав, що це я знаю особливо добре (насправді, я гадаю, що ви можете порахувати кількість людей у ​​світі, які дійсно знають PostScript за допомогою однієї руки).

Я відхилився від вашої характеристики в тому, що функція, яка використовується для створення грона, дозволена повернути ще один фрагмент; forceбуде продовжувати оцінювати, доки результат не буде nila 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 та виведіть перші десять значень:

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та, можливо, встановлюючи інші значення. Після того, як рекурсивний оцінки , поки ми не будемо мати nilабо cons, це також (через undef) видаляє /funcі, де це може бути застосовано, /data. Останній крок не є суворо необхідним ( /funcі /dataйого просто проігнорують), але залишення цього кроку просочить ще більше пам’яті :)
balpha

6

С

Я абсолютно початківець у С, цей код насправді є першою реальною справою, яку я зашифрував у C. Він збирається без будь-яких попереджень і працює нормально у моїй системі.

Як будувати

Спочатку дістаньте тарбол з мого сервера . Він включає в себе makefile, тому просто запустіть, makeщоб створити його і потімmake run запустити його. Потім програма виводить список перших 93 номерів полів. (Після числа 94 переливається неподписане 64-бітове ціле число)

Пояснення

Ядро програм - це файл lazy-list.c. У відповідному файлі заголовка я визначаю структуру 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є своєрідним тегом. Він позначає, чи ми досягли списків end ( NIL), комірку, яка вже оцінена ( CONS), або thunk ( 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 недостатньо, щоб знати, як ним користуватися. Натомість я використав тип content_t, який можна змінити на все, що підходить.
  • Можна було б витягнути фрагмент із визначення списку та використовувати його лише абстрактно, але це зробить код складнішим.
  • Можна вдосконалити частини мого коду, які не є хорошими.

Приємне представлення, особливо за те, що він є першокласником C. Що стосується поліморфізму, якщо ви готові виділити весь вміст на купі, ви можете використовувати його void*як тип content_t.
Кейсі

@Casey: Дуже дякую Я теж думав використати void*, але думав, що це зробить сторону типової системи занадто далеко. Хіба це не можливо за допомогою шаблонів?
FUZxxl

У C немає шаблонів, тобто C ++, але так, ви можете використовувати шаблони C ++, щоб зробити його загальним.
Кейсі

Я не знаю, як ними користуватися. Але я здогадуюсь, це просто те, що C є на зразок обмеженої в плані своєї типової системи. - Я навіть не зміг кодувати цю програму без використання void*та друзів.
FUZxxl

1
"Учасник kind- це тег". Ви можете просто назвати це tag, оскільки це досить прийнятий термін для концепції (наприклад, тегований союз , безшпинкова машина без шпильок . З іншого боку, "вид" має інше значення в Контекст 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 може бути використання об'єкта функції (він же "функтор") (що є, перевантажуйте ()оператора), і використовувати спадщину, щоб уникнути необхідності використання void*. Дивіться тут для тривіального прикладу цього.
Joey Adams

Повне джерело посилання зараз мертве. Ви можете повторно завантажити його? gist.github.com - це гарне місце для цього.
Джої Адамс

@JoeyAdams: зроблено.
Марина

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__. Зауважте, що це потрапляє на NotImplemented виняток, коли він отримує до 2971215073 (довгий), що, мабуть, є недійсним аргументом для int .__ add__. Щоб підтримати великі цілі числа, зробітьfib.append(fib.zip_with(lambda a,b: a+b, fib.tail()))
Joey Adams

1
Чому ви не можете приєднатись до порожнього чи спокійного?
PyRulez

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)

Код тоді досить простий, з хаком для скидання функції Thunk для налаштування рекурсивної фіб.

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

Google Go

Відносно нова мова, і я засвоїв її, CTRL+Fвикористовуючи 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()))
}

Проблема була виправлена ​​шляхом вирішення проблем із гронами. Однак, здається, що онлайн-компілятор не може взяти 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 .
Joey Adams

Ваша Fibsфункція не працює, оскільки Go використовує сувору оцінку і Fibsповторюється на собі без умови закінчення. Fibs0/ Fibs1використовує простий підхід генератора, а не алгоритм, описаний у моєму дописі, тому він не відповідає "вимогам". Я оновив свою публікацію, щоб детально розглянути питання про ледачу рекурсію, яку потрібно здійснити fibs = 0 : 1 : zipWith (+) fibs (tail fibs) .
Джої Адамс

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: Ви можете зробити якийсь набагато більш елегантний код, ніж цей, використовуючи інтерфейси для Списків та окремі типи для Thunks тощо
cthom06,

3

Кристал

Не дивлячись на дотримання сховища GitHub, я ще ніколи фактично не використовував Crystal. Кристал - це статично типовий варіант Рубі з висновком повного типу. Незважаючи на те, що вже є відповідь Рубі, статична типізація Кристала привела мене до використання поліморфізму, а не для масиву для представлення вузлів. Оскільки Crystal не дозволяє модифікувати self, я створив клас обгортки, іменований Node, який би обертав усе інше та керував гронами.

Поряд з класами, я створив функцію конструктори lnil, consі thunk. Я ніколи ще не використовував Рубі для більш ніж 20-рядкового сценарію, тому блоковий матеріал мене дуже відкинув.

Я базував fibфункцію на відповіді 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

Я трохи зігнув правила, тому що тут ще немає рішення .NET - або загалом рішення OOP, за винятком рішення в Python, яке використовує успадкування, але воно досить відрізняється від мого рішення, щоб зробити обидва цікавими (зокрема, оскільки Python дозволяє змінювати selfекземпляр, роблячи реалізацію Thunk простою).

Отже, це C # . Повне розкриття: я ніде не є початківцем на C #, але я не торкався мови, оскільки я зараз не використовую її на роботі.

Основні моменти:

  • Всі класи ( Nil, Cons, Thunk) що є результатом загального абстрактного базового класу, List.

  • ThunkКлас використовує Envelope-Letter шаблон. Це по суті імітує self.__class__ = node.__class__призначення у джерелі Python, оскільки thisпосилання не може бути змінено у C #.

  • IsEmpty, Headі 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.Cons, List.Thunkі List.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-код, але він не працює (= кидає придатність).

/ EDIT: Джої вказав на очевидний недолік у цьому рішенні. Однак, заміна другого рядка затиском також призводить до помилки (Mono segfaults; я підозрюю переповнення стека, яке 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функція не дозволяє вам брати з нуля ( хоча Haskell приймає , хоча). Спробуйте загорнути ревальвер другого рядка в об'єм, тому він буде стосуватися нового, fibа не старого.
Джої Адамс

@ Петер Так; ви також можете використовувати шаблон для відвідувачів для здійснення декількох відправок, але я хотів, щоб рішення залишалося простим.
Конрад Рудольф

@Joey Duh. Зараз сліпо очевидно. Однак рішення Thunk все ще не працює (див. Оновлену відповідь), але я занадто зайнятий зараз, щоб розслідувати.
Конрад Рудольф

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функція не є хворобою рекурсивною. не кажучи вже про те, що мені знадобилося 5 років, щоб зрозуміти, чому beginхвіст не був рекурсивним. Також в той час я писав переклад srfi-45 в Піко. виявилося, що beginчекало значення bдо повернення, коли його не потрібно було чекати. і як тільки я зрозумів, що я також зміг виправити, ifоскільки у нього була така ж проблема. і сталася ця інша помилка, яка зробила конструктор мета рівня makeнепрацездатним.

Pico дозволяє контролювати функцію, якщо її аргументи оцінюються перед тим, як функція викликається або просто упаковується як тиски. для цього коду я можу еліпсувати через дивацтва дзвінків за функцією .

Піко не має виводу типу. Я думав про це деякий час, але я побіг до проблеми через дивацтва дзвінків за функцією . я придумав твердження, що типи повинні кодувати існування зв'язаних імен змінних . але я в основному думав, як адаптувати висновки типу Хіндлі-Мілнера до підмножини Піко без мутації. Основна ідея полягала в тому, що перевірка типу повертає кілька можливих схем, якщо є більше одного можливого прив'язки, і перевірка типу успішна, якщо існує хоча б одна можлива схема типу . можлива схема - така, що жодне призначення не конфліктує.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.