F# Seq.exactlyOne的非抛出版本,用于测试单例序列

F# Seq.exactlyOne的非抛出版本,用于测试单例序列,f#,singleton,sequence,seq,F#,Singleton,Sequence,Seq,更新:我为此创建了UserVoice请求: 我需要Seq.exactlyOne的功能,但是有一些/没有语义。换句话说,我需要Seq.head,或者,如果序列为空或包含多个项目,我什么都不需要。在这种情况下,使用Seq.exactlyOne将抛出 我不认为有一种内置的方法可以实现这一点(尽管它听起来很琐碎,以至于人们会期望有一个对应的Seq.singleton)。我想到了这个,但感觉很复杂: let trySingleton sq = match Seq.isEmpty sq with

更新:我为此创建了UserVoice请求:

我需要
Seq.exactlyOne
的功能,但是有一些/没有语义。换句话说,我需要
Seq.head
,或者,如果序列为空或包含多个项目,我什么都不需要。在这种情况下,使用
Seq.exactlyOne
将抛出

我不认为有一种内置的方法可以实现这一点(尽管它听起来很琐碎,以至于人们会期望有一个对应的
Seq.singleton
)。我想到了这个,但感觉很复杂:

let trySingleton sq = 
    match Seq.isEmpty sq with 
    | true -> None 
    | false -> 
        match sq |> Seq.indexed |> Seq.tryFind (fst >> ((=) 1)) with
        | Some _ -> None
        | None -> Seq.exactlyOne sq |> Some
给出:

> trySingleton [|1;2;3|];;
val it : int option = None
> trySingleton Seq.empty<int>;;
val it : int option = None
> trySingleton [|1|];;
val it : int option = Some 1

(更好,但仍然感觉很尴尬)

为什么不通过强制处理枚举器来解决这个问题

let trySingleton' (xs : seq<_>) =
    use en = xs.GetEnumerator()
    if en.MoveNext() then
        let res = en.Current
        if en.MoveNext() then None
        else Some res
    else None

trySingleton' Seq.empty<int>    // None
trySingleton' [1]               // Some 1
trySingleton' [1;2]             // None
let trySingleton'(xs:seq)=
使用en=xs.GetEnumerator()
如果en.MoveNext()那么
设res=en.电流
如果en.MoveNext()则无
还有一些
没有别的
trySingleton'Seq.empty//None
trySingleton'[1]//一些1
trySingleton'[1;2]//无

我不知道有什么内置函数可以实现这一点,但这里有一种替代方法可以实现它:

let tryExactlyOne xs =
    match xs |> Seq.truncate 2 |> Seq.toList with
    | [x] -> Some x
    | _   -> None
FSI演示:

> [42] |> List.toSeq |> tryExactlyOne;;
val it : int option = Some 42

> [42; 1337] |> List.toSeq |> tryExactlyOne;;
val it : int option = None

> Seq.empty<int> |> tryExactlyOne;;
val it : int option = None
[42]|>List.toSeq |>tryExactlyOne;;
val it:int选项=大约42
>[42;1337]|>List.toSeq |>tryExactlyOne;;
val it:int选项=无
>Seq.empty |>tryExactlyOne;;
val it:int选项=无

它不是“更简单”,而是感觉“更干净”。任何一个答案都表明,我没有错过一个微不足道的方法(意思是:核心库)。这两种解决方案之间有点随意,我会接受这一个,因为它的创造性和干净性,谢谢:)。我不知道
truncate
没有抛出,但文档清楚地说(“最多X项).很好的解决方案!不确定
Seq.toList
是否会对性能产生不利影响,但由于这将只是一个0、1或2项列表,我希望它不会有什么影响。@Abel您需要尝试迭代两次以确定该项是否为单项,因此我认为您无法更有效地执行此操作然后(
Seq.truncate
是懒惰的)。kaefer和您自己的解决方案也会迭代两次。是的,没错。尽管您和我的解决方案会在调用时迭代两次(至少,我认为),kaefer的不会在调用时重复两次,但会重置迭代指针,因此第二次迭代只在多个场景中出现。因此,我认为他的解决方案“更干净”。@Abel同样适用于
Seq.truncate
。它使用
IEnumerator.MoveNext
在封面下:顺便说一句,你可以建议
tryExactlyOne
作为语言的补充:我自己会添加这个建议,但我在那个论坛上没有投票权。@MarkSeemann:done,as,tx同意这个建议。
> [42] |> List.toSeq |> tryExactlyOne;;
val it : int option = Some 42

> [42; 1337] |> List.toSeq |> tryExactlyOne;;
val it : int option = None

> Seq.empty<int> |> tryExactlyOne;;
val it : int option = None