简体   繁体   English

如何在 F# 中对附加到已区分联合值的数据进行分组?

[英]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.这会创建几个中间列表,因此在内部谨慎使用SeqArray可能会表现更好。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM