Javascript在数组中添加N次相同的元素

时间:2014-04-23 07:25:41

标签: javascript arrays performance

假设我有这样的地图:

var map = {"a" : 100, "b" : 200, "c": 700};

我想要一个由" a"组成的数组。 100次," b" 200次" c" 700次:

map_array = [a, a, a, a, ... a, b, b, b, ... b, c, c, c, ... c]

简单的解决方案是循环频率时间并推入数组:

var map_array = []
for(key in map)
{
    for(var i=1; i <= map[key] ; i++)
    {
       map_array.push(key)
    }
}

但这显然需要时间来处理大数据的流程,我们是否可以解决以上功能?

5 个答案:

答案 0 :(得分:4)

在我看来,这里真正的问题是构建重复的"a""b"&#39;和"c"&#的子数组。 39; S。一旦你拥有它们,你可以concat它们来制作你的最终阵列。 所以,我们真正想要的是一个函数f(x, n),它创建一个充满n x的数组。

因此,作为标准测试平台,我将定义一对clock函数。第一个测量一些阵列填充函数创建500000个阵列所需的时间,每个阵列包含2187 "a"个。第二个测量一些阵列填充功能创建500个阵列所需的时间,每个阵列包含1594323 "a"。我选择了3的幂,因为我的一些算法是基于二进制的,我想避免任何巧合。无论如何,所有算法都适用于任何n

var clock1=function(f)
{
    var m,t;
    m=500000;
    t=Date.now();
    while(m--)
    {
        f("a", 2187);
    }
    t=Date.now()-t;
    return t;
};

var clock2=function(f)
{
    var m,t;
    m=500;
    t=Date.now();
    while(m--)
    {
        f("a", 1594323);
    }
    t=Date.now()-t;
    return t;
};

我在严格模式下运行普通版v8的本地计算机上运行此测试。以下是f的一些候选人:


线性方法

正如Alex已经建议的那样,你可以使用线性循环来做到这一点。只需定义一个数组并运行一个执行n次的循环,每次向我们的数组添加一个x

var f=function(x,n)
{
    var y;
    y=Array(n);
    while(n--)
    {
        y[n]=x;
    }
    return y;
};

我们可以使用计数变量n进行优化,以避免调用pushy.length,以及将数组预初始化为所需的长度。 (两人都建议亚历克斯。)我的向后while循环只是一种习惯boost performance slightly

此功能需要2200ms才能通过clock1,需要90658ms才能通过clock2

部分二进制方法

我们也可以尝试使用二进制连接来构造它。这个想法是你从一个单元素数组开始,然后,如果它的长度远远小于目标长度,你concat它自己,有效地加倍它。当它接近目标大小时,切换回一次添加一个元素,直到达到目标大小:

var f=function(x,n)
{
    var y,m;
    y=[x];
    m=1;
    while(m<n)
    {
        if(m*2<=n)
        {
            y=y.concat(y);
            m*=2;
        }
        else
        {
            y[m]=x;
            m++;
        }
    }
    return y;
};

在这里,m只是一个计数变量,用于跟踪y的大小。

此函数需要3630毫秒才能通过clock1,需要42591毫秒才能通过clock2,使其比小阵列的线性方法慢65% ,但速度提高112%大的。

完整二进制方法

然而,通过使用完整的二进制构造,我们可以进一步提高性能。部分二进制方法会受到影响,因为当它接近目标长度(平均约75%的路径)时,它被迫逐个元素地添加。我们可以解决这个问题:

首先,将目标大小转换为二进制并将其保存到数组中。现在,将y定义为单元素数组z为空数组。然后,循环(向后)通过二进制数组,为每个元素concat y自己。在每次迭代中,如果相应的二进制数字为1,则将y保存到z。最后,将concat的所有元素z放在一起。结果是你的完整数组。

因此,为了填充长度为700的数组,它首先将700转换为二进制:

[1,0,1,0,1,1,1,1,0,0]

向后循环,它会执行9 concat个和6个元素添加,生成z,如下所示:

[0,0,4,8,16,32,128,512]
// I've written the lengths of the sub-arrays rather than the arrays themselves.

concat z中的所有内容同时出现时,它会得到一个长度为700的数组,我们的结果。

var f=function(x,n)
{
    var y,z,c;
    c=0;
    y=[x];
    z=[];
    while(n>0)
    {
        if(n%2)
        {
            z[c++]=y;
            n--;
        }
        if(n===0)
        {
            break;
        }
        n/=2;
        y=y.concat(y);
    }
    return z.concat.apply([],z);
};

为了优化,我在这里将二进制转换步骤和循环压缩在一起。 z.concat.apply([],z)使用一点apply魔法将z(数组数组)展平为单个数组。出于某种原因,这比在运行中z结束更快。第二个if语句阻止它在计算完成后最后一次加倍y

此函数需要3157ms才能通过clock1和26809ms才能通过clock2,这使得它比小数组的部分二进制方法快15%,大数组快59%。它仍然比小阵列的线性方法慢44%。

二进制字符串方法

concat函数很奇怪。相对而言,要连接的数组越大,它变得越有效。换句话说,组合两个长度为100的数组明显快于使用concat组合四个长度为50的数组。因此,随着所涉及的数组变大,concat变得比push或直接赋值更有效。这是二进制方法比大数组的线性方法更快的主要原因之一。不幸的是,concat也会受到影响,因为它每次都会复制所涉及的数组。因为数组是对象,所以这非常昂贵。字符串比数组复杂,所以使用它们可以避免这种消耗?我们可以简单地使用字符串加法(类似于连接)来构造我们的数组,并使用split生成的字符串。

基于字符串的完整二进制方法如下所示:

var f=function(x,n)
{
    var y,z;
    y=""+x;
    z="";
    while(n>0)
    {
        if(n%2)
        {
            z+=y;
            n--;
        }
        if(n===0)
        {
            break;
        }
        y+=y;
        n/=2;
    }
    return z.split("");
};

此函数需要3484ms才能通过clock1和14534ms才能通过clock2,这使得它在计算小型数组时比基于数组的完整二进制方法慢10%,但对于大型数组则要快85%。


总的来说,它是一个混合包。线性方法在较小的阵列上获得了非常好的性能,并且非常简单。然而,二进制字符串方法在大型数组上的速度快了524%,实际上比二进制数组方法稍微复杂一些。

希望这有帮助!

答案 1 :(得分:0)

编辑:我不推荐此解决方案,但请检查此答案的评论,以获得最佳效果。

    var arrays = Object.keys(map).map(function(obj) {
      var i = 0, l = map[obj], s = "";
      for(;i<l;++i) {
        s+= obj +",";
      }
      return s.split(",");
    });

它实际上会返回三个带有值的数组,但您可以稍后使用以下内容展平它们:

map_array = map_array.concat.apply(map_array, arrays);

http://jsperf.com/map-vs-forin

答案 2 :(得分:0)

也许定义数组长度可能会更高效,至少你的垃圾收集器会更开心:

map_array = new Array(map.length);
var c = 0;
for (key in map) {
  var max = map[key];
  for (var i = 1; i <= max; i++) {
    map_array[c] = key;
    c++;
  }
}

比使用 map()

更有效

http://jsperf.com/map-vs-forin/3

答案 3 :(得分:0)

ECMA6中有一项名为.repeat()

的新功能

它将解决您的问题:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat

答案 4 :(得分:0)

您可以这样做:

const map = {"a" : 10, "b" : 20, "c": 7};
const keys = Object.keys(map);
let finalArr = [];

keys.forEach(key=>{
  finalArr = [...finalArr,...((key+" ").repeat(map[key]).trim().split(" "))];
})

console.log(finalArr);