2
\$\begingroup\$

I want to pass a predicate into quick sort. The issue I'm running into is that the predicate must have two arguments, the pivot and the element of the list's tail. As far as I know though, predicates can only have 1 argument. Here's what I have so far (it runs, I just want to improve it):

import Data.List

quicksort :: [a] -> (a -> a -> Bool) ->  [a]
quicksort [] _ = []
quicksort (x:xs) pf = quicksort left pf ++ [x] ++ quicksort right pf
    where   pred = pf x
            pt = partition pred xs
            left = fst pt
            right = snd pt
\$\endgroup\$
3
\$\begingroup\$

As 200 said, the predicate should be first. Think of filter, takeWhile, dropWhile and similar functions. They take the predicate first.

Also, it's usually a good idea to get rid of arguments that get repeated in every recursive call. GHC should take care of that, but we can help it a little bit:

quicksort :: (a -> a -> Bool) -> [a] -> [a]
quicksort pf = go
  where
    go [] = []
    go (x:xs) = go left ++ [x] ++ go right
      where (left, right) = partition (comp x) xs

However, we're sorting. Therefore calling the comparison function a predicate is a misnomer. A predicate is a function a -> Bool, whereas a -> a -> Bool is a comparison. But a -> a -> Bool doesn't convey its meaning by its type. Do we return True if the first shall come first in the sorted arrangement? Or do we return False? Will quickSort (>) sort ascending or descending?

Those questions are easier to answer if we use a -> a -> Ord instead. This signature follows compare's, so we have a pretty good feeling what x `compare` y = LT means, namely that x < y holds true.

If we keep that in mind, we can write the following variants:

quicksortBy :: (a -> a -> Ord) -> [a] ->  [a]
quicksortBy comp = go
  where
    go []     = []
    go (x:xs) = go lesser ++ (x : equal) ++ go greater
      where
        (lesser, equal, greater) = partitionOrd (`comp` x) xs

quicksort :: Ord a => [a] -> [a]
quicksort = quicksortBy compare

The definition of partitionOrd :: (a -> Ord) -> [a] -> ([a], [a], [a]) is left as an exercise. Other than that, well done. Keep in mind that this isn't really Quicksort, though.

\$\endgroup\$
2
\$\begingroup\$

To facilitate currying, you should arrange the parameters from the least likely to vary to the most likely to vary. If you put the predicate first, then you could write these partial functions:

ascQuicksort = quicksort (>)
descQuicksort = quicksort (<)

There where clause should be simplified.

import Data.List

quicksort :: (a -> a -> Bool) -> [a] -> [a]
quicksort _ [] = []
quicksort pf (x:xs) = quicksort pf left ++ [x] ++ quicksort pf right
    where (left, right) = partition (pf x) xs
\$\endgroup\$
1
\$\begingroup\$

I haven't written Haskell in awhile, so I can't give a great review, but I see something that could be improved on that seems a little backwards to me. You create where bindings for left and right even though you only use each once, but write quicksort * pf twice and don't create a binding for it.

I have a personal love for using local functions to neaten code up, so I'd probably write your code as something closer to:

quicksort :: [a] -> (a -> a -> Bool) ->  [a]
quicksort [] _ = []
quicksort (x:xs) pf = recur (fst pt) ++ [x] ++ recur (snd pt)
    where pred = pf x
          pt = partition pred xs
          recur sub = quicksort sub pf

Using recur, you can remove the duplicate recursing calls. Also, by removing the left and right bindings you can shorten up the where which I personally find cleaner looking.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.