如何有效地找出序列是否至少有n项?

时间:2011-09-23 17:18:25

标签: f#

只是天真地使用Seq.length可能不够好,因为会爆炸无限序列。

使用像ss |> Seq.truncate n |> Seq.length这样的东西会变得更加花哨,但在场景后面会涉及IEnumerator MoveNext()对参数序列块的双遍遍。

到目前为止,我能够提出的最佳方法是:

let hasAtLeast n (ss: seq<_>) =
    let mutable result = true
    use e = ss.GetEnumerator()
    for _ in 1 .. n do result <- e.MoveNext()
    result

这仅涉及单个序列遍历(更准确地说,执行e.MoveNext() n次)并正确处理空序列和无限序列的边界情况。我可以进一步提出一些小的改进,比如显式处理列表,数组和ICollection的特定情况,或者对遍历长度进行一些削减,但是想知道是否存在任何更有效的方法来解决我可能缺失的问题?

感谢您的帮助。

编辑:拥有hasAtLeast函数的5个整体实现变体(2个我自己,2个由 Daniel 建议,另一个由 Ankur建议)我在这些之间安排了一场马拉松比赛。对所有实施都有效的结果证明 Guvante 是正确的:现有算法的最简单组合将是最好的,在过度工程中没有任何意义。

进一步抛出可读性因素我会使用我自己的纯F#基于

let hasAtLeast n (ss: seq<_>) =
    Seq.length (Seq.truncate n ss) >= n

Ankur 提出的基于Linq的完全等效的,可以利用.NET集成

let hasAtLeast n (ss: seq<_>) =
    ss.Take(n).Count() >= n

3 个答案:

答案 0 :(得分:3)

这是一个简短的功能性解决方案:

let hasAtLeast n items = 
  items 
  |> Seq.mapi (fun i x -> (i + 1), x)
  |> Seq.exists (fun (i, _) -> i = n)

示例:

let items = Seq.initInfinite id
items |> hasAtLeast 10000

这是一个最佳效率的方法:

let hasAtLeast n (items:seq<_>) = 
  use e = items.GetEnumerator()
  let rec loop n =
    if n = 0 then true
    elif e.MoveNext() then loop (n - 1)
    else false
  loop n

答案 1 :(得分:1)

使用Linq,这很简单:

let hasAtLeast n (ss: seq<_>) = 
   ss.Take(n).Count() >= n

如果没有足够的元素,Seq take方法会爆炸。

示例用法显示它仅遍历seq一次并且直到所需元素:

seq { for i = 0 to 5 do
        printfn "Generating %d" i
        yield i }
|> hasAtLeast 4 |> printfn "%A"

答案 2 :(得分:1)

功能编程将工作负载分解成小块,这些小块执行非常通用的任务,只做一件简单的事情。确定序列中是否至少有n项不是一项简单的任务。

您已经找到了这个“问题”的解决方案,现有算法的组合,适用于大多数情况,并创建自己的算法来解决问题。

但是我不得不怀疑你的第一个解决方案是否行不通。 MoveNext()仅在原始方法上被称为n次,从不调用Current,即使在某个包装类上调用MoveNext(),性能影响也可能很小除非n很大。

编辑:

我很好奇所以我写了一个简单的程序来测试这两种方法的时间。截断方法对于简单的无限序列和具有Sleep(1)的序列更快。当你的纠正听起来像过度工程时,我看起来是对的。

我认为需要澄清以解释这些方法中发生的事情。 Seq.truncate接受一个序列并返回一个序列。除了保存n的值之外,它在枚举之前不会执行任何操作。在枚举期间,它会在n值之后计数并停止。 Seq.length采用枚举并计数,在结束时返回计数。所以枚举只枚举一次,开销量是几个方法调用和两个计数器。