(define (list-without-last-pair items)
(let ((s (cdr items)))
(if (null? s)
null
(cons (car items)
(list-without-last-pair s)))))
(define (only-last-pair items)
(let ((s (cdr items)))
(if (null? s)
(car items)
(only-last-pair s))))
(define (reverse items)
(if (null? items)
null
(cons (only-last-pair items)
(reverse (list-without-last-pair items)))))
我的main方法和辅助方法中有很多代码重复。如何避免这种情况并改进解决方案?
预期输出:(reverse (list 1 2 3))
=> (3 2 1)
答案 0 :(得分:7)
将列表的“后端”用于任何事情是非常罕见的,它既低效又容易导致相当复杂的代码(正如您所注意到的那样)。
为了反转列表,您可以保存第一个元素,反转其余部分,然后将旧的第一个元素放在“反向休息”的后面。
(这与您正在进行的操作相同,但在列表的另一端。)
即,
(define (reverse lst)
(if (null? lst)
lst
(append (reverse (cdr lst)) (list (car lst)))))
这是非常低效的,所以通常你会使用尾递归版本(SICP中的“迭代过程”)。
如果将它分解为主函数和“帮助器”,则最容易理解尾递归实现:
(define (reverse-helper lst acc)
(if (null? lst)
acc
(reverse-helper (cdr lst) (cons (car lst) acc))))
(define (reverse lst)
(reverse-helper lst '()))
主要区别在于,在acc
参数中构建结果意味着我们可以使用cons
而不需要重复遍历结果以在其后面添加内容(这就是{ {1}}确实如此。
答案 1 :(得分:3)
如果您使用通常的car
和cdr
程序处理列表,则可以从正面到背面处理它。使用cons
构建列表会将其从构造回到前面。因此,您可以将这两种行为结合起来以反转列表;只需查看列表并cons
将car
添加到累加器:
(define (reverse lst)
(let loop ((lst lst) (acc null))
(if (null? lst)
acc
(loop (cdr lst) (cons (car lst) acc)))))
请注意,loop
不是预定义的过程或关键字(与Common Lisp相对),而只是我为内部过程选择的名称;上面的代码与
(define (reverse lst)
(define (loop lst acc)
(if (null? lst)
acc
(loop (cdr lst) (cons (car lst) acc))))
(loop lst null))
或者,如果您想避免使用2个过程,可以使用具有默认值的可选参数:
(define (reverse lst (acc null))
(if (null? lst)
acc
(reverse (cdr lst) (cons (car lst) acc))))
答案 2 :(得分:0)
定义reverse
就像使用cons
(define (reverse xs)
(foldl cons '() xs))
要了解它的工作原理,请评估折叠
(reverse '(1 2 3)) ;; ⇒ ?
;; first iteration
(cons 1 '()) ;; ⇒ '(1)
;; second iteration
(cons 2 '(1)) ;; ⇒ '(2 1)
;; third iteration
(cons 3 '(2 1)) ;; ⇒ '(3 2 1)
在评论中,您询问了如何实施foldl
(define (foldl f y xs)
(if (empty? xs)
y
(foldl f
(f (car xs) y)
(cdr xs))))
如果您不熟悉折叠,我认为使用sum
函数演示它们是最简单的。
如果你想总结一个数字列表1 2 3 4
,你会怎么做?可能类似
1 + 2 + 3 + 4
你看到每个之间放置+
吗?让我们看看我们如何评估这个
((1 + 2) + 3) + 4
(3 + 3) + 4
6 + 4
⇒ 10
好foldl
就是这样做的。它需要二进制过程,初始值和列表。在我们的案例中,我们将使用过程+
和0
的初始值进行演示。这次我们将使用s表达式((+ x y)
而不是中缀x + y
)来显示评估。
(foldl + 0 '(1 2 3 4))
(+ 4 (+ 3 (+ 2 (+ 1 0))))
(+ 4 (+ 3 (+ 2 1)))
(+ 4 (+ 3 3))
(+ 4 6)
⇒ 10
这个初始值很重要,因为如果输入是一个空列表,我们需要知道期待什么样的值
(foldl + 0 '())
;; ⇒ 0
所以,让我们用折叠来定义sum
(define (sum xs) (foldl + 0 xs))
(sum '(1 2 3 4)) ;; ⇒ 10
(sum '()) ;; ⇒ 0
总和很容易让我们理解,因为它们对我们来说非常熟悉,但reverse
程序可能不那么清楚。折叠减少到单个值,在我们的例子中,我们将输入列表减少到单个输出列表。
让我们快速重新审视sum
评估。请记住,我们折叠的程序为+
,初始值为0
(foldl + 0 '(1 2 3 4))
(+ 4 (+ 3 (+ 2 (+ 1 0))))
(+ 4 (+ 3 (+ 2 1)))
(+ 4 (+ 3 3))
(+ 4 6)
⇒ 10
现在让我们看看写出的reverse
评估。这里我们折叠的过程是cons
,初始值是'()
(空列表)
(foldl cons '() '(1 2 3))
(cons 3 (cons 2 (cons 1 '())))
(cons 3 (cons 2 '(1)))
(cons 3 '(2 1))
⇒ '(3 2 1)