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.

Était-ce utile?

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top