How to group data attached to discriminated union values, in F#? - 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.

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 {||}.

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)

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😀)

Related

match by value in a discriminated union, in F#

with this union:
type T =
| A
| B
| C
and a T list
I would like to implement something like this pseudo code:
let countOfType (t: Type) (l: T list) =
l
|> List.filter (fun x -> x.GetType() = t)
|> List.length
when I would pass if I want to count the 'A', 'B', etc..
but A.GetType() and B.GetType() return the T type, so this doesn't work.
Is there a way where I could check the type by passing it as a parameter?
The practical case here is that I have a Map that gets updated every few seconds and its values are part of the same DU. I need to be able to see how many of each type, without having to update the code (like a match block) each time an entry gets added.
Addendum:
I simplified the original question too much and realized it after seeing Fyodor's answer.
So I would like to add the additional part:
how could this also be done for cases like these:
type T =
| A of int
| B of string
| C of SomeOtherType
For such enum type T as you specified, you can just use regular comparison:
let countOfType t (l: T list) =
l
|> List.filter (fun x -> x = t)
|> List.length
Usage:
> countOfType A [A; A; B; C; A]
3
> countOfType B [A; A; B; C; A]
1
Try List.choose: ('a -> 'b option) -> 'a list -> 'b list, it filters list based on 'a -> 'b option selector. If selectors evaluates to Some, then value will be included, if selector evaluates to None, then value will be skipped. If you worry about allocations caused by instantiation of Some, then you'll have to implement version that will use ValueOption
let onlyA lis =
lis |> List.choose (function
| (A _) as a -> Some a
| _ -> None)
let onlyB lis =
lis |> List.choose (function
| (B _) as b -> Some b
| _ -> None)
let lis = [
A 1
A 22
A 333
B ""
B "123"
]
lis |> onlyA |> List.length |> printfn "%d"
You can pattern match, and throw away the data, to create a function for the filter.
type T =
| A of int
| B of string
| C of float
[A 3;A 1;B "foo";B "bar";C 3.1; C 4.6]
|> List.filter (fun x ->
match x with
| A _ -> true
| B _ -> false
| C _ -> false
)
|> List.length
But in general i would asume, that you create a predicate function in your modul.
let isA x =
match x with
| A _ -> true
| _ -> false
if you have those functions you can just write
[A 3;A 1;B "foo";B "bar";C 3.1; C 4.6]
|> List.filter isA
|> List.length

Is there a standard F# function for separating a sequence of Choices?

I'm looking for a standard F# function that takes a sequence of 2-choices and returns a pair of sequences:
let separate (choices : seq<Choice<'T1, 'T2>>) : seq<'T1> * seq<'T2> = ...
A naive implementation is pretty simple:
let separate choices =
let ones =
choices
|> Seq.choose (function
| Choice1Of2 one -> Some one
| _ -> None)
let twos =
choices
|> Seq.choose (function
| Choice2Of2 two -> Some two
| _ -> None)
ones, twos
This works fine, but iterates the sequence twice, which is less than ideal. Is this function defined in one of the semi-standard libraries? I looked around, but couldn't find it. (If it exists, I'm sure it goes by some other name.)
For bonus points, versions that work with 3-choices, 4-choices, and so on, would also be nice, as would versions for List, Array, etc. Thanks.
I can't find builtin implementation but can write my own.
It uses IEnumerator<> based approach, so it will work with any collection type but it's not optimal (e.g. arrays will work slower than could be). Order is reversed (easy to fix with ResizeArray but more code). Also this version is not lazy, but can be easily adapted to work with Choice<'a, 'b, 'c> and others
let splitChoices2 (choices: Choice<'a, 'b> seq) =
let rec inner (it: IEnumerator<_>) acc1 acc2 =
if it.MoveNext() then
match it.Current with
| Choice1Of2 c1 -> inner it (c1 :: acc1) acc2
| Choice2Of2 c2 -> inner it acc1 (c2 :: acc2)
else
acc1, acc2
inner (choices.GetEnumerator()) [] []
let choices = [
Choice1Of2 11
Choice2Of2 "12"
Choice1Of2 21
Choice2Of2 "22"
]
choices |> splitChoices2 |> printfn "%A"
Update: ResizeArray based approach without reversed order and potentially less expensive enumeration
let splitChoices2 (choices: Choice<'a, 'b> seq) =
let acc1 = ResizeArray()
let acc2 = ResizeArray()
for el in choices do
match el with
| Choice1Of2 c1 -> acc1.Add c1
| Choice2Of2 c2 -> acc2.Add c2
acc1, acc2
This is sort of inspired by TraverseA but has come out quite different. Here is a single pass solution (UPDATE: however while the core algorithm might be single pass from List to List, but getting it to match your type signature, and ordering the result the same way makes it 3*O(n), it depends how important the ordering and type signature are to you)
let choices = seq {Choice1Of2(1) ; Choice2Of2(2) ; Choice2Of2(3) ; Choice1Of2(4)}
let seperate' choices =
let rec traverse2ChoicesA tupleSeq choices =
match choices with
| [] -> fst tupleSeq |> List.rev |>Seq.ofList , snd tupleSeq |> List.rev |> Seq.ofList
| (Choice1Of2 f)::tl -> traverse2ChoicesA (f::fst tupleSeq, snd tupleSeq) tl
| (Choice2Of2 s)::tl -> traverse2ChoicesA (fst tupleSeq, s::snd tupleSeq) tl
traverse2ChoicesA ([],[]) <| List.ofSeq choices
seperate' choices;;
val seperate' : choices:seq<Choice<'a,'b>> -> seq<'a> * seq<'b>
val it : seq<int> * seq<int> = ([1; 4], [2; 3])
Update: To be clear, if ordering and List instead of Seq are ok then this is a single pass:
let choices = [Choice1Of2(1) ; Choice2Of2(2) ; Choice2Of2(3) ; Choice1Of2(4)]
let seperate' choices =
let rec traverse2ChoicesA (tupleSeq) choices =
match choices with
| [] -> tupleSeq
| (Choice1Of2 f)::tl -> traverse2ChoicesA (f :: fst tupleSeq, snd tupleSeq) tl
| (Choice2Of2 s)::tl -> traverse2ChoicesA (fst tupleSeq, s:: snd tupleSeq) tl
traverse2ChoicesA ([],[]) choices
seperate' choices;;
val choices : Choice<int,int> list =
[Choice1Of2 1; Choice2Of2 2; Choice2Of2 3; Choice1Of2 4]
val seperate' : choices:Choice<'a,'b> list -> 'a list * 'b list
val it : int list * int list = ([4; 1], [3; 2])
You might find something more general, performant and with appropriate type signature in the FSharpPlus "semi-standard" library using TraverseA?

How Reflection MakeUnion three level type?

I want to try to reflect all types of combinations,
I am using a recursive function
Working at two level
But it won't work at the third level.
open Microsoft.FSharp.Reflection
let rec getAll<'A> (c : UnionCaseInfo) : obj [] =
match c.GetFields() |> List.ofSeq with
| [ x ] when FSharpType.IsUnion x.PropertyType ->
FSharpType.GetUnionCases(x.PropertyType)
|> Array.map (fun uc ->
FSharpValue.MakeUnion(c, getAll(uc)))
|> Array.ofSeq
| _ ->
[| FSharpValue.MakeUnion(c, Array.empty) |]
type C = | C1 | C2
//type B = | B1 | B2
type B = | B1 of C | B2
type A =
| A1
| A2toB of B
| A3
static member GetAll =
FSharpType.GetUnionCases(typeof<A>)
|> Seq.collect getAll<A>
|> Seq.cast<A>
|> Array.ofSeq
(A2toB (B1 C1)).ToString() |> printfn "%A"
A.GetAll |> Array.map (fun t -> t.ToString() |> printfn "%A")
"A2toB (B1 C1)"
Unhandled Exception: System.Reflection.TargetParameterCountException: Parameter count mismatch.
when only use two levels
type B = | B1 | B2
Correct return
"A1"
"A2toB B1"
"A2toB B2"
"A3"
The reason you're getting the exception is that when you call getAll in the recursive case for B1, the field type is C, and C has two cases, C1 | C2, so you get back an array of two elements. Then, that array is passed to the MakeUnion call for B1, which expects only one element (a single instance of C). The call fails because there's an unexpected extra C passed in the array.
You can make this work for your example case by adding something like Array.take 1 to your recursive call to getAll, but it won't work in the general case. I'm not entirely sure what you're trying to accomplish, so providing a general solution is currently a little tricky. If you can clarify your requirements, we can probably provide a better solution.
Here's a version that works for your specific example (though as I said, this is not a good general solution):
let rec getAll<'A> (c : UnionCaseInfo) : obj [] =
match c.GetFields() |> List.ofSeq with
| [ x ] when FSharpType.IsUnion x.PropertyType ->
FSharpType.GetUnionCases(x.PropertyType)
|> Array.map (fun uc ->
FSharpValue.MakeUnion(c, getAll(uc) |> Array.take 1))
|> Array.ofSeq
| _ ->
[| FSharpValue.MakeUnion(c, Array.empty) |]
Here's the output:
"A1"
"A2toB (B1 C1)"
"A2toB B2"
"A3"
thanks Aaron M. Eshbach for found my recursive error, I fix my code
let rec getAll<'A> (c: UnionCaseInfo): obj [] =
match c.GetFields() |> List.ofSeq with
| [ x ] when FSharpType.IsUnion x.PropertyType ->
FSharpType.GetUnionCases(x.PropertyType)
|> Array.map (fun uc ->
let t = uc.Name
getAll (uc) |> Array.map (fun a ->
FSharpValue.MakeUnion(c, [| a |]))
)
|> Array.concat
|> Array.ofSeq
| _ ->
let t = c.Name
[| FSharpValue.MakeUnion(c, Array.empty) |]
I think your code can be simplified. Let us reduce the level of nesting by one; utilize an array sequence expression for generation; and also, let's recurse on System.Type instead of on the unwieldy UnionCaseInfo.
The type parameter, removed below, could have been used at run-time only for unboxing the outermost union type. The type of the other generated cases is necessarily obj, also demonstrating the somewhat limited utility of dynamically generated union cases.
let rec getCases t = [|
for ucinfo in FSharpType.GetUnionCases t do
match ucinfo.GetFields() with
| [|pinfo|] when FSharpType.IsUnion pinfo.PropertyType ->
for x in getCases pinfo.PropertyType ->
FSharpValue.MakeUnion(ucinfo, [|x|])
| _ -> yield FSharpValue.MakeUnion(ucinfo, [||]) |]
// val getCases : t:System.Type -> obj []
type A = A1 | A2toB of B | A3
and B = B1 of C | B2
and C = C1 | C2
getCases typeof<A>
// val it : obj [] = [|A1; A2toB (B1 C1); A2toB (B1 C2); A2toB B2; A3|]

F# : filtering None out and keeping only Some

A quick question on how to effectively group/filter list/seq.
Filter for only records where the optional field is not None
Remove the "option" parameter to make future processes easier (as None has been filtered out)
Group (this is of no problem I believe)
Am I using the best approach?
Thanks!
type tmp = {
A : string
B : int option }
type tmp2 = {
A : string
B : int }
let inline getOrElse (dft: 'a) (x: 'a option) =
match x with
| Some v -> v
| _ -> dft
let getGrouped (l: tmp list) =
l |> List.filter (fun a -> a.B.IsSome)
|> List.map (fun a -> {A = a.A ; B = (getOrElse 0 (a.B)) })
|> List.groupBy (fun a -> a.A)
The most natural approach for map+filter when option is involved is to use choose, which combines those two operations and drops the option wrapper from the filtered output.
Your example would look something like this:
let getGrouped (l: tmp list) =
l
|> List.choose (fun a ->
a.B
|> Option.map (fun b -> {A = a.A; B = b})
|> List.groupBy (fun a -> a.A)
The simple solution is just use the property that an option can be transformed to list with one or zero elements then you can define a function like:
let t1 ({A=a; B=b} : tmp) =
match b with
| (Some i) -> [{ A = a; B= i}]
| _ -> []
let getGrouped (l: tmp list) =
l |> List.collect t1
|> List.groupBy (fun a -> a.A)

How to write an F# union type chooser?

Is there a better way to do this if F#?
type T =
| A of int
| B of string
static member chooseA x = match x with A i -> Some i | _ -> None
static member chooseB x = match x with B s -> Some s | _ -> None
The usecase is the following:
let collection = [A 10; B "abc"]
let aItems = collection |> Seq.choose T.chooseA
let bItems = collection |> Seq.choose T.chooseB
Thanks!
Use List.partition to split your source elements:
type T =
| A of int
| B of string
let collection = [A 10; B "abc"; A 40; B "120"]
let result = List.partition (function | A _ -> true | _ -> false) collection
val result : T list * T list = ([A 10; A 40], [B "abc"; B "120"])
Then you can use fst and snd to select the relevant lists.
This is awkward, but I can see why it is not an important case F#'s design. Usually, there is a solution that allows for a complete pattern match instead of multiple, somewhat incomplete ones. For example, the two concrete item sequences can be constructed like this:
let aItems, bItems =
let accA, accB = ResizeArray(), ResizeArray()
collection |> Seq.iter (function A i -> accA.Add i | B s -> accB.Add s)
seq accA, seq accB
A similar solution without mutation can be made if you dislike it, but I see little reason to worry about encapsulated mutation. Note that the results are cast to seq.
This uses pattern matching in the manner it is designed for:
If another case is added to T, a warning will appear in the handling function, which is exactly where editing should continue: determining how to treat the new input case.
The program doesn't needlessly iterate the input multiple times for each kind of input, but rather goes over it once and handles each item when first encountered.
If the above isn't suitable, you can still shorten the question's code a bit by using the function keyword and declaring the chooser function as a lambda. For example:
let aItems = collection |> Seq.choose (function A i -> Some i | _ -> None)
Note that this is lazy, just like the proposal in the question: here, every iteration over aItems will needlessly iterate over all the B cases in the input.
I can offer the following variant:
open System.Reflection
type T =
| A of int
| B of string
let collection = [A 10; B "abc"; A 40; B "120"]
let sp (col: T list) (str:string) =
if col=[] then []
else
let names = "Is" + str
col |> List.filter(fun x-> let t = x.GetType()
if t.GetProperty(names) = null then false
else
t.InvokeMember(names, BindingFlags.GetProperty, null, x, null) :?> bool)
|> List.map(fun y ->
y.GetType().InvokeMember("get_Item", BindingFlags.InvokeMethod, null, y, null))
sp collection "A" |> printfn "%A\n"
sp collection "B" |> printfn "%A\n"
sp collection "C" |> printfn "%A\n"
Print:
[10; 40]
["abc"; "120"]
[]
http://ideone.com/yAytQk
I'm new to F#, so I think that can be done easier

Resources