【问题标题】:recursion with setImmediate()使用 setImmediate() 递归
【发布时间】:2018-03-26 23:12:17
【问题描述】:

我有以下递归函数:

 function explore(v, componentN) {
    return function () {
        nodes[v].visited = true;
        nodes[v].componentNumber = componentN;
        preVisit(v);
        adjList[v].forEach((item) => {
            if (!nodes[item].visited){
                ex(item, componentN)
            }
        });
        postVisit(v);
        return nodes;
    }
}
function ex(item, com){
    trampoline(function () {
        return explore(item, com)
    })
}
function trampoline(fn) {
    while(fn && typeof fn === 'function') {
        fn = fn()
    }
}

我想使用setImmediate() 以避免堆栈溢出问题(它应该以这种方式实现,而不是在迭代方法中)。然而,当我执行这个函数时,我只得到数组res,只有第一个元素,而如果我不使用setImmediate(),我会得到所有元素,当我使用nextTick()时会出现同样的情况(我事先知道应该有多少元素)。我做错了什么,如何在此处正确实现此功能(或模拟)?

这是应用蹦床之前的explore()函数

function explore(v, componentN) {
    nodes[v].visited = true;
    nodes[v].componentNumber = componentN;
    preVisit(v);
    adjList[v].forEach((item) => {
        if (!nodes[item].visited){
            explore(item, componentN)
        }
    });
    postVisit(v);
    return nodes;
}

它接受两个参数:vnodes 数组中应该探索的元素的索引,componentN 只是图中组件的计数器。 explore() 不返回任何内容,它只是将数组 nodesv 键下对象的状态从未探索更改为已探索。主要问题是preVisit(v)postVisit(v) 两个函数也改变了该对象的状态——分别写入第一次和第二次访问它的顺序(从先前的调用弹出堆栈时)。当我用蹦床转换explore()时,他们开始写出意想不到的错误结果。

该函数正在以这种方式在另一个函数中被调用:

for (let i = 0; i < vNum; i++) {
        if (nodes[i].visited) continue;
        explore(i, cN);
        cN++;
    }

还有两个函数postVisitpreVisit

function preVisit(v){
    nodes[v].preVisit = visitCounter;
    visitCounter++;
}
function postVisit(v) {
    nodes[v].postVisit = visitCounter;
    visitCounter++;
}

【问题讨论】:

  • 不要使用setImmediate来避免堆栈溢出 - 详情请参阅my answer
  • @naomik,感谢您的回答,我已阅读并尝试实现蹦床。所以它确实起作用了,堆栈溢出问题消失了,但又出现了另一个问题。我在这个问题中使用图形,我在递归函数体中有两个函数调用,即write()。添加蹦床后,他们写了错误的信息,我得到了意想不到的结果。你能告诉我如何解决这个问题吗?
  • 粘贴您的更新尝试
  • @naomik,感谢您的回答,我刚刚将最初问题中的代码从抽象变为真实。所以我遇到了preVisit()postVisit() 函数的问题,与我不使用蹦床的情况相比,它们写错了后序。我试图将它们包装在setInterval() 中,但没有任何成功。

标签: node.js recursion setimmediate


【解决方案1】:

假设我们有一个像这样的普通递归函数 - 这里的问题是我们有一个 forEach 循环,其中每次调用 explore 可以产生 0、1 或更多对 explore 的额外调用 - 解决这个问题,我们需要一些方法来对所有节点进行排序,以便我们可以一个接一个地处理它们而不会炸毁堆栈

const explore = node =>
  {
    node.visited = true
    preVisit (node)
    Array.from (node.adjacencies)
      .filter (n => n.visited === false)
      .forEach (n => explore (n))
    postVisit (node)
  }

我们在explore 函数中引入了一个主循环,它处理一个array 节点,而不仅仅是一个节点。当数组中有节点时,循环将继续运行。将对内部循环函数而不是外部explore 函数进行递归调用。这种数组方法效果很好,因为每个节点都有不明确的邻接数量——何时可以轻松地将 0、1 或更多相邻节点添加到此列表中——还请注意添加了延续 k,因此我们有一种方法来对 postVisit 调用进行排序顺序正确

最重要的是对loop 的递归调用位于尾部位置——这为我们准备下一次转换

const explore = node =>
  {
    const loop = ([x, ...xs], k) =>
      {
        if (x === undefined)
          k ()
        else {
          x.visited = true
          preVisit (x)
          loop (
            Array.from (x.adjacencies)
              .filter (n => n.visited === false)
              .concat (xs),
            () => (postVisit (x), k ())
          )
        }
      }
    return loop ([node], x => x)
  }

一旦我们有了尾递归循环,我们就可以引入loop/recur 进行堆栈安全递归

const recur = (...values) =>
  ({ type: recur, values })

const loop = f =>
  {
    let acc = f ()
    while (acc && acc.type === recur)
      acc = f (...acc.values)
    return acc
  }

const explore = $node =>
  loop (([x,...xs] = [$node], k = x => x) => {
    if (x === undefined)
      return k ()
    else {
      x.visited = true
      preVisit (x)
      return recur (
        Array.from (x.adjacencies)
          .filter (n => n.visited === false)
          .concat (xs),
        () => (postVisit (x), k ())
      )
    }
  })

// for completion's sake
let visitCounter = 0

const preVisit = node =>
  node.preVisit = visitCounter++

const postVisit = node =>
  node.postVisit = visitCounter++

【讨论】:

  • @Kirill,抱歉耽搁了,这对我来说真的很忙。我在这里添加了一篇文章,但是在没有包含功能代码演示的情况下做出答案是非常不寻常的。在不久的将来,我将尝试在这篇文章中附上一个,以表明它可以与数百万个节点和邻接一起使用而不会出现问题。同时,尝试理解我们引入数组和延续的第一个转换步骤。
  • 首先,感谢您的解释性回答!我现在正试图了解代码中发生的事情,但现在感觉很复杂)我肯定会尝试得到它,但我想这需要多次尝试)如果你有时间将演示附加到这个代码会很酷,但是,我想我还是会得到它)谢谢你的帮助!
猜你喜欢
  • 1970-01-01
  • 2023-03-19
  • 2020-10-06
  • 2015-08-14
  • 1970-01-01
  • 1970-01-01
  • 2015-12-15
相关资源
最近更新 更多