2009-05-05 14 views
6

Chcę wyodrębnić pojedynczy element z sekwencji w F # lub podać błąd, jeśli nie ma go lub jest więcej niż jeden. Jaki jest najlepszy sposób na zrobienie tego?Wyodrębnij pojedynczy element z listy w F #

Obecnie mam

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

wydaje się działać, ale jest to naprawdę najlepszy sposób?

Edit: Jak już wskazano we właściwym kierunku, wpadłem na następujący:

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

myślę, że to trochę ładniejszy.

+0

można podać sekwencję próbki i co szukasz? –

+0

Nie uważam tego za konieczne. Chcę znaleźć pierwszą wartość i podać błąd, jeśli jest więcej niż jeden. To jest to – erikkallen

+0

+1 - potrzebujesz odpowiednika F # do użytecznego operatora LINQ (System.Linq.Enumerable.Single) - normalnie jest odwrotnie! –

Odpowiedz

3

Sekwencja ma funkcję wyszukiwania.

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

ale jeśli chcesz mieć pewność, że nast ma tylko jeden element, a następnie robi Seq.filter, a następnie podjąć długość po filtrze i upewnić się, że równa się jeden, a następnie podjąć głową. Wszystko w Seq, nie trzeba konwertować do listy.

Edit: Na marginesie, ja był zamiar zaproponować sprawdzenie czy ogon od wyniku jest pusta (O (1), zamiast korzystać z funkcji length (O (n)) Tail. nie jest częścią seq, ale myślę, że można wypracować dobry sposób, aby naśladować tę funkcjonalność.

+0

Na długich sekwencjach z wieloma dopasowaniami, to się nie powiedzie bardzo wolno, na nieskończonych sekwencjach, wtedy prawidłowym rezultatem jest ciągłe obliczanie do nieskończoności lub kończenie, ale to też nie będzie (ta różnica jest dość marginalna w użyteczności) – ShuggyCoUk

+0

Tak, myślę nieskończone seq sprawią, że będziesz w obie strony, spróbuj znaleźć coś, co nie jest na nieskończonej liście ... Ale punkt zmierzenia długości zamiast sprawdzania, czy ogon jest pusty, jest dobry i co pierwotnie chciałem wspomnieć, ale seq nie ma funkcji ogonowej.Zmodyfikowałem swój post, aby odzwierciedlić to ograniczenie i użyć funkcji O (1). Dzięki – nlucaroni

+0

Funkcja pomijania Seq będzie działać jako alternatywa dla ogona, Seq.skip 1 filtr powinien być pusty i seq.hd po tym sprawdzi, czy ma co najmniej jeden (hd wyrzuci własny wyjątek, jeśli jest pusty, co jest przydatne) – ShuggyCoUk

4

zrobione w stylu istniejącej sekwencji standardowe funkcje

#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 

można zrobić czystą wdrożenia ale skończy się skakaniem przez obręcze być poprawny i efektywny (szybko kończące się na drugim meczu naprawdę domaga flagą z napisem „Znalazłem go już”)

1

użyj:

> 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

Nie należy pomijać obu hd i obliczyć głowę (więc jeśli występują efekty uboczne, widzisz je dwa razy)? –

+0

Tak. Jeśli są powolne lub mają niepożądane efekty uboczne, zechcesz to zoptymalizować. –

0

Moje dwa centy ... to działa z typ opcji, dzięki czemu mogę go użyć w mojej niestandardowej monadzie. może być modyfikowany bardzo łatwo, ale działa z wyjątkami zamiast:

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

Powinieneś prawdopodobnie buforować 'e.Current' przed wywołaniem' MoveNext' po raz drugi, ponieważ niektórzy enumeratorzy mogą rzucić wyjątek, jeśli 'e.Current' jest dostępny po osiągnięciu końca wyliczenia. Ponadto nie widzę żadnej korzyści z tworzenia zagnieżdżonej funkcji 'single', ponieważ jest ona zawsze wywoływana dokładnie raz. Wydaje się również dziwne, aby zwracać "Brak", jeśli jest 0 elementów, ale wyrzucać wyjątek, jeśli jest ich więcej niż 1 - w tym przypadku nazwałem metodę 'AtMostOne' zamiast' Single'. – kvb

+0

Funkcja zagnieżdżona jest dostępna tylko po to, aby być jasnym, że faktycznie działa na IEnumeratorze, a nie IEnumerable. Nikt i niektórzy nie są, więc to wtyczka do mojej może monady, w tym kontekście czyta się bardzo naturalnie. Używam tego w dostępie do danych dużo do łączenia połączeń zależnych. gdy żaden z nich nie zwarcia, gdy niektóre kontynuować coś innego – Brad

+0

Użycie typu opcji jest w porządku, ale dlaczego nie zwrócić "Brak", jeśli jest więcej niż jeden element? Dla mnie, jeśli spodziewasz się mieć jeden element, to przypadki 0 i 2 lub więcej elementów są prawdopodobnie równie wyjątkowe i powinny być traktowane w ten sam sposób, chyba że istnieją istotne powody dla ich odróżnienia (w w takim przypadku metoda powinna prawdopodobnie zostać nazwana czymś bardziej konkretnym dla jasności, np. 'AtMostOne'). – kvb

1

Co jest nie tak z wykorzystaniem istniejącej funkcji biblioteki?

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

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

Jest to jedna z niewielu metod rozszerzenia w System.Linq, która nie ma odpowiednika w modułach F #. Nie wiem, czy istnieje ku temu powód. –

0

Updated odpowiedzią byłoby użyć Seq.exactlyOne który podnosi ArgumentException