簡體   English   中英

如何在F#中編寫高效的list / seq函數? (mapFoldWhile)

[英]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.pickSeq.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.scanSeq.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.

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