Recursion 创建序列序列会导致StackOverflowException
我试着把一个大文件分割成许多小文件。每次拆分发生的位置基于检查每一行内容返回的谓词(Recursion 创建序列序列会导致StackOverflowException,recursion,f#,sequences,Recursion,F#,Sequences,我试着把一个大文件分割成许多小文件。每次拆分发生的位置基于检查每一行内容返回的谓词(isNextObject函数) 我试图通过file.ReadLines函数读取大文件,这样我就可以一次遍历一行文件,而不必将整个文件保存在内存中。我的方法是将序列分组成更小的子序列(每个文件一个要写出) 我发现了Tomas Petricek在fssnip上创建的一个名为。这个函数在我对文件的一小部分进行初始测试时非常有效,但是在使用真实文件时会抛出StackoverflowException。我不知道如何调整gr
isNextObject
函数)
我试图通过file.ReadLines
函数读取大文件,这样我就可以一次遍历一行文件,而不必将整个文件保存在内存中。我的方法是将序列分组成更小的子序列(每个文件一个要写出)
我发现了Tomas Petricek在fssnip上创建的一个名为。这个函数在我对文件的一小部分进行初始测试时非常有效,但是在使用真实文件时会抛出StackoverflowException。我不知道如何调整groupWhen函数来防止这种情况(我仍然是一个F#greenie)
下面是代码的简化版本,仅显示将重新创建StackOverflowXpetion:的相关部分:
// This is the function created by Tomas Petricek where the StackoverflowExcpetion is occuring
module Seq =
/// Iterates over elements of the input sequence and groups adjacent elements.
/// A new group is started when the specified predicate holds about the element
/// of the sequence (and at the beginning of the iteration).
///
/// For example:
/// Seq.groupWhen isOdd [3;3;2;4;1;2] = seq [[3]; [3; 2; 4]; [1; 2]]
let groupWhen f (input:seq<_>) = seq {
use en = input.GetEnumerator()
let running = ref true
// Generate a group starting with the current element. Stops generating
// when it founds element such that 'f en.Current' is 'true'
let rec group() =
[ yield en.Current
if en.MoveNext() then
if not (f en.Current) then yield! group() // *** Exception occurs here ***
else running := false ]
if en.MoveNext() then
// While there are still elements, start a new group
while running.Value do
yield group() |> Seq.ofList }
有没有什么好的方法来阻止团队成员的失败?我不确定如何将该函数转换为使用累加器(或者使用延续,我认为这是正确的术语)。问题在于
group()
函数返回一个列表,这是一个热切求值的数据结构,这意味着每次调用group()时
它必须运行到最后,收集列表中的所有结果,然后返回列表。这意味着递归调用在同一个计算中发生-即真正递归-从而产生堆栈压力
为了缓解此问题,您可以使用惰性序列替换列表:
let rec group() = seq {
yield en.Current
if en.MoveNext() then
if not (f en.Current) then yield! group()
else running := false }
然而,我会考虑较少激烈的方法。这个例子很好地说明了为什么您应该避免自己执行递归,而是求助于现成的折叠
例如,根据您的描述判断,似乎
Seq.windowed
可能适合您。在IMO的F#中很容易过度使用序列。您可能会意外地出现堆栈溢出,而且速度很慢
所以(实际上并没有回答你的问题),
就我个人而言,我会使用如下方式折叠一系列行:
let isNextObject line =
line = "---"
type State = {
fileIndex : int
filename: string
writer: System.IO.TextWriter
}
let makeFilename index =
sprintf "File%i" index
let closeFile (state:State) =
//state.writer.Close() // would use this in real code
state.writer.WriteLine("=== Closing {0} ===",state.filename)
let createFile index =
let newFilename = makeFilename index
let newWriter = System.Console.Out // dummy
newWriter.WriteLine("=== Creating {0} ===",newFilename)
// create new state with new writer
{fileIndex=index + 1; writer = newWriter; filename=newFilename }
let writeLine (state:State) line =
if isNextObject line then
/// finish old file here
closeFile state
/// create new file here and return updated state
createFile state.fileIndex
else
//write the line to the current file
state.writer.WriteLine(line)
// return the unchanged state
state
let processLines (lines: string seq) =
//setup
let initialState = createFile 1
// process the file
let finalState = lines |> Seq.fold writeLine initialState
// tidy up
closeFile finalState
(显然,真正的版本将使用文件而不是控制台)
是的,这是粗糙的,但很容易用理性来解释
没有令人不快的惊喜
这里有一个测试:
processLines [
"a"; "b"
"---";"c"; "d"
"---";"e"; "f"
]
以下是输出结果:
=== Creating File1 ===
a
b
=== Closing File1 ===
=== Creating File2 ===
c
d
=== Closing File2 ===
=== Creating File3 ===
e
f
=== Closing File3 ===
我在F#Looks interest@IsaacAbraham中为Hadoop/MR实现做了类似的事情,谢谢。我得去看看这个项目,让自己接触到更真实的F#。啊,是的,你说得对。我现在也看到了Seq.ofList调用,位于groupWhen函数的底部。将此更改为整个操作的顺序似乎有效,谢谢!我不知道Seq.windowed。我会看一看,看看是否能找到一个同样适用的解决方案。实际上,在这段代码中有两个
Seq.toList
调用,但您只能看到其中一个。group()
实现中的方括号-它们也被转换为对Seq.toList
的调用。这是我回答的第二个电话。是的,我明白。我的意思是,我还看到了代码中更深层的Seq.ofList调用,这是必需的,因为group()实现使用方括号(创建列表)而不是花括号(创建序列)。有趣的是,这与我第一次尝试解决问题的方式是一致的。我觉得我在做事情的F#方式方面不够习惯(我正试图停止用F#编写C风格的代码)。不过,这种方法确实有效,如果我不能让groupWhen函数工作,我可能会这样做。尽管我很想改变它(正如你所说,这更容易推理).我喜欢F#的一点是它的实用性,所以我不必太担心完美的风格。不过,我的代码功能完美——它使用不可变对象、高阶函数等。我要做的一件事是使它更易于测试(和纯粹)通过删除对IO和文件的任何引用,并将所需的依赖项作为函数传递进来。这样整个事情就可以被简单地模拟。此外,我会小心将问题强制到解决方案中。序列的顺序很酷,但我不确定你的问题是否真的与该技术相匹配,而不是以一种肤浅的方式。用于考试例如,如果文件段之间的解析逻辑变得更加复杂,或者您需要在继续读取主文件的同时并行编写新文件,那么seq of seq实现可能会开始妨碍您的工作,而不是起到帮助作用。只需我的2c:)谢谢您的建议。我一定要小心。这都是问题的一部分我猜是学习的过程。现在去F#为了好玩和赚钱,我去读更多的书。。。
processLines [
"a"; "b"
"---";"c"; "d"
"---";"e"; "f"
]
=== Creating File1 ===
a
b
=== Closing File1 ===
=== Creating File2 ===
c
d
=== Closing File2 ===
=== Creating File3 ===
e
f
=== Closing File3 ===