Как говорили другие, прямой перевод заключается в том, чтобы изменить ваш алгоритм, чтобы использовать монаду, которая будет считать сравнения. Вместо State
я предпочел бы использовать Writer
, потому что она описывает более естественно, что происходит: Каждый результат дополняется (аддитивное) количеством сравнений, что требуется:
import Control.Applicative
import Control.Monad
import Control.Monad.Writer.Strict
import Data.Monoid
type CountM a = Writer (Sum Int) a
Тогда давайте определим функцию, которая оборачивает чистую значение в монадический тот, который получает приращение счетчика:
count :: Int -> a -> CountM a
count c = (<$ tell (Sum c))
Теперь мы можем определить
qSortM :: Ord a => [a] -> CountM [a]
qSortM [] = return []
qSortM (p:xs) =
concatM [ qSortM =<< filtM (< p)
, filtM (== p)
, return [p]
, qSortM =<< filtM (> p)
]
where
filtM p = filterM (count 1 . p) xs
concatM :: (Monad m) => [m [a]] -> m [a]
concatM = liftM concat . sequence
Это не так хорошо, как т он оригинальная версия, но все же пригодная для использования.
Обратите внимание, что вы сравниваете каждый элемент списка три раза, в то время как этого достаточно, чтобы сделать это один раз.Это имеет еще одно неудачное следствие того, что исходный список должен храниться в памяти до тех пор, пока все три фильтра не закончатся. Итак, давайте вместо этого определить
-- We don't care we reverse the order of elements in the buckets,
-- we'll sort them later anyway.
split :: (Ord a) => a -> [a] -> ([a], [a], [a], Int)
split p = foldl f ([], [], [], 0)
where
f (ls, es, gs, c) x = case compare x p of
LT -> (x : ls, es, gs, c')
EQ -> (ls, x : es, gs, c')
GT -> (ls, es, x : gs, c')
where
c' = c `seq` c + 1
Это выполняет все сразу расщепление в три ведра, а также вычисляет длину списка, так что мы можем обновлять счетчик сразу. Список расходуется сразу и может быть отброшен сборщиком мусора.
Теперь наша быстрая сортировка станет немного компактнее
qSortM :: Ord a => [a] -> CountM [a]
qSortM [] = return []
qSortM (p:xs) = count c =<<
concatM [ qSortM ls
, return (p : es)
, qSortM gs
]
where
(ls, es, gs, c) = split p xs
concatM = liftM concat . sequence
Мы могли бы достичь того же результата без использования Writer
, только при наличии qSortM
возвращения (Int, [a])
явно. Но тогда нам пришлось бы вручную обрабатывать результаты рекурсивного qSortM
, что было бы более грязным. Более того, монадический способ позволяет нам добавлять более позднюю информацию, такую как максимальная глубина, без какого-либо нарушения основной части.
Обратите внимание, что алгоритм неверен, например, для последовательности '[15,11,9,15, -3]'. – bitmask
@ битмаска, исправлена. –
Пожалуйста, объясните, что вы сделали, чтобы попытаться подсчитать количество сравнений. –