2009-05-05 8 views
6

Voglio estrarre un singolo elemento da una sequenza in F #, o dare un errore se non ce n'è uno o più di uno. Qual è il modo migliore per farlo?Estrai singolo elemento dalla lista in F #

Al momento ho

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

sembra funzionare, ma è davvero il modo migliore?

Edit: Come mi è stato fatto nella direzione giusta, mi si avvicinò con il seguente:

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

Credo che sia un po 'più bello.

+0

Potete fornire una sequenza di campione e quello che stai cercando? –

+0

Non lo trovo necessario. Voglio trovare il primo valore e dare un errore se ce n'è più di uno. Questo è il – erikkallen

+0

+1 - vuoi un F # equivalente a un utile operatore LINQ (System.Linq.Enumerable.Single) - normalmente è il contrario! –

risposta

3

La sequenza ha una funzione di ricerca.

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

, ma se si vuole garantire che la ss ha un solo elemento, quindi facendo un Seq.filter, poi prendere la lunghezza dopo il filtro e assicurarsi che è uguale a uno, e poi prendere la testa. Tutto in Seq, non c'è bisogno di convertire in una lista.

Edit: Su un lato nota, ho era andando a suggerire controllando che il coda di un risultato è vuoto (O (1), invece di utilizzare la funzione di length (O (n)) di coda. non è una parte di ss, ma penso che si può elaborare un buon modo per emulare tale funzionalità.

+0

Su sequenze lunghe con molte corrispondenze questo fallirà molto lentamente, su sequenze infinite quindi il risultato corretto è di continuare a calcolare all'infinito o terminare in anticipo ma non lo farà (questa differenza è piuttosto marginale nell'utilità) – ShuggyCoUk

+0

Sì, penso Seq infinito sta andando a prenderti in entrambi i modi, prova a trovare qualcosa che non è in una lista infinita ... Ma, il punto di prendere la lunghezza invece di controllare che la coda sia vuota è buona, e quello che in origine volevo menzionare, ma il seq non ha una funzione di coda.Ho modificato il mio post per riflettere questa limitazione e utilizzare una funzione che è O (1). Grazie – nlucaroni

+0

La funzione di salto di Seq funzionerà come alternativa alla coda, Seq.skip 1 il filtro dovrebbe essere vuoto e seq.hd dopo che ne verificherà almeno uno (l'hd genererà la propria eccezione se è vuota e utile) – ShuggyCoUk

4

fatto nello stile della sequenza esistente funzioni 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 

si potrebbe fare un'implementazione puro ma finirà per saltare mortali per essere corretta ed efficiente (che chiude rapidamente sul secondo match chiama davvero per una bandiera dicendo 'ho trovato gia')

1

Utilizzare questa:

> 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

Non saltare sia hd sia per calcolare la testa (quindi se ci sono effetti collaterali, li vedi due volte)? –

+0

Sì. Se sono lenti o hanno effetti collaterali indesiderati, allora ti consigliamo di ottimizzare questo. –

0

I miei due centesimi ... questo funziona con il tipo di opzione in modo che io possa usarlo nella mia custom forse monad. potrebbe essere modificato molto facilmente però per lavorare con le eccezioni invece

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

Probabilmente dovresti memorizzare nella cache 'e.Current' prima di chiamare' MoveNext' una seconda volta, dato che alcuni enumeratori potrebbero lanciare un'eccezione se si accede a 'e.Current' dopo aver raggiunto la fine dell'enumerazione. Inoltre, non vedo alcun vantaggio nella creazione della funzione nidificata 'single', poiché viene sempre chiamata esattamente una volta. Sembra strano anche restituire "None" se ci sono 0 elementi, ma lanciare un'eccezione se ce ne sono più di 1 - in tal caso chiamerei il metodo "AtMostOne" piuttosto che "Single". – kvb

+0

la funzione annidata è lì solo per essere chiari che sta effettivamente lavorando su IEnumerator e non su IEnumerable. il nessuno e alcuni sono lì quindi questo si inserisce nella mia forse monade, in quel contesto si legge molto naturalmente. Uso questo in accesso ai dati molto per concatenare le chiamate dipendenti. quando nessuno cortocircuito quando alcuni continuano a fare il genere – Brad

+0

L'uso di un tipo di opzione va bene, ma allora perché non restituire "Nessuno" se c'è anche più di un elemento? Per me, se ci si aspetta di avere un singolo elemento, allora i casi 0 e 2 o più elementi sono probabilmente ugualmente eccezionali e dovrebbero essere trattati allo stesso modo a meno che non vi sia una motivazione razionale per distinguerli (in In questo caso il metodo dovrebbe probabilmente essere chiamato qualcosa di più specifico per chiarezza, come 'AtMostOne'). – kvb

1

Cosa c'è di sbagliato nell'usare la funzione di libreria esistente?

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

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

Questo è uno dei soli metodi di estensione in System.Linq che non ha un equivalente nei moduli F #. Non sono sicuro se c'è una ragione per questo. –

0

Aggiornato risposta sarebbe quella di utilizzare Seq.exactlyOne che solleva un ArgumentException