(注:答案在本文底部)第二个函数,
(define (swap-ends x)
(if (or (equal (length x) 0) (equal (length x) 1))
x
(cons (first (swap-ends (rest x)))
(swap-ends (cons (first x)
(rest (swap-ends (rest x))))))))
“你问它是做什么的?”,if
语句的替代子句数据流图如下:
/-> first ----------------------> cons
x --> first ------/-------------> cons --> swap --/
\-> rest -> swap ---> rest ---/
(从左到右按箭头方向)。因此,
[] -> []
[1] -> [1]
/-> 2 -----------------------> [2,1]
[1,2] --> 1 --------/------------> [1] --> [1] --/
\-> [2] -> [2] ---> [] ---/
/-> 3 -------------------------> [3,2,1]
[1,2,3] --> 1 ------------/----------> [1,2] --> [2,1] --/
\-> [2,3] -> [3,2] -> [2] --/
/-----> 4 ----------------------------> [4,2,3,1]
[1,2,3,4] --> 1 ------------/---------------> [1,3,2] -> [2,3,1] -/
\-> [2,3,4] -> [4,3,2] -> [3,2] -/
到目前为止,它确实交换了列表的末尾元素。让我们通过自然归纳来证明它,
< p > < em > < strong > < code > true(N-1)=> true(N) :
/-> N --------------------------------------> [N,2..N-1,1]
[1..N] --> 1 ---------/-----------> [1,3..N-1,2] -> [2,3..N-1,1] -/
\-> [2..N] -> [N,3..N-1,2] /
-> [3..N-1,2] -/
所以它被证明了。因此,我们需要设计一个数据流图,在假设反转一个(N-1)长度的列表的情况下,可以反转一个N长度的列表:
[1..N] --> 1 ------------------------------------\
\-> [2..N] -> [N,N-1..2] -> N -------------\------------------\
\-> [N-1,N-2..2] -> [2..N-1] -> [1..N-1] -> rev -> cons
这给我们提供了实现。
(define (rev ls)
(cond
((null? ls) ls)
((null? (rest ls)) ls)
(else
(cons (first (rev (rest ls)))
(rev (cons (first ls)
(rev (rest (rev (rest ls))))))))))
(rev '(1 2 3 4 5))
在评论中的Haskell翻译非常自然地遵循了图表。它实际上是可读的:
a
是最后一个元素,
b
是反转的“核心”(即输入列表除去第一个和最后一个元素),所以我们反转了反转的核心,加上第一个元素得到输入列表的
butlast部分,然后将其反转并添加最后一个元素。
简单明了。 :)
2020更新: 这里有一个基于
代码的Scheme版本,由
@Rörd从
评论中提供,使其同样易读,使用参数解构代替Haskell的模式匹配。
(define (bind lst fun)
(apply fun lst))
(define (rev lst)
(if (or (null? lst)
(null? (cdr lst)))
lst
(bind lst
(lambda (first . rest)
(bind (rev rest)
(lambda (last . revd-core)
(cons last (rev (cons first (rev revd-core))))))))))