Реализуйте ленивые списки, желательно на языке, который вы плохо знаете [закрыто]


21

Это хорошее упражнение для того, чтобы стать более свободно говорящим на том языке программирования, который вы хотели выучить, но с которым легко повозились. Это включает в себя работу с объектами, использование или моделирование замыканий и растяжение системы типов.

Ваша задача - написать код для управления отложенными списками, а затем использовать его для реализации этого алгоритма генерации чисел Фибоначчи:

Примеры кода в Хаскеле

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]

Ваша реализация отложенного списка должна соответствовать этим рекомендациям:

  • Узел List - это одна из трех вещей:
    • Ноль - пустой список.
      []
    • Минусы - один элемент в сочетании со списком оставшихся элементов:
      1 : [2,3,4,5]
      ( :является оператором cons в Haskell)
    • Thunk - отложенное вычисление, которое при необходимости создает узел List.
  • Поддерживаются следующие операции:
    • nil - создать пустой список
    • минусы - построить против клетки.
    • thunk - Создайте Thunk, учитывая функцию, которая не принимает аргументов и возвращает Nil или Cons.
    • force - задан узел List:
      • Если это ноль или минусы, просто верните его.
      • Если это Thunk, вызовите его функцию, чтобы получить ноль или минусы. Замените громоотвод с этим ноль или минусы и верните его.
        Примечание. Замена thunk его принудительным значением является важной частью определения «ленивый» . Если этот шаг пропущен, приведенный выше алгоритм Фибоначчи будет слишком медленным.
    • empty - посмотреть, является ли узел List нулем (после его форсирования).
    • head (он же «машина») - Получить первый элемент списка (или бросить приступ, если это ноль).
    • tail (он же "cdr") - получает элементы после заголовка списка (или подбрасывает, если это Nil).
    • zipWith - учитывая двоичную функцию (например (+)) и два (возможно, бесконечные) списка, примените функцию к соответствующим элементам списков. Пример:
      zipWith (+) [1,2,3] [1,1,10] == [2,3,13]
    • take - Учитывая число N и (возможно, бесконечный) список, возьмите первые N элементов списка.
    • печать - печать всех элементов в списке. Это должно работать постепенно, когда дан длинный или бесконечный список.
  • fibsиспользует себя в своем собственном определении. Настроить ленивую рекурсию немного сложно; вам нужно сделать что-то вроде этого:

    • Выделите громоотвод для fibs. Оставьте это в фиктивном состоянии пока.
    • Определите функцию thunk, которая зависит от ссылки на fibs.
    • Обновите Thunk с его функцией.

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

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

  • Не беспокойтесь об управлении памятью. Даже языки со сборкой мусора имеют тенденцию переносить объекты, которые вы никогда больше не будете использовать (например, в стеке вызовов), поэтому не удивляйтесь, если ваша программа утечет память при обходе бесконечного списка.

Не стесняйтесь слегка отклониться от этих рекомендаций, чтобы учесть особенности вашего языка или изучить альтернативные подходы к ленивым спискам.

Правила:

  • Выберите язык, который вы плохо знаете. Я не могу «требовать» этого, отсюда и тэг «honor-system». Тем не менее, избиратели могут проверить вашу историю, чтобы увидеть, на каких языках вы публиковали.
  • Не используйте встроенную поддержку ленивых списков вашего языка, чтобы делать все. Опубликовать что-то существенное или хотя бы интересное.

    • Хаскелл в значительной степени отсутствует. То есть, если вы не сделаете что-то вроде этого:

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

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

    • Python:

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

Каким должно быть поведение при вызове zipWithдвух списков разной длины?
Балфа

@balpha: Я выбрал поведение Haskells: если один из списков равен нулю, вернуть ноль.
FUZxxl

@balpha: в Haskell zipWith останавливается, когда в любом списке заканчивается пункт. Следовательно, zipWith (+) [1,2,3,4,5] [0,0,0] == [1,2,3]. Однако это не имеет значения для алгоритма Фибоначчи, приведенного выше, поскольку оба аргумента zipWith представляют собой бесконечные списки.
Джои Адамс

В этом задании был скрытый сюрприз: вам нужно сделать что-то особенное для fibsправильной реализации , так как это зависит от самого себя. Я обновил вопрос, чтобы развить ленивую рекурсию. FUZxxl понял это сам.
Джои Адамс

Что вы подразумеваете под «работать постепенно», когда печатаете большой список?
Lowjacker

Ответы:


6

PostScript

Я играл с PostScript раньше , но я бы не сказал, что знаю его особенно хорошо (на самом деле, я предполагаю, что вы можете посчитать количество людей в мире, которые действительно знают PostScript, используя одну руку).

Я отклонился от вашей спецификации в том, что функция, используемая для создания thunk, может возвращать другой thunk; 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 и отобразите первые десять значений:

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, этот код на самом деле является первой реальной вещью, которую я написал в C. Он компилируется без каких-либо предупреждений и прекрасно работает в моей системе.

Как построить

Во-первых, получите архив с моего сервера . Он включает в себя make-файл, поэтому просто запустите его 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(). Проблема заключается в том, что нам нужно передать указатель первой и второй ячейки на блок третьей, но для создания этих ячеек нам также нужен указатель на блок. Чтобы решить эту проблему, я заполнил указатель на thunk NULLпервым и изменил его на thunk после его создания. Вот прослушивание:

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;

Возможные улучшения

  • Мое решение не использует полиморфизм. Хотя, возможно, мои навыки Си не достаточны, чтобы знать, как их использовать. Вместо этого я использовал тип content_t, который можно изменить на любой подходящий.
  • Можно извлечь thunk из определения списка и использовать его только абстрактно, но это усложнит код.
  • Можно улучшить части моего кода, которые не являются хорошими C.

Хорошая подача, особенно для того, чтобы быть C-новичком. Что касается полиморфизма, если вы хотите разместить весь свой контент в куче, вы можете использовать его void*как тип content_t.
Кейси

@Casey: Большое спасибо. Я void*тоже думал об использовании , но думал, что это обойдет систему типов слишком далеко. Разве это не возможно с помощью шаблонов?
FUZxxl

C не имеет шаблонов, то есть C ++, но да, вы можете использовать шаблоны C ++, чтобы сделать его универсальным.
Кейси

Я не знаю, как их использовать. Но я думаю, просто C немного ограничен в своей системе типов. - Я даже не смог закодировать эту программу без использования void*и друзей.
FUZxxl

1
«Элемент kindявляется своего рода тегом». Вы могли бы просто назвать его tag, поскольку это довольно приемлемый термин для концепции (например , теговое объединение , G-машина без тегов Spineless . С другой стороны, «вид» имеет другое значение в . Haskell контекст: тип типа Intимеет вид *, []имеет вид * -> *, и (,)имеет вид * -> * -> *.
Джои 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 ++ может быть использование функционального объекта (он же «functor») (что перегрузить ()оператор) и использовать наследование, чтобы избежать необходимости использования void*. Смотрите здесь для тривиального примера этого.
Джои Адамс

Полная ссылка на источник сейчас мертва. Не могли бы вы повторно загрузить его? gist.github.com - хорошее место для этого.
Джои Адамс

@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__. Обратите внимание, что это вызывает исключение NotImplemented, когда оно достигает 2971215073 (long), что, очевидно, является недопустимым аргументом для int .__ add__. Чтобы поддержать большие целые числа, сделайтеfib.append(fib.zip_with(lambda a,b: a+b, fib.tail()))
Джои Адамс

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

Проблема была решена, имея дело с thunk-in-thunks. Тем не менее, кажется, что онлайн-компилятор не может взять 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 bools вы можете использовать один тег, возможные значения которого являются константами, сгенерированными iotaгенератором констант. См. Пример в спецификации языка программирования Go и ответ на StackOverflow .
Джои Адамс

Ваша 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 - это статически типизированный вариант Ruby с полным выводом типа. Несмотря на то, что ответ Ruby уже есть, статическая типизация Crystal привела меня к использованию полиморфизма, а не массива, для представления узлов. Поскольку Crystal не разрешает модификацию self, я создал класс-оболочку с именем Node, который будет оборачивать все остальное и управлять thunks.

Наряду с классами, я создал функцию конструкторы lnil, consи thunk. Раньше я никогда не использовал Ruby более чем для 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 - или, в более общем смысле, решения ООП, за исключением решения в Python, которое использует наследование, но оно достаточно отличается от моего решения, чтобы сделать оба интересными (в частности, начиная с Python позволяет модифицировать selfэкземпляр, делая реализацию thunk простой).

Так что это C # . Полное раскрытие: я далеко не новичок в C #, но я давно не касался этого языка, так как в настоящее время я не пользуюсь им на работе.

Существенные моменты:

  • Все классы ( Nil, Cons, Thunk) вытекают из общего абстрактного базового класса, List.

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

  • IsEmpty, HeadИ Tailявляются свойствами.

  • Все соответствующие функции реализуются рекурсивно и лениво (за исключением того Print, что не может быть ленивым), возвращая thunks. Например, это 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излишне сложно, так как мне нужно обернуть аргументы в thunks, чтобы заставить его работать:

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, но он не работает (= подбрасывает).

/ РЕДАКТИРОВАТЬ: Джои указал на очевидный недостаток в этом решении. Тем не менее, замена второй строки с thunk также приводит к ошибке (Mono segfaults; я подозреваю, переполнение стека, которое Mono плохо обрабатывает):

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

Полный исходный код можно найти в GistHub .


«К сожалению, C # не имеет многократной отправки» - вы можете получить эффект, используя события, хотя это довольно хакерски.
Питер Тейлор

Ирония в ленивой оценке заключается в том, что для ее реализации требуется состояние. fib.ZipWithи fib.Tailиспользовать старое fib, которое остается [0,1]и не меняется. Таким образом, вы получаете [0,1,1](я думаю), и ваша Takeфункция не позволяет вам брать из нуля ( хотя Haskell принимает , но). Попробуйте обернуть значение второй строки в thunk, чтобы оно ссылалось на новое, fibа не на старое.
Джои Адамс

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

@ Джои Дух. Теперь это очевидно. Тем не менее, решение thunk по-прежнему не работает (см. Обновленный ответ), но я слишком занят, чтобы заняться расследованиями.
Конрад Рудольф

2

Pico

для справки , это решение использует перевод силы задержки схемы, как определено в 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 позволяет функции контролировать, оцениваются ли ее аргументы перед вызовом функции или просто упакованы как thunks. для этого кода я могу сделать многоточие на странностях вызова по функции .

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.