【问题标题】:Little Schemer: write function that only supports lists of length ≤ 2Little Schemer:编写只支持长度≤2的列表的函数
【发布时间】:2015-06-08 18:19:34
【问题描述】:

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)) ;;

实现我的目标。但是这段代码距离第一个sn-p还不止一步。

也许,我不应该改变:

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

【问题讨论】:

  • 问题到底是什么?此外,您可能会在答案(或链接的问题)中找到答案:Little Schemer: length0 and mk-length。我不会将其标记为重复,因为我不确定你在这里问的到底是什么。
  • 我的意思是做与我在这里粘贴的第三个代码相同的事情。但应该来自第一个代码的想法。这意味着不要在第 1 个代码中更改第 4 个代码。
  • 您能否提供有关为什么您正在寻找这个的任何见解。这实际上是一个不平凡的问题,因为这种递归练习的重点是,由于列表是递归数据结构,因此您只需要处理两种情况:空列表和非空列表。
  • 第二个代码只需将(mk-length eternity )改为(mk-length mk-length),即可实现任意长度。很难想,所以我想实现一个只是<=2 的版本。在第一个代码的基础上
  • 这是this question 的副本,但那里的答案是length≤∞,而不是length≤2

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


【解决方案1】:

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


您的第一个代码 sn-p (;A.) 仅适用于长度为 01 的列表。为了使其也适用于 2,替换

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

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

有效。

(注意:(mk-length eternity) 本身计算了length≤0,但整体函数变成了length≤1这个是所有进一步的length≤i cmets 所指的。)

仔细观察

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

我们可以看到;(2)处的(mk-length <b>...</b>)的结果用于处理(cdr l),而;(2)处的argumentmk-length将替换mk-length在处理(cddr l)时的那个调用中。

如果使用(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≤∞

依次将列表的下一个元素处理成

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

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

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

【讨论】:

  • 这个答案对我来说确实值得一看。 =) 感谢您的回答本身并通知我!
  • @FilippW。不客气,感谢您这样做的动力(即您对这组问题的回答和编辑)。回复:你的编辑,那些不是错别字。我的意思是最初的l,所以在第二次迭代中调用(... (cdr l)),当这个l实际上是(cdr l)对于原始l,是(.... (cddr l))。第三个是(cdddr l)。这有点草率和令人困惑,但仅使用 (cdr l) 也不会这样做。稍后我会想办法澄清这件事。谢谢!
  • 哦,可惜我没有立刻掌握它。不过感谢您的解释!
【解决方案2】:

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

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

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

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

对于长度,这意味着:

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

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

【讨论】:

  • 我想你已经回答了我的问题!您能否详细说明如何使用您的答案来获取代码 (mk-length mk-length) 以适用于任何长度的列表?将mk-length 替换为第一个 lambda 很容易想到,但是很难再次考虑重新替换内部。对我来说,考虑它不是天性。但是对其他人来说很容易。
  • 我不确定你在问什么。如果您在我的回答中将(mk-length eternity) 替换为(mk-length mk-length),它应该可以工作。但是你的 cond 中仍然会有一个多余的子句。
  • 我可能在问(add1 ((mk-lenght mk-lenght) (cdr l)) 中的mk-length 是如何工作的?这种思维可以用python或其他过程语言重写吗?在递归中仍然很难想象一个自动创造的乐趣。
  • @jiamo 我已经添加了 Python 翻译。
猜你喜欢
  • 1970-01-01
  • 2012-09-06
  • 2014-08-28
  • 1970-01-01
  • 2015-10-22
  • 2011-11-10
  • 1970-01-01
  • 2022-01-15
  • 2010-12-15
相关资源
最近更新 更多