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 ===