使用 setImmediate() 递归

recursion with setImmediate()

我有以下递归函数:

 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() 不会 return 任何东西,它只是将 v 键下的数组 nodes 中的对象状态从未探索更改为已探索。主要问题是两个函数 preVisit(v)postVisit(v) 也改变了该对象的状态 - 第一次和第二次访问它的写入顺序(当从以前的调用中弹出堆栈时) 分别。当我用 trampoline 转换 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++;
}

假设我们有一个像这样的普通递归函数——这里的问题是我们有一个 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 函数中引入了一个主循环,它处理 数组 个节点,而不仅仅是一个节点。当数组中有节点时,循环将继续到 运行。将对内部循环函数而不是外部 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++