下面是一个例子:
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
这会创建几个中间列表,因此在内部谨慎使用Seq
或Array
可能会更好。您必须进行基准测试才能确定。
测试用例:
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",所以它给出一个不完整的模式匹配警告😀)