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

Javascript在数组中添加相同元素N次,javascript,arrays,performance,Javascript,Arrays,Performance,假设我有这样的地图: var map = {"a" : 100, "b" : 200, "c": 700}; [0,0,4,8,16,32,128,512] // I've written the lengths of the sub-arrays rather than the arrays themselves. var f=function(x,n) { var y,z; y=""+x; z=""; while(n>0) {

假设我有这样的地图:

var map = {"a" : 100, "b" : 200, "c": 700};
[0,0,4,8,16,32,128,512]
// I've written the lengths of the sub-arrays rather than the arrays themselves.
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("");
};
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);
我想要一个由“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)
    }
}
var-map_数组=[]
用于(输入地图)
{

对于(var i=1;i编辑:我不推荐此解决方案,但请查看此答案上的注释,以获得最有效的答案

    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 = 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\u数组=新数组(map.length);
var c=0;
用于(输入地图){
var max=映射[键];

对于(var i=1;i,在我看来,这里真正的问题是构造重复的
“a”
“b”
、和
“c”
)的子数组。一旦你有了它们,你就可以将它们压缩成最终的数组。 因此,我们真正想要的是一个函数
f(x,n)
,它创建了一个填充
n
x
的数组

因此,作为一个标准的测试平台,我将定义一对
时钟
函数。第一个测量数组填充函数创建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
进行优化,以避免调用
push
y.length
,并将数组预初始化为所需的长度。(这两个都是Alex建议的。)我的向后
,而
循环只是一个可能会导致错误的旧习惯

此功能需要2200ms才能通过
时钟1
,90658ms才能通过
时钟2

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

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;
};
它向后循环,执行9个
concat
和6个元素添加,生成一个
z
,如下所示:

var map = {"a" : 100, "b" : 200, "c": 700};
[0,0,4,8,16,32,128,512]
// I've written the lengths of the sub-arrays rather than the arrays themselves.
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("");
};
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);
当它将
concat
中的所有内容都放在一起时,它将得到一个长度为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
magic将数组的数组
z
展平为单个数组。由于某些原因,这比动态浓缩到
z
要快。第二个
if
语句防止它在计算完成后最后一次将
y
加倍

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

二进制字符串方法
concat
函数很奇怪。相对而言,要连接的数组越大,效率就越高。换句话说,使用
concat
组合两个长度为100的数组要比组合四个长度为50的数组快得多。因此,当涉及的数组变大时,
concat
比推送或直接赋值更有效。这是二进制方法比线性方法对大型数组更快的主要原因之一。不幸的是,
concat
也会受到影响,因为它每次都复制所涉及的数组。因为数组是对象,所以成本相当高。字符串不太复杂x比数组多,所以也许使用它们可以避免这种消耗?我们可以简单地使用字符串加法(类似于串联)来构造数组,然后使用
拆分
生成的字符串

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

var map = {"a" : 100, "b" : 200, "c": 700};
[0,0,4,8,16,32,128,512]
// I've written the lengths of the sub-arrays rather than the arrays themselves.
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("");
};
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);
此函数需要3484ms才能通过
clock1
和14534ms才能通过
clock2
,这使得它在计算小型阵列时比基于阵列的全二进制方法慢10%,而在计算大型阵列时则快85%


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


希望这有帮助!

ECMA6中有一个新功能,名为
.repeat()


它将神奇地解决您的问题:

您可以这样做:

var map = {"a" : 100, "b" : 200, "c": 700};
[0,0,4,8,16,32,128,512]
// I've written the lengths of the sub-arrays rather than the arrays themselves.
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("");
};
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);

那么你将如何处理一个节省下来的毫秒呢?处理实际数据大约需要2到3秒,我想我们可以减少这个时间:)看看其他的ways@Teemu(O)N^2=糟糕的性能。@Gaurav你确定这是JS在花时间,而不是任何渲染吗?尝试直接赋值,函数调用总是需要一些额外的时间。你的“forin”测试是有偏差的,因为你在循环的每一步都在map中获取密钥。通过这个简单的优化forin wins:评论不错。显然,@Alex的回答是curren这是最快的。很高兴我们讨论了:)。耶,很有趣!我看到了