双循环性能问题

时间:2014-06-18 10:03:27

标签: javascript arrays performance object for-loop

关于模糊标题的道歉,并不确定如何描述问题。

我最近碰到了一个案例,我必须遍历一个对象数组来比较多个值,我选择在for循环中使用for循环来比较每个对象和其他所有对象。

虽然这在小数组上运行良好,但是一旦我的数组变得更大(比如10,000个对象),性能往往是个大问题。

此数组包含以下类型的对象:

[{ char: '_', from: 0, to: 2, chrLength: 2 },
{ char: '_', from: 0, to: 7, chrLength: 7 },
{ char: 'a', from: 1, to: 3, chrLength: 2 },
{ char: 'a', from: 1, to: 6, chrLength: 5 },
{ char: '_', from: 2, to: 7, chrLength: 5 },
{ char: 'a', from: 3, to: 6, chrLength: 3 }]

我的想法是,我只能选择fromto不与任何其他对象重叠的对象。 (fromto是另一个数组中的索引)

因此,对于示例数组,可能的结果将是:

[{ char: '_', from: 0, to: 2, chrLength: 2 },
 { char: 'a', from: 1, to: 3, chrLength: 2 },
 { char: 'a', from: 1, to: 6, chrLength: 5 },
 { char: 'a', from: 3, to: 6, chrLength: 3 }]

我处理它的方式如下:

var canUse = true,
    posibilities = [];
for(i = 0; i < l; i++) {
    canUse = true;
    for(var j = 0; j < l; j++) {
        if((results[i].from < results[j].from && results[i].to > results[j].to)) {
            canUse = false;
            break;
        }
    }

    if(canUse) posibilities.push(results[i]);
}

对于更大的阵列,看到性能非常糟糕,我想知道是否有更好的解决方案可以做到这一点?

3 个答案:

答案 0 :(得分:1)

首先对chrLength属性上的对象进行排序。当您查找使对象不被包含的对象时,您只需要检查至少两个字符的对象。

results.sort(function(x, y){ return x.chrLength - y.chrLength; });

var posibilities = [];
for (var i = 0; i < l; i++) {
  var canUse = true, len = results[i].chrLength - 2;
  for (var j = 0; results[j].chrLength <= len; j++) {
    if((results[i].from < results[j].from && results[i].to > results[j].to)) {
      canUse = false;
      break;
    }
  }
  if(canUse) posibilities.push(results[i]);
}

使用您的示例数据,这会将检查次数从原始代码中的36减少到仅为8。

比较:http://jsfiddle.net/Guffa/5jsSb/

编辑:

您可以创建一个数组,其中每个项目都是具有相同chrLength的对象数组,然后对from属性上的每个数组进行排序。通过这种方式,您可以轻松跳到对象开始重叠的位置,并在它们不再重叠时立即停止比较:

var map = [];
for (var i = 0; i < l; i++) {
  var ch = results[i].chrLength;
  while (map.length <= ch) map.push([]);
  map[ch].push(results[i]);
}
for (var i = 1; i < map.length; i++) {
  map[i].sort(function(x, y){ return x.from - y.from; });
}

var posibilities = [];
for (var i = 0; i < l; i++) {
  var canUse = true, len = results[i].chrLength - 2, from = results[i].from, to = results[i].to;
  for (var j = 1; canUse && j <= len; j++) {
    if (map[j][map[j].length - 1].from > from) {
      var k;
      for (k = 0; map[j][k].from <= from; k++);
      for (;k < map[j].length && map[j][k].from < to; k++) {
        if (map[j][k].to < to) {
          canUse = false;
          break;
        }
      }
    }
  }
  if(canUse) posibilities.push(results[i]);
}

这将分两个阶段对fromto属性的检查进行拆分,因此完整检查的数量(评估map[j][k].to < to的数量)实际上小于总数对象。

免责声明:当然,您需要验证代码是否正确。我已经检查过结果的项目数是否相同,但我没有比较每个项目。

答案 1 :(得分:1)

这是一个想法(Demo):

  1. 您需要某种自平衡树来支持O(log n)的插入和删除操作。为了简单起见,我使用了红黑树。
  2. 您需要将间隔的中点用作关键字(from + to)/2
  3. 假设您已将“k项”插入树中并即将插入k+1。您的步骤是:
  4. 如果k+1覆盖根目录 - 请忽略它。
  5. 如果根目录覆盖k+1 - 从树中删除根并再次尝试。
  6. 通过比较k+1的mid和root的子树,继续左边或右边的子树。
  7. 当插入所有内容时,遍历生成的树,收集每个节点。
  8. 利润......我用自己的数组将你的数组用了4倍。我的机器在Chrome下的结果是116ms(冷启动)和64ms(预热后)。
  9. 代码

     function process() {
        console.log('Processing results of length: ' + l);
    
        console.time('Processing');
    
        var comparator = function(a, b) { //Comparator to build a tree
               return a.mid - b.mid;
            },
            isAinB = function(a, b) { //util function to check if a is inside b
                return b.from < a.from && b.to > a.to;    
            },
            rbtree = new RBTree(comparator), //Build an empty tree
            i = results.length - 1, item, posibilities = [];
    
        function check(root, x) { //Recursive checker
            var data;        
    
            if(!root) { //Either tree is empty or we've reached a leaf
                rbtree.insert(x);
                return;
            }
    
            data = root.data;
    
            if(isAinB(data, x)) { //4
                return;    
            }
            if(isAinB(x, data)) { //5
                rbtree.remove(data);
                check(rbtree._root, x);
                return;
            }    
    
            check(root[comparator(data, x) > 0 ? 'left' : 'right'], x); //6
        }
    
        for(; i >= 0; i--) { 
            item = results[i];
            item.mid = (item.from + item.to)/2; //2
            check(rbtree._root, item); //3
        }
    
        rbtree.each(function(item) { //7
            posibilities.push(item);
        });
        console.timeEnd('Processing');
    
        console.log(posibilities.length);
    }
    

    BTW我已经使用了这个RBTree implementation。不确定它是否是最好的一个:)

答案 2 :(得分:0)

对于初学者来说,一旦canUsefalse,您就不需要继续进行内循环了。

您可以添加break;或将第二个for循环更改为:

for (var j = 0; canUse && (j < l); j++)

你可能会看到一个有用的加速。