Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 518
Thomas Avatar asked Oct 23 '25 14:10

Thomas


2 Answers

You can fold back the list of events to avoid writing a recursive function and reversing results. With an anonymous record you will need to define it first and then pipe both arguments ||> to 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 {||}.

like image 53
Sergey Berezovskiy Avatar answered Oct 26 '25 09:10

Sergey Berezovskiy


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. 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)
like image 34
Brian Berns Avatar answered Oct 26 '25 10:10

Brian Berns



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!