[英]How lazy is Haskell's `++`?
我很好奇我應該如何改進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
,就像因為它從一些非常晚的字母開始。 這個對嗎?
xs ++ ys
在xs
所有列表單元格中增加了一些開銷,但是一旦到達xs
的末尾它就是免費的 - 它只返回ys
。
查看(++)
的定義有助於了解原因:
[] ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)
也就是說,它必須在遍歷結果時“重新構建”整個第一個列表。 本文非常有助於理解如何以這種方式推理惰性代碼。
要意識到的關鍵是不能一次性完成追加; 通過首先遍歷所有xs
然后將ys
放在[]
所在的位置來逐步構建新的鏈接列表。
所以,你不必擔心到達的終點b
,突然招致“追加”的一次性費用a
到它; 成本分散在b
所有元素上。
矢量完全是另一回事; 他們的結構很嚴格,所以即使只檢查xs V.++ ys
的第一個元素,也會產生分配新向量並將xs
和ys
復制到它的全部開銷 - 就像在嚴格的語言中一樣。 這同樣適用於可變向量(除了在執行操作時產生成本,而不是強制生成向量時),盡管我認為你必須用這些來編寫自己的追加操作。 如果這對你來說是一個問題,你可以將一堆附加(不可變)向量表示為[Vector a]
或類似[Vector a]
,但這只是將開銷移動到將其展平為單個向量時,它聽起來像你是對可變載體更感興趣。
嘗試
minimumrotation :: Ord a => [a] -> [a]
minimumrotation xs = minimum . take len . map (take len) $ tails (cycle xs)
where
len = length xs
我希望它比你擁有的更快,盡管在未裝箱的Vector
或UArray
上的索引玩雜耍可能會更快。 但是,它真的是一個瓶頸嗎?
如果您對快速連接和快速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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.