質問

F#のシーケンスから1つのアイテムを抽出するか、1つまたは複数ある場合にエラーを返します。これを行う最良の方法は何ですか?

現在

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."))

動作するようですが、本当に最良の方法ですか?

編集:正しい方向に向けられたので、次のことを思いつきました:

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."))

もう少し良いと思います。

役に立ちましたか?

解決

シーケンスには検索機能があります。

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

ただし、seqに要素が1つしかないことを確認する場合は、Seq.filterを実行し、filterの後の長さを取得し、1に等しいことを確認してからheadを取得します。すべてSeqで、リストに変換する必要はありません。

編集: 副次的に、結果の tail が空であることを確認することを提案するつもりでした (関数 length (O(n))。Tailはseqの一部ではありませんが、その機能をエミュレートする良い方法を考え出すことができると思います。

他のヒント

既存のシーケンス標準関数のスタイルで行われます

#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

純粋な実装を行うこともできますが、正確かつ効率的にするためにフープをジャンプすることになります(2回目のマッチですぐに終了すると、「すでに見つかった」というフラグが必要になります)

これを使用:

> 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

既存のライブラリ関数の使用の何が問題になっていますか?

let single f xs = System.Linq.Enumerable.Single(xs, System.Func<_,_>(f))

[1;2;3] |> single ((=) 4)

私の2セント...これはオプションタイプで機能するので、カスタムの多分モナドで使用できます。代わりに例外を使用するように非常に簡単に変更できます

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

更新された答えは、ArgumentExceptionを発生させるSeq.exactlyOneを使用することです

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top