在任意数量的阵列之间找到共同项的最有效方法

时间:2016-06-10 20:53:31

标签: javascript arrays optimization unique

我需要能够在任意数量的数组之间找到一个公共项。例如,让我们说有一个像这样的对象:

var obj = {
  a: [ 15, 23, 36, 49, 104, 211 ],
  b: [ 9, 12, 23 ],
  c: [ 11, 17, 18, 23, 38 ],
  d: [ 13, 21, 23, 27, 40, 85]
};

我需要确定每个阵列之间的公共项目。 (在这种情况下,23)。

我的解决方案是找到最短的数组,并遍历其中的每个项目,检查其他数组的索引。

var shortest = {};
var keys = [];
for ( var key in obj ) {

  if ( obj.hasOwnProperty( key ) && Array.isArray( obj[ key ] ) ) {
    keys.push( key );

    if ( !shortest.hasOwnProperty( 'length' ) || obj[ key ].length < shortest.length ) {
      shortest.name = key;
      shortest.length = obj[ key ].length;
    }
  }
}



var res = obj[ shortest.name ].filter(function ( v ) {

  for ( var i = 0; i < keys.length; i++ ) {

    if ( obj[ keys[ i ] ].indexOf( v ) === -1 ) {
      return false;
    }

    return true;
  }
};

然而,这似乎效率极低,而且我试图确定是否有更好的方法,最好不要多次循环。

4 个答案:

答案 0 :(得分:2)

我认为不可能在O(N)以内执行此操作,其中N是所有数组中的项目数。您当前的解决方案效率较低,因为indexOf对于每个数组都是O(N),并且您可以针对最短数组中的每个项目运行所有这些解决方案。

我认为基于地图的选项为O(N)

var counts = {};
var keys = Object.keys(obj);
var arr, el;
for (var k = 0; k < keys.length; k++) {
    arr = obj[keys[k]];
    for (var i = 0; i < arr.length; i++) {
        el = arr[i];
        if (counts[el] === undefined) {
            // new value, start counting
            counts[el] = 1;
        } else {
            // increment count for this value
            counts[el]++;
            // if we have as many values as keys, we're done
            if (counts[el] === keys.length) {
                return el;
            }
        }
    }
}

这里有一些警告:

  • 这假定元素可以用作对象键(即它们可以唯一地转换为字符串),并且您没有像1和{{1}这样的令人讨厌的边缘情况在不同的数组中。

  • 这假设每个数组的数组值都是唯一的。

  • 这假设交叉点中只有一个元素。

https://jsfiddle.net/xzfa75og/

答案 1 :(得分:2)

另一个O(n)集联合解决方案,在功能上编码得更多。作为对象键警告的元素仍然适用,尽管这将返回交集中所有共享元素的set(数组)。

function common(o) {
  // Map each of the object key arrays to a set.
  return Object.keys(o).map(function(k) {
    return o[k].reduce(function(a, e) {
      a[e] = 1;
      return a;
    }, {});
  }).reduce(function(a, e) {
    // Perform a set union.
    Object.keys(e).forEach(function(k) {
      if (!a[k]) {
        delete e[k];
      }
    });
    return e;
  })
}

var obj = {
  a: [ 15, 23, 36, 49, 104, 211 ],
  b: [ 9, 12, 23 ],
  c: [ 11, 17, 18, 23, 38 ],
  d: [ 13, 21, 23, 27, 40, 85]
};

common(obj);

答案 2 :(得分:0)

我会通过Array.prototype.intersect()的发明来完成这项工作。当阵列中有重复项时,它不会失败。如果在每个数组中有相同的项目重复,您将在交集中得到副本。让我们看看它将如何运作

&#13;
&#13;
Array.prototype.intersect = function(...a) {
      return [this,...a].reduce((p,c) => p.filter(e => c.includes(e)));
    };
var obj = {
           a: [ 15, 23, 36, 49, 104, 211 ],
           b: [ 9, 12, 23 ],
           c: [ 11, 17, 18, 23, 38 ],
           d: [ 13, 21, 23, 27, 40, 85]
          },
   arrs = Object.keys(obj).map(e => obj[e]);

console.log(JSON.stringify(arrs.pop().intersect(...arrs))); // take the last one out to invoke the method over the rest
&#13;
&#13;
&#13;

答案 3 :(得分:0)

ES5中的另一个解决方案,在 O(n)中有一个临时对象。

&#13;
&#13;
var obj = { a: [15, 23, 36, 49, 104, 211], b: [9, 12, 23, 15], c: [11, 17, 18, 23, 38], d: [13, 21, 23, 27, 40, 85] },
    result = Object.keys(obj).reduce(function (o) {
        return function (r, k, i) {
            var t = o;
            o = Object.create(null);
            return obj[k].filter(function (a) {
                return (!i || t[a]) && (o[a] = true);
            });
        };
    }(Object.create(null)), []);

console.log(result);
&#13;
&#13;
&#13;