我很好奇我应该如何改进Haskell例程的性能,该例程找到字典的字典最小循环旋转。
import Data.List
swapAt n = f . splitAt n where f (a,b) = b++a
minimumrotation x = minimum $ map (\i -> swapAt i x) $ elemIndices (minimum x) x
我想我应该使用Data.Vector而不是列表,因为Data.Vector提供了就地操作,可能只是将一些索引操作到原始数据中。我自己实际上不需要费心去追踪索引,以避免过多的复制,对吗?
我很好奇++
如何影响优化。我想它会产生一个懒惰的字符串thunk,直到字符串被读取到远处才会附加。因此,a
实际上永远不会被附加到b
,只要最小可以提前消除该字符串,就像因为它以一些非常晚的字母开头。这是对的吗?
答案 0 :(得分:10)
xs ++ ys
在xs
的所有列表单元格中增加了一些开销,但是一旦它到达xs
的结尾它就是免费的 - 它只返回ys
。
查看(++)
的定义有助于了解原因:
[] ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)
即,它必须在遍历结果时“重新构建”整个第一个列表。 This article非常有助于理解如何以这种方式推理惰性代码。
要意识到的关键是不是一次性完成追加;首先遍历所有xs
,然后将ys
放在[]
所在的位置,逐步构建新的链接列表。
所以,你不必担心到达b
的末尾并突然产生“追加”a
的一次性费用;费用分散在b
的所有要素上。
矢量完全是另一回事;他们的结构很严格,所以即使只检查xs V.++ ys
的第一个元素,也会产生分配新向量并将xs
和ys
复制到其中的全部开销 - 就像在严格的语言。这同样适用于可变向量(除了在执行操作时产生成本,而不是强制生成向量时),尽管我认为你必须用这些来编写自己的追加操作。你可以将一堆附加的(不可变的)向量表示为[Vector a]
或类似的,如果这对你来说是一个问题,但只是将开销移到你将它展平成一个Vector时,它听起来像你'对可变载体更感兴趣。
答案 1 :(得分:5)
尝试
minimumrotation :: Ord a => [a] -> [a]
minimumrotation xs = minimum . take len . map (take len) $ tails (cycle xs)
where
len = length xs
我希望它比你拥有的更快,尽管未装箱的Vector
或UArray
上的索引玩杂耍可能会更快。但是,它真的是一个瓶颈吗?
答案 2 :(得分:3)
如果您对快速连接和快速splitAt
感兴趣,请使用Data.Sequence。
我对你的代码进行了一些风格修改,使它看起来更像是惯用的Haskell,但逻辑完全相同,除了Seq
之间的一些转换:
import qualified Data.Sequence as S
import qualified Data.Foldable as F
minimumRotation :: Ord a => [a] -> [a]
minimumRotation xs = F.toList
. F.minimum
. fmap (`swapAt` xs')
. S.elemIndicesL (F.minimum xs')
$ xs'
where xs' = S.fromList xs
swapAt n = f . S.splitAt n
where f (a,b) = b S.>< a