2009-05-05 7 views
6

Je veux extraire un seul élément d'une séquence en F #, ou donner une erreur s'il n'y en a pas ou plus d'une. Quelle est la meilleure façon de procéder?Extrait un seul élément de la liste dans F #

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

Il semble fonctionner, mais est-ce vraiment la meilleure façon?

Edit: Comme je l'ai été souligné dans la bonne direction, je suis venu avec 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 pense qu'il est un peu plus agréable.

+0

Pouvez-vous fournir une séquence d'échantillons et ce que vous cherchez? –

+0

Je ne trouve pas cela nécessaire. Je veux trouver la première valeur et donner une erreur s'il y en a plus d'une. C'est – erikkallen

+0

+1 - vous voulez un F # équivalent à un opérateur LINQ utile (System.Linq.Enumerable.Single) - normalement c'est l'inverse! –

Répondre

3

La séquence a une fonction de recherche.

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

mais si vous voulez vous assurer que les suivants ne comporte qu'un seul élément, puis en faisant un Seq.filter, puis prendre la longueur après filtre et assurez-vous qu'il est égal à un, puis prendre la tête. Tout en Seq, pas besoin de convertir en liste.

Edit: Sur une note de côté, je était va suggérer de vérifier que la queue d'un résultat est vide (O (1), au lieu d'utiliser la fonction length (O (n)) Queue. ne fait pas partie des suivants, mais je pense que vous pouvez trouver une bonne façon d'imiter cette fonctionnalité.

+0

Sur de longues séquences avec beaucoup de correspondances cela échouera très lentement, sur des séquences infinies alors le résultat correct est de continuer à calculer à l'infini ou à se terminer tôt mais ça ne sera pas non plus (cette différence est plutôt marginale en utilité) – ShuggyCoUk

+0

Ouais, je pense l'infini seq vous mènera dans les deux sens, essayez de trouver quelque chose qui n'est pas dans une liste infinie ... Mais, le point de prendre la longueur au lieu de simplement vérifier que la queue est vide est bon, et ce que je voulais initialement mentionner, mais seq n'a pas de fonction de queue.J'ai modifié mon message pour refléter cette limitation et utiliser une fonction qui est O (1). Merci – nlucaroni

+0

La fonction de saut de Seq fonctionnera comme une alternative à la queue, Seq.skip 1 filtre devrait être vide et seq.hd après cela vérifiera qu'il en a au moins un (hd lancera sa propre exception si elle est vide ce qui est utile) – ShuggyCoUk

4

fait dans le style de la séquence existante des fonctions standard

#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 mise en œuvre pur mais il finira par sauter à travers des cerceaux pour être correcte et efficace (se terminant rapidement sur le deuxième match appelle vraiment un drapeau en disant: «Je l'ai trouvé déjà)

1

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 
+0

Ne pas sauter et hd à la fois calculer la tête (donc s'il y a des effets secondaires, vous les voyez deux fois)? –

+0

Oui. Si elles sont lentes ou ont des effets secondaires indésirables, alors vous aurez envie d'optimiser cela. –

0

Mes deux cents ... cela fonctionne avec le type d'option, donc je peux l'utiliser dans mon custom peut-être monad. pourrait être modifié très facile bien que de 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 
+0

Vous devriez probablement mettre en cache 'e.Current' avant d'appeler' MoveNext' une seconde fois, car certains énumérateurs pourraient lancer une exception si 'e.Current' est accédé après avoir atteint la fin de l'énumération. En outre, je ne vois aucun avantage à créer la fonction 'single' imbriquée, car elle est toujours appelée une seule fois. Il semble également étrange de retourner 'None' s'il y a 0 éléments, mais de lancer une exception s'il y en a plus d'un - dans ce cas j'appellerais la méthode' AtMostOne' plutôt que 'Single'. – kvb

+0

la fonction imbriquée est là juste pour être clair qu'elle fonctionne sur IEnumerator et non IEnumerable. le none et certains sont là donc cela se branche peut-être dans mon monad, dans ce contexte il se lit très naturellement. Je l'utilise beaucoup dans l'accès aux données pour enchaîner les appels dépendants. quand aucun court-circuit quand certains continuent quelque chose – Brad

+0

L'utilisation d'un type d'option est bien, mais alors pourquoi ne pas retourner 'None' s'il y a plus d'un élément? Pour moi, si vous vous attendez à avoir un seul élément, alors les cas 0-élément et 2-ou-plusieurs-éléments sont probablement tout aussi exceptionnels et doivent être traités de la même manière à moins qu'il y ait une justification convaincante pour les distinguer (dans Dans ce cas, la méthode devrait probablement être appelée quelque chose de plus spécifique pour plus de clarté, comme 'AtMostOne'. – kvb

1

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) 
+0

C'est l'une des seules méthodes d'extension dans System.Linq qui n'a pas d'équivalent dans les modules F #. Je ne sais pas s'il y a une raison à cela. –

0

réponse Mise à jour serait d'utiliser Seq.exactlyOne qui soulève une ArgumentException

Questions connexes