2009-05-05 10 views
6

Quiero extraer un único elemento de una secuencia en F #, o dar un error si no hay ninguno o más. ¿Cuál es la mejor manera de hacer esto?Extraer un solo elemento de la lista en F #

Actualmente tengo

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

parece que funciona, pero ¿es realmente la mejor manera?

Edit: Como me señaló en la dirección correcta, me ocurrió lo siguiente:

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

supongo que es un poco más agradable.

+0

¿Puede proporcionar una secuencia de muestras y lo que está buscando? –

+0

No me parece necesario. Quiero encontrar el primer valor y dar un error si hay más de uno. Eso es todo – erikkallen

+0

+1 - quiere un F # equivalente a un operador LINQ útil (System.Linq.Enumerable.Single) - ¡normalmente es al revés! –

Respuesta

3

La secuencia tiene una función de búsqueda.

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

pero si usted quiere asegurarse de que la SEC tiene un solo elemento, a continuación, haciendo un Seq.filter, luego tomar la longitud después del filtro y asegúrese de que es igual a uno, y luego tomar la cabeza. Todo en Seq, no es necesario convertirlo a una lista.

Editar: En una nota lateral, me fue va a sugerir la comprobación de que el cola de un resultado está vacía (O (1), en lugar de utilizar la función length (O (n)) de la cola. no es una parte de la SEC, pero creo que se puede trabajar a cabo una buena manera de emular esa funcionalidad.

+0

En secuencias largas con muchas coincidencias esto fallará muy lentamente, en secuencias infinitas, entonces el resultado correcto es seguir calculando hasta el infinito o terminar temprano, pero tampoco (esta diferencia es bastante marginal en cuanto a la utilidad) – ShuggyCoUk

+0

Sí, creo infinito seq te va a conseguir de cualquier manera, intenta encontrar algo que no está en una lista infinita ... Pero, el punto de tomar la longitud en lugar de solo verificar que la cola esté vacía es buena, y lo que originalmente quería mencionar, pero seq no tiene una función de cola.Modifiqué mi publicación para reflejar esa limitación y usar una función que es O (1). Gracias – nlucaroni

+0

La función de salto de Seq funcionará como una alternativa a la cola, el filtro Seq.skip 1 debería estar vacío y seq.hd después de eso comprobará que tiene al menos uno (hd arrojará su propia excepción si está vacío, lo cual es útil) – ShuggyCoUk

4

hecho en el estilo de la secuencia existente funciones estándar

#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 

se podría hacer una aplicación pura pero terminará saltando por el aro para ser correcta y eficiente (que termina rápidamente en el segundo partido de verdad exige una bandera que dice 'me encontré con que ya')

1

Utilice esta:

> 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

No salte ni hd ambos calculen la cabeza (por lo tanto, si hay efectos secundarios, ¿los ve dos veces)? –

+0

Sí. Si son lentos o tienen efectos secundarios indeseables, querrá optimizar esto. –

0

Mis dos centavos ... esto funciona con el tipo de opción para que pueda usarlo en mi mónada tal vez personalizada. Sin embargo, podría modificarse muy fácilmente para trabajar con excepciones

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

Probablemente deberías guardar en caché 'e.Current' antes de llamar a' MoveNext' por segunda vez, ya que algunos enumeradores podrían lanzar una excepción si se accede a 'e.Current' después de llegar al final de la enumeración. Además, no veo ningún beneficio para crear la función 'single' anidada, ya que siempre se llama exactamente una vez. También parece extraño devolver 'None' si hay 0 elementos, pero lanza una excepción si hay más de 1; en ese caso, llamaría al método' AtMostOne' en lugar de 'Single'. – kvb

+0

la función anidada está allí para que quede claro que realmente está funcionando en IEnumerator y no en IEnumerable. ninguno y algunos están allí, así que esto se conecta a mi tal vez mónada, en ese contexto se lee muy naturalmente. Lo uso mucho en acceso a datos para encadenar llamadas dependientes. cuando no hay cortocircuito cuando algunos continúan algo así como – Brad

+0

El uso de un tipo de opción está bien, pero ¿por qué no se devuelve 'None' si también hay más de un elemento? Para mí, si esperas tener un solo elemento, entonces los casos de 0 elementos y 2 o más elementos son probablemente igualmente excepcionales y deben tratarse de la misma manera a menos que exista una lógica convincente para distinguirlos (en en cuyo caso, el método probablemente debería llamarse algo más específico para mayor claridad, como 'AtMostOne'). – kvb

1

¿Qué problema hay con el uso de la función de biblioteca existente?

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

[1;2;3] |> single ((=) 4) 
+0

Este es uno de los únicos métodos de extensión en System.Linq que no tiene un equivalente en los módulos F #. No estoy seguro de si hay una razón para eso. –

0

Actualizado respuesta sería utilizar Seq.exactlyOne lo que plantea un ArgumentException

Cuestiones relacionadas