重复字符串-Javascript


271

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

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

function repeat(s, n){
    var a = [];
    while(a.length < n){
        a.push(s);
    }
    return a.join('');
}

5
十多年前,我有一个著名的解决方案,在您问这个问题几个月前,我在一个JavaScript优化文章中将其用作示例:webreference.com/programming/javascript/jkm3/3 .html显然,大多数人都忘记了该代码,而且我认为以下任何解决方案都不如我的好。最好的算法看起来像是从我的代码中提取出来的;除了由于对我的代码的工作方式有误解之外,它还执行了指数级联的额外步骤,而在我的原始代码中则通过特殊循环消除了这一步骤。
Joseph Myers 2013年

10
没有人提出约瑟夫的解决方案。 该算法已有3700年的历史。额外步骤的成本可以忽略不计。并且本文包含有关Javascript中字符串连接的错误和误解。如果您对Javascript如何真正在内部处理字符串感兴趣,请参阅Rope
artistoex 2013年

4
似乎没有人注意到,至少在firefox中已定义并实现了String原型重复。
肯尼伯2014年

3
@kennebec:是的,这是EcmaScript 6功能,在提出此问题时并没有出现。现在已经得到了很好的支持。
rvighne 2014年

3
@rvighne-我刚刚检查了 kangax.github.io/compat-table/es6/#String.prototype.repeat 我不会认为仅来自firefox和chrome的支持是“得到很好的支持”
aaaaaa 2015年

Answers:


405

给新读者的注意:这个答案是旧的,并且不是很实用-它只是“聪明”,因为它使用Array的东西来完成String的工作。当我写“更少的过程”时,我绝对是指“更少的代码”,因为正如其他人在随后的答案中指出的那样,它的表现像猪一样。因此,如果速度对您很重要,请不要使用它。

我直接将此函数放到String对象上。无需创建数组,填充数组并使用空字符将其连接,只需创建适当长度的数组,然后将其与所需的字符串连接即可。结果相同,过程更少!

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

alert( "string to repeat\n".repeat( 4 ) );

36
我尝试不扩展本机对象,但是否则这是一个很好的解决方案。谢谢!
brad

34
@布拉德-为什么不呢?您宁愿使用具有定义明确的主目录(String对象)的函数来污染全局名称空间?
彼得·贝利

16
实际上,您的两个参数也都适用于全局名称空间。如果我要扩展一个命名空间并可能发生冲突,我宁愿这样做:1)不在全局中进行; 2)在相关的一个中进行; 3)易于重构。这意味着将其放在String原型上,而不是放在全局上。
彼得·贝利

11
我对该函数所做的一个更改是将parseInt()放在“ num”周围,因为如果您有数字字符串,由于JS的类型变乱,您可能会得到奇怪的行为。例如:“ my string” .repeat(“ 6”)==“ 61”
尼克

19
如果你不希望延长本机对象,你可以把函数的字符串对象,而不是像这样:String.repeat = function(string, num){ return new Array(parseInt(num) + 1).join(string); };。像这样称呼它:String.repeat('/\', 20)
Znarkus 2010年

203

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

这是我得到的最快的变体

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

它基于artistoex算法。真的很快。并且count,与传统new Array(count + 1).join(string)方法相比,它越大,运行速度越快。

我只更改了两件事:

  1. 替换pattern = thispattern = this.valueOf()(清除了一种明显的类型转换);
  2. 在函数顶部添加了if (count < 1)prototypejs的检查,以排除不必要的操作。
  3. 丹尼斯 答案的应用优化(加快了5-7%)

UPD

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

变量count〜0 .. 100:

性能图

常数count= 1024:

性能图

如果可以的话,请使用它并使其更快:)


4
干得好!我认为这种count < 1情况确实是不必要的优化。
JayVee 2013年

出色的算法O(log N)。感谢您对valueOf()的出色优化
vp_arth 2013年

2
图片链接无效。
本杰明·格伦鲍姆

链接很好。可能是暂时无法使用
2014年

测试JSFiddle无法正常工作;似乎只是一遍又一遍地运行第一个功能(确定要运行半个小时)
RevanProdigalKnight 2015年

47

此问题是JavaScript的众所周知的“经典”优化问题,原因是JavaScript字符串是“不可变的”,而且即使将单个字符串联在一起也需要创建字符串,包括创建内存以及将其复制到,一个新的字符串。

不幸的是,此页面上的可接受答案是错误的,其中“错误”的意思是简单的单字符字符串的性能系数是3倍,短字符串重复多次的性能系数是8x-97x,重复句子的性能系数是300x,当出现错误时则是无限错误将算法复杂度的比率限制n为无穷大。另外,此页面上还有另一个答案几乎是正确的(基于过去13年间在整个Internet上传播的正确解决方案的许多代和变体之一)。但是,这种“几乎正确”的解决方案错过了正确算法的关键点,从而导致性能下降50%。

JS性能结果:可接受的答案,效果最佳的其他答案(基于此答案中原始算法的降级版本),以及使用我13年前创建的算法得出的答案

〜2000年10月,我发布了针对该确切问题的算法,对该算法进行了广泛的修改,修改,然后最终使人们难以理解并忘记了它。为了解决此问题,2008年8月,我发表了一篇文章http://www.webreference.com/programming/javascript/jkm3/3.html,其中介绍了该算法,并将其用作简单的通用JavaScript优化示例。到目前为止,Web Reference已从本文中清除了我的联系信息,甚至我的名字。再一次,该算法已被广泛地修改,修改,然后被人们理解并被很大程度上遗忘。

Joseph Myers编写的原始字符串重复/乘法JavaScript算法,大约在2000年左右,作为Text.js中的文本乘法函数;通过Web参考以这种形式在2008年8月发布: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并在我的监视下飞到了现在,那时显然已经再次忘记了该问题的原始算法。此堆栈溢出页面上可用的最佳解决方案是我的解决方案的修改版,可能需要几代之隔。不幸的是,修改破坏了解决方案的最优性。实际上,通过更改原始结构的循环结构,修改后的解决方案执行了完全不需要的指数复制额外步骤(因此,将正确答案中使用的最大字符串自身连接了额外的时间,然后将其丢弃)。

下面讨论了一些与该问题的所有答案相关的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在代码中有一个单纯的引用对象属性会影响其性能。更糟糕的是,通过假定读者了解JavaScript字符串对象的属性,使用此对象属性会降低程序的简单性。

使用此对象属性会破坏计算机程序的通用性。该程序假定该x字符串必须为长度为一的字符串。这限制了该stringFill1()功能的应用范围,除了重复单个字符外。如果单个字符包含HTML实体之类的多个字节,则即使它们不能使用&nbsp;

由于不必要地使用对象属性而导致的最严重问题是,如果在空的输入字符串上进行测试,该函数将创建一个无限循环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()填充一个100字节的字符串需要47.297微秒(百万分之一秒),而stringFill2()完成相同操作需要27.68微秒。通过避免引用对象属性,性能几乎翻了一番。

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

我们以前的结果看起来不错-实际上很好。stringFill2()由于使用了我们的前两个优化,改进后的功能要快得多。如果我告诉您可以将其改进的速度比现在快很多倍,您会相信吗?

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

与我们的原始功能相比,短期行为似乎还不错。计算机科学家喜欢分析函数或计算机程序算法的“渐近行为”,这意味着通过用较大的输入进行测试来研究其长期行为。有时,如果不做进一步的测试,就永远不会意识到可以改进计算机程序的方式。为了了解会发生什么,我们将创建一个200字节的字符串。

出现的问题 stringFill2()

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

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

实际上,要在100字节的字符串中再增加一个字节,则需要101字节的工作量。让我们简要分析创建一个N字节字符串的计算成本。添加第一个字节的成本为1个计算单位。添加第二个字节的成本不是一个单位而是2个单位(将第一个字节复制到新的字符串对象以及添加第二个字节)。第三个字节需要3个单位的费用,以此类推。

C(N) = 1 + 2 + 3 + ... + N = N(N+1)/2 = O(N^2)。该符号O(N^2)的发音为N平方的大O,意味着从长远来看,计算量与字符串长度的平方成正比。创建100个字符需要10,000个工作单位,而创建200个字符需要40,000个工作单位。

这就是为什么创建200个字符比使用100个字符花费两倍多的时间的原因。实际上,它应该花费四倍的时间。我们的编程经验是正确的,因为对于较长的字符串,工作效率更高一些,因此只花费了大约三倍的时间。一旦函数调用的开销对于我们要创建的字符串的长度可以忽略不计,创建一个两倍的字符串实际上将花费四倍的时间。

(历史记录:此分析不一定适用于源代码中的字符串,例如html = 'abcd\n' + 'efgh\n' + ... + 'xyz.\n',因为JavaScript源代码编译器可以在将字符串组合成JavaScript字符串对象之前将它们连接在一起。就在几年前,KJS实现当加载带有加号的长字符串源代码时,JavaScript会死机或崩溃,因为计算时间很短,O(N^2)所以使Web页面超载Konqueror Web浏览器或使用KJS JavaScript引擎核心的Safari并不困难。我在开发标记语言和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

您可能无法识别的代码的另一部分是bitwise和operator &n & 1如果最后一个二进制数字n为1,则表达式的计算结果为true;如果最后一个二进制数字n为0 ,则表达式的计算结果为false 。

新的高效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()创建一个200字节的字符串需要88.7微秒(百万分之一秒),stringFill2()需要62.54,stringFill3()仅需要4.608。是什么使该算法如此出色?所有功能都利用了局部函数变量,但是利用第二和第三种优化技术使的性能提高了20倍stringFill3()

更深入的分析

是什么让这一特殊功能将竞争从水中吹了出来?

如前所述,这两个函数stringFill1()stringFill2()运行如此缓慢的原因是JavaScript字符串是不可变的。无法重新分配内存以允许一次将一个以上的字节附加到JavaScript存储的字符串数据中。每次在字符串末尾再增加一个字节,整个字符串将从头到尾重新生成。

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

例如,要创建一个16个字母的字符串,首先要预先计算一个2字节的字符串。然后,两个字节的字符串将被重新使用以预先计算一个四个字节的字符串。然后,该四字节字符串将被重用以预先计算一个八字节字符串。最后,两个八字节字符串将被重用以创建所需的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

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

与十秒钟相比,这几乎是三个月!

我之所以仅回答(迟了几年),是因为我对这个问题的最初解决方案已经在Internet上流传了10多年,而且显然仍然为数不多的记得它的人对此并不了解。我认为,通过在此处撰写有关该文章的内容,我将对您有所帮助:

高速JavaScript的性能优化/第3页

不幸的是,这里介绍的其他一些解决方案仍然是一些需要三个月才能产生与适当解决方案在10秒内产生的输出量相同的输出的解决方案。

我想花时间在此处重现本文的部分内容,作为有关Stack Overflow的规范答案。

请注意,此处表现最佳的算法显然是基于我的算法,并且可能是从其他人的第三代或第四代适应中继承而来的。不幸的是,这些修改导致其性能下降。这里提出的解决方案的变体可能无法理解我的令人困惑的for (;;)表达式,该表达式看起来像是用C语言编写的服务器的主无限循环,并且仅设计为允许精心定位的break语句进行循环控制,这是最紧凑的方法避免以指数方式将字符串多余一个多余的时间复制一次。


4
这个答案应该没有收到太多的赞誉。首先,约瑟夫的所有权主张是可笑的。底层算法已有3700年的历史了。
artistoex 2013年

2
其次,它包含许多错误信息。在执行串联时,现代Javascript实现甚至不涉及字符串的内容(v8将串联的字符串表示为ConsString类型的对象)。所有其余的增强都可以忽略不计(就渐进复杂性而言)。
artistoex 2013年

3
您关于如何串联字符串的想法是错误的。要连接两个字符串,Javascript根本不读取组成字符串的字节。相反,它仅创建一个引用左右部分的对象。这就是为什么循环中的最后一个串联比第一个串联更昂贵的原因。
artistoex13年

3
当然,这将导致索引字符串的开销大于O(1)的开销,因此串联可以稍后放平,这确实值得进一步评估。
artistoex

1
这是一本很棒的书。您应该写一本关于效率的书!

39

这是一个非常有效的

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

11
@Olegs,我认为投票的想法少于为一个人或一个人的创造力投票(的确值得称赞),但该想法是为最完整的解决方案投票,以便可以轻松地在投票站找到。列表中的第一名,而不必阅读搜索中所有最佳答案的答案。(因为不幸的是,我们所有人的时间都很有限...)
Sorin Postelnicu

38

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

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

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

MDN具有不支持浏览器的polyfill


感谢报告当前的状况,尽管我认为Mozilla polyfill对于大多数需求而言是非常复杂的(我认为他们试图模仿其有效的C实现的行为)-因此无法真正满足OP对简洁性的要求。设置为polyfill的其他任何方法都必须更加简洁;-)
Guss 2014年

2
绝对!但是使用内置功能必须是最简洁的版本。由于polyfill基本上只是反向端口,因此为了确保与规格(在这种情况下为建议的规格)兼容,它们有些复杂。我添加它是为了完整性,我想这取决于OP决定使用哪种方法。
安德烈·拉斯洛


17

扩展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主意!


2
改变了一点:String.prototype.repeat = function(n){return new Array(isNaN(n) ? 1 : ++n).join(this);}
jerone

无论如何,根据此测试(jsperf.com/string-repeat/2),在Chrome上执行简单的for循环并进行字符串连接似乎比使用Array.join更快。不好笑吗?
Marco Demaio 2011年


5
/**  
@desc: repeat string  
@param: n - times  
@param: d - delimiter  
*/

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

这是使用定界符多次重复字符串的方法。


4

这在被拒绝的答案上提高了5-7%。

通过在处停止count > 1并展开循环来result += pattnern展开循环。这样可以避免最终未使用的循环,pattern += pattern而不必使用昂贵的if-check。最终结果将如下所示:

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

以下是放宽版本的小提琴,用于展开版本:http : //jsfiddle.net/wsdfg/


2
function repeat(s, n) { var r=""; for (var a=0;a<n;a++) r+=s; return r;}

2
字符串连接不是很昂贵吗?至少在Java中是如此。
Vijay Dev

为什么是他们。但是,它实际上不能在javarscript中进行优化。:(
McTrafik 2011年

如何改善性能:var r=s; for (var a=1;...:))))无论如何,根据此测试(jsperf.com/string-repeat/2),使用字符串连接进行简单的for循环,就像您建议的那样,在Chrome上似乎比使用Array更快。加入。
Marco Demaio 2011年

@VijayDev-不符合此测试要求:jsperf.com/ultimate-concat-vs-join
jbyrd 2011年

2

各种方法的测试:

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

2

这是JSLint安全版本

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

2

对于所有浏览器

这简直就是简洁:

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(''); }

如果要比较两个选项的性能,请参阅此Fiddle此Fiddle进行基准测试。在我自己的测试中,第二个选项在Firefox中大约快2倍,在Chrome中大约快4倍!

仅对于现代浏览器:

在现代浏览器中,您现在还可以执行以下操作:

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

此选项不仅比其他两个选项都短,而且比第二个选项还要快

不幸的是,它不适用于任何版本的Internet Explorer。表格中的数字指定了完全支持该方法的第一个浏览器版本:

在此处输入图片说明



2

只是另一个重复功能:

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

2

ES2015已经实现了这种repeat()方法!

http://www.ecma-international.org/ecma-262/6.0/#sec-string.prototype.repeat
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/字符串/重复
http://www.w3schools.com/jsref/jsref_repeat.asp

/** 
 * 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


1

这可能是最小的递归方法:

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


1

简单递归串联

我只是想给它一个bash,然后这样做:

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具有其最大的优势之一。它对不良行为具有很高的容忍度,而且如此灵活,它会向后弯曲以返回结果,如果它对每个人来说都可能更好的话!

“强大的力量伴随着巨大的责任” ;-)

但更重要和更重要的是,尽管像这样的一般性问题的确会以一种聪明的答案的形式带来令人敬畏的答案:如果不做其他事情,就可以扩大自己的知识和视野,最后,手头的任务-使用所得方法的实用脚本-可能需要少一点,或者多一点聪明比建议。

这些“完美”的算法很有趣,而且万无一失,但是量身定做几乎不可能比量身定制的更好。

这篇讲道是由于缺乏睡眠和短暂的兴趣而带给您的。继续编码!


1

首先,OP的问题似乎与简洁有关(我理解为“简单易读”,而大多数答案似乎与效率有关),这显然不是一回事,而且我认为除非您实施一些非常特定的大数据操作算法,当您实现基本的数据操作Javascript函数时,请不要担心。简洁更重要。

其次,正如AndréLaszlo所指出的那样,String.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%。尽管显然没有实现它的方法,但是它比两者都简单得多。

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


1

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

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

3
代码应可读。如果您实际上只是打算一次使用它,那么请正确格式化它(Array(N + 1).join(str)如果不是性能瓶颈,则使用该方法)。如果您几乎没有机会使用它两次,请将其移到一个适当命名的函数中。
cloudfeet

1

将Lodash用于Javascript实用程序功能,例如重复字符串。

Lodash具有出色的性能和ECMAScript兼容性。

我强烈建议将其用于UI开发,并且在服务器端也很有效。

以下是使用Lodash将字符串“ yo”重复两次的方法:

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

0

使用分而治之的递归解决方案:

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

0

我随机来到这里,从来没有理由重复过JavaScript中的char。

artistoex的处理方式和结果给我留下了深刻的印象。我注意到最后一个字符串concat是不必要的,正如Dennis所指出的那样。

在将抽样抽样合并在一起时,我注意到了其他一些内容。

结果变化很大,通常有利于最后一次运行,类似的算法通常会争夺位置。我更改的一件事是不使用JSLitmus生成的count作为调用的种子;而是 由于各种方法产生的计数不同,因此我添加了一个索引。这使事情变得更加可靠。然后,我查看了如何确保将大小不同的字符串传递给函数。这避免了我看到的某些变化,其中某些算法在单个字符或较小的字符串上表现更好。但是,无论字符串大小如何,前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();

然后,我包括了丹尼斯(Dennis)的修复程序,并决定看看我是否可以找到一种方法来寻求更多信息。

由于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;
}

与Dennis的修复相比,平均提高了1-2%。但是,不同的运行方式和不同的浏览器将显示出足够的差异,以至于与前面的2种算法相比,这种额外的代码可能不值得花精力。

图表

编辑:我主要是在chrome下执行此操作。Firefox和IE经常会偏爱Dennis。



0

人们将其复杂化到可笑的程度或浪费性能。数组?递归?你在开玩笑吧。

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
}

您有string += string一半的时间是多余的。
nikolay

0

根据数字串联字符串。

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

希望有帮助!


0

借助ES8,您也可以为此使用padStartpadEnd。例如。

var str = 'cat';
var num = 23;
var size = str.length * num;
"".padStart(size, str) // outputs: 'catcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcat'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.