String 哈斯克尔有多懒;s`++;`?
我很好奇我应该如何改进Haskell例程的性能,该例程可以发现字符串的字典最小循环旋转String 哈斯克尔有多懒;s`++;`?,string,optimization,haskell,lazy-evaluation,String,Optimization,Haskell,Lazy Evaluation,我很好奇我应该如何改进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而不是list,因为Data.Vector提供了就地操作,可能只是在原始数据中
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而不是list,因为Data.Vector提供了就地操作,可能只是在原始数据中操纵一些索引。我其实不需要亲自跟踪索引以避免过度复制,对吗
我很好奇
++
是如何影响优化的。我可以想象它会产生一个懒惰的字符串thunk,在字符串被读取到那么远之前,它不会进行追加。因此,a
永远不应该实际附加到b
上,只要minimum可以提前删除该字符串,比如因为它以某个非常晚的字母开头。这是否正确?xs++ys
会在xs
的所有列表单元格中增加一些开销,但一旦到达xs
的末尾,它就免费了-它只返回ys
查看(++)
的定义有助于了解原因:
[] ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)
i、 例如,它必须在遍历结果时“重新构建”整个第一个列表。这对于理解如何以这种方式推理惰性代码非常有帮助
要认识到的关键是,追加并不是一下子完成的;通过首先遍历所有xs
,然后将ys
放在[]
的位置,以增量方式构建一个新的链表
因此,您不必担心到达b
的末尾,然后突然产生“附加”a
的一次性成本;成本分布在b
的所有要素上
矢量是完全不同的事物;它们的结构非常严格,因此即使只检查xs V.++ys
的第一个元素,也会产生分配新向量并将xs
和ys
复制到向量的全部开销,就像在严格的语言中一样。这同样适用于可变向量(除了在执行操作时产生的成本,而不是强制生成向量时),尽管我认为您必须使用这些向量编写自己的追加操作。如果这对您来说是个问题,您可以将一组附加(不可变)向量表示为[Vector a]
或类似的向量,但这只会将开销转移到将其展平为单个向量时,听起来您对可变向量更感兴趣。试试看
minimumrotation :: Ord a => [a] -> [a]
minimumrotation xs = minimum . take len . map (take len) $ tails (cycle xs)
where
len = length xs
我希望这会比你拥有的更快,尽管在未绑定的
向量
或UArray
上进行索引杂耍可能会更快。但是,这真的是一个瓶颈吗?如果您对快速串联和快速拆分感兴趣,请使用
我对您的代码做了一些风格上的修改,使其看起来更像惯用的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
导入符合条件的数据。顺序为S
导入符合条件的数据。可折叠为F
最小旋转::Ord a=>[a]->[a]
最小旋转xs=F.toList
. F.最低限度
. fmap(`swapAt`xs')
. S.elemIndicesL(F.最小x')
$xs'
其中xs'=S.fromList xs
斯瓦帕特n=f。S.splitAt n
其中f(a,b)=b S.>
很好,但可能会切换到数据。Vector
解决了这个问题,是吗?还是会造成单独的复制惩罚?在这种情况下,我应该简单地创建我自己的twovectors
类型或其他什么?@JeffBurdges:我已经扩展了我的答案,涵盖了Vectors:)谢谢!另一个小问题:如果我写minimumrotation x=minimum$map f$elemIndices(minimum x)x其中f I=take(length x)$drop I(x++x)
。当去除f
时,长度x
和x++x
是否只计算一次?@JeffBurdges:可能,但我;GHC对这种优化持保守态度。您可能应该给length x
一个名称(与f
的定义在同一where块中);我不会担心(x++x)
部分。(请注意,f
本身已经处于弱头部正常形式,因此永远不会被强迫(“脱驼”);对于不同的i
值,f i
将被强迫)@JeffBurdges:这没有帮助;必须将表达式提升到lambda-expression之外。循环是否比xs++xs
快?我会先假设是的。我认为交换两个take
s不会影响性能,因为所有这些thunk都必须进行计算,对吗?cycle xs
只是fix(xs++)
,所以如果有什么xs++xs
会更便宜,但我不会担心它;开销将是微乎其微的。交换take len
和map(take len)
不会有任何效果。如果xs++xs
和cycle xs
之间有任何性能差异,如果它不是很小,我会感到惊讶。我不认为交换take len
和map(take len)
会产生可测量的差异,但我还没有对其进行基准测试。@LightnessRacesinOrbit:很明显,你从来没有在游戏中见过Haskell程序!我的幽默和善意的评论被删除了(想想看。啊,这里有几个巧妙的技巧,包括中缀swapAt
)。lol@JeffBurdges-另一个选项是(翻转swapAt xs')
,但我个人更喜欢中缀部分。当然,最好始终使用序列,这样toList
和fromList
就不会占用程序很多时间