文字列を繰り返す-JavaScript


271

任意の回数繰り返される文字列を返すための最良または最も簡潔な方法は何ですか?

以下は、これまでの私の最高のショットです。

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

5
10年以上前、この問題に対するよく知られた解決策がありました。この質問をする数か月前にJavaScript最適化の記事で例として使用しました。webreference.com / programming / javascript / jkm3 / 3 .htmlどうやら、ほとんどの人はそのコードを忘れてしまったようです。私が以下に挙げたような優れた解決策はありません。最高のアルゴリズムは、私のコードから持ち上げられたように見えます。私のコードがどのように機能するかについての誤解によるものを除いて、それは指数連結の1つの追加のステップを実行します。
ジョセフ・マイヤーズ

10
誰もジョセフの解決策を取り上げませんでした。 アルゴリズムは3700年前のものです。追加のステップのコストはごくわずかです。また、この記事には、JavaScriptの文字列連結に関するエラーと誤解が含まれています。Javascriptが内部で文字列を実際に処理する方法に興味がある人は、Ropeを参照してください。
artistoex

4
String protoype repeatが少なくともFirefoxで定義され実装されていることに誰も気付いていないようです。
ケネベック2014年

3
@kennebec:はい、これはEcmaScript 6の機能であり、この質問が出されたときにはありませんでした。今ではかなりサポートされています。
rvighne 2014年

3
@rvighne-kangax.github.io/compat- table/es6/#String.prototype.repeat を確認した ところ、FirefoxとChromeからのサポートのみを「かなりサポートされている」とは見なしませんでした
aaaaaa

回答:


406

新しい読者への注意:この答えは古く、あまり実用的ではありません。文字列の処理を行うために配列のものを使用するため、これは単に「賢い」ものです。「より少ないプロセス」を書いたとき、私は間違いなく「より少ないコード」を意味しました。これは、他の回答で指摘されているように、豚のように機能するためです。したがって、速度が重要な場合は使用しないでください。

この関数をStringオブジェクトに直接配置します。配列を作成してそれを埋め、空の文字で結合する代わりに、適切な長さの配列を作成し、目的の文字列と結合するだけです。同じ結果、少ないプロセス!

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

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

36
私はネイティブオブジェクトを拡張しないようにしていますが、それ以外の場合、これは美しい解決策です。ありがとう!
ブラッド

34
@ブラッド-なぜいけないのですか?かなり明確に定義されたホーム(Stringオブジェクト)を持つ関数でグローバル名前空間を汚染したいですか?
Peter Bailey、

16
実際には、両方の引数がグローバル名前空間にも適用されます。名前空間を拡張して競合が発生する可能性がある場合は、1)グローバルではなく、2)関連するもの、および3)リファクタリングが簡単な方法で実行します。つまり、グローバルではなく文字列プロトタイプに配置します。
Peter Bailey、

11
この関数に加える変更の1つは、「num」の周りにparseInt()を配置することです。これは、数値文字列がある場合、JSの型のジャグリングが原因で奇妙な動作が発生する可能性があるためです。例: "my string" .repeat( "6")== "61"
nickf

19
ネイティブオブジェクトを拡張したくない場合は、代わりに次のようにStringオブジェクトに関数を配置できますString.repeat = function(string, num){ return new Array(parseInt(num) + 1).join(string); };。このようにそれを呼び出すString.repeat('/\', 20)
Znarkus

204

提案されたすべてのアプローチのパフォーマンスをテストしました。

ここで最速の変種私が持っています。

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)アプローチます。

変更したのは2つだけです。

  1. に置き換えられpattern = thisましたpattern = this.valueOf()(明らかな型変換が1つクリアされます);
  2. その場合に不要なアクションを除外するために、prototypejsからのif (count < 1)チェックを関数の先頭に追加しました。
  3. デニスの 回答から最適化を適用(5-7%スピードアップ)

UPD

興味のある方のために、ここにパフォーマンステスト用の遊び場を作りました。

変数count〜0 .. 100:

Performance diagram

定数count= 1024:

Performance diagram

それを使用して、できればそれをさらに速くしてください:)


4
よくやった!count < 1本当に不必要な最適化だと思います。
JayVee 2013年

優れたアルゴリズムO(log N)。valueOf()による優れた最適化に感謝
vp_arth

2
画像リンクが切れています。
Benjamin Gruenbaum 2014年

リンクは問題ありません。一時的に利用できなくなる可能性があります
14年

テストJSFiddleが正しく機能しなくなりました。最初の関数を繰り返し実行し続けているようです(確実に30分実行したままにしておきます)
RevanProdigalKnight

47

この問題は、JavaScriptのよく知られた/「古典的な」最適化の問題であり、JavaScript文字列は「不変」であり、1文字でも文字列に連結して追加するには、メモリの割り当てやへのコピーなどの作成が必要です。 、まったく新しい文字列。

残念ながら、このページで受け入れられた答えは間違っています。「間違った」とは、単純な1文字の文字列のパフォーマンス係数が3倍、短い文字列の8倍から97倍が繰り返され、繰り返しの文が300倍であることを意味します。アルゴリズムの複雑さの比率の限界を n無限に行くように。また、このページにはほぼ正しい別の答えがあります(過去13年間にインターネット全体に流通している正しいソリューションの多くの世代とバリエーションの1つに基づいています)。ただし、この「ほぼ正しい」ソリューションは、正しいアルゴリズムの要点を逃しており、パフォーマンスが50%低下しています。

承認された回答、他のトップパフォーマンスの回答(この回答の元のアルゴリズムの劣化バージョンに基づく)、および13年前に作成された私のアルゴリズムを使用したこの回答のJSパフォーマンス結果

2000年10月〜この正確な問題のアルゴリズムを公開しました。このアルゴリズムは広く適応され、変更された後、最終的にはよく理解されずに忘れられました。この問題を解決するために、2008年8月に、http: //www.webreference.com/programming/javascript/jkm3/3.htmlアルゴリズムを説明し、それを単純な汎用JavaScript最適化の例として使用する記事を公開しました。今までに、Web Referenceは私の連絡先情報と、この記事から私の名前さえも削除しました。そしてもう一度、アルゴリズムは広く適応され、変更され、その後、よく理解されておらず、ほとんど忘れられています。

ジョセフ・マイヤーズによるオリジナルの文字列反復/乗算JavaScriptアルゴリズム、2000年頃、Text.js内のテキスト乗算関数として。2008年8月、この形式でWeb参照により公開された: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;
}

その記事の公開から2か月以内に、この同じ質問がStack Overflowに投稿され、今まで私の問題の元のアルゴリズムが忘れられていた私のレーダーの下で飛びました。このスタックオーバーフローページで利用できる最適なソリューションは、私のソリューションの修正バージョンであり、おそらく数世代離れている。残念ながら、変更によりソリューションの最適性が損なわれました。実際、ループの構造を元の構造から変更することにより、修正されたソリューションは、指数複製の完全に不要な追加のステップを実行します(したがって、適切な回答で使用される最大の文字列をそれ自体と余分な時間で結合してから破棄します)。

以下では、この問題に対するすべての回答に関連し、すべての利益のために、JavaScriptの最適化について説明します。

テクニック:オブジェクトまたはオブジェクトプロパティへの参照を回避する

この手法がどのように機能するかを説明するために、必要な長さの文字列を作成する実際のJavaScript関数を使用します。後で見るように、さらに最適化を追加できます!

ここで使用されるような機能は、テキストの列を揃えるためのパディングを作成すること、お金をフォーマットすること、またはブロックデータを境界まで埋めることです。テキスト生成関数では、テキストを操作する他の関数をテストするための可変長入力も可能です。この関数は、JavaScriptテキスト処理モジュールの重要なコンポーネントの1つです。

先に進むにつれて、元のコードを文字列を作成するための最適化されたアルゴリズムに開発しながら、さらに2つの最も重要な最適化手法について説明します。最終結果は、私がどこでも使用してきた、産業向けの強力な機能です。JavaScriptの注文フォーム、データのフォーマット、電子メール/テキストメッセージのフォーマット、その他の多くの用途で、アイテムの価格と合計を調整します。

文字列を作成するための元のコード stringFill1()

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

構文はここに明確です。ご覧のとおり、さらに最適化を行う前に、ローカル関数変数を既に使用しています。

オブジェクトプロパティへの無害な参照が1つあることに注意してください。 s.lengthパフォーマンスを低下させるコード内の。さらに悪いことに、このオブジェクトプロパティを使用すると、読者がJavaScript文字列オブジェクトのプロパティについて知っていると想定して、プログラムの単純さが低下します。

このオブジェクトプロパティを使用すると、コンピュータープログラムの一般性が失われます。プログラムは、それxが長さ1のストリングでなければならないことを前提としています。これにより、stringFill1()関数の適用が単一文字の繰り返し以外に制限されます。HTMLエンティティのように複数のバイトが含まれている場合、単一の文字でも使用できません&nbsp;

オブジェクトプロパティのこの不必要な使用によって引き起こされる最悪の問題は、空の入力文字列でテストすると、関数が無限ループを作成することxです。一般性をチェックするには、プログラムを可能な限り最小の入力に適用します。利用可能なメモリ量を超えるように要求されたときにクラッシュするプログラムには言い訳があります。何も生成しないように要求されたときにクラッシュするこのようなプログラムは許容できません。きれいなコードは有毒なコードである場合があります。

シンプルさはコンピュータープログラミングの曖昧な目標かもしれませんが、一般的にはそうではありません。プログラムに一般性の妥当なレベルがない場合、「プログラムはそれで十分です」と言っても無効です。ご覧のように、string.lengthプロパティを使用すると、このプログラムが一般的な設定で機能しなくなり、実際には、不適切なプログラムがブラウザまたはシステムのクラッシュを引き起こす可能性があります。

このJavaScriptのパフォーマンスを改善し、これら2つの深刻な問題に対処する方法はありますか?

もちろん。整数を使用してください。

文字列を作成するための最適化されたコード 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マイクロ秒(100万分の1秒)かかります。 stringFill2()かかり、同じことを行うのに27.68マイクロ秒かかります。これは、オブジェクトプロパティへの参照を回避することで、パフォーマンスがほぼ2倍になります。

テクニック:長い文字列に短い文字列を追加しない

前回の結果は良さそうでした-実際、とても良かったです。改善された機能stringFill2()最初の2つの最適化を使用しているため、ははるかに高速です。今よりも何倍も速くなるように改善できると私が言ったら、あなたはそれを信じますか?

はい、その目標を達成できます。ここで、短い文字列を長い文字列に追加しないようにする方法を説明する必要があります。

短期的な振る舞いは、元の関数と比較してかなり良いようです。コンピュータサイエンティストは、関数またはコンピュータプログラムアルゴリズムの「漸近的振る舞い」を分析することを好みます。つまり、より大きな入力でテストすることにより、その長期的な振る舞いを研究します。場合によっては、さらにテストを行わなければ、コンピュータプログラムを改善できる方法に気付くことはありません。何が起こるかを確認するために、200バイトの文字列を作成します。

現れる問題 stringFill2()

タイミング関数を使用すると、100バイトの文字列の27.68と比較して、200バイトの文字列の時間は62.54マイクロ秒に増加することがわかります。2倍の作業を行うには時間が2倍になるようですが、代わりに3倍または4倍になります。プログラミングの経験から、この結果は奇妙に思えます。なぜなら、作業がより効率的に行われているため(関数呼び出しごとに100バイトではなく、関数呼び出しごとに200バイト)、関数は少し高速になるはずです。この問題は、JavaScript文字列の陰湿なプロパティに関係しています。JavaScript文字列は「不変」です。

不変とは、一度作成された文字列を変更できないことを意味します。一度に1バイトずつ追加することで、もう1バイトの労力を使い果たすことはありません。実際には、文字列全体ともう1バイトを再作成しています。

実際、100バイトの文字列に1バイトを追加するには、101バイトの作業が必要です。Nバイト文字列を作成するための計算コストを簡単に分析してみましょう。最初のバイトを追加するコストは、1ユニットの計算量です。2番目のバイトを追加するコストは1ユニットではなく2ユニットです(最初のバイトを新しい文字列オブジェクトにコピーし、2番目のバイトを追加する)。3番目のバイトには、3ユニットなどのコストが必要です。

C(N) = 1 + 2 + 3 + ... + N = N(N+1)/2 = O(N^2)。シンボルO(N^2)は、Nの2乗のBig Oと発音されます。これは、長期的に計算コストが文字列の長さの2乗に比例することを意味します。100文字を作成するには10,000単位の作業が必要で、200文字を作成するには40,000単位の作業が必要です。

これが、100文字ではなく200文字を作成するのに2倍以上の時間がかかった理由です。実際には、4倍の時間がかかっているはずです。私たちのプログラミング経験は、長い文字列に対して作業がわずかに効率的に行われるという点で正しかったため、約3倍の時間しかかかりませんでした。関数呼び出しのオーバーヘッドが、作成する文字列の長さに関して無視できる程度になると、実際には、文字列を2倍作成するのに4倍の時間がかかります。

(歴史的メモ:html = 'abcd\n' + 'efgh\n' + ... + 'xyz.\n'JavaScriptソースコードコンパイラは文字列を結合してからJavaScript文字列オブジェクトにすることができるため、この分析は必ずしものようなソースコードの文字列に適用されるわけではありません。ほんの数年前のKJSの実装プラス記号で結合されたソースコードの長い文字列を読み込むと、JavaScriptがフリーズまたはクラッシュします。計算時間がO(N^2)かかっていたため、KJS JavaScriptエンジンコアを使用するKonqueror WebブラウザーまたはSafariをオーバーロードするWebページを作成することは難しくありませんでした。マークアップ言語とJavaScriptマークアップ言語パーサーを開発しているときにこの問題に遭遇し、JavaScriptインクルード用のスクリプトを記述したときに問題の原因を発見しました。)

明らかに、このパフォーマンスの急速な低下は大きな問題です。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)、であるため、文字列の長さを2倍にすると、作業量が4倍になるのではなく、作業量が約2倍になります。

この新しいアイデアを実装するコードは、計算コストの公式とほぼ同じくらい複雑です。それを読むときは>>= 1、1バイト右にシフトすることを意味します。したがって、n = 10011が2進数の場合、n >>= 1結果はになりますn = 1001

あなたが認識しない場合がありますコードの他の部分が書かれ、ビット単位のAND演算子です&。式n & 1は、の最後の2進数nが1の場合はtrueを評価し、の最後の2進数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マイクロ秒(1秒の百万分のを)かかりstringFill2()62.54を取り、そしてstringFill3()唯一の4.608をとります。このアルゴリズムの優れた点は何ですか?すべての関数はローカル関数変数の使用を利用しましたが、2番目と3番目の最適化手法を利用すると、のパフォーマンスが20倍向上しましたstringFill3()

より深い分析

この特定の機能が競争を水から吹き飛ばすのはなぜですか

すでに述べたように、これらの関数、stringFill1()およびのstringFill2()実行速度が非常に遅いのは、JavaScript文字列が不変だからです。JavaScriptによって保存された文字列データに一度に1バイトずつ追加できるようにメモリを再割り当てすることはできません。文字列の最後に1バイトが追加されるたびに、文字列全体が最初から最後まで再生成されます。

したがって、スクリプトのパフォーマンスを向上させるには、2つの文字列を事前に連結して、より長い文字列を事前に計算し、目的の文字列長を再帰的に構築する必要があります。

たとえば、16文字のバイト文字列を作成するには、最初に2バイトの文字列を事前に計算します。次に、2バイト文字列を再利用して4バイト文字列を事前計算します。次に、4バイト文字列を再利用して、8バイト文字列を事前計算します。最後に、2つの8バイト文字列を再利用して、16バイトの新しい文字列を作成します。合計4つの新しい文字列を作成する必要があり、1つは長さ2、1つは長さ4、1つは長さ8、もう1つは長さ16の合計コストは2 + 4 + 8 + 16 = 30です。

長期的には、この効率は逆の順序で追加し、最初の項a1 = Nで始まり、r = 1/2の共通比率を持つ幾何級数を使用して計算できます。幾何級数の合計はで与えられa_1 / (1-r) = 2Nます。

これは、長さ2の新しい文字列を作成するために1文字を追加するよりも効率的です。長さ3、4、5などの新しい文字列を作成して、16まで続けます。以前のアルゴリズムでは、一度に1バイトを追加するプロセスを使用していました、それの総コストはになりますn (n + 1) / 2 = 16 (17) / 2 = 8 (17) = 136

明らかに、136は30よりもはるかに大きい数であるため、以前のアルゴリズムでは文字列を構築するのにはるかに長い時間がかかります。

2つの方法を比較すると、再帰アルゴリズム(「分割統治」とも呼ばれます)が長さ123,457の文字列でどれだけ高速であるかがわかります。私のFreeBSDコンピューターでは、このアルゴリズムはstringFill3()関数に実装されており、0.001058秒で文字列を作成しますが、元のstringFill1()関数は0.0808秒で文字列を作成します。新しい機能は76倍高速です。

文字列の長さが長くなると、パフォーマンスの違いが大きくなります。より大きな文字列が作成されるにつれて、元の関数はC1(定数)回のN^2ように動作し、新しい関数はC2(定数)回のように動作しNます。

我々の実験から、我々はの値を決定することができますC1ようにC1 = 0.0808 / (123457)2 = .00000000000530126997、との値がC2あることをC2 = 0.001058 / 123457 = .00000000856978543136。新しい関数は10秒で、1,166,890,359文字を含む文字列を作成できます。これと同じ文字列を作成するには、古い関数では7,218,384秒の時間が必要です。

これは、10秒に比べてほぼ3か月です。

私が答えているのは(数年遅れ)だけです。この問題に対する私の元の解決策は10年以上インターネット上に浮かんでいて、それを覚えている少数の人々にはまだ十分に理解されていないためです。私はそれについてここに記事を書くことによって私が助けると思った:

高速JavaScriptのパフォーマンスの最適化/ページ3

残念ながら、ここで紹介する他のソリューションの一部は、適切なソリューションが10秒で作成するのと同じ量の出力を生成するのに3か月かかるソリューションの一部です。

Stack Overflowの標準的な回答として、ここにある記事の一部を時間をかけて再現したいと思います。

ここで最も優れたアルゴリズムは明らかに私のアルゴリズムに基づいており、おそらく他の誰かの第3または第4世代の適応から継承されたものであることに注意してください。残念ながら、変更によりパフォーマンスが低下しました。ここに示した私のソリューションのバリエーションは、おそらく私の混乱を理解していませんでしたfor (;;) Cで記述されたサーバーのメイン無限ループのように見え、ループ制御のために慎重に配置されたbreakステートメントを許可するように設計された式を性があります。文字列を指数関数的に1回余分に不必要に複製しないでください。


4
この回答は、多くの賛成票を受け取るべきではありませんでした。まず第一に、ジョセフによる所有権の主張はあざけりです。基本的なアルゴリズムは3700年前のものです。
artistoex

2
第二に、それは多くの誤った情報を含んでいます。最新のJavascript実装は、連結を実行するときに文字列の内容にさえ触れません(v8は、連結された文字列をConsString型のオブジェクトとして表します)。残りの拡張機能はすべて無視できます(漸近的な複雑さの観点から)。
artistoex

3
文字列がどのように連結されるかについてのあなたの考えは間違っています。2つの文字列を連結するために、JavaScriptは構成文字列のバイトをまったく読み取りません。代わりに、左と右の部分を参照するオブジェクトを作成するだけです。これが、ループの最後の連結が最初の連結よりもコストがかからない理由です。
artistoex

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 2013年

38

朗報!String.prototype.repeat、今はJavaScriptの一部

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

このメソッドは、Internet ExplorerとAndroid Webviewを除くすべての主要なブラウザーでサポートされています。最新のリストについては、MDN:String.prototype.repeat> Browser compatibilityを参照してください。

MDNにはサポートなしのブラウザー用のポリフィルがあります。


Mozillaのポリフィルはほとんどのニーズに対してかなり複雑であると思いますが(私は彼らが効率的なC実装の動作を模倣しようとしていると思います)、OPの簡潔性の要件には実際には答えません。ポリフィルとして設定された他のアプローチは、より簡潔になるはずです;-)
Guss

2
間違いなく!しかし、ビルトインの使用は最も簡潔なバージョンでなければなりません。ポリフィルは基本的に単なるバックポートであるため、仕様(またはこの場合は提案された仕様)との互換性を確保するために少し複雑になることがあります。完全を期すために追加しました。使用する方法を決定するのはOP次第だと思います。
アンドレ・ラズロ


17

P.ベイリーのソリューションの拡張:

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

編集:彼のエレガントなアイデアのためのジェロンへのクレジット++num


2
少し変更しました:String.prototype.repeat = function(n){return new Array(isNaN(n) ? 1 : ++n).join(this);}
jerone 10/07/13

とにかく、このテスト(jsperf.com/string-repeat/2)によると、文字列連結を使用して単純なforループを実行すると、Array.joinを使用するよりもChromeでの方がはるかに高速に見えます。面白くないですか!
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

Disfatedの回答が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;
};

そして、ここに展開されたバージョンのために分岐したdisfatedのフィドルがあります: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

このパフォーマンス向上についてはどうですか:var r=s; for (var a=1;...:))))とにかく、このテスト(jsperf.com/string-repeat/2)によると、配列を使用するよりも、Chromeの方が文字列連結を使用して単純なforループを実行する方が高速であるようです。参加する。
Marco Demaio、2011年

@VijayDev-このテストに従わない:jsperf.com/ultimate-concat-vs-join
jbyrd

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

すべてのブラウザ

これは、ほぼ同じくらい簡潔です。

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

両方のオプションのパフォーマンスを比較する場合は、このフィドルこのフィドルを参照してくださいベンチマークテストのために。私自身のテストでは、2番目のオプションは、Firefoxで約2倍、Chromeで約4倍高速でした。

最新のブラウザのみ:

最近のブラウザでは、これもできるようになりました:

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

このオプションは、他の両方のオプションよりも短いだけでなく、2番目のオプションよりさらに高速です。

残念ながら、これは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/en-US/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();

なぜなら、非再帰的メソッドは、コールスタックの制限に達することなく、任意の大きな繰り返しを処理しますが、はるかに遅いからです。

なぜ賢い半分ではないメソッドをさらに追加するのかすでに投稿したですか?

自分の娯楽のために、そして最も簡単な方法で指摘するために、猫に皮をむく方法はたくさんあることを知っています。状況によっては、明らかに最良の方法が理想的ではない可能性があります。

比較的高速で洗練された方法は、特定の状況下では効果的にクラッシュして燃焼する可能性がありますが、より低速で単純な方法は、最終的には仕事を完了させる可能性があります。

いくつかの方法は、エクスプロイトより少しであってもよく、になりやすいなどされている固定の存在のうち、他の方法は、すべての条件に美しく働くかもしれないが、そうように構成されている1は、単にそれがどのように動作するかわかりません。

「それで、もし私がそれがどのように機能するか知らない場合はどうなりますか?!」

マジ?

JavaScriptはその最大の長所の1つに悩まされています。それは悪い振る舞いに非常に耐性があり、とても柔軟なので、折り返して結果を返すことができます。

「大きな力には大きな責任が伴う ;-)

しかし、もっと真剣にそして重要なことですが、このような一般的な質問は巧妙な答えという形で素晴らしいことにつながりますが、他に何もなければ、知識と視野を広げ、結局手元のタスク-結果として得られる方法を使用する実用的なスクリプト- 提案されているよりも少し少ない、または少し賢いかもしれません。

これらの「完璧な」アルゴリズムはすべて楽しいものですが、「1つのサイズですべてに適合する」というのは、テーラーメイドより優れているとは限りません。

この説教は、睡眠不足と関心の高さのおかげであなたにもたらされました。進んでコードを書いてください!


1

まず、OPの質問は簡潔さに関するものであるように見えます-私が理解するのは「シンプルで読みやすい」という意味ですが、ほとんどの答えは効率に関するもののようです-これは明らかに同じことではありません。特定の大きなデータ操作アルゴリズム。基本的なデータ操作のJavaScript関数を実装する際に心配する必要はありません。簡潔さははるかに重要です。

アンドレ・ラズロが述べたように第二に、String.repeatはECMAScriptの6の一部といくつかの一般的な実装ではすでに利用可能である-ので、ほとんどの簡潔な実装では、String.repeatそれを実装することではありません;-)

最後に、ECMAScript 6実装を提供しないホストをサポートする必要がある場合、AndréLaszloによって言及されているMDNのポリフィルは簡潔ではありません。

だから、さらなる騒ぎなしに-ここに私の簡潔なポリフィルがあります:

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

はい、これは再帰です。私は再帰が好きです。再帰は単純で、正しく実行すると理解しやすいです。効率に関しては、言語がそれをサポートしている場合、正しく記述されていれば非常に効率的です。

私のテストでは、この方法はArray.joinアプローチよりも60%高速です。明らかに、これはどこにも近い実装ではありませんが、両方よりもはるかに単純です。

私のテストセットアップはノードv0.10で、「Strictモード」を使用して(ある種のTCOを有効にすると思います)、repeat(1000)10文字の文字列を100万回呼び出します。


1

これらすべてのプロトタイプ定義、配列の作成、および結合操作がやり過ぎだと思われる場合は、必要な場所で1行のコードを使用してください。N回繰り返す文字列S:

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

3
コードは読み取り可能でなければなりません。文字通りすべてを一度だけ使用する場合は、適切にフォーマットします(またはArray(N + 1).join(str)、パフォーマンスのボトルネックでない場合はメソッドを使用します)。2回使用する可能性が少しでもある場合は、適切な名前の関数に移動します。
cloudfeet 2013年

1

文字列の繰り返しなど、JavaScriptユーティリティの機能にはLodashを使用します。

Lodashは優れたパフォーマンスとECMAScript互換性を提供します。

UI開発には強くお勧めします。サーバー側でもうまく機能します。

Lodashを使用して文字列 "yo"を2回繰り返す方法は次のとおりです。

> _.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のやり方に感銘を受け、結果に不満を感じました。デニスも指摘したように、最後の文字列連結は不要であることに気づきました。

サンプリングされたdisfatedを組み合わせて遊んでいると、さらにいくつかのことに気づきました。

結果はかなりの量で変化し、多くの場合、最後の実行を支持し、同様のアルゴリズムは、しばしばポジションの騎手になります。私が変更したことの1つは、呼び出しのシードとして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();

次に、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;
}

これにより、デニスの修正よりも平均で1〜2%改善されました。ただし、実行やブラウザが異なれば、かなりの差異が生じるため、この追加のコードは、前の2つのアルゴリズムに比べて努力する価値がありません。

図表

編集:私はこれをほとんどクロムの下でやった。多くの場合、FirefoxとIEはDennisを数%支持します。


0

簡単な方法:

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

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半分の時間を冗長にしています。
ニコライ

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では、padStartまたはpadEndこのために使用することもできます。例えば。

var str = 'cat';
var num = 23;
var size = str.length * num;
"".padStart(size, str) // outputs: 'catcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcat'
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.