如何在f#中分组附加到判别联合值的数据?

  • 本文关键字:数据 f# discriminated-union
  • 更新时间 :
  • 英文 :


下面是一个例子:

type Events =
| A of AData
| B of BData
| C of CData

我有一个列表

let events : Events list = ...

我需要按事件类型构建一个列表。现在我这样做:

let listA =
events
|> List.map (fun x ->
match x with
| A a -> Some a
| _ -> None
)
|> List.choose id

我还想我可以这样做:

let rec split events a b c =
match events with
| [] -> (a |> List.rev, b |> List.rev, c |> List.rev)
| h :: t ->
let a, b, c =            
match h with
| A x -> x::a, b, c
| B x -> a, x::b, c
| C x -> a, b, x::c
split t a b c

是否有更优雅的方式来解决这个问题?

处理大量的数据,所以速度在这里很重要。

您可以折叠事件列表,以避免编写递归函数并反转结果。对于匿名记录,您需要首先定义它,然后将两个参数||>管道到List.foldBack:

let eventsByType =
(events, {| listA = []; listB = []; listC = [] |})
||> List.foldBack (fun event state ->
match event with
| A a -> {| state with listA = a :: state.listA |}
| B b -> {| state with listB = b :: state.listB |}
| C c -> {| state with listC = c :: state.listC |})

使用命名记录会更优雅:

{ listA = []; listB = []; listC = [] } |> List.foldBack addEvent events

addEvent与上面的lambda相同,只是使用了命名记录{}而不是{||}

我认为您的解决方案非常好,尽管您确实为颠倒列表付出了代价。我能想到的另一种半优雅的方法是解压缩元组列表:

let split events =
let a, b, c =
events
|> List.map (function 
| A n -> Some n, None, None
| B s -> None, Some s, None
| C b -> None, None, Some b)
|> List.unzip3
let choose list = List.choose id list
choose a, choose b, choose c

这会创建几个中间列表,因此在内部谨慎使用SeqArray可能会更好。您必须进行基准测试才能确定。

测试用例:

split [
A 1
A 2
B "one"
B "two"
C true
C false
] |> printfn "%A"   // [1; 2],[one; two],[true; false]
顺便说一下,您当前的解决方案可以简化为:
let listA =
events
|> List.choose (function A a -> Some a | _ -> None)

如果您保留联合用例,您可以像这样对列表项进行分组。

let name = function
| A _ -> "A"
| B _ -> "B"
| C _ -> "C"
let lists =
events 
|> List.groupBy name
|> dict

然后你可以提取你想要的数据。

let listA = lists["A"] |> List.map (fun (A data) -> data)

(编译器没有意识到列表只包含"A",所以它给出一个不完整的模式匹配警告😀)

相关内容

  • 没有找到相关文章

最新更新