在任意数量的数组之间找到共同项的最有效方法
Posted
技术标签:
【中文标题】在任意数量的数组之间找到共同项的最有效方法【英文标题】:Most Efficient Way to Find Common Item Between Arbitrary Number of Arrays 【发布时间】:2016-10-11 22:32:39 【问题描述】:我需要能够在任意数量的数组之间找到一个共同的项目。例如,假设有这样一个对象:
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;
;
但是,这似乎非常低效,我正在尝试确定是否有更好的方法,最好不必多次循环。
【问题讨论】:
因此,所有任意数组都恰好在您的示例代码中进行了排序。这只是偶然还是事实? @Quirk Happenstance。我相信数据源将返回排序,但我犹豫是否指望它。 如果所有数组都已排序,那么最坏情况的运行时间可能是它们大小之和的线性,即O(n1 + n2 + ...)
见How to calculate intersection of multiple arrays in javascript?
检查***.com/questions/11076067/…
【参考方案1】:
我认为在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;
这里有一些警告:
1234563 .这假定每个数组的数组值都是唯一的。
这假设交叉点中只有一个元素。
https://jsfiddle.net/xzfa75og/
【讨论】:
您肯定不能在少于O(N)
的时间内做到这一点。您至少必须读取所有值,并且已经是 O(N)
。
最坏的情况总是O(N)
,但如果您知道交集中只有一个元素,则不必读取常见情况下的所有值。
这似乎正是我所需要的。
如果每个数组项都不唯一,则此解决方案将失败。如果数组中有重复项,则会通过产生错误计数来欺骗算法。
@Redu - 是的,补充说明【参考方案2】:
另一个O(n)
设置联合解决方案,编码更实用。元素作为对象键的警告仍然适用,尽管这将返回交集中所有共享元素的集合(数组)。
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);
【讨论】:
这看起来不像 O(n)。当您将 Object.keys 映射到对象时,您的 O(n) 时间就完成了,然后您执行了更多的 reduce 和 forEach 操作。在我看来像 O(2n)。此外,此算法对于重复条目也会失败,例如,如果每个数组有两个或更多项为 23,则只会找到其中一个。 是的,这确实设置了交集。如果要计算倍数(即袋子),可以在累加器中保持计数(将a[e] = 1
替换为a[e]++
,根据需要进行初始化)并在reduce
上取min(a[k], e[k])
。 O(n)
仅表示线性渐近增长。使用这个符号,O(2n) = O(n)
因为两者都是线性的。请参阅here 了解更多信息。【参考方案3】:
ES5 中的另一种解决方案,在 O(n) 中有一个临时对象。
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);
【讨论】:
这是在给定答案内完成 O(n) 工作的唯一算法。我想我必须修改我的Array.prototype.intersect()
以使用过滤器回调作为您的方法,即使 includes()
看起来更酷。
你或许应该重新考虑一下你与 Edge 的恋情。我不知道 Edge 是如何向 Node.js 申请拉取请求的。几乎没有任何东西适用于 Edge,包括解构、扩展运算符等,天知道还有什么。我认为最好的是 Firefox(基本上我很欣赏他们令人惊叹的文档和对社区的帮助),而且我还没有看到任何在 FF 中不起作用的东西。 Chrome 还可以,但刚刚从 V50 开始,能够通过像 myArr = [...nodeList]
这样的扩展运算符将 nodeList 转换为数组。但为什么是 Edge..?【参考方案4】:
我会通过Array.prototype.intersect()
的发明来完成这项工作。当数组中有重复项时它不会失败。如果您在每个数组中都有相同的项目重复,您将在交叉点中得到应有的重复项。让我们看看它是如何工作的
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
【讨论】:
以上是关于在任意数量的数组之间找到共同项的最有效方法的主要内容,如果未能解决你的问题,请参考以下文章
从 JavaScript 中的关联数组中获取第一项的最有效方法是啥?