只是天真地使用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
答案 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
采用枚举并计数,在结束时返回计数。所以枚举只枚举一次,开销量是几个方法调用和两个计数器。