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

假设我有这样的地图:

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)
    }
}

但是,这显然需要时间来处理大量数据,我们可以采取措施来提高以上功能的效率吗?


在我看来,真正的问题在于构建重复的"a""b""c"的子阵列。 一旦你有他们,你可以concat他们作最后的阵列。 所以,我们真正想要的是一个函数f(x, n) ,它创建一个填充n x的数组。

所以,作为一个标准测试平台,我要定义一对clock函数。 第一种测量是使用一些数组填充函数创建500000个数组,每个数组包含2187个"a" 。 第二种测量是使用某个数组填充函数创建500个数组的时间,每个数组包含1594323个"a" 。 我选择了三个幂,因为我的一些算法是基于二进制的,我想避免任何巧合。 无论如何,所有的算法都适用于任何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 ,以及将数组预先初始化为所需的长度。 (两者都是由Alex建议的)我的反向while循环只是一个可能会提升性能的旧习惯。

该函数需要2200ms传递clock1传递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的大小。

这个函数需要3630ms传递clock1传递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%,实际上比二进制数组方法稍微复杂一点。

希望这可以帮助!


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

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


编辑:我不推荐这个解决方案,但检查这个答案的意见,以获得最高性能的答案。

    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

链接地址: http://www.djcxy.com/p/78669.html

上一篇: Javascript add same element N times in an Array

下一篇: qualifiers member function is ambiguous