重复字符串- Javascript

返回任意次数的字符串的最佳或最简洁的方法是什么?

以下是我目前为止拍得最好的照片:

function repeat(s, n){
var a = [];
while(a.length < n){
a.push(s);
}
return a.join('');
}
154124 次浏览
function repeat(s, n) { var r=""; for (var a=0;a<n;a++) r+=s; return r;}
这个答案是旧的,而且不是很实用-它只是“聪明”,因为它使用数组的东西来获取 串事情做完了。当我写“更少的过程”时,我肯定是指 “更少的代码”,因为,正如其他人在随后的回答中指出的那样,它 表演像猪一样。所以如果速度对你来说很重要,就不要用它

我将把这个函数直接放到String对象上。与其创建一个数组,填充它,然后用一个空字符连接它,不如创建一个适当长度的数组,然后用你想要的字符串连接它。同样的结果,更少的过程!

String.prototype.repeat = function( num )
{
return new Array( num + 1 ).join( this );
}


alert( "string to repeat\n".repeat( 4 ) );
/**
@desc: repeat string
@param: n - times
@param: d - delimiter
*/


String.prototype.repeat = function (n, d) {
return --n ? this + (d || '') + this.repeat(n, d) : '' + this
};

这是如何重复字符串多次使用delimeter。

扩大P.Bailey的解决方案:

String.prototype.repeat = function(num) {
return new Array(isNaN(num)? 1 : ++num).join(this);
}

这样你就可以避免意外的参数类型:

var foo = 'bar';
alert(foo.repeat(3));              // Will work, "barbarbar"
alert(foo.repeat('3'));            // Same as above
alert(foo.repeat(true));           // Same as foo.repeat(1)


alert(foo.repeat(0));              // This and all the following return an empty
alert(foo.repeat(false));          // string while not causing an exception
alert(foo.repeat(null));
alert(foo.repeat(undefined));
alert(foo.repeat({}));             // Object
alert(foo.repeat(function () {})); // Function

编辑:感谢jerone为他优雅的++num想法!

这个很有效

String.prototype.repeat = function(times){
var result="";
var pattern=this;
while (times > 0) {
if (times&1)
result+=pattern;
times>>=1;
pattern+=pattern;
}
return result;
};

我已经测试了所有提议的方法的性能。

这是最快的变体我已经得到。

String.prototype.repeat = function(count) {
if (count < 1) return '';
var result = '', pattern = this.valueOf();
while (count > 1) {
if (count & 1) result += pattern;
count >>= 1, pattern += pattern;
}
return result + pattern;
};

或作为独立的函数:

function repeat(pattern, count) {
if (count < 1) return '';
var result = '';
while (count > 1) {
if (count & 1) result += pattern;
count >>= 1, pattern += pattern;
}
return result + pattern;
}
它基于wnrph的算法。 它真的很快。与传统的new Array(count + 1).join(string)方法相比,count越大,它的执行速度就越快

我只改变了两件事:

  1. pattern = this.valueOf()替换pattern = this(清除一个明显的类型转换);
  2. if (count < 1)检查从prototypejs添加到函数的顶部,以排除在这种情况下不必要的操作。
  3. 丹尼斯 回答应用优化(速度提高5-7%)

乌利希期刊指南

为感兴趣的人创建了一个小的性能测试游乐场在这里

变量count ~ 0 ..100:

性能图

常量count = 1024:

性能图

如果可以的话,使用它,让它更快:)

这可能是最小的递归:-

String.prototype.repeat = function(n,s) {
s = s || ""
if(n>0) {
s += this
s = this.repeat(--n,s)
}
return s}

使用分治法的递归解:

function repeat(n, s) {
if (n==0) return '';
if (n==1 || isNaN(n)) return s;
with(Math) { return repeat(floor(n/2), s)+repeat(ceil(n/2), s); }
}

各种方法的测试:

var repeatMethods = {
control: function (n,s) {
/* all of these lines are common to all methods */
if (n==0) return '';
if (n==1 || isNaN(n)) return s;
return '';
},
divideAndConquer:   function (n, s) {
if (n==0) return '';
if (n==1 || isNaN(n)) return s;
with(Math) { return arguments.callee(floor(n/2), s)+arguments.callee(ceil(n/2), s); }
},
linearRecurse: function (n,s) {
if (n==0) return '';
if (n==1 || isNaN(n)) return s;
return s+arguments.callee(--n, s);
},
newArray: function (n, s) {
if (n==0) return '';
if (n==1 || isNaN(n)) return s;
return (new Array(isNaN(n) ? 1 : ++n)).join(s);
},
fillAndJoin: function (n, s) {
if (n==0) return '';
if (n==1 || isNaN(n)) return s;
var ret = [];
for (var i=0; i<n; i++)
ret.push(s);
return ret.join('');
},
concat: function (n,s) {
if (n==0) return '';
if (n==1 || isNaN(n)) return s;
var ret = '';
for (var i=0; i<n; i++)
ret+=s;
return ret;
},
artistoex: function (n,s) {
var result = '';
while (n>0) {
if (n&1) result+=s;
n>>=1, s+=s;
};
return result;
}
};
function testNum(len, dev) {
with(Math) { return round(len+1+dev*(random()-0.5)); }
}
function testString(len, dev) {
return (new Array(testNum(len, dev))).join(' ');
}
var testTime = 1000,
tests = {
biggie: { str: { len: 25, dev: 12 }, rep: {len: 200, dev: 50 } },
smalls: { str: { len: 5, dev: 5}, rep: { len: 5, dev: 5 } }
};
var testCount = 0;
var winnar = null;
var inflight = 0;
for (var methodName in repeatMethods) {
var method = repeatMethods[methodName];
for (var testName in tests) {
testCount++;
var test = tests[testName];
var testId = methodName+':'+testName;
var result = {
id: testId,
testParams: test
}
result.count=0;


(function (result) {
inflight++;
setTimeout(function () {
result.start = +new Date();
while ((new Date() - result.start) < testTime) {
method(testNum(test.rep.len, test.rep.dev), testString(test.str.len, test.str.dev));
result.count++;
}
result.end = +new Date();
result.rate = 1000*result.count/(result.end-result.start)
console.log(result);
if (winnar === null || winnar.rate < result.rate) winnar = result;
inflight--;
if (inflight==0) {
console.log('The winner: ');
console.log(winnar);
}
}, (100+testTime)*testCount);
}(result));
}
}

这是JSLint的安全版本

String.prototype.repeat = function (num) {
var a = [];
a.length = num << 0 + 1;
return a.join(this);
};

disfated的答案提高了5-7%。

通过停止在count > 1展开循环,并在循环之后执行额外的result += pattnern concat。这将避免循环最后未使用的pattern += pattern,而不必使用昂贵的if检查。 最终结果如下所示:

String.prototype.repeat = function(count) {
if (count < 1) return '';
var result = '', pattern = this.valueOf();
while (count > 1) {
if (count & 1) result += pattern;
count >>= 1, pattern += pattern;
}
result += pattern;
return result;
};

这里是disfate的小提琴分叉的展开版本:http://jsfiddle.net/wsdfg/

如果您认为所有这些原型定义、数组创建和连接操作都是多余的,那么只需在需要的地方使用一行代码。字符串S重复N次:

for (var i = 0, result = ''; i < N; i++) result += S;

我随机来到这里,从来没有理由在javascript中重复字符。

我对artistoex的做法印象深刻,对结果感到失望。我注意到最后一个串连接是不必要的,正如丹尼斯也指出的那样。

我注意到一些更多的东西,当玩抽样disfate放在一起。

结果变化很大,往往有利于最后一次运行,类似的算法经常会争夺位置。我改变的一件事是不再使用JSLitmus生成的计数作为调用的种子;由于不同方法生成的计数不同,我放入一个索引。这使得它更加可靠。然后,我研究了如何确保将不同大小的字符串传递给函数。这阻止了我看到的一些变化,其中一些算法在单个字符或较小的字符串上表现得更好。然而,不管字符串大小如何,前3个方法都做得很好。

分叉测试集

http://jsfiddle.net/schmide/fCqp3/134/

// repeated string
var string = '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789';
// count paremeter is changed on every test iteration, limit it's maximum value here
var maxCount = 200;


var n = 0;
$.each(tests, function (name) {
var fn = tests[name];
JSLitmus.test(++n + '. ' + name, function (count) {
var index = 0;
while (count--) {
fn.call(string.slice(0, index % string.length), index % maxCount);
index++;
}
});
if (fn.call('>', 10).length !== 10) $('body').prepend('<h1>Error in "' + name + '"</h1>');
});


JSLitmus.runAll();

然后我加入了丹尼斯的解决方案,并决定看看我是否能找到更多的方法。

由于javascript不能真正优化,提高性能的最好方法是手动避免一些东西。如果我把前4个琐碎的结果从循环中取出,我可以避免2-4个字符串存储,并将最后的存储直接写入结果。

// final: growing pattern + prototypejs check (count < 1)
'final avoid': function (count) {
if (!count) return '';
if (count == 1) return this.valueOf();
var pattern = this.valueOf();
if (count == 2) return pattern + pattern;
if (count == 3) return pattern + pattern + pattern;
var result;
if (count & 1) result = pattern;
else result = '';
count >>= 1;
do {
pattern += pattern;
if (count & 1) result += pattern;
count >>= 1;
} while (count > 1);
return result + pattern + pattern;
}

这比丹尼斯的修复方案平均提高了1-2%。然而,不同的运行和不同的浏览器会显示相当大的差异,这额外的代码可能不值得在前面的两种算法上付出努力。

<一个href = " http://chart.apis.google.com/chart?chtt=String.repeat%28%29%20mesure%20-%20jsFiddle%20demo%20by%20schmide |运维/秒28 chrome % 2025.0.1364.172 % 20 % 20% % 20 windows % 20元% 29,十= 000000,十,十= bhg&冠心病= t: 43623, 125090, 796212, 1650378, 3130496, 3242799, 3402753,冠心病= 0,3402753,chxt = x& chxl = 0% 3 | 0 | 3.4 m& chsp = 0, 1, chm = t1. % 20新% 20 array.join 28 prototypejs % % 28% 29% 20% 29% 2843.6 k % 29, 000000, 0, 0, 10 | t2. % 20 array.push % 28% 29.加入% 28% 29% 28125.1 k % 29日,000000年,0,1,10 | t3. % 20% 28结果% 20% 2 b % 3 d % 20 string%29%20x%20count%28796.2 . 2k %29,000000,0,2,10|t4.%20result%20%2b%3D%20growing%20pattern%281.7M%29,000000,0,3,10 b| t5.%20final%283.1M%29,000000,0,4,10|t6.%20final%20unrolled%283.2M%29,000000,0,5,10|t7.%20final%20avoid%283.4M%29,000000,0,6,10 |t7. chbh=15,0,5&chs=250x210" rel="nofollow">一个图表

编辑:我这样做主要是在chrome浏览器。Firefox和IE通常对Dennis的偏爱程度为百分之几。

简单的方法:

String.prototype.repeat = function(num) {
num = parseInt(num);
if (num < 0) return '';
return new Array(num + 1).join(this);
}

这个问题是JavaScript的一个众所周知的/“经典”优化问题,原因是JavaScript字符串是“不可变的”,即使是将单个字符连接到字符串也需要创建一个完整的新字符串,包括内存分配和复制。

不幸的是,本页上公认的答案是错误的,其中“错误”意味着简单的单字符字符串的性能因子为3x,重复多次的短字符串为8x-97x,重复句子为300x,当n趋于无穷大时,将算法复杂性比率的极限取为无限错误。此外,本页上还有另一个答案,它几乎是正确的(基于过去13年在互联网上流传的许多代和变化的正确解决方案之一)。然而,这个“几乎正确”的解决方案忽略了正确算法的一个关键点,导致性能下降了50%。

被接受的答案的JS性能结果,性能最好的其他答案(基于这个答案中原始算法的降级版本),这个答案使用我13年前创建的算法

到2000年10月,我针对这个问题发表了一个算法,它被广泛地改编、修改,然后最终被理解得很差并被遗忘了。为了解决这个问题,在2008年8月,我发表了一篇文章http://www.webreference.com/programming/javascript/jkm3/3.html解释了算法,并将其作为一个简单的通用JavaScript优化的例子。到目前为止,Web引用已经从这篇文章中删除了我的联系信息,甚至我的名字。再一次,这个算法被广泛地改编、修改,然后被理解得很差,基本上被遗忘了。

原始字符串重复/乘法JavaScript算法由 Joseph Myers,大约Y2K作为text .js中的文本相乘函数; 2008年8月由Web Reference发布: http://www.webreference.com/programming/javascript/jkm3/3.html ( 文章用这个函数作为JavaScript优化的例子, 这是唯一的奇怪的名字“stringFill3.”)

/*
* Usage: stringFill3("abc", 2) == "abcabc"
*/


function stringFill3(x, n) {
var s = '';
for (;;) {
if (n & 1) s += x;
n >>= 1;
if (n) x += x;
else break;
}
return s;
}

在这篇文章发表后的两个月内,同样的问题被发布到Stack Overflow上,直到现在我才发现,显然这个问题的原始算法再次被遗忘了。Stack Overflow页面上可用的最佳解决方案是我的解决方案的修改版本,可能隔了几代。不幸的是,修改破坏了解决方案的最优性。事实上,通过改变我原来的循环结构,修改后的解决方案执行了完全不需要的额外的指数复制步骤(从而将正确答案中使用的最大字符串与自己连接一个额外的时间,然后丢弃它)。

下面将讨论与此问题的所有答案相关的一些JavaScript优化,并使大家受益。

技巧:避免引用对象或对象属性

为了说明这种技术是如何工作的,我们使用一个实际的JavaScript函数来创建所需长度的字符串。正如我们将看到的,可以添加更多优化!

类似于这里使用的函数是创建填充来对齐文本列、格式化货币或填充块数据直到边界。文本生成函数还允许可变长度的输入,以测试对文本进行操作的任何其他函数。这个函数是JavaScript文本处理模块的重要组件之一。

接下来,我们将介绍另外两种最重要的优化技术,同时将原始代码开发为用于创建字符串的优化算法。最终的结果是一个工业强度、高性能的函数,我在任何地方都使用它——在JavaScript订单表单中对齐商品价格和总额、数据格式化和电子邮件/文本消息格式化以及许多其他用途。

创建字符串stringFill1()的原始代码

function stringFill1(x, n) {
var s = '';
while (s.length < n) s += x;
return s;
}
/* Example of output: stringFill1('x', 3) == 'xxx' */

这里的语法很清楚。正如您所看到的,在进行更多优化之前,我们已经使用了局部函数变量。

请注意,代码中有一个对对象属性s.length的无害引用会损害其性能。更糟糕的是,这个object属性的使用假设读者知道JavaScript字符串对象的属性,从而降低了程序的简单性。

使用这个object属性破坏了计算机程序的通用性。程序假设x必须是一个长度为1的字符串。这限制了stringFill1()函数的应用,除了单个字符的重复。即使单个字符也不能使用,如果它们包含多个字节,如HTML实体&nbsp;

这种不必要的使用object属性所导致的最严重的问题是,如果在空输入字符串x上进行测试,该函数将创建一个无限循环。要检查通用性,可以将程序应用于尽可能少的输入。当程序被要求超过可用内存量时崩溃是有理由的。像这样的程序在被要求不生成任何东西时崩溃是不可接受的。有时候漂亮的代码是有毒的代码。

简单性可能是计算机编程的一个模糊目标,但通常不是。当一个程序缺乏任何合理程度的通用性时,说“这个程序就目前而言已经足够好了”是不成立的。正如你所看到的,使用string.length属性会阻止该程序在常规设置下工作,事实上,不正确的程序会导致浏览器或系统崩溃。

有没有办法在解决这两个严重问题的同时提高JavaScript的性能呢?

当然可以。只使用整数。

用于创建字符串stringFill2()的优化代码

function stringFill2(x, n) {
var s = '';
while (n-- > 0) s += x;
return s;
}

比较stringFill1()stringFill2()的计时代码

function testFill(functionToBeTested, outputSize) {
var i = 0, t0 = new Date();
do {
functionToBeTested('x', outputSize);
t = new Date() - t0;
i++;
} while (t < 2000);
return t/i/1000;
}
seconds1 = testFill(stringFill1, 100);
seconds2 = testFill(stringFill2, 100);

stringFill2()的成功到目前为止

stringFill1()需要47.297微秒(百万分之一秒)来填充一个100字节的字符串,而stringFill2()需要27.68微秒来完成同样的事情。通过避免对对象属性的引用,性能几乎提高了一倍。

技巧:避免在长字符串中添加短字符串

我们之前的结果看起来不错——实际上非常不错。由于使用了前两个优化,改进后的函数stringFill2()要快得多。如果我告诉你它可以比现在快很多倍,你会相信吗?

是的,我们可以实现这个目标。现在我们需要解释如何避免将短字符串附加到长字符串中。

与我们原来的函数相比,短期行为看起来很好。计算机科学家喜欢分析一个函数或计算机程序算法的“渐近行为”,这意味着通过用更大的输入测试它来研究它的长期行为。有时不做进一步的测试,人们永远不会意识到计算机程序可以改进的方法。为了看看会发生什么,我们将创建一个200字节的字符串。

stringFill2()出现的问题

使用计时函数,我们发现200字节字符串的时间增加到62.54微秒,而100字节字符串的时间为27.68微秒。看起来做两倍的工作时间应该是两倍,但实际上是三倍或四倍。从编程经验来看,这个结果似乎很奇怪,因为如果有的话,函数应该稍微快一点,因为工作更有效地完成了(每个函数调用200字节,而不是每个函数调用100字节)。这个问题与JavaScript字符串的一个潜在属性有关:JavaScript字符串是“不可变的”。

不可变意味着一旦创建了字符串就不能更改。通过一次增加一个字节,我们不会多消耗一个字节的精力。我们实际上是在重新创建整个字符串,再加上一个字节。

实际上,要向100字节的字符串中再添加一个字节,就需要101个字节的工作。让我们简单分析一下创建N字节字符串的计算成本。添加第一个字节的代价是1单位的计算工作量。添加第二个字节的代价不是一个单位,而是两个单位(将第一个字节复制到新的字符串对象以及添加第二个字节)。第三个字节需要3个单位的成本,等等。

C(N) = 1 + 2 + 3 + ... + N = N(N+1)/2 = O(N^2)。符号O(N^2)发音为大O (N的平方),这意味着长期运行的计算成本与字符串长度的平方成正比。创造100个字符需要1万个单位的工作,创造200个字符需要4万个单位的工作。

这就是为什么创建200个字符所花费的时间是创建100个字符的两倍多。事实上,它应该花四倍的时间。我们的编程经验是正确的,因为对于更长的字符串,工作完成得稍微更有效率,因此只需要大约三倍的时间。一旦函数调用的开销对于我们所创建的字符串的长度变得可以忽略不计,那么创建一个两倍长的字符串实际上需要四倍的时间。

(历史注:此分析不一定适用于源代码中的字符串,例如html = 'abcd\n' + 'efgh\n' + ... + 'xyz.\n',因为JavaScript源代码编译器可以在将字符串连接在一起之前将它们变成JavaScript字符串对象。就在几年前,JavaScript的KJS实现在加载由加号连接的长源代码字符串时会冻结或崩溃。由于计算时间是O(N^2),所以在使用KJS JavaScript引擎核心的Konqueror Web浏览器或Safari上重载Web页面并不困难。我第一次遇到这个问题是在我开发标记语言和JavaScript标记语言解析器时,然后我在为JavaScript Includes编写脚本时发现了导致这个问题的原因。)

显然,这种性能的快速下降是一个巨大的问题。既然我们不能改变JavaScript将字符串作为不可变对象处理的方式,我们该如何处理它呢?解决方案是使用一种算法,尽可能少地重新创建字符串。

澄清一下,我们的目标是避免将短字符串添加到长字符串中,因为为了添加短字符串,整个长字符串也必须被复制。

算法如何避免将短字符串添加到长字符串

这里有一个减少创建新字符串对象次数的好方法。将较长的字符串连接在一起,以便每次向输出中添加一个以上的字节。

例如,要创建长度为N = 9的字符串:

x = 'x';
s = '';
s += x; /* Now s = 'x' */
x += x; /* Now x = 'xx' */
x += x; /* Now x = 'xxxx' */
x += x; /* Now x = 'xxxxxxxx' */
s += x; /* Now s = 'xxxxxxxxx' as desired */

这样做需要创建一个长度为1的字符串,创建一个长度为2的字符串,创建一个长度为4的字符串,创建一个长度为8的字符串,最后,创建一个长度为9的字符串。我们节省了多少成本?

旧成本C(9) = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 9 = 45

新成本C(9) = 1 + 2 + 4 + 8 + 9 = 24

注意,我们必须将一个长度为1的字符串添加到一个长度为0的字符串中,然后将一个长度为1的字符串添加到一个长度为1的字符串中,然后将一个长度为2的字符串添加到一个长度为2的字符串中,然后将一个长度为4的字符串添加到一个长度为4的字符串中,然后将一个长度为8的字符串添加到一个长度为1的字符串中,以便获得一个长度为9的字符串。我们所做的事情可以概括为避免将短字符串添加到长字符串中,换句话说,尝试将长度相等或几乎相等的字符串连接在一起。

对于旧的计算代价,我们找到了一个公式N(N+1)/2。新的成本有公式吗?是的,但是很复杂。重要的是它是O(N),因此将字符串长度翻倍将使工作量大约翻倍,而不是翻四倍。

实现这个新想法的代码几乎和计算代价的公式一样复杂。当你读取它时,记住>>= 1表示右移1个字节。因此,如果n = 10011是二进制数,则n >>= 1的结果值为n = 1001

你可能不认识的代码的另一部分是按位和操作符,写为&。表达式n & 1如果n的最后一个二进制数字为1,则计算为真;如果n的最后一个二进制数字为0,则计算为假。

新的高效stringFill3()函数

function stringFill3(x, n) {
var s = '';
for (;;) {
if (n & 1) s += x;
n >>= 1;
if (n) x += x;
else break;
}
return s;
}

在外行人看来,它很丑,但它的表演却非常可爱。

让我们看看这个函数的性能如何。在看到结果后,你可能永远不会忘记O(N^2)算法和O(N)算法之间的区别。

stringFill1()需要88.7微秒(百万分之一秒)来创建一个200字节的字符串,stringFill2()需要62.54,而stringFill3()只需要4.608。是什么让这个算法这么好?所有的函数都利用了使用局部函数变量的优势,但是利用第二和第三个优化技术使stringFill3()的性能提高了20倍。

深入的分析

是什么让这个特殊的功能在竞争中脱颖而出呢?

如上所述,stringFill1()stringFill2()这两个函数运行如此缓慢的原因是JavaScript字符串是不可变的。不能重新分配内存,以允许每次多一个字节追加到JavaScript存储的字符串数据中。每当一个字节被添加到字符串的末尾,整个字符串就从头到尾重新生成。

因此,为了提高脚本的性能,必须预先通过将两个字符串连接在一起来计算更长的字符串长度,然后递归地构建所需的字符串长度。

例如,要创建一个16个字母的字节字符串,首先要预先计算两个字节的字符串。然后,两个字节的字符串将被重用,以预计算一个四字节的字符串。然后,4字节的字符串将被重用以预计算8字节的字符串。最后,两个8字节的字符串将被重用,以创建所需的16字节的新字符串。总共要创建四个新的字符串,一个长度为2,一个长度为4,一个长度为8,一个长度为16。总成本是2 + 4 + 8 + 16 = 30。

从长远来看,这种效率可以通过反向加法和使用一个几何级数来计算,该级数的第一项为a1 = N,公比为r = 1/2。几何级数的和由a_1 / (1-r) = 2N给出。

这比添加一个字符创建一个长度为2的新字符串,再创建一个长度为3、4、5的新字符串,直到16更有效。之前的算法使用了每次添加一个字节的过程,它的总代价将是n (n + 1) / 2 = 16 (17) / 2 = 8 (17) = 136

显然,136比30大得多,所以之前的算法要花很多很多的时间来构建一个字符串。

为了比较这两种方法,您可以看到递归算法(也称为“分治”)在长度为123,457的字符串上的速度有多快。在我的FreeBSD计算机上,这个算法在stringFill3()函数中实现,在0.001058秒内创建字符串,而原始的stringFill1()函数在0.0808秒内创建字符串。新功能要快76倍。

性能上的差异随着字符串长度的增大而增大。在创建越来越大的字符串时,原始函数的行为大致为C1(常量)乘以N^2,而新函数的行为为C2(常量)乘以N

从我们的实验中,我们可以确定C1的值为C1 = 0.0808 / (123457)2 = .00000000000530126997C2的值为C2 = 0.001058 / 123457 = .00000000856978543136。在10秒内,新函数可以创建一个包含1,166,890,359个字符的字符串。为了创建相同的字符串,旧函数将需要7,218,384秒的时间。

这几乎是3个月,而不是10秒钟!

我之所以回答这个问题(晚了几年),是因为我对这个问题的最初解决方案已经在互联网上流传了10多年,显然仍然没有被少数记得它的人理解。我想在这里写一篇关于它的文章会有所帮助:

性能优化的高速JavaScript / Page 3

不幸的是,这里介绍的其他一些解决方案仍然需要花费3个月才能产生与正确的解决方案在10秒内生成的相同数量的输出。

我想花点时间在这里重现这篇文章的一部分,作为关于Stack Overflow的规范答案。

请注意,这里表现最好的算法显然是基于我的算法,可能是从其他人的第三代或第四代适应中继承来的。不幸的是,这些修改降低了它的性能。我在这里提出的解决方案的变化可能不理解我令人困惑的for (;;)表达式,它看起来像一个用C编写的服务器的主无限循环,它只是为了允许一个仔细定位的break语句用于循环控制,这是避免以指数方式额外复制字符串的最紧凑的方法。

小提琴:http://jsfiddle.net/3Y9v2/

function repeat(s, n){
return ((new Array(n+1)).join(s));
}
alert(repeat('R', 10));

人们将这一问题过于复杂,甚至浪费了表现。数组?递归?你在跟我开玩笑吧。

function repeat (string, times) {
var result = ''
while (times-- > 0) result += string
return result
}

编辑。我运行了一些简单的测试来与artistoex / disfated和一堆其他人发布的按位版本进行比较。后者只是稍微快一点,但内存效率高了几个数量级。对于单词'blah'的1000000次重复,使用简单的串联算法(上面),Node进程的容量达到46兆字节,而使用对数算法只有5.5兆字节。后者绝对是正确的选择。为了清晰起见,我将其转发:

function repeat (string, times) {
var result = ''
while (times > 0) {
if (times & 1) result += string
times >>= 1
string += string
}
return result
}

简单的递归连接

我只是想试一试,然后做了这个:

function ditto( s, r, c ) {
return c-- ? ditto( s, r += s, c ) : r;
}


ditto( "foo", "", 128 );

我不能说我想得太多,它可能表明:-)

可以说更好

String.prototype.ditto = function( c ) {
return --c ? this + this.ditto( c ) : this;
};


"foo".ditto( 128 );

这很像已经发布的答案-我知道。

但为什么要递归呢?

还有一点默认行为呢?

String.prototype.ditto = function() {
var c = Number( arguments[ 0 ] ) || 2,
r = this.valueOf();
while ( --c ) {
r += this;
}
return r;
}


"foo".ditto();

因为,虽然非递归方法将处理任意大的重复而不达到调用堆栈限制,但它要慢得多。

为什么我要麻烦地添加更多的方法,而这些方法的聪明的还不及那些已经发布的方法的一半呢?

部分是为了我自己的娱乐,部分是为了以最简单的方式指出,我知道有很多方法来剥猫的皮,根据情况,显然最好的方法很可能不是理想的。

一个相对快速和复杂的方法可能会在某些情况下有效地崩溃和烧毁,而一个更慢,更简单的方法可能最终会完成工作。

有些方法可能只是利用漏洞,因此容易被固定排除在外,而其他方法可能在所有条件下都能很好地工作,但其构造方式使得一个根本不知道它是如何工作的。

“就算我不知道它是怎么工作的又怎样?”

严重吗?

JavaScript有一个最大的优势;它对不良行为具有高度的容忍度,而且非常灵活,它会竭尽全力地返回结果,而如果它崩溃了,可能对每个人都更好!

“权力越大,责任越大”;; -)

但更严肃和重要的是,尽管像这样的一般问题确实会以聪明的的形式得到令人敬畏的答案,如果没有其他的话,扩展一个人的知识和视野,最终,手头的任务-使用结果方法的实际脚本-可能需要比建议的更少或更多的聪明的

这些“perfect"算法很有趣,但“一刀切”;很少会比量身定做的更好。

这篇布道是由于你睡眠不足和一时兴趣所致。 开始编码吧!< / p >

好消息!String.prototype.repeat现在是JavaScript的一部分

"yo".repeat(2);
// returns: "yoyo"

除Internet Explorer外,所有主流浏览器都支持该方法。有关最新列表,请参见MDN: String.prototype.repeat >浏览器兼容性

MDN对不支持的浏览器有一个polyfill

首先,OP的问题似乎是关于简明性的——我理解的意思是“简单易读”,而大多数答案似乎是关于效率的——这显然不是一回事,而且我认为,除非你实现了一些非常具体的大型数据操作算法,否则当你实现基本的数据操作Javascript函数时,不应该担心。简洁更重要。

其次,正如André Laszlo所指出的,字符串。repeat是ECMAScript 6的一部分,并且已经在几个流行的实现中可用——所以String.repeat最简洁的实现是不实现它;-)

最后,如果您需要支持不提供ECMAScript 6实现的主机,André Laszlo提到的MDN的polyfill一点也不简洁。

所以,废话不多说——下面是我的简洁的 polyfill:

String.prototype.repeat = String.prototype.repeat || function(n){
return n<=1 ? this : this.concat(this.repeat(n-1));
}

是的,这是递归。我喜欢递归——它们很简单,如果操作正确,很容易理解。关于效率,如果语言支持它,如果写得正确,它们可以非常高效。

从我的测试来看,这种方法比Array.join方法快60%。虽然它显然与disfate的实现相去甚远,但它比两者都要简单得多。

我的测试设置是节点v0.10,使用“严格模式”(我认为它启用了某种TCO),对10个字符的字符串调用repeat(1000)一百万次。

String.prototype.repeat现在是ES6标准。

'abc'.repeat(3); //abcabcabc

使用Array(N+1).join("string_to_repeat")

使用Lodash来实现Javascript的实用功能,比如重复字符串。

Lodash提供了良好的性能和ECMAScript兼容性。

我强烈推荐它用于UI开发,它在服务器端也很好用。

下面是如何使用Lodash重复字符串“yo”2次:

> _.repeat('yo', 2)
"yoyo"

适用于所有浏览器

这是最简洁的:

function repeat(s, n) { return new Array(n+1).join(s); }

如果你也关心性能,这是一个更好的方法:

function repeat(s, n) { var a=[],i=0;for(;i<n;)a[i++]=s;return a.join(''); }

如果你想比较这两个选项的性能,请参阅< >强这个小提琴< / >强< >强这个小提琴< / >强进行基准测试。在我自己的测试中,第二个选项在Firefox中大约快2倍,在Chrome中大约快4倍!

仅适用于现代浏览器:

在现代浏览器中,你现在也可以这样做:

function repeat(s,n) { return s.repeat(n) };

这个选项不仅比其他两个选项都短,而且比第二个选项的< >强更快< / >强

不幸的是,它不能在任何版本的ie浏览器中运行。表中的数字指定了完全支持该方法的第一个浏览器版本:

enter image description here

function repeat(pattern, count) {
for (var result = '';;) {
if (count & 1) {
result += pattern;
}
if (count >>= 1) {
pattern += pattern;
} else {
return result;
}
}
}

你可以在JSFiddle处测试它。粗略地说,以hack Array.join和我的Array.join为基准的是10倍(Chrome)到100倍(Safari)到200倍(Firefox)(取决于浏览器)。

基于数字连接字符串。

function concatStr(str, num) {
var arr = [];


//Construct an array
for (var i = 0; i < num; i++)
arr[i] = str;


//Join all elements
str = arr.join('');


return str;
}


console.log(concatStr("abc", 3));

希望有帮助!

ES-Next方法中有很多方法

1. ES2015/ES6已经实现了这个repeat()方法!

/**
* str: String
* count: Number
*/
const str = `hello repeat!\n`, count = 3;


let resultString = str.repeat(count);


console.log(`resultString = \n${resultString}`);
/*
resultString =
hello repeat!
hello repeat!
hello repeat!
*/


({ toString: () => 'abc', repeat: String.prototype.repeat }).repeat(2);
// 'abcabc' (repeat() is a generic method)


// Examples


'abc'.repeat(0);    // ''
'abc'.repeat(1);    // 'abc'
'abc'.repeat(2);    // 'abcabc'
'abc'.repeat(3.5);  // 'abcabcabc' (count will be converted to integer)
// 'abc'.repeat(1/0);  // RangeError
// 'abc'.repeat(-1);   // RangeError

2. ES2017/ES8 new add String.prototype.padStart()

const str = 'abc ';
const times = 3;


const newStr = str.padStart(str.length * times, str.toUpperCase());


console.log(`newStr =`, newStr);
// "newStr =" "ABC ABC abc "

3.ES2017/ES8 new add String.prototype.padEnd()

const str = 'abc ';
const times = 3;


const newStr = str.padEnd(str.length * times, str.toUpperCase());


console.log(`newStr =`, newStr);
// "newStr =" "abc ABC ABC "

参考文献

http://www.ecma-international.org/ecma-262/6.0/#sec-string.prototype.repeat

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd

只是另一个重复函数:

function repeat(s, n) {
var str = '';
for (var i = 0; i < n; i++) {
str += s;
}
return str;
}

在ES8中,你也可以使用padStartpadEnd来实现。如。

var str = 'cat';
var num = 23;
var size = str.length * num;
"".padStart(size, str) // outputs: 'catcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcat'

要在指定次数内重复一个字符串,可以使用JavaScript中内置的repeat()方法。

下面是一个重复以下字符串4次的例子:

const name = "king";


const repeat = name.repeat(4);


console.log(repeat);

输出:

"kingkingkingking"

或者我们可以创建自己版本的repeat()函数,如下所示:

function repeat(str, n) {
if (!str || !n) {
return;
}


let final = "";
while (n) {
final += s;
n--;
}
return final;
}


console.log(repeat("king", 3))

(最初发布于https://reactgo.com/javascript-repeat-string/)