Domanda

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?

Attualmente 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?

Modifica: quando sono stato indicato nella giusta direzione, ho pensato a quanto segue:

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

Immagino sia un po 'più bello.

È stato utile?

Soluzione

La sequenza ha una funzione find.

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

ma se vuoi assicurarti che il seq abbia solo un elemento, quindi esegui un filtro Seq., quindi prendi la lunghezza dopo il filtro e assicurati che sia uguale a uno, quindi prendi la testa. Tutto in Seq, non è necessario convertirlo in un elenco.

Modifica: In una nota a margine, io stavo suggerendo di verificare che la coda di un risultato fosse vuota (O (1), invece di usare la funzione length (O (n)). Tail non fa parte di seq, ma penso che tu possa trovare un buon modo per emulare quella funzionalità.

Altri suggerimenti

fatto nello stile delle funzioni standard di sequenza esistenti

#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

Potresti fare un'implementazione pura ma finirà per saltare attraverso i cerchi per essere corretto ed efficiente (terminare rapidamente nella seconda partita richiede davvero una bandiera che dice "l'ho già trovato")

Usa questo:

> 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

Cosa c'è che non va nell'uso della funzione di libreria esistente?

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

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

I miei due centesimi ... funziona con il tipo di opzione, quindi posso usarlo nella mia monade forse personalizzata. potrebbe essere modificato molto facilmente sebbene funzioni invece con eccezioni

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 risposta aggiornata sarebbe usare Seq.exactlyOne che genera ArgumentException

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top