Reputation: 17038
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.
Upvotes: 2
Views: 148
Reputation: 738
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?
Upvotes: 0
Reputation: 4488
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
Upvotes: 3