【问题标题】:Detect infinite recursion?检测无限递归?
【发布时间】:2012-03-26 01:44:51
【问题描述】:

假设我有一个爬过数组的函数...

flatten([a, b, c, d, [e, f, g, [h, i, j, k], l], m, n, o, p])
>> [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p]

Flatten 会爬过代码,遇到的每个数组都会递归地进入该数组并返回值,这样您就有了一个平面数组。

直到我们有一个数组,例如:

a    = [];
a[0] = a;

这显然会产生无限递归:

Array[1]
0: Array[1]
 0: Array[1]
  0: Array[1]
   0: Array[1]
    0: Array[1]
     0: Array[1]
      ...

如何在不修改数组的情况下检测到这种行为,以便函数可以处理这种情况?

【问题讨论】:

  • 我们是否可以维护我们已经爬取的内容,如果我们已经爬取了那个数组,那么我们就处于上述情况......
  • 查看this post。它基本上展示了如何编写一个限制递归深度的函数装饰器。

标签: javascript recursion pass-by-reference


【解决方案1】:

如果需要检测递归,您将不得不为此交换内存空间:创建一个您解析的对象数组(您递归发送的参数)并对照它检查每个新参数。如果你已经解析过了,立即返回。

【讨论】:

  • +1。另外,如果可以的话,对数组进行排序(我不熟悉 javascript 和它的对象表示,但我想你可以获取东西的地址),所以你可以使用二进制搜索,或者其他一些适合的数据结构比数组更好。
  • 我的第一个想法是说你不能得到这样的对象的数字 id,但我实际上很确定你可以;很好的建议!
  • 你可以使用一个集合,一个对象,它的键代表集合的元素,这些键的值可以是null(或其他一些虚拟对象)
【解决方案2】:

您必须在flatten() 函数中保留一个访问过的数组数组,并在您诅咒它之前检查它们是否存在。不过,您必须将访问过的元素列表作为第二个参数传递给 recurse

function recurse(ar, visited) {
    visited = visited || [];

    function isVisited(obj) {
        for (var i=0;i<visited.length;i++) {
            if (visited[i] == obj) {
                return true;
            }
        }

        visted.push(obj); // but we've visited it now
        return false;
    }

    // blah blah blah, do your thing... check isVisted[i]
} 

检查你的数组是否很深会变得很昂贵,所以你可以作弊并在你访问的每个数组上设置一个属性,然后检查它(当然,你正在修改数组(但不是很大) )。

function recurse(ar) {  
    function isVisited(obj) {
        var key = 'visited';
        var visited = obj.hasOwnProperty(key);

        obj[key] = true; // but we've visited it now

        return visited;
    }

    // blah blah blah, do your thing... check isVisted[i]
} 

【讨论】:

  • 一旦弱映射被标准化/广泛可用,它们将是为每个数组添加属性的好选择。见wiki.ecmascript.org/…
  • 因为数组也是对象,您可以为访问的每个数组设置一个属性,例如 array.visited = true;
【解决方案3】:

跟踪已经访问过的数组的一种便捷方法是为每个数组添加一个“平面”属性,也许为每个操作设置一个唯一值。当每个数组都已经是一个完美的对象时,没有必要再弄乱单独的地图了。

【讨论】:

  • 如果您拥有该对象,则为真(即您可以随意更改它),但情况可能并非总是如此。另外,您必须遍历它两次才能将 flat 属性重置为 false(然后您必须再次处理递归),或者您无法将对象展平两次。
  • 是的 - 我猜你以后总是可以擦掉标记。
【解决方案4】:

另一种更肮脏的方式(但有利于尽量减少大惊小怪)是只使用 JSON 编码器:

var is_recursive = false;

try {
    JSON.stringify(my_recursive_object);
} catch (e) {
    if (e.name = 'TypeError')
        is_recursive = true;
    else
        throw e;
}

我确实发现这个问题正在寻找一个更好的答案,尽管这可能会帮助那些想要一个好的 hack 的人 ;-)

【讨论】:

    【解决方案5】:

    在现实世界中,第一步是找到给你一个自引用对象并用棍子打他们的违规开发人员。

    然而,通过将它们作为自引用消除,问题得到了解决。把它们变成副本。 Array.slice 返回数组的一部分(包括完整的副本)而不改变原始数组。

    if(thisElement.constructor.prototype.slice){ //is it an array
        thisElement = thisElement.slice(0); //now it's a new but identical array
    }
    

    请注意,这仅在主数组引用本身上。内部数组引用仍然需要更改,因为复制的引用仍然指向同一事物。

    此外,如果您可以对绝对不会出现的字符做出假设,您可能会发现 slice/join 非常有用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-08-17
      • 2012-02-20
      • 1970-01-01
      • 1970-01-01
      • 2018-06-22
      • 2011-01-31
      • 1970-01-01
      相关资源
      最近更新 更多