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

Little Schemer: write function that only supports lists of length ≤ 2

小谋士一书中,我们发现这个函数只支持长度小于等于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)

Lisp 中的列表(Scheme、Common Lisp 等)是由 cons 单元和一个特殊的(空列表)构建的。也就是说,只要你有一个列表,它就是 either:

  • 空列表()
  • 一个 cons cell(也称为对),其中 car 是列表的第一个元素,cdr这对是列表的其余部分。

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

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

对于长度,这意味着:

  • 空列表的长度为0。
  • 当list的rest的长度为n时,整个list的长度为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 的列表,它对所有其他列表都是不正确的,但它对所有其他列表都是 return 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

Can this thinking rewrite by python or something process language? It is still hard to image a auto created fun in recurse.

同样的原则适用于 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

TL;DR: (mk-length <b>A</b>)(里面的cond 形式) 计算适用于长度列表 0length 函数,并将使用 (<b>A</b> A) 计算将用于处理尾部的 length 函数(即结果(cdr ...)) 的参数列表。


您的第一个代码片段 (;A.) 仅适用于长度为 01 的列表。要使其也适用于 2,请替换

                               (mk-length <b>eternity</b>)               ; length≤1

                               (mk-length   ; (2)                 ; length≤2
                                  <b>(lambda (x) (mk-length eternity))</b>)

有效。

(注意:(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
                                  <b>(lambda (x) (mk-length eternity))</b> )
                               (cdr l))))))))
     '(1 2))

我们可以看到(mk-length <b>...</b>);(2)的结果是用来处理的(cdr l),而 ;(2) 处的 argumentmk-length 将在处理 [=] 时替换该调用中的 mk-length 27=].

如果使用 (mk-length <b>eternity</b>)(如您的第一个代码),(cdr l) 处理正常但是 ((<b>eternity</b> eternity) (cddr l)) 自然失败。

如果使用(mk-length <b>(lambda (x) (mk-length eternity))</b>)(cdr l) 处理正常然后 (<b>(lambda (x) (mk-length eternity))</b> (lambda (x) (mk-length eternity))) = (mk -length <b>eternity</b>) 用于处理 (cddr l) 这也是可以的(因此,长度 2 被正确处理), 然后 ((<b>eternity</b> 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 <b>(lambda (x) (mk-length x))</b>)   ; (2)       ; length≤∞

在处理列表的下一个元素时变成

                     (<b>(lambda (x) (mk-length x))</b> (lambda (x) (mk-length x)))
    =
                     (<b>mk-length</b> (lambda (x) (mk-length x)))

因此适用于 每个 列表,无论其长度是多少。

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