First, your code is trivially fixed by changing one cdr
to 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
because the top continuation used in partition
is list
, and so the call
(partition pivot lst)
is equivalent to the call
(list { x IN lst SUCH THAT x < pivot }
{ x IN lst SUCH THAT x >= pivot } )
(the parts in {...}
are pseudocode, where we don't care about the implementation, just the results)
And so to access the two parts of that list built by partition
you need to use car
and cadr
.
Or you could keep the cdr
in the accessing part of your code in quicksort
if you'd change that top continuation to 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`
This because of the general principle in programming, where the functions used to build our data dictate which functions are to be used to access that data:
(list <A> <B> )
car cadr
(cons <A> <B> )
car cdr
( this particular correspondence is because (list <A> <B>)
is the same as (cons <A> (cons <B> '()))
and (cadr <C>)
is the same as (car (cdr <C>))
: )
(list <A> <B> )
=
(cons <A> (cons <B> '()))
car cdr
car
And conversely, the functions we use to access our data dictate the implementation of the function which must be used to build that data.
Of course that way of coding in your question is considered unnecessarily convoluted by modern standards since it emulates recursion through argument passing and reuse, -- just like in the famous Y combinator, -- but any Scheme worthy of its name already supports recursion.
So this partition
would normally be written as the fully equivalent yet more readable code using the "named let
" construct,
(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)))))))))
except the name loop
is conventionally used in place of s
here (which itself most probably is intended as the shortening of "self
").
But the true trouble with your quicksort/partition pair is algorithmic.
Yes I say pair (in non-cons
sense of course) since the two go together -- just as with the data access/creation functions which must work together too.
Implementation of one dictates the implementation of the other -- in both directions, too. partition
's code dictates quicksort
's, or if we'd written quicksort
first, we'd need to implement the partition
in the corresponding way -- so that the two work together. Which means quicksort
indeed producing the correct results, turning any input list into a sorted one:
(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))
}
So then, what is that trouble? It is that the true quicksort
does no work whatsoever after the partitioning. None:
(define (quicksort! lst)
(if (null? lst)
'()
(let* ((y (car lst))
(pn (partition! y lst)))
(quicksort! (car pn)) ; no `append`, NB!
(quicksort! (cdr pn))))) ; no (list y) either
How is that even possible? What kind of partition!
implementation would make that work? Well, most certainly not a functional one.
Instead it must be changing (i.e. mutating) the very lst
itself somehow:
{ a, b, c, ....., k, l, m, ..... }
-->
{ d, e, ...., p, n, o, ..... }
~~~~~~~~~~~ ~~~~~~~~~~~
where we denote with p
the partition point -- so that indeed all that's left to do after this kind of partitioning "in-place" is to sort the first part, and then to sort the second part, -- and then there's nothing more left to be done, after that! Which was the key insight in the original Tony Hoare's formulation of it:
TO SORT
{ a, b, c, ....., k, l, m, ..... } DO:
PARTITION it into
{ d, e, ...., p, n, o, ..... } AND THEN:
~~~~~~~~~~~ ~~~~~~~~~~~
SORT! SORT!
DONE.
This partitioning is usually implemented with swap!
which actually swaps two elements in the underlying data structure. Most usually that data structure is an array with its facilities to change the value stored in it at any given index.
But it can also be a list, where the change i.e. mutation can be done with the set-car!
primitive.
Looks like we'd need to build a list of cdr
s out of the input list, and another one in reverse, -- to be able to iterate over them in both directions, back and forth, -- to make that happen.
I'll leave that for another day, for now.