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?
我想从F#中的序列中提取单个项目,或者如果没有或多个则提供错误。做这个的最好方式是什么?
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.
我想这有点好看。
6 个解决方案
#1
Sequence has a find function.
Sequence具有find功能。
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.
但是如果你想确保seq只有一个元素,那么做一个Seq.filter,然后在过滤后取长度并确保它等于一个,然后取头。全部在Seq中,无需转换为列表。
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.
编辑:在旁注中,我建议检查结果的尾部是否为空(O(1),而不是使用函数长度(O(n))。尾部不是seq的一部分,但是我认为您可以找到一种模拟该功能的好方法。
#2
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')
你可以做一个纯粹的实现,但它最终会通过箍跳到正确和有效(在第二场比赛中快速终止真的需要一个标记'我发现它已经'')
#3
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
#4
What's wrong with using the existing library function?
使用现有的库函数有什么问题?
let single f xs = System.Linq.Enumerable.Single(xs, System.Func<_,_>(f))
[1;2;3] |> single ((=) 4)
#5
My two cents... this works with the option type so I can use it in my custom maybe monad. could be modified very easy though to work with exceptions instead
我的两分钱......这适用于选项类型,所以我可以在我的自定义monad中使用它。可以很容易地修改,但是要使用异常
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
#6
Updated answer would be to use Seq.exactlyOne which raises an ArgumentException
更新的答案是使用Seq.exactlyOne引发ArgumentException
#1
Sequence has a find function.
Sequence具有find功能。
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.
但是如果你想确保seq只有一个元素,那么做一个Seq.filter,然后在过滤后取长度并确保它等于一个,然后取头。全部在Seq中,无需转换为列表。
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.
编辑:在旁注中,我建议检查结果的尾部是否为空(O(1),而不是使用函数长度(O(n))。尾部不是seq的一部分,但是我认为您可以找到一种模拟该功能的好方法。
#2
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')
你可以做一个纯粹的实现,但它最终会通过箍跳到正确和有效(在第二场比赛中快速终止真的需要一个标记'我发现它已经'')
#3
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
#4
What's wrong with using the existing library function?
使用现有的库函数有什么问题?
let single f xs = System.Linq.Enumerable.Single(xs, System.Func<_,_>(f))
[1;2;3] |> single ((=) 4)
#5
My two cents... this works with the option type so I can use it in my custom maybe monad. could be modified very easy though to work with exceptions instead
我的两分钱......这适用于选项类型,所以我可以在我的自定义monad中使用它。可以很容易地修改,但是要使用异常
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
#6
Updated answer would be to use Seq.exactlyOne which raises an ArgumentException
更新的答案是使用Seq.exactlyOne引发ArgumentException