Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extract single element from list in F#

I want to extract a single item from a sequence in F#, or give an error if there is none or more than one. What is the best way to do this?

I currently have

let element = data |> (Seq.filter (function | RawXml.Property (x) -> false | _ -> true))
                   |> List.of_seq
                   |> (function head :: [] -> head | head :: tail -> failwith("Too many elements.") | [] -> failwith("Empty sequence"))
                   |> (fun x -> match x with MyElement (data) -> x | _ -> failwith("Bad element."))

It seems to work, but is it really the best way?

Edit: As I was pointed in the right direction, I came up with the following:

let element = data |> (Seq.filter (function | RawXml.Property (x) -> false | _ -> true))
                   |> (fun s -> if Seq.length s <> 1 then failwith("The sequence must have exactly one item") else s)
                   |> Seq.hd
                   |> (fun x -> match x with MyElement (_) -> x | _ -> failwith("Bad element."))

I guess it's a little nicer.

like image 875
erikkallen Avatar asked May 05 '09 16:05

erikkallen


2 Answers

Sequence has a find function.

val find : ('a -> bool) -> seq<'a> -> 'a

but if you want to ensure that the seq has only one element, then doing a Seq.filter, then take the length after filter and ensure it equals one, and then take the head. All in Seq, no need to convert to a list.

Edit: On a side note, I was going to suggest checking that the tail of a result is empty (O(1), instead of using the function length (O(n)). Tail isn't a part of seq, but I think you can work out a good way to emulate that functionality.

like image 185
nlucaroni Avatar answered Oct 20 '22 05:10

nlucaroni


done in the style of the existing sequence standard functions

#light

let findOneAndOnlyOne f (ie : seq<'a>)  = 
    use e = ie.GetEnumerator()
    let mutable res = None 
    while (e.MoveNext()) do
        if f e.Current then
            match res with
            | None -> res <- Some e.Current
            | _ -> invalid_arg "there is more than one match"          
    done;
    match res with
        | None -> invalid_arg "no match"          
        | _ -> res.Value

You could do a pure implementation but it will end up jumping through hoops to be correct and efficient (terminating quickly on the second match really calls for a flag saying 'I found it already')

like image 4
ShuggyCoUk Avatar answered Oct 20 '22 05:10

ShuggyCoUk