Little Schemer:只支持长度≤2的列表的写函数

时间:2015-04-03 16:22:29

标签: recursion scheme y-combinator the-little-schemer anonymous-recursion

The Little schemer 一书中,我们发现此函数仅支持长度小于或等于 1 的列表:

 (((lambda (mk-length)                                  ; A.
      (mk-length mk-length))
  (lambda (mk-length)
      (lambda (l)
          (cond
              ((null? l ) 0)
              (else (add1 ((mk-length  eternity ) (cdr l))))))))
 '(1))

我想逐步学习,并希望编写类似的函数,只支持长度小于或等于 2 的列表。

请不要通过提供以下代码来回答这个问题:

(((lambda (mk-length)                                   ; B.
      (mk-length mk-length))
  (lambda (mk-length)
      (lambda (l)
          (cond
              ((null? l) 0 )
              (else (add1((mk-length mk-length) (cdr l))))))))
 '(a b c d))

因为此函数支持任何长度。

我已经知道如何编写这样的函数:

(((lambda (mk-length)                                   ; C.
      (mk-length
          (mk-length (mk-length eternity))))
  (lambda (length)
      (lambda (l)
          (cond
              ((null? l) 0)
              (else (add1 (length (cdr l))))))))
 '(1 2)) ;;

实现我的目标。但是这段代码距离第一段代码还有一步之遥。

也许,我不应该改变:

(lambda (mk-length)                                     ; D.
      (mk-length mk-length)

2 个答案:

答案 0 :(得分:2)

Lisp中的列表(Scheme,Common Lisp等)是由cons单元格和一个特殊的(空列表)构建的。那,只要你有一个列表的东西,它就是

  • 空列表()
  • 一个cons单元格(也称为一对),其中 car 是该列表的第一个元素,该对的 cdr 是其余的列表。

因为有两种类型的列表,列表上的递归算法通常只回答两个问题:

  • 空列表的结果是什么?
  • 列表其余部分的结果是什么?如何将结果变成整个列表的结果?

对于 length ,这意味着:

  • 空列表的长度为0.
  • 当列表的 rest 的长度为 n 时,整个列表的长度为 n + 1

The Little Schemer 中提出的解决方案类型首先开发出一个解决第一种情况的解决方案,这意味着它适用于长度≤0的列表。只要您有一个解决方案来处理这两种情况(正确),您就有了适用于任何长度列表的解决方案。没有真正的增量解决方案可以将函数扩展为长度≤2的列表。

你可以 ,我想,做了类似的事情:

 (((lambda (mk-length)
      (mk-length mk-length))
  (lambda (mk-length)
      (lambda (l)
          (cond
              ((null? l ) 0)
              (else 1)))))

这适用于长度为0的列表和长度为1的列表,对于所有其他列表,它将不正确,但对于所有其他列表,它将返回1。除非你改变递归的结构,否则我认为这是你能做的最好的。如果您愿意更改递归的结构,可以执行:

 (((lambda (mk-length)
      (mk-length mk-length))
  (lambda (mk-length)
      (lambda (l)
          (cond
              ((null? l) 0)
              ((null? (cdr l)) 1)
              (else (add1 ((mk-length  eternity ) (cdr l))))))))

但你真的不应该采用这种方法,因为现在你处理三种不同情况下的列表而不是两种情况,这几乎就是你想做的事情。

Python的翻译

  

这可以用python或某种流程语言重写吗?在递归时仍然难以对自动创建的乐趣进行成像。

相同的原理适用于Python或任何支持高阶函数的语言。在Python中本地定义函数然后调用它而不是直接调用lambda表达式更加惯用,所以这对你来说可能更具可读性:

def length(l):
    def driver(recurse):
        "Returns the result of calling `recurse` with itself."
        return recurse(recurse);
    def zeroForEmptyListOrElseOnePlusRecurse(recurse):
        """Returns a function that returns 0 if its input is the
        empty list, or else returns one plus whatever calling 
        `recurse(recurse)` with the tail of the list returns."""
        def length(l):
            """Returns 0 if l is the empty list, and one plus
            the results of `(recurse(recurse))(l)` otherwise."""
            if l == []:
                return 0;
            else:
                _, *rest = l
                return 1+(recurse(recurse))(rest)
        return length
    return (driver(zeroForEmptyListOrElseOnePlusRecurse))(l)

print(length([1,2,3])) # 3
print(length([1,2]))   # 2
print(length([1]))     # 1
print(length([]))      # 0

答案 1 :(得分:2)

TL; DR: (mk-length A)(在cond表单内)计算适用于长度列表的 length 函数 0 并将使用(A A)来计算将用于处理尾部的 length 函数(即{{1}的结果})参数列表。

您的第一个代码段((cdr ...))仅适用于长度 0 1 的列表。要使其适用于 2 ,请替换

;A.

                               (mk-length eternity)               ; length≤1

的工作原理。

(注意: (mk-length ; (2) ; length≤2 (lambda (x) (mk-length eternity)))本身会计算(mk-length eternity),但整体功能变为length≤0; 是所有进一步的length≤1条评论引用的内容到。)

密切关注

length≤i

我们可以看到 (((lambda (mk-length) (mk-length mk-length)) (lambda (mk-length) ; (1) (lambda (l) (cond ((null? l ) 0) (else (add1 ((mk-length ; (2) ; length≤2 (lambda (x) (mk-length eternity)) ) (cdr l)))))))) '(1 2)) (mk-length ...)的结果用于处理;(2),而 (cdr l) 用于argument在处理mk-length时,;(2)处的mk-length将替换(cddr l)

如果使用(mk-length eternity)(与您的第一个代码一样),(cdr l)处理正常,但((eternity eternity) (cddr l))自然会失败。

如果使用(mk-length (lambda (x) (mk-length eternity))),则(cdr l)处理正常,然后((lambda (x) (mk-length eternity)) (lambda (x) (mk-length eternity))) = (mk-length eternity)用于处理(cddr l),这也是正常的(因此,长度 2 处理正确),然后((eternity eternity) (cdddr l))自然失败(长度 3 及以上)。

因此,要处理最多三个元素的列表,

                              ((mk-length   ; (2)                 ; length≤3
                                  (lambda (x) (mk-length 
                                                (lambda (x) (mk-length eternity)))) )

可以使用:

    (define (eternity x) (- 1))    ; to get an error instead of looping

    (((lambda (mk-length)
          (mk-length mk-length))
      (lambda (mk-length)
          (lambda (l)
              (cond
                  ((null? l ) 0)
                  (else (add1 ((mk-length   ; (2)                 ; length≤3
                                  (lambda (x) (mk-length
                                               (lambda (x) (mk-length eternity)))) )
                               (cdr l))))))))

      '(1 2 3))        ; => 3

    ; ...........
    ; '(1 2 3 4))      ; => **error**

正如你所推测的那样,这个是使用

的临时步骤
                     (mk-length (lambda (x) (mk-length x)))   ; (2)       ; length≤∞

将处理列表的下一个元素转换为

                     ((lambda (x) (mk-length x)) (lambda (x) (mk-length x)))
    =
                     (mk-length (lambda (x) (mk-length x)))

因此适用于每个列表,无论其长度如何。

通过eta转换,这只是(mk-length mk-length)