在 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)
答案 0 :(得分:2)
Lisp中的列表(Scheme,Common Lisp等)是由cons单元格和一个特殊的(空列表)构建的。那,只要你有一个列表的东西,它就是 :
因为有两种类型的列表,列表上的递归算法通常只回答两个问题:
对于 length ,这意味着:
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中本地定义函数然后调用它而不是直接调用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)
。