Although there's already an accepted answer, I thought you might appreciate a Scheme translation of the Sheep Trick from The Pitmanual. Your code is actually quite similar to it already. Scheme does support do
loops, but they're not particularly idiomatic, whereas named let
s are much more common, so I've used the latter in this code. As you've noted, choosing the first element as the pivot cause perfomance problems if the list is already sorted. Since you have to traverse the list on each iteration, there might be some clever thing you could do to pick the pivots for the left and right sides for the recursive calls in advance.
(define (nconc l1 l2)
;; Destructively concatenate l1 and l2. If l1 is empty,
;; return l2. Otherwise, set the cdr of the last pair
;; of l1 to l2 and return l1.
(cond
((null? l1)
l2)
(else
(let loop ((l1 l1))
(if (null? (cdr l1))
(set-cdr! l1 l2)
(loop (cdr l1))))
l1)))
(define (quicksort lst)
(if (null? lst) lst
(let ((pivot (car lst))
(left '())
(right '()))
(let loop ((lst (cdr lst))) ; rebind to (cdr lst) since pivot wasn't popped
(if (null? lst)
(nconc (quicksort left)
(cons pivot
(quicksort right)))
(let ((tail (cdr lst)))
(cond
((< (car lst) pivot)
(set-cdr! lst left)
(set! left lst))
(else
(set-cdr! lst right)
(set! right lst)))
(loop tail)))))))
(quicksort (list 9 1 8 2 7 3 6 4 5))
;=> (1 2 3 4 5 6 7 8 9)
Scheme does support do
, so if you are interested in that (it does make the Common Lisp and Scheme version very similar), it looks like this:
(define (quicksort lst)
(if (null? lst) lst
(do ((pivot (car lst))
(lst (cdr lst)) ; bind lst to (cdr lst) since pivot wasn't popped
(left '())
(right '()))
((null? lst)
(nconc (quicksort left)
(cons pivot
(quicksort right))))
(let ((tail (cdr lst)))
(cond
((< (car lst) pivot)
(set-cdr! lst left)
(set! left lst))
(else
(set-cdr! lst right)
(set! right lst)))
(set! lst tail)))))
(display (quicksort (list 9 1 8 2 7 3 6 4 5)))
;=> (1 2 3 4 5 6 7 8 9)