如何只评估一部分懒惰序列?

时间:2012-07-03 14:28:30

标签: f# functional-programming

懒惰的评估对于处理一次性不适合主内存的大文件这样的东西来说是一个很大的好处。但是,假设序列中有一些元素需要立即评估,而其余元素可以延迟计算 - 有没有办法指定它?

具体问题:(如果有助于回答问题)

具体来说,我使用一系列IEnumerables作为多个序列的迭代器 - 这些序列是从使用BinaryReader流打开的文件中读取的数据(每个序列负责从其中一个文件读取数据)。这些上的MoveNext()将按特定顺序调用。例如。 iter0然后iter1然后iter5然后iter3 ....依此类推。此订单以另一个序列index = {0,1,5,3,....}指定。然而,序列是懒惰的,仅在需要时自然地进行评估。因此,当序列的IEnumerables正在移动时,文件读取(对于从磁盘上的文件读取的开头的序列)。这导致非法文件访问 - 再次访问由一个进程读取的文件(根据错误消息)。

确实,非法文件访问可能是出于其他原因,并且在我尽力调试其他原因之后,部分懒惰的评估可能值得尝试。

2 个答案:

答案 0 :(得分:3)

虽然我同意Tomas的评论:如果文件共享得到妥善处理,你不应该这样做,这是热切评估第一个 N 元素的一种方法:

let cacheFirst n (items: seq<_>) =
  seq {
    use e = items.GetEnumerator()
    let i = ref 0
    yield! 
      [ 
        while !i < n && e.MoveNext() do
          yield e.Current
          incr i
      ]
    while e.MoveNext() do
      yield e.Current
  }

实施例

let items = Seq.initInfinite (fun i -> printfn "%d" i; i)

items
|> Seq.take 10
|> cacheFirst 5
|> Seq.take 3
|> Seq.toList

输出

0
1
2
3
4
val it : int list = [0; 1; 2]

答案 1 :(得分:1)

丹尼尔的解决方案是合理的,但我认为我们不需要其他操作员,大多数情况下只需要Seq.cache

首先缓存您的序列:

let items = Seq.initInfinite (fun i -> printfn "%d" i; i) |> Seq.cache

急切评估,然后从一开始就进行延迟访问:

let eager = items |> Seq.take 5 |> Seq.toList
let cached = items |> Seq.take 3 |> Seq.toList

这将评估前5个元素(在eager期间),但要将它们缓存以进行二次访问。