F#のリストから単一の要素を抽出する
-
05-07-2019 - |
質問
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を使用することです
所属していません StackOverflow