实现懒惰列表,最好使用您不太熟悉的语言[关闭]


21

这是一个很好的练习,可以使您更加流利地使用您一直想学习的编程语言,但是只需要稍加修改即可。这涉及使用对象,使用或模拟闭包以及扩展类型系统。

您的任务是编写代码来管理惰性列表,然后使用它来实现生成斐波那契数字的算法:

代码示例在Haskell中

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

结果:

[0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,5702887,9227465,14930352,24157817,39088169,63245986]

您的惰性列表实现应符合以下准则:

  • 列表节点是三件事之一:
    • 无-空列表。
      []
    • 缺点-一个单一项目,其余项目的列表配对:
      1 : [2,3,4,5]
      :是在Haskell利弊运营商)
    • Thunk-延迟计算,在需要时会生成List节点。
  • 它支持以下操作:
    • nil-构造一个空列表。
    • 缺点-构建一个缺点单元格。
    • thunk-给定一个不带参数且返回Nil或Cons的函数,构造一个Thunk。
    • 强制-给定一个List节点:
      • 如果是Nil或Cons,只需将其返回即可。
      • 如果是Thunk,则调用其函数以获取Nil或Cons。用该Nil或Cons替换该thunk,然后将其返回。
        注意:用其强制值替换thunk是“ lazy”定义的重要组成部分。如果跳过此步骤,则上面的斐波那契算法将太慢。
    • 空-查看List节点是否为Nil(强制后)。
    • head(又名“ car”)-获取列表的第一项(如果为Nil,则抛出合适值)。
    • tail(aka“ cdr”)-在列表的开头之后获取元素(如果为Nil,则抛出拟合)。
    • zipWith-给定一个二进制函数(例如(+))和两个(可能是无限个)列表,请将函数应用于列表的相应项。例:
      zipWith (+) [1,2,3] [1,1,10] == [2,3,13]
    • 拿-给定一个数字N和一个(可能是无限的)列表,抓住列表的前N个项目。
    • 打印-打印列表中的所有项目。给定一个长列表或无限列表时,此选项应递增工作。
  • fibs在自己的定义中使用自身。设置懒惰的递归有点棘手。您需要执行以下操作:

    • 为...分配一个钱fibs。现在将其保留为虚拟状态。
    • 定义thunk函数,该函数取决于对的引用fibs
    • 使用其功能更新thunk。

    您可能想通过定义一个函数来隐藏此管道,fix该函数使用自己的返回值调用List-returning函数。考虑打个na,这样这个想法就可以成立。

  • 多态性(使用任何类型的项目列表的能力)不是必需的,但是请查看是否可以找到一种使用您的语言惯用的方法。

  • 不用担心内存管理。即使是带有垃圾回收的语言,也倾向于随身携带您永远不会再使用的对象(例如,在调用堆栈上),因此,如果程序在遍历无限列表时泄漏内存,也不要感到惊讶。

请随意偏离这些准则,以适应您的语言特点,或探索替代方法。

规则:

  • 选择一种您不太熟悉的语言。我不能“要求”这个,因此“荣誉系统”标记。但是,选民可以检查您的历史记录,以查看您所张贴的语言。
  • 不要使用您语言的内置懒惰列表支持来做所有事情。发表实质性或至少有趣的内容。

    • Haskell即将淘汰。也就是说,除非您执行以下操作:

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

      注意:Haskell的非严格评估不是禁止的,但是您的懒惰列表实现不应直接从那里获取其功能。实际上,看到不需要懒惰的高效,纯功能解决方案会很有趣。

    • 蟒蛇:

      • 不要使用itertools。
      • 生成器很好,但是使用它们时,您必须找到某种方式来记住强制值。

调用zipWith两个不同长度的列表时,行为是什么?
2011年

@balpha:我选择了Haskells行为:如果列表中的任何一个为nil,则返回nil。
2011年

@balpha:在Haskell中,当任何一个列表中的项目用完时,zipWith停止。因此,zipWith (+) [1,2,3,4,5] [0,0,0] == [1,2,3]。但是,这对于上面的Fibonacci算法并不重要,因为zipWith的两个参数都是无限列表。
乔伊·亚当斯

这项挑战有一个隐藏的惊喜:您需要做一些特殊的事情才能fibs正确实施,因为这取决于自身。我更新了问题,以详细介绍延迟递归。 FUZxxl自己弄清楚了。
乔伊·亚当斯

打印大列表时,“递增工作”是什么意思?
Lowjacker

Answers:


6

后记

我以前玩过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并可能设置其他值。经过递归评估直到我们有了一个nilcons,它也(通过undef)将其删除,/func并在适用的情况下将/data。最后一步是不是绝对必要的(/func并且/data也只是被忽略),但在离开这一步了会泄漏甚至更多的内存:)
balpha

6

C

我是C语言的初学者,实际上这是我用C语言编写的第一篇真正的文章。它编译时没有任何警告,并且在我的系统上运行良好。

如何建造

首先,从我的服务器上获取压缩包。它包含一个makefile,因此只需运行make即可构建它,然后再make run运行它。然后,程序将打印出前93个斐波那契数字的列表。(在编号94之后,无符号的64位整数溢出)

说明

程序的核心是文件lazy-list.c。在相应的头文件中,我定义了struct list,这是我们的惰性列表。看起来像这样:

enum cell_kind {
  NIL,
  CONS,
  THUNK
};

typedef enum cell_kind cell_kind;

typedef long int content_t;

struct list {
  cell_kind kind;
  union {
    struct {
      content_t* head;
      struct list* tail;
    } cons;
    struct {
      struct list* (*thunk)(void*);
      /* If you want to give arguments to the thunk, put them in here */
      void* args;
    } thunk;
  } content;
};

该成员kind是一种标签。无论我们是重新整理列表的结尾(NIL),还是标记已经评估的单元格(CONS)或重排(THUNK)。然后,进行联合。它是

  • 一个已经评估过的具有值和尾部的单元格
  • 或具有功能指针和结构的thunk,如果需要,可以包含该函数的一些参数。

联合的内容由标签声明。如果标记为NIL,则联合的内容不确定。

通过定义以上规范中提到的辅助函数,通常可以从使用中抽象出列表定义。您只需致电nil()即可获得一个空列表,而无需自己创建一个。

三种最有趣的功能是zipWithtakefibonaccis。但是我不想解释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,但是为​​了创建这些单元格,我们还需要一个指向thunk的指针。为了解决该问题,我NULL先将指向thunk的指针填充到thunk中,然后在创建thunk后将其更改为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;

可能的改进

  • 我的解决方案不使用多态。尽管可能,但我的C语言技能不足以知道如何使用它。取而代之的是,我使用了一个type content_t,它可以更改为任何适合的类型。
  • 可以从列表的定义中提取thunk,然后仅抽象地使用它,但是这样做会使代码更复杂。
  • 可以改善我的代码中不好的C部分。

不错的提交方式,尤其是对于C初学者而言。关于多态,如果您愿意在堆上分配所有内容,则可以将其void*用作类型content_t
Casey

@Casey:非常感谢。我也想使用它void*,但是我认为那会避开类型系统。使用模板不可能吗?
2011年

C没有模板,即C ++,但是可以,您可以使用C ++模板使其通用。
凯西

我不知道如何使用它们。但是我猜想,就类型系统而言,C有点受限制。-如果没有使用void*和朋友,我什至无法编写此程序。
FUZxxl 2011年

1
“成员kind有点像标签。”您可以称之为它tag,因为这是该概念的一个公认的术语(例如,带标签的工会Spineless Tagless G-machine。另一方面,“种类”在。哈斯克尔方面:一个类型的类型 Int有样*[]有一种* -> *(,)具有那种* -> * -> *
乔伊·亚当斯

5

C ++

这是我用C ++编写过的最大的东西。我通常使用Objective-C。

它是多态的,但永远不会释放任何东西。

我的main函数(以及addZipWith结束的函数)看起来像这样:

int add(int a, int b) {return a + b;}

int main(int argc, char **argv) {
    int numFib = 15; // amount of fibonacci numbers we'll print
    if (argc == 2) {
        numFib = atoi(argv[1]);
    }

    // list that starts off 1, 1...
    LazyList<int> fibo = LazyList<int>(new Cons<int>(1,
                     new LazyList<int>(new Cons<int>(1))));
    // zip the list with its own tail
    LazyList<int> *fiboZip = LazyList<int>::ZipWith(add, &fibo, fibo.Tail());
    // connect the begin list to the zipped list
    fibo.Tail() -> ConnectToList(fiboZip);

    // print fibonacci numbers
    int *fibonums = fibo.Take(numFib);    
    for (int i=0; i<numFib; i++) cout << fibonums[i] << " ";

    cout<<endl;

    return 0;
}

这给

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

这些类的工作方式如下:

make a thunk:    LazyList<T>(new Thunk<T>( function, *args )) 
make empty list: LazyList<T>(new Nil<T>())
make cons:       LazyList<T>(new Cons<T>( car, *cdr ))

list empty:      list.Empty()
list's head:     list.Head()
list's tail:     list.Tail()
zipWith:         LazyList<T>::ZipWith(function, a, b)
take:            list.Take(n)
print:           list.Print()

完整资料:在这里。这是一团糟,主要是因为它在一个大文件中。

编辑:更改链接(旧的链接已死)。


3
出色的工作,感谢您从字面上“适当地投入” :-)我绝不是C ++专家,但是实现Thunk的一种更C ++-y的方式可能是使用函数对象(也称为“ functor”)(是,请重载()运算符),并使用继承来避免必须使用void*请参阅此处的简单示例。
乔伊·亚当斯

完整的源链接现在已失效。您可以重新上传吗? gist.github.com是放置它的好地方。
乔伊·亚当斯

@JoeyAdams:完成了。
marinus 2012年

4

蟒蛇

不使用生成器来实现列表,而只是实现__iter__要与一起使用的方法for

class Node(object):
    def __init__(self, head, tail):
        self.__head__ = head
        self.__tail__ = tail

    def force(self):
        return self

    def empty(self):
        return False

    def head(self):
        return self.__head__

    def tail(self):
        return self.__tail__

    def zip_with(self, func, other):
        def gen_func():
            if other.empty():
                return other
            return Node(func(self.head(), other.head()), self.tail().zip_with(func, other.tail()))
        return Thunk(gen_func)

    def __iter__(self):
        while not self.empty():
            yield self.head()
            self = self.tail()

    def append(self, other):
        while not self.tail().empty():
            self = self.tail()
        self.__tail__ = other

    def take(self, n):
        if n == 0:
            return NullNode()
        else:
            return Node(self.__head__, self.__tail__.take(n - 1))

    def _print(self):
        for item in self:
            print item

class NullNode(Node):
    def __init__(self):
        pass

    def empty(self):
        return True

    def head(self):
        raise TypeError("cannot get head of nil")

    def tail(self):
        raise TypeError("cannot get tail of nil")

    def zip_with(self, func, other):
        return self

    def append(self, other):
        raise TypeError("cannot append to nil")

    def take(self, n):
        return self

class Thunk(Node):
    def __init__(self, func):
        self.func = func

    def force(self):
        node = self.func()
        self.__class__ = node.__class__
        if not node.empty():
            self.__head__ = node.head()
            self.__tail__ = node.tail()
        return self

    def empty(self):
        return self.force().empty()

    def head(self):
        return self.force().head()

    def tail(self):
        return self.force().tail()

    def take(self, n):
        return self.force().take(n)

斐波那契列表是这样创建的:

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

1
这很漂亮。我最喜欢的台词是self.__class__ = node.__class__。请注意,这在达到2971215073(长)时会遇到NotImplemented异常,这显然是int .__ add__的无效参数。要支持大整数,请这样做fib.append(fib.zip_with(lambda a,b: a+b, fib.tail()))
乔伊·亚当斯

1
为什么不能将其附加到空或重击?
PyRulez 2014年

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)

然后,代码非常简单,可以通过hack重置thunk函数来设置递归fib。

def nil_()
  return Array[]
end

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

def thunk(f)
  return Array[f]
end

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

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

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

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

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

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

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

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

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

print(take(40, fibs))

您可以使用[...]代替Array[...]
Lowjacker

3

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

我使用在线编译器测试了代码,因为无法在Windows上轻松安装Go。


这是非常好的和简单的。但是,您可以使用一个标记,而不是3个布尔值,其可能的值是由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))))),它超出内存
明腾

我尝试过Cons(0, Cons(1, Thunk(func() List { return ZipWith(Plus, Thunk(Fibs), Thunk(func() List { return Tail(Fibs()) })) }))),但是得到了无效的内存地址错误
Ming-Tang

1
既然你仍然在学习围棋:你可以做一些更优雅的代码比这使用接口的列表和单独类型的thunk等
cthom06

3

水晶

尽管遵循了GitHub存储库,但到目前为止,我从未真正使用过 Crystal。Crystal是具有完整类型推断功能的静态类型Ruby变体。尽管已经有了Ruby的答案,但Crystal的静态类型使我不得不使用多态性而不是数组来表示节点。因为Crystal不允许修改self,所以我创建了一个名为的包装器类,该包装器Node将包装所有其他包装并管理包装。

随着班,我创建的构造函数lnilconsthunk。之前,我也从未使用过Ruby来编写20行以上的脚本,因此这些块内容使我大吃一惊。

我基于Go答案fib功能。

class InvalidNodeException < Exception
end

abstract class LazyValue
end

class LNil < LazyValue
    def empty?
        true
    end

    def force!
        self
    end

    def head
        raise InvalidNodeException.new "cannot get head of LNil"
    end

    def tail
        raise InvalidNodeException.new "cannot get tail of Nil"
    end

    def take(n)
        Node.new self
    end
end

class Cons < LazyValue
    def initialize(@car, @cdr)
    end

    def empty?
        false
    end

    def force!
        @cdr.force!
        self
    end

    def head
        @car
    end

    def tail
        @cdr
    end

    def take(n)
        Node.new n > 0 ? Cons.new @car, @cdr.take n-1 : LNil.new
    end
end

class Thunk < LazyValue
    def initialize(&@func : (-> Node))
    end

    def empty?
        raise Exception.new "should not be here!"
    end

    def force!
        @func.call()
    end

    def head
        self.force!.head
    end

    def tail
        self.force!.tail
    end

    def take(n)
        self.force!.take n
    end
end

class Node
    def initialize(@value = LNil.new)
    end

    def empty?
        self.force!
        @value.empty?
    end

    def force!
        @value = @value.force!
        self
    end

    def head
        self.force!
        @value.head
    end

    def tail
        self.force!
        @value.tail
    end

    def take(n)
        self.force!
        return @value.take n
    end

    def print
        cur = self
        while !cur.empty?
            puts cur.head
            cur = cur.tail
        end
    end
end

def lnil
    Node.new LNil.new
end

def cons(x, r)
    Node.new Cons.new x, r
end

def thunk(&f : (-> Node))
    Node.new Thunk.new &f
end

def inf(st=0)
    # a helper to make an infinite list
    f = ->() { lnil }
    f = ->() { st += 1; cons st, thunk &f }
    thunk { cons st, thunk &f }
end

def zipwith(a, b, &f : Int32, Int32 -> Int32)
    thunk { a.empty? || b.empty? ? lnil :
            cons f.call(a.head, b.head), zipwith a.tail, b.tail, &f }
end

def fibs
    # based on the Go answer
    fibs2 = ->(a : Int32, b : Int32) { lnil }
    fibs2 = ->(a : Int32, b : Int32) { cons a+b, thunk { fibs2.call b, a+b } }
    cons 0, cons 1, thunk { fibs2.call 0, 1 }
end

fibs.take(40).print
zipwith(inf, (cons 1, cons 2, cons 3, lnil), &->(a : Int32, b : Int32){ a+b }).print

2

我稍微修改一下规则是因为这里没有.NET解决方案,或更普遍的是OOP解决方案,除了Python中使用继承的解决方案,但这与我的解决方案有很大不同,使得两者都很有趣(特别是因为Python允许修改 self实例,从而使thunk实现更简单)。

所以这是C#。全面披露:我距离C#的初学者还差得很远,但是我有一段时间没接触过该语言了,因为我目前在工作中没有用过它。

重点:

  • 所有类(NilConsThunk)由普通的抽象基类派生,List

  • Thunk类使用信封-信件模式。self.__class__ = node.__class__由于this引用不能在C#中修改,因此,这实际上在Python源中模拟了赋值。

  • IsEmptyHeadTail是属性。

  • Print通过返回thunk,所有适当的函数都以递归和延迟的方式实现(除了,不能延迟)。例如,这是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声明。no,没有骰子。

现在,我对自己的实现并不满意。到目前为止,我很高兴,因为上述所有内容都非常简单。但是。我觉得的定义Fib不必要地复杂,因为我需要将参数包装到thunk中以使其起作用:

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代码的作用-但它没有用(=拟合)。

/编辑:乔伊指出了该解决方案中的明显缺陷。但是,用thunk替换第二行也会产生错误(Mono segfaults;我怀疑Mono无法很好处理的堆栈溢出):

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

完整的源代码可以在GitHub上找到要点


“不幸的是,C#没有多重调度”-您可以使用事件来获得效果,尽管这很hacky。
彼得·泰勒

具有讽刺意味的是,惰性评估需要状态才能实现。 fib.ZipWithfib.Tail使用旧的fib,它仍然存在[0,1]并且不会改变。因此,您得到了[0,1,1](我认为),并且您的Take函数不允许您从null中吸取(尽管,Haskell的take可以)。尝试将第二行的右值包装在一个thunk中,这样它将引用新的fib而不是旧的。
乔伊·亚当斯

@彼得是的; 您还可以使用Visitor模式实施多个调度,但我希望解决方案保持简单。
Konrad Rudolph

@Joey Duh。现在真是令人眼花obvious乱。但是,重击解决方案仍然不起作用(请参阅更新的答案),但是我现在很忙,无法进行调查。
Konrad Rudolph

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允许函数控制是否在调用函数或将其打包为thunk之前评估其参数。对于此代码,我可以省略按函数调用的奇数。

Pico没有类型推断。我想了一会儿,但由于函数调用的奇怪性,我遇到了一个问题。我想出了一个声明,即类型必须对绑定变量名的存在进行编码。但我主要是在考虑如何使Hindley-Milner类型推断适应无突变的Pico子集。主要思想是,如果存在多个可能的绑定则类型检查器将返回多个可能的方案;如果至少存在一种可能的类型方案则类型检查成功。一种可能的方案是没有类型分配冲突的方案。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.