非抛出版本的Seq.exactlyOne来测试单例序列

Abe*_*bel 4 singleton f# sequence seq

更新:我为此创建了一个UserVoice请求:展开Seq的基数函数.

我需要功能Seq.exactlyOne,但有Some/None语义.换句话说,我需要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
Run Code Online (Sandbox Code Playgroud)

得到:

> 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
Run Code Online (Sandbox Code Playgroud)

是否有更简单甚至是内置的方式?我可以尝试/赶上Seq.exactlyOne,但这是围绕异常构建业务逻辑,我宁愿不(并且它很昂贵).

更新: 我不知道该Seq.tryItem功能,这将使这更简单:

let trySingleton sq =
    match sq |> Seq.tryItem 1 with
    | Some _ -> None
    | None -> Seq.tryHead sq
Run Code Online (Sandbox Code Playgroud)

(更好,但它仍然感觉相当尴尬)

kae*_*fer 6

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

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
Run Code Online (Sandbox Code Playgroud)