[英]How to write efficient list/seq functions in F#? (mapFoldWhile)
我試圖編寫一個通用的mapFoldWhile
函數,它只是mapFold
但是要求state
是一個option
並在遇到None
狀態時立即停止。
我不想使用mapFold
因為它會轉換整個列表,但是我希望它一旦找到無效狀態(即None
)就停止。
這是我的第一次嘗試:
let mapFoldWhile (f : 'State option -> 'T -> 'Result * 'State option) (state : 'State option) (list : 'T list) =
let rec mapRec f state list results =
match list with
| [] -> (List.rev results, state)
| item :: tail ->
let (result, newState) = f state item
match newState with
| Some x -> mapRec f newState tail (result :: results)
| None -> ([], None)
mapRec f state list []
List.rev
讓我List.rev
厭煩,因為練習的目的是提前退出並構建一個新的列表應該更慢。
所以我查看了F#自己的map
所做的事情,其中包括:
let map f list = Microsoft.FSharp.Primitives.Basics.List.map f list
可以在這里找到不祥的Microsoft.FSharp.Primitives.Basics.List.map
,如下所示:
let map f x =
match x with
| [] -> []
| [h] -> [f h]
| (h::t) ->
let cons = freshConsNoTail (f h)
mapToFreshConsTail cons f t
cons
consNoTail
東西也在這個文件中:
// optimized mutation-based implementation. This code is only valid in fslib, where mutation of private
// tail cons cells is permitted in carefully written library code.
let inline setFreshConsTail cons t = cons.(::).1 <- t
let inline freshConsNoTail h = h :: (# "ldnull" : 'T list #)
所以我猜結果F#的不可變列表實際上是可變的,因為性能? 我有點擔心這個,使用了prepend-then-reverse列表方法,因為我認為這是F#中的“方法”。
我對F#或函數式編程一般都不是很有經驗,所以也許(可能)創建一個新的mapFoldWhile
函數的想法是錯誤的,但那我該做什么呢?
我經常發現自己處於需要“提前退出”的情況,因為收集項目是“無效的”,我知道我不必看其余的。 我在某些情況下使用List.pick
或Seq.takeWhile
,但在其他情況下我需要做更多( mapFold
)。
是否有一個有效的解決方案來解決這類問題(特別是mapFoldWhile
和一般的“早退”),或者我是否必須切換到命令式解決方案/使用Collections.Generics.List
?
在大多數情況下,使用List.rev
是一個非常充分的解決方案。
你是對的,F#核心庫使用變異和其他臟黑客來從F#list操作中擠出更多的性能,但我認為那里的微優化並不是特別好的例子。 F#list函數幾乎在所有地方使用,因此它可能是一個很好的權衡,但在大多數情況下我不會遵循它。
使用以下命令運行您的功能:
let l = [ 1 .. 1000000 ]
#time
mapFoldWhile (fun s v -> 0, s) (Some 1) l
當我運行該功能而沒有更改時,我在第二行得到~240ms。 當我只是刪除List.rev
(以便它以其他順序返回數據)時,我大約需要190毫秒。 如果你真的經常調用這個函數,這很重要,那么你必須使用變異(實際上,你自己的可變列表類型),但我認為這很少值得。
對於一般的“退出早期”問題,您通常可以將代碼編寫為Seq.scan
和Seq.takeWhile
的組合。 例如,假設您想要對序列中的數字求和,直到達到1000.您可以寫:
input
|> Seq.scan (fun sum v -> v + sum) 0
|> Seq.takeWhile (fun sum -> sum < 1000)
使用Seq.scan
生成一個在整個輸入上的和的序列,但由於這是懶惰生成的,因此使用Seq.takeWhile
會在退出條件發生時立即停止計算。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.