在 F# 中,如何在不重新计算序列的情况下获取序列的头部/尾部



我正在读取一个文件,我想对第一行做一些事情,对所有其他行做其他事情

let lines = System.IO.File.ReadLines "filename.txt" |> Seq.map (fun r -> r.Trim())
let head = Seq.head lines
let tail = Seq.tail lines

'''

问题:对tail的调用失败,因为TextReader已关闭。 这意味着Seq被评估两次:一次获得head一次获得tail

如何获得第一行和最后一行,同时保持Seq而不重新评估顺序?

签名可以是,例如:

let fn: ('a -> Seq<'a> -> b) -> Seq<'a> -> b

最简单的方法可能是使用Seq.cache来包装lines序列:

let lines =
System.IO.File.ReadLines "filename.txt"
|> Seq.map (fun r -> r.Trim())
|> Seq.cache

文档中的注意事项:

此结果序列将具有与输入序列相同的元素。结果可以枚举多次。输入序列最多枚举一次,并且仅在必要时枚举。当重复计算原始序列中的项在计算上很昂贵时,或者如果迭代序列会导致用户不希望多次重复的副作用,则缓存序列通常很有用。

我通常使用seq表达式,其中Stream的作用域在表达式内。 这将允许您在释放流之前完全枚举序列。 我通常使用这样的函数:

let readLines file =
seq {
use stream = File.OpenText file
while not stream.EndOfStream do
yield stream.ReadLine().Trim()
}

然后,您应该能够调用Seq.head并获取失败中的第一行,Seq.last获取文件中的最后一行。 我认为这在技术上会创建两个不同的枚举器。 如果您只想只读取文件一次,那么将序列具体化为列表或使用像Seq.cache这样的函数将是您的最佳选择。

我有一个重要的用例,我使用 Seq.unfold 通过 REST 读取读取大量块,并按顺序处理每个块,并进行进一步的 REST 读取。

序列的读取必须既"延迟",又要缓存以避免重复的重新评估(每个Seq.tail操作(。

因此找到这个问题和接受的答案(Seq.cache(。谢谢!

我用Seq.cache进行了实验,发现它像声称的那样工作(即,懒惰并避免重新评估(,但有一个值得注意的条件 - 序列的前五个元素总是首先读取(并保留"缓存"(,因此对五个或更小数字的实验不会显示惰性评估。但是,在五之后,每个元素都会启动惰性评估。

此代码可用于试验。尝试 5,看到没有惰性求值,然后是 10,并根据需要查看 5 之后的每个元素都是"惰性"读取。同时删除Seq.cache以查看我们正在解决的问题(重新评估(

// Get a Sequence of numbers.
let getNums n  = seq { for i in 1..n do printfn "Yield { %d }" i; yield i}
// Unfold a sequence of numbers
let unfoldNums (nums : int seq) =
nums
|> Seq.unfold
(fun (nums : int seq) ->
printfn "unfold: nums = { %A }" nums
if Seq.isEmpty nums then
printfn "Done"
None
else
let num = Seq.head nums // Value to yield
let tl = Seq.tail nums // Next State. CAUSES RE-EVALUTION!
printfn "Yield: < %d >, tl =  { %A }" num tl
Some (num,tl))

// Get n numbers as a sequence, then unfold them as a sequence
// Observe that with 'Seq.cache' input is not re-evaluated unnecessarily, 
// and also that lazy evaulation kicks in for n > 5
let experiment n =
getNums n
|> Seq.cache
// Without cache, Seq.tail causes the sequence to be re-evaluated
|> unfoldNums
|> Seq.iter (fun x -> printfn "Process: %d" x)

相关内容

最新更新