我正在通过 Cormen et。 Al。,Algorithms算法导论,第3版。,但我也对Haskell感兴趣。第8.2节(第194页)包括计数排序。我对如何在haskell中实现它和许多算法感兴趣,因为它们经常使用数组访问和破坏性更新。我看了RosettaCode上的实现(下面复制了),我觉得很难理解。
import Data.Array
countingSort :: (Ix n) => [n] -> n -> n -> [n]
countingSort l lo hi = concatMap (uncurry $ flip replicate) count
where count = assocs . accumArray (+) 0 (lo, hi) . map (\i -> (i, 1)) $ l
我喜欢haskell的一个方面是算法如何非常清晰(例如Haskell quicksort示例),至少作为未经优化的规范。这似乎很不清楚,我想知道它是否必然如此或过度。
有人可以吗
答案 0 :(得分:7)
它确实在进行计数。这是一个稍微改写的版本,我觉得更容易理解:
import Data.Array
countingSort :: (Ix n) => [n] -> n -> n -> [n]
countingSort l lo hi = concat [replicate times n | (n, times) <- counts]
where counts = assocs (accumArray (+) 0 (lo, hi) [(i, 1) | i <- l])
让我们一步一步地分解它。我们将使用列表[5, 3, 1, 2, 3, 4, 5]
。
*Main> [(i, 1) | i <- [5, 3, 1, 2, 3, 4, 5]]
[(5,1),(3,1),(1,1),(2,1),(3,1),(4,1),(5,1)]
我们只是采用列表中的每个元素并将其转换为1的元组。这是我们计数的基础。现在我们需要一种方法来总结每个元素的计数。这是accumArray
发挥作用的地方。
*Main> accumArray (+) 0 (1, 5) [(i, 1) | i <- [5, 3, 1, 2, 3, 4, 5]]
array (1,5) [(1,1),(2,1),(3,2),(4,1),(5,2)]
accumArray
的第一个参数是在累积期间应用的操作(对我们来说只是简单的添加)。第二个参数是起始值,第三个参数是边界。因此,我们最终得到一个数组映射数字到它们在输入中的计数。
接下来,我们使用assocs
从地图中获取键/值元组:
*Main> assocs $ accumArray (+) 0 (1, 5) [(i, 1) | i <- [5, 3, 1, 2, 3, 4, 5]]
[(1,1),(2,1),(3,2),(4,1),(5,2)]
然后replicate
根据其数量重复每个数字:
*Main> [replicate times n | (n, times) <- assocs $ accumArray (+) 0 (1, 5) [(i, 1) | i <- [5, 3, 1, 2, 3, 4, 5]]]
[[1],[2],[3,3],[4],[5,5]]
最后,我们使用concat将这个列表列表转换为单个列表:
*Main> concat [replicate times n | (n, times) <- assocs $ accumArray (+) 0 (1, 5) [(i, 1) | i <- [5, 3, 1, 2, 3, 4, 5]]]
[1,2,3,3,4,5,5]
在我上面写的实际功能中,我使用where
打破了这个单行。
我发现列表理解比map
和concatMap
更容易处理,所以这些是我在函数版本中所做的主要更改。
(uncurry $ flip replicate)
是一个不错的技巧...... flip replicate
为您提供了replicate
的版本,它以相反的顺序获取其参数。 uncurry
接受那个curried函数并将其转换为一个以元组作为参数的函数。这些产生与我的列表理解相同的结果,它解构了元组,然后以相反的顺序传递其参数。我对Haskell不太熟悉,不知道这是一个常见的习语,但对我来说,列表理解更容易理解。
答案 1 :(得分:3)
count
到accumArray
用作直方图构建器。也就是说,对于从lo
到hi
的每个数字,它返回参数列表中出现的次数。
count :: (Ix i, Num e) => [i] -> i -> i -> [(i, e)]
count l lo hi = assocs . accumArray (+) 0 (lo, hi) . map (\i -> (i, 1)) $ l
count [6,2,1,6] 0 10 ==
[(0,0),(1,1),(2,1),(3,0),(4,0),(5,0),(6,2),(7,0),(8,0),(9,0),(10,0)]
count
的结果用于从此规范生成原始元素。这是通过replicate
元组fst
的每个snd
元素来完成的。这会生成连接在一起的列表列表。
f l lo hi = map (uncurry $ flip replicate ) $ count l lo hi
f [6,2,1,6] 0 10 == [[],[1],[2],[],[],[],[6,6],[],[],[],[]]
完整解决方案相当于
countingSort l lo hi = concat $ f l lo hi