【问题标题】:How can I iterate over all unique pairs of entries in an object?如何遍历对象中所有唯一的条目对?
【发布时间】:2012-05-17 07:56:06
【问题描述】:

我目前有一个数组数据结构,我像这样迭代它,在每对唯一的元素上调用 foo。

for(var i = 0; i < arr.length; i++) {
    for(var j = i + 1; j < arr.length; j++) {
        foo(arr[i], arr[j]);
    }
}

但是,我意识到我宁愿使用对象而不是数组,因为这样我可以很容易地按名称添加和删除元素。

但是,我看不到迭代此类对象的明显方法。我能得到的最接近的是:

for(i in obj) {
    for(j in obj) {
        foo(obj[i], obj[j]);
    }
}

显然,这将对每一对执行两次,甚至会产生一对相同的元素。有没有一种简单的方法可以像我在第一个代码示例中的数组中那样迭代对象?

更新:

在jsperf 上测试解决方案的性能。

【问题讨论】:

  • 在内循环中添加if (i &lt; j) 条件怎么样?这不是最快的解决方案,但我相信它可以工作。
  • @elclanrs:我不是在递归迭代——我是在迭代对象/数组中所有可能的条目组合。例如,在数组[1, 2, 3]中,我想调用foo(1, 2)、foo(1, 3)和foo(2, 3)。
  • @SimonAndréForsberg 这在每种情况下都不起作用,因为对象的键不必排序,它们也不必是相同的数据类型。
  • @Eric 你是对的。但是字符串(对象键)与整数(数组键)的比较方式不同(例如,“10”
  • @Jasd:不,数组键也是字符串。尝试使用数组执行 for ... in 循环。你得到的键不是整数,而是字符串。

标签: javascript iteration javascript-objects


【解决方案1】:

假设我理解你的问题......也许检查一下外部循环是否已经访问了该值?

var visited = {}
for(i in obj) {
    visited[i] = true;
    for(j in obj) {
        if(j in visited){ continue; }
        foo(obj[i], obj[j]);
    }
}

【讨论】:

    【解决方案2】:

    您可以将对象键推送到数组中:

    var obj_keys = [];
    for (i in obj) {
      obj_keys.push(i);
    }
    
    for(i = 0; i < obj_keys.length; ++i) {
        for(j = i + 1; j < obj_keys.length; ++j) {
            foo(obj[obj_keys[i]], obj[obj_keys[j]]);
        }
    }
    

    【讨论】:

      【解决方案3】:

      使用 Object.keys() 将键列表作为数组取出:

      keys = Object.keys();
      for(i=0;i<keys.length;i++) {
          for(j=i+1;j<keys.length;j++) {
              foo(obj[keys[i]], obj[keys[j]]);
          }
      }
      

      【讨论】:

      • 最好将Object.keys() 存储在变量中,而不是评估它n + 3*n*n 次
      • 为什么我的编辑被拒绝了?你的代码不像你说的那样工作。我已经修好了。
      【解决方案4】:

      也许您可以尝试取消设置已使用的对象:

      for(i in obj) {
          var a = obj[i];
          delete obj[i];
          for(j in obj) {
              foo(a, obj[j]);
          }
      }
      

      http://jsfiddle.net/bXcvb/

      如果您需要原始obj,请参阅:How do I correctly clone a JavaScript object?

      【讨论】:

      • +1 是一种有趣的方法,但我不想每次迭代时都将对象拉开!
      • 我不确定你是否能有性能和整洁。要么是要么...但是感谢+1 :)
      • 是的,但是当你需要克隆它时,它就不再那么好了:jsperf.com/adjacent-pair-iteration/2
      • 尽管如此,当您需要对一个无论如何都会被删除的对象进行快速迭代时,知道这一点可能会很好……只需要等待那一刻。我很惊讶地看到,复制时的速度和我的解决方案差不多。
      • 我认为它对对象 (obj[key] = ...) 的关键分配非常昂贵。如果js里面有deep memcopy的话,这个可以改进很多。
      【解决方案5】:

      我的解决方案最初是作为评论写的:

      在内循环中添加if (i &lt; j) 条件。它可能不是最好的解决方案,但只要 foo 函数对 foo(2, 10) 和 foo(10, 2) 执行相同的操作,它就可以工作:

      for(i in obj) {
          for(j in obj) {
              if (i < j) {
                  foo(obj[i], obj[j]);
              }
          }
      }
      

      【讨论】:

      • 你确定这不是最快的吗?字符串比较的成本比属性查找高还是低?
      • 老实说,我不确定这是否是最快的解决方案:) 编辑了那部分。它可能是最简单、最简洁和最快的解决方案(至少在您的情况下)。
      • tbh,速度不是一个大问题,因为foo 比字符串比较贵几个数量级。尽管如此,在jsperf.com/adjacent-pair-iteration 做一些性能测试
      • 这比循环所有结果更高效。事实上,它比其他一些响应快 2 倍,并且在认知上更容易理解。
      猜你喜欢
      • 2016-11-18
      • 2012-08-08
      • 2016-09-24
      • 2013-11-04
      • 1970-01-01
      • 2018-01-02
      • 2014-10-07
      • 2013-07-20
      • 2011-02-18
      相关资源
      最近更新 更多