Извлечь один элемент из списка в F # - PullRequest
6 голосов
/ 05 мая 2009

Я хочу извлечь один элемент из последовательности в 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."))

Я думаю, это немного лучше.

Ответы [ 6 ]

4 голосов
/ 05 мая 2009

выполнено в стиле существующей последовательности стандартных функций

#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

Вы могли бы сделать чистую реализацию, но в итоге она будет прыгать через обручи, чтобы быть правильной и эффективной (быстрое завершение во втором матче действительно вызывает флаг, говорящий: «Я уже его нашел»)

3 голосов
/ 05 мая 2009

Последовательность имеет функцию поиска.

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

но если вы хотите убедиться, что в seq есть только один элемент, то выполните Seq.filter, затем возьмите длину после фильтра и убедитесь, что она равна единице, а затем возьмите голову. Все в Seq, не нужно преобразовывать в список.

Edit: Кстати, я собирался предложить проверить, что хвост результата пуст (O (1), вместо использования функции length (O (n)). Хвост не является частью seq, но я думаю, что вы можете найти хороший способ эмулировать эту функциональность.

1 голос
/ 24 ноября 2011

Что не так с использованием существующей библиотечной функции?

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

[1;2;3] |> single ((=) 4)
1 голос
/ 22 мая 2009

Используйте это:

> 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
0 голосов
/ 04 мая 2016

Обновленный ответ будет использовать Seq.exactlyOne, который вызывает ArgumentException

0 голосов
/ 24 ноября 2011

Мои два цента ... это работает с типом параметра, так что я могу использовать его в своей монаде, возможно. может быть изменен очень легко, хотя вместо этого можно работать с исключениями

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