首先,您的代码通过将cdr 更改为cadr 来轻松修复:
(define (partition pivot lst)
((lambda (s) (s s lst list))
......)) ; ^^^^ `list` the top continuation
(define (quicksort lst)
(if (null? lst) '()
(let* ((y (car lst))
(pn (partition y (cdr lst))))
(append (quicksort (car pn))
(list y)
(quicksort (cadr pn))))))
;; ^^^^ cdr --> cadr
因为partition 中使用的顶部延续是list,所以调用
(partition pivot lst)
相当于调用
(list { x IN lst SUCH THAT x < pivot }
{ x IN lst SUCH THAT x >= pivot } )
({...} 中的部分是伪代码,我们不关心实现,只关心结果)
因此要访问由partition 构建的列表的两个部分,您需要使用car 和cadr。
或者你可以将cdr 保留在quicksort 中代码的访问部分,如果你想将顶部延续更改为cons,
(define (partition pivot lst)
((lambda (s) (s s lst cons))
......)) ; ^^^^ `cons` the top continuation
(define (quicksort lst)
(if (null? lst)
'()
(let* ((y (car lst))
(pn (partition y (cdr lst))))
(append (quicksort (car pn))
(list y)
(quicksort (cdr pn)))))) ; cdr works fine with cons
这是因为编程中的一般原则,用于构建数据的函数决定了哪些函数将用于访问该数据:
(list <A> <B> )
car cadr
(cons <A> <B> )
car cdr
(这种特殊的对应关系是因为(list <A> <B>)与(cons <A> (cons <B> '()))相同,(cadr <C>)与(car (cdr <C>))相同:)
(list <A> <B> )
=
(cons <A> (cons <B> '()))
car cdr
car
当然,现代标准认为这种编码方式不必要地令人费解,因为它通过参数传递和重用模拟递归,就像著名的 Y 组合器一样, -- 但是任何名副其实的 Scheme 都已经支持递归了。
所以这个partition通常会写成完全等效但更易读的“命名let”结构,
(define (partition pivot lst)
(let s ( (l* lst) ; first `l*` is `lst`
(c cons) ) ; top `c` is `cons`
(if (null? l*)
(c '() '())
(let ((x (car l*)))
(s (cdr l*)
(lambda (a b)
(if (< x pivot)
(c (cons x a) b)
(c a (cons x b)))))))))
除了名称loop 在这里通常用来代替s(它本身很可能是“self”的缩写)。
但您的快速排序/分区对的真正问题是算法。
是的,我说的是pair(当然不是cons 意义上的),因为两者是一起的。一个的实现决定了另一个的实现——在两个方向。 partition 的代码决定了 quicksort 的代码,或者如果我们先写了 quicksort,我们需要以相应的方式实现 partition——这样两个 一起工作。这意味着quicksort 确实产生了正确的结果,将任何输入列表转换为排序列表:
(quicksort lst) ---> { xs SUCH THAT
FOR ANY splitting xs = { ..., x, ...ys }
AND ANY splitting ys = { ..., y, ... }
IT HOLDS THAT x <= y
AND ALSO xs is a permutation of lst
(which implies (length lst) == (length xs))
}
那么,是什么麻烦呢?真正的 quicksort 在分区后没有任何作用。无:
(define (quicksort lst)
(if (null? lst)
'()
(let* ((y (car lst))
(pn (partition y (cdr lst))))
(quicksort (car pn)) ; no `append`, NB!
;; ; no (list y) either
(quicksort (cdr pn)))))
这怎么可能?什么样的partition 实现会让这成为可能?好吧,肯定不是功能性的。
相反,它必须以某种方式改变(即变异)非常lst本身:
{ a, b, c, ....., k, l, m, ..... }
-->
{ d, e, ...., p, n, o, ..... }
~~~~~~~~~~~ ~~~~~~~~~~~
我们用p 表示分区点——所以实际上在这种类型的分区之后剩下的就是对第一部分进行排序——然后对第二部分进行排序——之后就没有什么可做的了!这是托尼·霍尔最初的表述中的关键见解:
TO SORT
{ a, b, c, ....., k, l, m, ..... } DO
PARTITION:
{ d, e, ...., p, n, o, ..... }
~~~~~~~~~~~ ~~~~~~~~~~~
SORT! SORT!
DONE.
这种分区通常是用swap! 实现的,它实际上交换两个元素在底层数据结构中。大多数情况下,该数据结构是一个数组,它可以在任何给定索引处更改存储在其中的值。
但它也可以是一个列表,其中的更改,即 mutation 可以使用 set-car! 原语来完成。
看起来我们需要从输入列表中构建一个cdrs 列表,然后再反向构建一个列表,以实现这一目标。
我将把它留到另一天,暂时。