簡體   English   中英

哈斯克爾的“++”多么懶惰?

[英]How lazy is Haskell's `++`?

我很好奇我應該如何改進Has​​kell例程的性能,該例程找到一個字符串的按字典順序最小的循環旋轉。

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 ++ ysxs所有列表單元格中增加了一些開銷,但是一旦到達xs的末尾它就是免費的 - 它只返回ys

查看(++)的定義有助於了解原因:

[] ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)

也就是說,它必須在遍歷結果時“重新構建”整個第一個列表。 本文非常有助於理解如何以這種方式推理惰性代碼。

要意識到的關鍵是不能一次性完成追加; 通過首先遍歷所有xs然后將ys放在[]所在的位置來逐步構建新的鏈接列表。

所以,你不必擔心到達的終點b ,突然招致“追加”的一次性費用a到它; 成本分散在b所有元素上。

矢量完全是另一回事; 他們的結構很嚴格,所以即使只檢查xs V.++ ys的第一個元素,也會產生分配新向量並將xsys復制到它的全部開銷 - 就像在嚴格的語言中一樣。 這同樣適用於可變向量(除了在執行操作時產生成本,而不是強制生成向量時),盡管我認為你必須用這些來編寫自己的追加操作。 如果這對你來說是一個問題,你可以將一堆附加(不可變)向量表示為[Vector a]或類似[Vector a] ,但這只是將開銷移動到將其展平為單個向量時,它聽起來像你是對可變載體更感興趣。

嘗試

minimumrotation :: Ord a => [a] -> [a]
minimumrotation xs = minimum . take len . map (take len) $ tails (cycle xs)
  where
    len = length xs

我希望它比你擁有的更快,盡管在未裝箱的VectorUArray上的索引玩雜耍可能會更快。 但是,它真的是一個瓶頸嗎?

如果您對快速連接和快速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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM