Extract a single item from a list in F #

I want to extract one element 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.")) 

This seems to work, but is this really the best way?

Edit: Since I was directed 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 think this is a little better.

+6
functional-programming f # sequence
source share
6 answers

The sequence has a search function.

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

but if you want to make sure that seq has only one element, then doing Seq.filter, then take the length after the filter and make sure it is equal to one, and then take the head. Everything in Seq, no need to convert to list.

Edit: On the side of the note, I was going to suggest checking that the tail of the result is empty (O (1), instead of using the length (O (n)) function. The tail is not part of seq, but I think you can plan this functionality well.

+3
source share

performed in the style of existing standard sequence 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 make a clean implementation, but in the end it will jump through the hoops to be correct and effective (the second match quickly ends, it really causes a flag saying "I found it already")

+4
source share

Use this:

 > let only s = if not(Seq.isEmpty s) && Seq.isEmpty(Seq.skip 1 s) then Seq.hd s else raise(System.ArgumentException "only");; val only : seq<'a> -> 'a 
+1
source share

What happened with using an existing library function?

 let single f xs = System.Linq.Enumerable.Single(xs, System.Func<_,_>(f)) [1;2;3] |> single ((=) 4) 
+1
source share

My two cents ... this works with the option type, so I can use it in my regular, possibly monad. could be very easily modified, but work with exceptions instead

 let Single (items : seq<'a>) = let single (e : IEnumerator<'a>) = if e.MoveNext () then if e.MoveNext () then raise(InvalidOperationException "more than one, expecting one") else Some e.Current else None use e = items.GetEnumerator () e |> single 
0
source share

The updated answer will be to use Seq.exactlyOne, which throws an ArgumentException

0
source share

All Articles