[英]How to group data attached to discriminated union values, in F#?
Here is an example:这是一个例子:
type Events =
| A of AData
| B of BData
| C of CData
and I have a list of those:我有一个清单:
let events : Events list = ...
I need to build a list by event type.我需要按事件类型构建列表。 Right now I do this:
现在我这样做:
let listA =
events
|> List.map (fun x ->
match x with
| A a -> Some a
| _ -> None
)
|> List.choose id
and, repeat for each type...并且,对每种类型重复...
I also thought I could do something like:我还以为我可以做类似的事情:
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
Is there a more elegant manner to solve this?有没有更优雅的方式来解决这个问题?
This processes a lot of data, so speed is important here.这会处理大量数据,因此速度在这里很重要。
I think your solution is pretty good, although you do pay a price for reversing the lists.我认为您的解决方案非常好,尽管您确实为颠倒列表付出了代价。 The only other semi-elegant approach I can think of is to unzip a list of tuples:
我能想到的唯一其他半优雅的方法是解压缩元组列表:
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
This creates several intermediate lists, so careful internal use of Seq
or Array
instead might perform better.这会创建几个中间列表,因此在内部谨慎使用
Seq
或Array
可能会表现更好。 You would have to benchmark to be sure.您必须进行基准测试才能确定。
Test case:测试用例:
split [
A 1
A 2
B "one"
B "two"
C true
C false
] |> printfn "%A" // [1; 2],[one; two],[true; false]
By the way, your current solution can be simplified to:顺便说一下,您当前的解决方案可以简化为:
let listA =
events
|> List.choose (function A a -> Some a | _ -> None)
You can fold back the list of events to avoid writing a recursive function and reversing results.您可以折叠事件列表以避免编写递归 function 和反转结果。 With an anonymous record you will need to define it first and then pipe both arguments
||>
to List.foldBack
:对于匿名记录,您需要先定义它,然后 pipe 和 arguments
||>
到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 |})
With a named record it is more elegant:使用命名记录更优雅:
{ listA = []; listB = []; listC = [] } |> List.foldBack addEvent events
addEvent
is the same as the lambda above except usage of a named record {}
instead of {||}
. addEvent
与上面的 lambda 相同,除了使用命名记录{}
而不是{||}
。
If you keep the union cases, you can group the list items like this.如果保留联合案例,则可以像这样对列表项进行分组。
let name = function
| A _ -> "A"
| B _ -> "B"
| C _ -> "C"
let lists =
events
|> List.groupBy name
|> dict
And then you can extract the data you want.然后你可以提取你想要的数据。
let listA = lists["A"] |> List.map (fun (A data) -> data)
(The compiler doesn't realize the list only consists of "A" cases, so it gives an incomplete pattern match warning) (编译器没有意识到列表只包含“A”案例,所以它给出了一个不完整的模式匹配警告)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.