Extraire un seul élément de la liste en F #
-
05-07-2019 - |
Question
Je souhaite extraire un seul élément d'une séquence en fa # ou donner une erreur s'il n'y en a pas ou plus d'un. Quelle est la meilleure façon de faire cela?
J'ai actuellement
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."))
Cela semble fonctionner, mais est-ce vraiment la meilleure solution?
Modifier: Comme je me suis dirigé dans la bonne direction, j’ai proposé ce qui suit:
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."))
Je suppose que c'est un peu plus agréable.
La solution
La séquence a une fonction de recherche.
val find : ('a -> bool) -> seq<'a> -> 'a
mais si vous voulez vous assurer que le seq n'a qu'un seul élément, faites alors un filtre Seq.filter, puis prenez la longueur après le filtre et assurez-vous qu'il est égal à un, puis prenez la tête. Tout en seq, pas besoin de convertir en liste.
Modifier:
Sur une note de côté, je allais suggérer de vérifier que la queue d'un résultat est vide (O (1), au lieu d'utiliser la fonction longueur
(O (n)). La queue ne fait pas partie de seq, mais je pense que vous pouvez trouver un bon moyen d’émuler cette fonctionnalité.
Autres conseils
fait dans le style des fonctions standard de séquence existantes
#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
Vous pouvez faire une implémentation pure mais cela finira par sauter les étapes pour être correct et efficace (se terminer rapidement lors du deuxième match appelle vraiment un drapeau disant "je l’ai déjà trouvé")
Utilisez ceci:
> 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
Quel est le problème avec l'utilisation de la fonction de bibliothèque existante?
let single f xs = System.Linq.Enumerable.Single(xs, System.Func<_,_>(f))
[1;2;3] |> single ((=) 4)
Mes deux cents ... cela fonctionne avec le type d'option afin que je puisse l'utiliser dans ma monade personnalisée. pourrait être modifié très facilement mais pour travailler avec des exceptions à la place
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
La réponse mise à jour consisterait à utiliser Seq.exactlyOne qui soulève une exception ArgumentException