2009-05-05 39 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