正如其他人所说,直接翻译是修改您的算法以使用 monad,它将计算比较。而不是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
它没有原始版本那么好,但仍然可以使用。
请注意,您将列表中的每个元素比较了 3 次,而只做一次就足够了。这有另一个不幸的结果,原始列表必须保存在内存中,直到所有三个过滤器都完成。所以让我们改为定义
-- 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])
但是接下来我们将不得不手动处理 recursive 的结果qSortM
,这将更加混乱。此外,单子方式允许我们稍后添加更多信息,例如最大深度,而不会以任何方式破坏核心部分。