如何将同步和异步递归函数转换为JavaScript中的迭代

时间:2017-12-23 03:22:58

标签: javascript node.js algorithm asynchronous recursion

寻找同步和异步递归函数的特定实现,这些函数可以用作将未来递归函数转换为平面迭代的起点。

以下是递归函数的两个示例:同步异步

我正在寻找的是使用堆栈而不递归的实现。

例如,它可能会像这样工作:

var output = syncStack(myRecursiveFunctionTurnedIterative, [])

或者如果那是不可能的,那么只需使用堆栈重新实现下面的两个函数,这应该是一个足够好的开始。 E.g。

var stack = []

function circularReferences(object, references, stack) {
  var output = {}
  if (object.__circularid__) return true
  Object.defineProperty(object, '__circularid__', { value: id++ })
  for (var key in object) {
    var value = object[key]
    if (value && typeof value == 'object') {
      console.log(value)
      stack.push(???)
      circularReferences()
      stack.pop()
      if (is) output[key] = '[Circular]'
    } else {
      output[key] = value
    }
  }
}

这个问题的原因是,多年来我一直试图学习如何做到这一点,但从未找到过(a)容易记住怎么做以及(b)实用的系统。

同步

var references = {}
var object = {
  a: {
    b: {
      c: {
        d: {
          e: 10,
          f: 11,
          g: 12
        }
      }
    }
  }
}
object.a.b.c.d.x = object
object.a.b.c.d.y = object.a.b

var id = 1

var x = circularReferences(object, references)
console.log(x)

function circularReferences(object, references) {
  var output = {}
  if (object.__circularid__) return true
  Object.defineProperty(object, '__circularid__', { value: id++ })
  for (var key in object) {
    var value = object[key]
    if (value && typeof value == 'object') {
      console.log(value)
      var is = circularReferences(value, references)
      if (is) output[key] = '[Circular]'
    } else {
      output[key] = value
    }
  }
}

台异步

var items = [
  async1a,
  async1b,
  async1c
  // ...
]

asynca(items, function(){
  console.log('done')
})

function asynca(items, callback) {
  var i = 0

  function next() {
    var item = items[i++]
    if (!item) return callback()

    item(next)
  }
}

function async1a(callback) {
  // Some stuff...
  setTimeout(function(){
    if (true) {
      var items = [
        async2a,
        // ...
      ]

      asynca(items, callback)
    } else {
      callback(null, true)
    }
  }, 200)
}

function async1b(callback) {
  // Some stuff...
  setTimeout(function(){
    if (true) {
      var items = [
        async2a,
        // ...
      ]

      asynca(items, callback)
    } else {
      callback(null, true)
    }
  }, 200)
}

function async1c(callback) {
  // Some stuff...
  setTimeout(function(){
    if (true) {
      var items = [
        async2a,
        // ...
      ]

      asynca(items, callback)
    } else {
      callback(null, true)
    }
  }, 200)
}

function async2a(callback) {
  return callback()
}

例如,可能会开始看起来像:

var items = [
  async1a,
  async1b,
  async1c
  // ...
]

asynca(items, function(){
  console.log('done')
}, [])

function asynca(items, callback, stack) {
  var i = 0

  function next() {
    var item = items[i++]
    if (!item) return callback()
    stack.push(item)
  }
}

但那是我迷路的地方。不确定如何传递堆栈一般如何设置功能

想知道如何在实践中将它们写为非递归函数。我见过Way to go from recursion to iteration,但他们都非常理论化。

4 个答案:

答案 0 :(得分:6)

为了让我们转换一个带有调用另一个函数的函数的程序(无论它是否具有相同的功能,又称'递归',没有区别),我们将需要将其分成在此类呼叫之前发生的过程以及呼叫之后的任何过程。如果在out-call之后没有程序,并且out-call是相同的函数,我们可以将其描述为" tail recursive,"这可以使迭代转换更简单,只需将调用参数推送到堆栈(Example)。事实上,将尾递归转换为迭代堆栈过程有助于我克服浏览器的问题。多个实例中的递归深度限制。

转换为tail-recursive

为了将递归转换为尾递归,我们必须考虑如何处理从递归调用传递的信息,以及我们是否可以转换此过程以在递归本身中使用参数。因为在您的特定示例中,out-call结果发生的唯一事情是设置局部变量output,而output是一个对象,在JavaScript中通过引用传递,我们能够进行这种转变。所以这是一个简单的重构,它将使我们能够使用一个简洁的堆栈(我跳过尾递归代码到堆栈实现;留给读者练习):



var references = {}
var object = {
  a: {
    b: {
      c: {
        d: {
          e: 10,
          f: 11,
          g: 12
        }
      }
    }
  }
}
object.a.b.c.d.x = object
object.a.b.c.d.y = object.a.b

var id = 1

//var x = circularReferences(object, references)
//console.log(x)

//function circularReferences(object, references) {
// => add parameters, 'output' and 'key'
var stack = [[object, references, null, null]];

while (stack.length){
  [_object, _references, _callerOutput, _key] = stack.pop()

  var output = {}
  
  if (_object.__circularid__){
    _callerOutput[_key] = '[Circular]'
    
    // Log our example
    console.log('OUTPUT VALUE: ' + JSON.stringify(_callerOutput))
    
    // Exit
    continue;
  }
  
  Object.defineProperty(_object, '__circularid__', { value: id++ })
  
  for (var key in _object) {
    var value = _object[key]
    
    if (value && typeof value == 'object') {
      console.log(value)
      
      //var is = circularReferences(value, references)
      // if (is) output[key] = '[Circular]'
      stack.push([value, _references, output, key])
        
    } else {
      output[key] = value
    }
  }
}




概括堆栈和订购操作

由于将递归转换为尾递归可能并不总是容易和直接,因此我们考虑如何使用堆栈以类似于原始递归的方式迭代地对操作进行排序。我们还会稍微概括一下我们的堆栈,这将有助于我们的第二个,#34; Asynchronous,"例。不要只使用调用参数,而是要存储要调用的函数以及参数。类似的东西:

(stack) [
  [function A, parameters for this call of A, additional refs for this call of A],
  [function B, parameters for this call of B, additional refs for this call of B]
]

正如我们所知,堆栈操作"持续,先出,"这意味着如果我们在外部调用另一个函数之后有一个带有操作的函数,则需要在调用之前将这些后续操作推送到堆栈,以便从堆栈将是这样的:

(stack) [first_call]
pop stack
  => first_call:
       process procedure before out_call
       push procedure after out_call
         => (stack) [procedure after out_call]
       push out_call
         => (stack) [procedure after out_call,
                     out_call]
pop stack
  => out_call
     (maybe followed by a whole other series of stack interactions)
pop stack
  => procedure after out_call (maybe use stored result)

(所有这些都是利用堆栈概念来命令我们的操作的一个黑客。如果你想获得真正的想象(甚至更复杂),将每个指令编码为一个函数并模拟一个实际的call stack,能够暂停主程序中的下一条指令,因为对其他函数的调用被推送到它。)

现在让我们将这个想法应用到您的示例中:

同步示例

我们不仅在这里有呼出后程序,而且我们有一个完整的for循环这样的调用。 (请注意,直接在代码段查看器中查看的控制台日志不完整。请查看浏览器的JS控制台以获取完整的日志。)



var references = {};
var object = {
  a: {
    b: {
      c: {
        d: {
          e: 10,
          f: 11,
          g: 12
        }
      }
    }
  }
};

object.a.b.c.d.x = object;
object.a.b.c.d.y = object.a.b;

var id = 1;


let iterativeProcess = {
  stack: [],
  currentResult: undefined,
  start: function(){
    // Garbage collector :)
    iterativeProcess.currentResult = undefined

    console.log('Starting stack process')
    
    // Used for debugging, to avoid an infinite loop
    let keep_going = 100;
    
    while (iterativeProcess.stack.length && keep_going--){
        let [func_name, func, params, refs] = iterativeProcess.stack.pop();

        console.log('\npopped: [' + func_name + ', ' + params + ', ' + JSON.stringify(refs) + ']');
        
        params.unshift(refs);
        
        func.apply(func, params);
    }
    
    return 'Stack process done\n\n';
  }
};


let circularReferences = {
  preOutCall: function(refs, _object, _references){
    var output = {};
    
    if (_object.__circularid__){
      console.log('preOutCall: _object has __circularid__ setting currentResult true')
      iterativeProcess.currentResult = true;
      
      // Exit
      return;
    }
    
    Object.defineProperty(_object, '__circularid__', { value: id++ })
    
    // Push post-out-call-procedure to stack
    console.log('Pushing to stack postOutCall ' + Object.keys(_object)[0])
    iterativeProcess.stack.push(['postOutCall', circularReferences.postOutCall, [], output]);
    
    // Call for-loop in reverse
    let keys = Object.keys(_object);

    for (let i=keys.length-1; i >=0; i--)
      circularReferences.subroutineA(output, _object, keys[i], _references);
  },
  
  subroutineA: function(refs, _object, key, _references){
    var value = _object[key];
      
    if (value && typeof value == 'object'){
      console.log('subroutineA: key: ' + key + '; value is an object: ' + value);
      
      console.log('Pushing to stack postSubroutineA ' + key)
      iterativeProcess.stack.push(['postSubroutineA', circularReferences.postSubroutineA, [key], refs]);
      
      // Push out-call to stack
      console.log('Pushing to stack preOutCall-' + key)
      iterativeProcess.stack.push(['preOutCall-' + key, circularReferences.preOutCall, [value, _references], refs]);
  
    } else {
      console.log('subroutineA: key: ' + key + '; value is not an object: ' + value);
      console.log('Pushing to stack subroutineA1 ' + key)
      iterativeProcess.stack.push(['subroutineA1', circularReferences.subroutineA1, [key, value], refs]);
    }
  },
  
  subroutineA1: function(refs, key, value){
    console.log('subroutineA1: setting key ' + key + ' to ' + value);
    
    refs[key] = value;
  },
  
  postSubroutineA: function(refs, key){
    let is = iterativeProcess.currentResult; //circularReferences(value, _references)
        
    if (is){
      refs[key] = '[Circular]';
      
      console.log('postSubroutineA: Object key: ' + key + ' is circular; output: ' + JSON.stringify(refs));
      
    } else {
      console.log('postSubroutineA: key: ' + key + '; currentResult: ' + iterativeProcess.currentResult + '; output: ' + JSON.stringify(refs));
    }
  },
  
  postOutCall: function(){
    // There is no return statement in the original function
    // so we'll set current result to undefined
    iterativeProcess.currentResult = undefined;
  }
};

// Convert the recursive call to iterative

//var x = circularReferences(object, references)
//console.log(x)
console.log('Pushing to stack')
iterativeProcess.stack.push(['preOutCall', circularReferences.preOutCall, [object, references]]);
console.log(iterativeProcess.start());




异步示例

(我冒昧地在next()结尾处添加了对asynca的电话,我想你已经忘记了。)

这里,除了多个交织函数调用之外,我们还有一个复杂的调用是异步的,这基本上意味着会有多个堆栈进程。由于在这个特定的例子中,堆栈过程不会在时间上重叠,我们只使用一个堆栈,顺序调用。 (请注意,直接在代码段查看器中查看的控制台日志不完整。请查看浏览器的JS控制台以获取完整的日志。)



let async = {
  asynca: function(refs, items, callback){
    let i = 0;
    
    function next(refs){
      console.log('next: i: ' + i);
      
      let item = items[i++];
      
      if (!item){
        console.log('Item undefined, pushing to stack: callback');
        iterativeProcess.stack.push(['callback', callback, [], refs]);
        
      } else {
        console.log('Item defined, pushing to stack: item');
        iterativeProcess.stack.push(['item', item, [next], refs]);
      }
    }
      
    console.log('asynca: pushing to stack: next');
    iterativeProcess.stack.push(['next', next, [], refs]);
  },

  async1a: function(refs, callback) {
    // Some stuff...
    setTimeout(function(){
      if (true) {
        var items = [
          async.async2a,
          // ...
        ]
  
        console.log('async1a: pushing to stack: asynca');
        iterativeProcess.stack.push(['asynca', async.asynca, [items, callback], refs]);
        
      } else {
        console.log('async1a: pushing to stack: callback');
        iterativeProcess.stack.push(['callback', callback, [null, true], refs]);
      }
      
      // Since there was a timeout, we have to restart the stack process to simulate
      // another thread
      iterativeProcess.start();
    }, 200)
  },

  async1b: function(refs, callback) {
    // Some stuff...
    setTimeout(function(){
      if (true) {
        var items = [
          async.async2a,
          // ...
        ]
  
        console.log('async1b: pushing to stack: asynca');
        iterativeProcess.stack.push(['asynca', async.asynca, [items, callback], refs]);
  
      } else {
        console.log('async1b: pushing to stack: callback');
        iterativeProcess.stack.push(['callback', callback, [null, true], refs])
      }
      
      // Since there was a timeout, we have to restart the stack process to simulate
      // another thread
      console.log(iterativeProcess.start());
    }, 200)
  },

  async1c: function(refs, callback) {
    // Some stuff...
    setTimeout(function(){
      if (true) {
        var items = [
          async.async2a,
          // ...
        ]
  
        console.log('async1c: pushing to stack: asynca');
        iterativeProcess.stack.push(['asynca', async.asynca, [items, callback], refs]);
        
      } else {
        console.log('async1c: pushing to stack: callback');
        iterativeProcess.stack.push(['callback', callback, [null, true], refs]);
      }
      
      // Since there was a timeout, we have to restart the stack process to simulate
      // another thread
      console.log(iterativeProcess.start());
    }, 200)
  },

  async2a: function(refs, callback) {
    console.log('async2a: pushing to stack: callback');
    iterativeProcess.stack.push(['callback', callback, [], refs]);
  }
}

let iterativeProcess = {
  stack: [],
  currentResult: undefined,
  start: function(){
    // Garbage collector :)
    iterativeProcess.currentResult = undefined

    console.log('Starting stack process')
    
    // Used for debugging, to avoid an infinite loop
    let keep_going = 100;
    
    while (iterativeProcess.stack.length && keep_going--){
        let [func_name, func, params, refs] = iterativeProcess.stack.pop();

        console.log('\npopped: [' + func_name + ', [' + params.map(x => typeof x)  + '], ' + JSON.stringify(refs) + ']');
        
        params.unshift(refs);
        
        func.apply(func, params);
    }
    
    return 'Stack process done\n\n';
  }
};

let _items = [
  async.async1a,
  async.async1b,
  async.async1c
  // ...
];

console.log('Pushing to stack: asynca');
iterativeProcess.stack.push(['asynca', async.asynca, [_items, function(){console.log('\ndone')}]]);
console.log(iterativeProcess.start());




调用堆栈仿真

我不确定我是否有时间去做这个,但这里有一些通用模板的想法。将其他函数调用为相关的较小函数以实现暂停执行的单独函数,可能将它们编码为具有操作数组和键的对象,以模拟局部变量。

然后编写一个控制器和接口,可以区分对另一个函数的调用(类似编码,如果它也有调用),推送"函数"的对象(或堆栈帧) )到堆栈,记住下一个指令的位置。有很多创造性的方法可以通过JavaScript实现,使用对象键来返回"返回地址"例如,被调用函数可以使控制器知道。

正如其他人在此处所指出的,每个函数调用另一个函数都会将其转换为迭代序列。但是可能有许多功能可以适用于这样的方案,并且允许我们从执行限制和排序的附加控制中受益。

答案 1 :(得分:3)

我认为这是一个坏主意。我觉得这里的好处纯粹是一种智力练习,但是你要对一个可以用其他方法解决的简单资源问题做出非常混乱和复杂的答案。

当Javascript中的函数执行时,它会将返回地址,函数参数和局部变量推送到堆栈中。通过迫使它进入循环,通过自然递归的无限深度问题,几乎不可能在这些方面节省很多钱。之前的响应(其名称我无法输入或甚至复制粘贴,因为它在RTL enconding中)讨论了尾部递归,如果你可以适应模式,你可以稍微节省一下不将返回地址推到堆栈,但Javascript引擎无论如何都可以为你处理。

与其他语言(例如PHP)相比,一个差异和额外的性能负载将是Javascript还为每次调用函数创建一个闭包。在非常深的,非常宽的数据集中,这可能是微不足道的或大量的资源成本,这种类型可能实际上导致问题,其中可能认为非递归将解决该问题。但是,有一种方法可以通过使用Function构造函数来调用没有闭包的函数。我们回避这些因为eval-is-evil而且他们确实需要额外的一步来编译,但是一旦创建,这可能会减少深度递归中闭包的任何额外开销。实时编译性能命中应该通过减少深度递归调用的开销来补偿。

其他性能拖动可以创建参数对象和执行上下文(this)。我们现在还有箭头函数,它们只会继承外部作用域的实例。所以实际的递归部分应该是箭头函数。如果它需要this,请通过外部Function关闭传递,或将其作为参数传递。

这是一张通用地图:

const outerFunction=new Function(
    'arg1,arg2,arg3',
    `
        // Initialize and create items that simply must be transferred by closure
        ...
        const recursiveFunction=(param1,param2,param3)=>{
            ...
            const value=recursiveFunction(newParam1,newParam2,newParam3)
            ...
        }
        return recursiveFunction(firstParam1,firstParam2,firstParam3)
    `
)

进入一般模式后,尽可能优化内循环。如果原件可以作为参考传递,则不要构建和重新组合对象和数组。不要在递归部分进行任何duckchecking或无关处理。 Javascript程序员并没有太多关于内存泄漏的知识,因为该语言传统上是在页面刷新时被擦除的短片段,因此学会发现不再需要的项目不能被垃圾收集的实例,以及对于那个问题,减少需要垃圾收集的项目,如果问题是速度而不是内存。请记住,所有基元都是不可变的,旧值在分配新值时将受GC影响。考虑使用明智的Typed Arrays,以防止这种情况发生。

即使这并没有解决OP的担忧,我希望这种方法可以对在Google上发现此问题的其他人有所帮助。

答案 2 :(得分:2)

让我们定义一个简单的函数以及我们的参数。

function syncLoop(iterations, process, exit){
    // Body of the function
}

只是快速谈论params;

iterations = the number of iterations to carry out
process    = the code/function we're running for every iteration
exit       = an optional callback to carry out once the loop has completed

所以我们有了我们的函数shell,现在我们需要实例化一个索引,并使用一个布尔值来跟踪我们是否完成了循环。

function syncLoop(iterations, process, exit){
    var index = 0,
        done = false;
    // Body of function
}

现在我们可以跟踪我们的位置,以及我们是否完成(在循环中都很重要!)。完成布尔值将是我们检查是否要在调用时实际再次运行的方法。

是的,这是稍微复杂一点的地方。我们将创建一个实际上是我们的循环对象的对象循环,我们将返回它,以便我们可以从函数外部控制循环。

function syncLoop(iterations, process, exit){
    var index = 0,
        done = false;
    var loop = {
        // Loop structure
    };
    return loop;
}

我会回到这一段时间。好的,所以我们有循环。循环中有什么重要的?好吧,我们需要一种方法来访问索引,继续循环,以及一种杀死循环的方法 - 所以让我们实现这些方法。

function syncLoop(iterations, process, exit){
    var index = 0,
        done = false,
        shouldExit = false;
    var loop = {
        next:function(){
            if(done){
                if(shouldExit && exit){
                    return exit(); // Exit if we're done
                }
            }
            // If we're not finished
            if(index < iterations){
                index++; // Increment our index
                process(loop); // Run our process, pass in the loop
            // Otherwise we're done
            } else {
                done = true; // Make sure we say we're done
                if(exit) exit(); // Call the callback on exit
            }
        },
        iteration:function(){
            return index - 1; // Return the loop number we're on
        },
        break:function(end){
            done = true; // End the loop
            shouldExit = end; // Passing end as true means we still call the exit callback
        }
    };
    return loop;
}

好的,稍微谈谈这个;

loop.next()是我们的循环控制器。我们的流程在希望完成迭代并转到下一个迭代时应调用loop.next()。基本上,所有loop.next()都会再次调用我们想要的进程,除非我们完成,在这种情况下它会调用最终的回调。

loop.iteration()函数只返回我们所在的索引。第一次初始化意味着我们将始终是当前迭代之前的一个索引,因此我们返回index - 1。

loop.break()只是告诉循环完成当前迭代。您可以传递一个可选值来告诉循环正常结束,并根据需要调用exit()回调。这对于需要自行清理的循环非常有用。

是的,我们这里拥有大部分身体。所以,让我们开始吧,在我们返回循环之前调用loop.next()

function syncLoop(iterations, process, exit){
    var index = 0,
        done = false,
        shouldExit = false;
    var loop = {
        next:function(){
            if(done){
                if(shouldExit && exit){
                    return exit(); // Exit if we're done
                }
            }
            // If we're not finished
            if(index < iterations){
                index++; // Increment our index
                process(loop); // Run our process, pass in the loop
            // Otherwise we're done
            } else {
                done = true; // Make sure we say we're done
                if(exit) exit(); // Call the callback on exit
            }
        },
        iteration:function(){
            return index - 1; // Return the loop number we're on
        },
        break:function(end){
            done = true; // End the loop
            shouldExit = end; // Passing end as true means we still call the exit callback
        }
    };
    loop.next();
    return loop;
}

我们已经完成了!现在重要的是实现我们的循环并运行它,所以让我们看一个例子;

syncLoop(5, function(loop){
    setTimeout(function(){
        var i = loop.iteration();
        console.log(i);
        loop.next();
    }, 5000);
}, function(){
    console.log('done');
});

上面的代码只打印出当前迭代,我们在每次打印之间有5秒钟,然后在完成后完成日志。继续在浏览器控制台中尝试。我们还要检查我们的loop.break()是否按预期工作。

var myLoop = syncLoop(5, function(loop){
    setTimeout(function(){
        var i = loop.iteration();
        console.log(i);
        loop.next();
    }, 5000);
}, function(){
    console.log('done');
});

setTimeout(myLoop.break, 10000);

在这种情况下,我们应该只看到在循环结束之前打印的前两次迭代。因为我们没有将一个布尔值传递给myLoop.break(),所以它没有注销完成。我们可以通过使用以下内容来改变它:

setTimeout(function(){
    myLoop.break(true);
}, 10000);

需要注意的一件重要事情是,你不能在执行中期时杀死循环(干净利落),它会等到当前迭代完成(这实际上很有意义)。它将在下一次迭代开始时将中断排队,并在loop.next()中进行检查。

答案 3 :(得分:1)

有一种通用方法可以将递归函数转换为使用显式堆栈:模拟编译器处理递归调用的方式。将所有本地状态保存在堆栈上,将参数值更改为传递给递归调用的值,然后跳转到函数顶部。然后函数返回的地方,而不仅仅是返回,检查堆栈。如果非空,则弹出状态并跳转到将返回递归调用的位置。由于javascript不允许跳转(goto),因此有必要对代码进行代数转换为循环。

从递归代码开始,比原始代码更容易处理。这只是一个经典的递归DFS,带有我们已经搜索过的对象(图形节点)的“访问”标记,以及从最顶层(根)对象到当前对象的路径上的对象的“当前”标记。如果对于每个对象引用(图形边缘),我们将发现所有周期,我们检查目标是否标记为“当前”。

沿途删除当前标记。搜索图表后,访问的标记仍然存在。

function get_back_refs(obj, back_refs) {
  if (obj && typeof obj == 'object' && !('__visited__' in obj)) {
    mark(obj, '__visited__')
    mark(obj, '__current__')
    var iter = getKeyIterator(obj)
    while (iter.hasNext()) {
      var key = iter.next()
      if ('__current__' in obj[key]) {
        back_refs.push([obj, obj[key]])
      } else {
        get_back_refs(obj[key], back_refs)
      }
    }
    unmark(obj, '__current__')
  }
}

var object = {
  a: {
    b: {
      c: {
        d: {
          e: 10,
          f: 11,
          g: 12
        }
      }
    }
  }
}
object.a.b.c.d.x = object
object.a.b.c.d.y = object.a.b

var id = 0

function mark(obj, name) {
  Object.defineProperty(obj, name, { value: ++id, configurable: true })
}

function unmark(obj, name) {
  delete obj[name]
}

function getKeyIterator(obj) {
  return {
    obj: obj,
    keys: Object.keys(obj).filter(k => obj[k] && typeof obj[k] == 'object'),
    i: 0,
    hasNext: function() { return this.i < this.keys.length },
    next: function() { return this.keys[this.i++] }
  }
}

var back_refs = []
get_back_refs(object, back_refs)
for (var i = 0; i < back_refs.length; ++i) {
  var pair = back_refs[i]
  console.log(pair[0].__visited__ + ', ' + pair[1].__visited__)
}

请注意,我认为这可以修复代码中的错误。由于层次结构是一般的有向图,因此您希望避免两次搜索对象。跳过此操作很容易导致图形大小的运行时指数。然而,图中的共享结构并不一定意味着存在循环。该图可以是DAG指导的和非循环的。

在这种情况下,本地状态很好地包含在迭代器中,所以我们在堆栈中需要的只是:

function get_back_refs2(obj, back_refs) {
  var stk = []
  var iter = null
 start:
  if (obj && typeof obj == 'object' && !('__visited__' in obj)) {
    mark(obj, '__visited__')
    mark(obj, '__current__')
    iter = getKeyIterator(obj)
    while (iter.hasNext()) {
      var key = iter.next()
      if ('__current__' in obj[key]) {
        back_refs.push([obj, obj[key]])
      } else {
        stk.push(iter) // Save state on stack.
        obj = obj[key] // Update parameter value.
        goto start     // Eliminated recursive call.          
       rtn:            // Where call would have returned.
      }
    }
    unmark(obj, '__current__')
  }
  if (stk.length == 0) return
  iter = stk.pop()  // Restore iterator from stack.
  obj = iter.obj    // Restore parameter value.
  goto rtn
}

现在消除gotoThis article描述了一个非常类似的搜索树而不是一般图形的转换,所以我不会在这里讨论它。我们最终得到了这个中间结果:

function get_back_refs2(obj, back_refs) {
  var stk = []
  var iter = null
  for (;;) {
    if (obj && typeof obj == 'object' && !('__visited__' in obj)) {
      mark(obj, '__visited__')
      mark(obj, '__current__')
      iter = getKeyIterator(obj)
      var key = null
      while (iter.hasNext()) {
        key = iter.next()
        if ('__current__' in obj[key]) back_refs.push([obj, obj[key]])
        else break
      }
      if (key) {
        stk.push(iter)
        obj = obj[key]
        continue           
      }
      unmark(obj, '__current__')
    }
    for(;;) {
      if (stk.length == 0) return
      iter = stk.pop()
      obj = iter.obj
      var key = null
      while (iter.hasNext()) {
        key = iter.next()
        if ('__current__' in obj[key]) back_refs.push([obj, obj[key]])
        else break
      }
      if (key) {
        stk.push(iter)
        obj = obj[key]
        break
      }           
      unmark(obj, '__current__')
    }
  }
}

用它们导致执行的代码替换goto s导致重复。但我们可以通过共享的本地功能来解决这个问题:

function get_back_refs2(obj, back_refs) {
  var stk = []
  var iter = null
  var descend_to_next_child = function() {
    var key = null
    while (iter.hasNext()) {
      key = iter.next()
      if ('__current__' in obj[key]) back_refs.push([obj, obj[key]])
      else break
    }
    if (key) {
      stk.push(iter)
      obj = obj[key]
      return true           
    }
    unmark(obj, '__current__')
    return false
  }
  for (;;) {
    while (obj && typeof obj == 'object' && !('__visited__' in obj)) {
      mark(obj, '__visited__')
      mark(obj, '__current__')
      iter = getKeyIterator(obj)
      if (!descend_to_next_child()) break
    }
    for(;;) {
      if (stk.length == 0) return
      iter = stk.pop()
      obj = iter.obj
      if (descend_to_next_child()) break
    }
  }
}

除非我犯了代数错误,这当然是可能的,否则这是原始递归版本的替代品。

虽然该方法不涉及代码代数之外的推理,但现在我们已经完成了很清楚,第一个循环下降到图中,总是发现它不是后引用的第一个子节点,将迭代器推入堆栈。第二个循环弹出堆栈,寻找一个迭代器,剩下的工作要做:至少要搜索一个子节点。当找到一个时,它将控制权返回给第一个循环。这正是递归版本所做的,以不同的方式表达。

构建一个自动执行这些转换的工具会很有趣。