简体   繁体   English

如何在这个F#程序中避免堆栈溢出(递归树搜索)?

[英]How to avoid stack overflow in this F# program (recursive tree search)?

I've got a discriminated union tree like this: 我有一个像这样的歧视联盟树:

type rbtree =
    | LeafB of int
    | LeafR of int
    | Node of int*rbtree*rbtree

And what I have to do is to search for every LeafB present in the tree, so I came with a this recursive function: 我要做的是搜索树中的每个LeafB,所以我带来了这个递归函数:

let rec searchB (tree:rbtree) : rbtree list = 
    match tree with
    | LeafB(n) -> LeafB(n)::searchB tree
    | LeafR(n) -> []
    | Node(n,left,right) -> List.append (searchB left) (searchB right)

But when I try to test it I get stack overflow exception and I have no idea how to modify it to work properly. 但是,当我尝试测试它时,我得到堆栈溢出异常,我不知道如何修改它以正常工作。

As @kvb says your updated version isn't truely tail-rec and might cause a stackoverflow as well. 正如@kvb所说,你的更新版本不是真正的尾部调整,也可能导致堆栈溢出。

What you can do is using continuations essentially using heap space instead of stack space. 你可以做的是使用continuation本质上使用堆空间而不是堆栈空间。

let searchB_ tree =
  let rec tail results continuation tree =
    match tree with
    | LeafB v           -> continuation (v::results)
    | LeafR _           -> continuation results
    | Node  (_, lt, rt) -> tail results (fun leftResults -> tail leftResults continuation rt) lt
  tail [] id tree |> List.rev

If we looks at the generated code in ILSpy it looks essentially like this: 如果我们查看ILSpy生成的代码,它看起来基本上是这样的:

internal static a tail@13<a>(FSharpList<int> results, FSharpFunc<FSharpList<int>, a> continuation, Program.rbtree tree)
{
  while (true)
  {
    Program.rbtree rbtree = tree;
    if (rbtree is Program.rbtree.LeafR)
    {
      goto IL_34;
    }
    if (!(rbtree is Program.rbtree.Node))
    {
      break;
    }
    Program.rbtree.Node node = (Program.rbtree.Node)tree;
    Program.rbtree rt = node.item3;
    FSharpList<int> arg_5E_0 = results;
    FSharpFunc<FSharpList<int>, a> arg_5C_0 = new Program<a>.tail@17-1(continuation, rt);
    tree = node.item2;
    continuation = arg_5C_0;
    results = arg_5E_0;
  }
  Program.rbtree.LeafB leafB = (Program.rbtree.LeafB)tree;
  int v = leafB.item;
  return continuation.Invoke(FSharpList<int>.Cons(v, results));
  IL_34:
  return continuation.Invoke(results);
}

So as expected with tail recursive functions in F# it is tranformed into a while loop. 正如预期的那样,F#中的尾递归函数将其转换为while循环。 If we look at the non-tail recursive function: 如果我们看一下非尾递归函数:

// Program
public static FSharpList<int> searchB(Program.rbtree tree)
{
  if (tree is Program.rbtree.LeafR)
  {
    return FSharpList<int>.Empty;
  }
  if (!(tree is Program.rbtree.Node))
  {
    Program.rbtree.LeafB leafB = (Program.rbtree.LeafB)tree;
    return FSharpList<int>.Cons(leafB.item, FSharpList<int>.Empty);
  }
  Program.rbtree.Node node = (Program.rbtree.Node)tree;
  Program.rbtree right = node.item3;
  Program.rbtree left = node.item2;
  return Operators.op_Append<int>(Program.searchB(left), Program.searchB(right));
}

We see the recursive call at the end of the function Operators.op_Append<int>(Program.searchB(left), Program.searchB(right)); 我们在函数Operators.op_Append<int>(Program.searchB(left), Program.searchB(right));的末尾看到递归调用Operators.op_Append<int>(Program.searchB(left), Program.searchB(right));

So the tail-recursive function allocates continuations functions instead of creating a new stack frame. 因此,尾递归函数分配continuation函数而不是创建新的堆栈帧。 We can still run out of heap but there's lot more heap than stack. 我们仍然可以用完堆,但堆的数量远远超过堆。

Full example demonstrating a stackoverflow: 演示stackoverflow的完整示例:

type rbtree =
  | LeafB of int
  | LeafR of int
  | Node  of int*rbtree*rbtree

let rec searchB tree = 
  match tree with
  | LeafB(n) -> n::[]
  | LeafR(n) -> []
  | Node(n,left,right) -> List.append (searchB left) (searchB right)

let searchB_ tree =
  let rec tail results continuation tree =
    match tree with
    | LeafB v           -> continuation (v::results)
    | LeafR _           -> continuation results
    | Node  (_, lt, rt) -> tail results (fun leftResults -> tail leftResults continuation rt) lt
  tail [] id tree |> List.rev

let rec genTree n =
  let rec loop i t =
    if i > 0 then
      loop (i - 1) (Node (i, t, LeafB i))
    else
      t
  loop n (LeafB n)

[<EntryPoint>]
let main argv =
  printfn "generate left leaning tree..."
  let tree  = genTree 100000
  printfn "tail rec"
  let s     = searchB_  tree
  printfn "rec"
  let f     = searchB   tree

  printfn "Is equal? %A" (f = s)

  0

Oh, I might came with an solution: 哦,我可能会找到一个解决方案:

let rec searchB (tree:rbtree) : rbtree list = 
match tree with
| LeafB(n) -> LeafB(n)::[]
| LeafR(n) -> []
| Node(n,left,right) -> List.append (searchB left) (searchB right)

Now it looks like working properly when I try it. 现在看起来我在尝试时工作正常。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM