2009-05-05 78 views
6

我想從F#中的一個序列中提取單個項目,或者在沒有或多個項目時發出錯誤。做這個的最好方式是什麼?從F列表中提取單個元素#

我現在有

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

看來工作,但它真的是最好的方法是什麼?

編輯:正如我指出了正確的方向,我想出了以下內容:

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

我想這是一個更好一點。

+0

你能提供一個樣本序列,你在找什麼? – 2009-05-05 17:17:46

+0

我不覺得有必要。我想找到第一個值,並在出現多個錯誤時給出錯誤。就是這樣 – erikkallen 2009-05-05 18:05:57

+0

+1 - 你想要一個F#相當於一個有用的LINQ運算符(System.Linq.Enumerable.Single) - 通常情況就是這樣! – 2009-05-25 09:53:05

回答

3

序列具有查找功能。

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

,但如果你想確保序列中只有一個元素,那麼做一個Seq.filter,然後採取過濾後的長度,並確保它等於一個,然後搭頭。全部在Seq中,無需轉換爲列表。

編輯: 在一個側面說明,我會建議檢查結果的是空的(O(1),而不是使用功能length(O(n))的尾巴。是不是序列的一部分,但我認爲你可以工作,以仿真功能的好方法。

+0

對於有很多匹配的長序列,這將失敗非常緩慢,無限序列,然後正確的結果是保持計算無限或終止早期但它不會(這種差異是相當邊際的效用,雖然) – ShuggyCoUk 2009-05-05 17:39:30

+0

是的,我認爲無限seq會讓你找到任何一種方式,嘗試找到一些不在無限列表中的東西... 但是,考慮長度而不是僅僅檢查尾部是否是空的是一個好的方法,我原本想提到的,但seq沒有尾巴功能。我修改了我的帖子以反映該限制,並使用了O(1)的函數。 由於 – nlucaroni 2009-05-05 18:40:59

+0

SEQ的跳過功能將作爲備用對尾,Seq.skip 1濾波器應該是空的和seq.hd後,將檢查它具有至少一個(HD將拋出它自己的異常,如果它是空的,這是有用) – ShuggyCoUk 2009-05-06 08:54:17

4

在現有序列的方式進行的標準功能

#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 

你可以做一個純粹的實現但最終會跳躍火圈是正確的和有效的(在第二場比賽結束很快真的要求一個標誌說:「我發現它已經」)

1

使用此:

> 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

不要同時跳過和高速計算頭(如果有副作用,你會看到他們兩次)? – 2009-08-01 05:08:10

+0

是的。如果他們很慢或有不良副作用,那麼你會想優化這一點。 – 2009-08-02 21:22:46

0

我的兩分錢......這一點也適用選項類型,所以我可以在我的習慣,也許monad中使用它。可以修改非常容易,雖然與異常工作相反

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

您應該在第二次調用MoveNext之前緩存'e.Current',因爲如果在枚舉結束後訪問'e.Current'時某些枚舉器可能會拋出異常。另外,我沒有看到創建嵌套'single'函數的好處,因爲它總是被調用一次。如果有0個元素,返回'None'也很奇怪,但如果有多個元素,則拋出一個異常 - 在這種情況下,我會調用方法AtMostOne而不是Single。 – kvb 2011-11-23 21:22:29

+0

嵌套函數只是爲了清楚它實際上在IEnumerator上工作,而不是IEnumerable。沒有人和一些人在那裏,所以這插入我可能monad,在這種情況下,它讀取非常自然。我在數據訪問中大量使用它來鏈接相關的調用。當沒有短路時,一些繼續有點東西 – Brad 2011-12-13 22:54:53

+0

選項類型的使用很好,但如果還有多個元素,爲什麼不返回'None'?對我而言,如果你期望有一個單一的元素,那麼0元素和2元或更多元素的情況可能同樣出類拔萃,應該以同樣的方式對待,除非有一個令人信服的理由來區分它們(在在這種情況下,該方法應該被稱爲更清晰的特定內容,比如'AtMostOne')。 – kvb 2011-12-13 23:04:32

1

使用現有的庫函數有什麼問題?

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

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

這是System.Linq中唯一一個在F#模塊中沒有等效功能的擴展方法之一。不確定是否有這個原因。 – 2017-06-02 21:34:00

0

更新的答案是使用Seq.exactlyOne這引起了一個ArgumentException