Извлечь один элемент из списка в F#
-
05-07-2019 - |
Вопрос
Я хочу извлечь один элемент из последовательности в F # или выдать ошибку, если их нет или их несколько.Каков наилучший способ сделать это?
В настоящее время у меня есть
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 содержит только один элемент, то выполните Seq.filter, затем возьмите длину после filter и убедитесь, что она равна единице, а затем возьмите head.Все в последовательности, нет необходимости преобразовывать в список.
Редактировать:С другой стороны, я был собираюсь предложить проверить, что хвост результата пусто (O(1), вместо использования функции 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
Вы могли бы сделать чистую реализацию, но она в конечном итоге будет прыгать через обручи, чтобы быть правильной и эффективной (быстрое завершение во втором матче действительно вызывает флаг, говорящий: «Я уже его нашел»)
Используйте это:
> 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)
Мои два цента ... это работает с типом параметра, поэтому я могу использовать его в своей монаде, возможно. может быть изменен очень легко, хотя вместо этого работать с исключениями
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
Обновленный ответ - использовать Seq.exactlyOne, который вызывает ArgumentException