これは、ユーザーが入力した単語の可能なすべての組み合わせを返すJavaScript関数です。このコードを少しクリーンアップしたいと思っています…どんな提案でも大歓迎です!
function allAnagrams (word) { if (word.length < 2) { return [word]; } else { var allAnswers = []; for (var i = 0; i < word.length; i++) { var letter = word[i]; var shorterWord = word.substr(0, i) + word.substr(i + 1, word.length - 1); var shortwordArray = allAnagrams(shorterWord); for (var j = 0; j < shortwordArray.length; j++) { allAnswers.push(letter + shortwordArray[j]); } } return allAnswers; } } allAnagrams("")
コメント
回答
順列を生成するヒープの方法の反復バージョンを使用して帽子をリングに投げ込みます。 参照ソースのアルゴリズムにタイプミスがあるため、正しく理解するのに少し時間がかかりました-_-。
function swap(chars, i, j) { var tmp = chars[i]; chars[i] = chars[j]; chars[j] = tmp; } function getAnagrams(input) { var counter = [], anagrams = [], chars = input.split(""), length = chars.length, i; for (i = 0; i < length; i++) { counter[i] = 0; } anagrams.push(input); i = 0; while (i < length) { if (counter[i] < i) { swap(chars, i % 2 === 1 ? counter[i] : 0, i); counter[i]++; i = 0; anagrams.push(chars.join("")); } else { counter[i] = 0; i++; } } return anagrams; }
「codereview」のアナグラムをカウントするためのいくつかのサンプルタイミング:
$ time node anagrams-heap-iterative.js 3628800 real 0m0.855s user 0m0.000s sys 0m0.015s $ time node anagrams-megawac.js 3628800 real 0m2.286s user 0m0.000s sys 0m0.000s
コメント
- これははるかに勝者であり、 3,628,800件の結果 が 〜5,000msで!
- いいですね!配列サイズを事前に計算する必要があります(アナグラムとカウンターは大規模な配列であるため)。私はこれを打ち負かすために分散型アプローチをとらなければならないと思います:)
- ‘これがさまざまなサイズの入力でどのようにスケーリングするか興味深い jsperf.com/getanagramsc-r/2
- @megawacこのアプローチの長所を活用した後、あなたのアプローチを見てみたいと思います。
回答
より良いソリューションに取り組んでいるうちに、新しい JSイテレータ&ジェネレータについて読んでいます。今日はおそらく6時間かけてこのソリューションをいじっていましたが、 1分ごとの価値があります。フォーマットエラーが発生しましたが、パフォーマンスに完全に集中します
これを大きな単語に対して実行すると、マシンが炸裂します
わかりました。それで、少しもうまくいくかもしれません。はるかに、しかし真剣に、 これは高価な関数です。 私は数学の第一人者ではないので、 Math.SEでこの質問をしました。
可能な組み合わせの数を計算する式は非常に急です:
次のように考えてください:
最初の文字にはいくつの選択肢がありますか? 4:4_ _ __。
2番目は? 3:4⋅3__。
このように続けると、合計4⋅3⋅2⋅1= 4!= 24の選択肢があることがわかります。 –MrRicci
単語に対して関数を実行する「百科事典」をクライアント側で実行すると、確実にページがクラッシュします。それで、それを修正するために、私はいくつかの考えをしました。
私と一緒に-これはあなたが読むにつれてますます関連性が高くなります。
実際の単語を生成してみたかった(codezを楽しんで)
Anアナグラムは単語遊びの一種であり、単語またはフレーズの文字を再配置して、元のすべての文字を1回だけ使用して、新しい単語またはフレーズを生成した結果です-Wikipeda
アナグラムについて考えるとき、結果は本物の単語またはフレーズであると思います。さて、このコードレビューの目的はコードの機能を変更することではありませんが、私はそれを楽しみたいと思っています。そこで、結果から実際の単語アナグラムを除外して印刷できるソリューションを探すことにしました。
ついにのプレーンテキスト文字列を追跡しました。 em> 〜130,000英語の単語を単語リストとして使用するには、このリストをJSに変換する必要がありました。そこで、各スペースで文字列をスライスし、キー付きプロパティを持つオブジェクトの構造を出力する関数を作成しました。これにより、worldList["word"] -> returns true if word is valid
。
130,000語のリストをJSON文字列としてドキュメントの本文に出力することは、関数と同様に、非常にコストがかかります。
…そして関連性:
ここで、あなたの問題が私が自分で発明した問題と出会う場所です。 どのように処理しますかWebページをクラッシュさせずに非常に高価なループを作成しますか?
可能性を実験して、関数で次のことを実行できるようにする方法を実際に発見しました。時間の経過とともにワークロードのバランスをとることで、130,000語を処理し、任意のサイズの単語を維持する機能
ループが速すぎる場合があります
ループはできるだけ速く作られているため、ほとんどの人に最適です。 Webページでそれらを使用するタスクのしかし、処理しすぎると、ブラウザで処理できなくなります。
そのため、ページで処理できるようにするために、時間の経過とともに作業負荷を分散できるソリューションが必要でした。問題のない高価なループ。
私の解決策は、x
回呼び出し、 1〜2ms 反復間の遅延。これにより、ページにループを処理するのに十分な時間が与えられました遅延なしで、単語を処理しました毎回クラッシュする前にC “sまでめちゃくちゃ速い。これは、JSON単語リスト構造を生成するのに最適でした。
しかし、遅延ループは、すでに再帰的な関数に実装するのが非常に難しいものでした。
これは、最近JavaScriptで読んだソリューションを利用するのに最適な状況であることに気付きました…
JavaScriptイテレーターとジェネレーター
では、JavaScriptイテレーター&ジェネレーターが役に立ちます。
現在、このレビューはすでにモンスターになるリスクがあるため、ジェネレーターについて説明する代わりに、これがまもなく人気のある回答になります
を引用します。 a> “s one-liner:
TL; DR:[aを使用する]ジェネレーターの本質は、コード実行の一時停止を制御することです。
そして、ジェネレーターがイテレーターを実装する方法が改善されていることを簡単に説明します。
残りは、そこにある回答で自分で読むことができます。その答えがうまくいかない場合は、次のリソースを使用して理解を深めることができます。
これで、ジェネレーターはコールバック地獄を回避するタスクに大きな可能性を秘めていますが、これからここでは、&構造のフォーマットではなく、パフォーマンスのために使用します。これは、(パフォーマンスに偏った意見では)最もエレガントな理由です。
ジェネレーターがここで最良のソリューションであるかどうかはまだわかりませんが、ジェネレーターは私が持っている最良のアイデアであり、これは
ジェネレーターを使用したコード
作業中この解決策はまだです。正常に動作するはずです。まだ終了する時間がなく、改善されたコードを後でではなく早く提供したいと考えていました。このソリューションをすぐに投稿します。
更新:数日後で、ジェネレーターを使ってこの関数を正しく記述できるようになるまで、ジェネレーターの学習を終えていません。構文が少し良くなったら、間違いなくここに投稿します。
そして最後に、ジェネレーターは新しく、まだどこでもサポートされていないので..ジェネレーターのないコードの改良版
注:私のフォーマット方法は一般的に私自身の読みやすさですベストプラクティスではなく設定。このようにコードをフォーマットする義務を感じないでください。
/* Improved Real Time Anagram Engine */ // Without generators, it"s difficult to write this function // with the capability of processing a word of any length without // crashing the web browser, so this is written as the original, // with non-controlled execution speed. Long (9+ char) words may // crash browser. function generateAnagrams(word) { if (word.length < 2) { return [word]; } else { // By declaring all variables outside of the loop, // we improve efficiency, avoiding the needless // declarations each time. var anagrams = []; var before, focus, after; var shortWord, subAnagrams, newEntry; for (var i = 0; i < word.length; i++) { before = word.slice(0, i); focus = word[i]; after = word.slice(i + 1, word.length + 1); shortWord = before + after; subAnagrams = generateAnagrams(shortWord); for (var j = 0; j < subAnagrams.length; j++){ newEntry = focus + subAnagrams[j]; anagrams.push(newEntry); } } return anagrams; } } var result = generateAnagrams(word);
ループの外側で変数を宣言することにより、次のように大幅に節約できます。結果に表示されます。また、word.slice()
のパフォーマンスはword.substr()
よりもわずかに優れていると思います。これら2つの最適化の間で、このコード実行します元のコードよりも古い。
最後になりますが、パフォーマンスの結果は最低ではありません!
元のコード :結果が更新されました!ログなしでページがクラッシュすることはありません。
- “コード”、4文字: 24件の結果、〜1ms
- “coder”、5文字: 120件の結果、〜1ms
- “codere”、 6文字: 720件の結果、〜6ms
- “coderev”、7文字: 5040件の結果、〜11ms
- “coderevi”、8文字: 40,320件の結果、〜75ms
- “coderevie”、9文字: 362,880件の結果、ページ失敗
- “codereview”、10文字: 3,628,800件の結果、ページの失敗
コードの改善 :結果が更新されました-ログがないとページがクラッシュしません。
- “コード “、4文字: 24件の結果、〜0.5ms
- “コーダー “、5文字: 120件の結果、〜0.5ms
- “codere”、6文字: 720件の結果、〜1.5ms
- “coderev”、7文字: 5040の結果、〜8ms
- ” coderevi “、8文字: 40,320件の結果、〜53ms
- ” coderevie “、9文字: 362,880件の結果、〜556ms
- “codereview”、10文字: 3,628,800件の結果、〜12725ms
ジェネレーターを使用したリアルタイム
(コードが完了していません-ジェネレーターが少し難しいことを証明しています)
console.time()
ミリ秒単位で正確な測定を達成し、それぞれ3〜5回の試行を完了し、rに基づいて共同海損を推定します。結果。もちろん、これらの結果はマシンごとに異なります。 比較の時間は、速度ではなくベンチマークを読み取るための鍵です。
Tl; Dr Summary、約束どおり
ループは非常に強力ですプログラミングのツールですが、Webブラウザでは高価な反復のモンスター数をうまく処理できません。ただし、このような高価なループ関数は、ワークロードを時間の経過とともに分散すると完全に機能します。これを行う興味深い方法の1つは、JSジェネレータを使用することです。 、今日Webストアで入手できる新しいおもちゃですが、必ずしもローカルマーケットで入手できるわけではないため、JS 2.0がより広く実装されるまで注意して使用してください! JSジェネレーターのパワーを利用して、パフォーマンスの問題が発生することなく、長時間実行される高額なプロセスを管理できます。
コメント
- ジェネレーターを理解したことがありますか?
- @megawac何時間も費やしましたが、実際にその方法を理解するのに十分なガイド/情報/チュートリアルがありませんでした。I’費やしてきたこの投稿以降、毎日自由に勉強できる時間はありますが… ‘生のドキュメントからすぐに学ぶことはできません。ジェネレーターとイテレーターに関する優れたリソースを知っていますか?
- 優れたスタックオーバーフロースレッドのように聞こえます@ jt0dd
- ローカルペーパーのソルバーを作成しました’のごちゃ混ぜパズル。私は各単語の文字をソートするために前処理した600,000のスクラブル単語のリストを持っていました。次に、検索文字についても同じことを行い、単語を見つけようとしました。ミリ秒単位で10文字を指定すると、4〜10文字のすべての単語をスキャンできます。 ‘ throb ‘の文字を任意の順序で入力すると、’ bhort iv id =になります。 ‘ throb ‘および’に直接リンクする “60343a1230”> broth ‘、すべての組み合わせを試す必要はありません。たとえば、5文字で4文字の単語を検索する場合、文字を並べ替えるときの組み合わせは4つだけで、そうでない場合は120です。
- @ jt0dd I ‘ mスライスを使用する場合のword.length + 1の使用について。説明してもらえますか?
回答
@ jt0ddのコードを見て、どうすればよいか疑問に思いました。さらにいくつかのケースを処理できます。「キャッシュレイヤーを追加して、結果の配列の長さを事前に計算しましょう!
var generateAnagrams = (function() { // precomputed first 20 factorials var fact20 = [1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000, 20922789888000, 355687428096000, 6402373705728000, 121645100408832000, 2432902008176640000]; // Substrings seen already which we can cache. Try and avoid stack overflow as much as possible var seenStrings = {}; function generateAnagrams(word) { var length = word.length; if (length < 2) { seenStrings[word] = word; return [word]; } var anagrams = Array(fact20[length - 1]); // Stuff we"ll need along the way var before, focus, after; var shortWord, subAnagrams, newEntry; var idx = 0, j, jlen; for (var i = 0; i < length; i++) { before = word.slice(0, i); focus = word[i]; after = word.slice(i + 1, word.length + 1); shortWord = before + after; subAnagrams = seenStrings[shortWord] || generateAnagrams(shortWord); for (j = 0, jlen = subAnagrams.length; j < jlen; j++) { newEntry = focus + subAnagrams[j]; anagrams[idx++] = newEntry; } } seenStrings[word] = anagrams; return anagrams; } return function anagrams(word) { var computed = generateAnagrams(word); // clear cache (going to consume a ton of memory) seenStrings = {}; return computed; }; })();
Man that “醜い!その間ずっとが無駄ではなかったといいのですが…
事前に計算されたハッシュ
- “コード”、4 chars: 24件の結果、< = 1ms
- 「コーダー」、5文字: 120件の結果、< = 1ms
- “codere”、6文字: 720件の結果、< = 1ms
- “coderev”、7文字: 5040の結果、< = 3ms
- ” coderevi “、8文字: 40,320件の結果、< = 18ms
- “coderevie”、9文字: 362,880件の結果、135ms
- “codereview”、10文字: 3,628,800件の結果、2.1秒
うわー、私たちは挑戦に応えました。どれだけ先に進むことができるか見てみましょう。codereview
は、e
文字を複製するだけの難しい単語です。次のためにもっと簡単なことを試してみましょう。 "anagramarmy"
… … …
さて、ブラウザが噛むことができる以上のことをやめました4,000万の文字列順列が制限を超えているようです:(
スタックオーバーフローを回避するためにアルゴリズムを再設計したことに気付くでしょう。ルックアップは再帰の外部で実行する必要があります。
アルゴリズムを非同期にし、停止を回避するためにミリ秒ごとにすべてのsay 100,000
要素を実行することで、アルゴリズムがクラッシュしない可能性を高めることができます。
コメント
- すごい!これは、私が取り組んでいるジェネレータバージョン’よりもはるかに優れています。 +1 (この答えは受け入れられるべきです。’は、私のジェネレーターソリューションよりもはるかに効率的です。)
- 素晴らしい…ただ素晴らしい。私は本当にこのような。リアルタイムのハーモニージェネレーターソリューションが完成したら(または独自に実装することもできます)、自由にそれを使用して同じことを行ってください。 ‘遅くなりますが、結果はリアルタイムで返され、処理できる単語の大きさに終わりはありません。
- ああ、mjolkaは私たちを打ち負かしました両方とも far 。
- こんにちは。ドメイン内でhtmlとcssと一緒にこのコードを実行するにはどうすればよいですか?私は’この無料のものを使用しています: morsetovoynich.000webhostapp.com
回答
2つのスクリプトを作成しました。
- 最初のスクリプトは高速ですが、あまり保持できません。
- 2つ目は遅いですが、無制限の結果を表示できます。
どちらも、私が作成したプロトタイプ(fastPush、fastSplit、fastJoin)を使用します。これらは、標準の方法である分割と結合よりも高速です。 modはコンピューターで可能な最も遅い操作であることがわかっているので、ビット単位を使用して奇数か偶数かを確認します。
最初のスクリプトは、10文字の順列チャレンジで他のスクリプトよりも750ミリ秒以上(平均で)勝っています。 。
Array.prototype.swap = function(i, j) { var el1 = this[i], el2 = this[j]; this[i] = el2; this[j] = el1; return this; }; Array.prototype.fastPush = function(element) { this[this.length] = element; }; String.prototype.fastSplit = function() { var array = []; for(var i = 0; i < this.length; i++) array.fastPush(this[i]); return array; }; Array.prototype.fastJoin = function() { var string = ""; for(var i = 0; i < this.length; i++) string += this[i]; return string; }; String.prototype.anagrams = function() { var i = 1, N = this.length, p = [], anagrams = [], k, word = this.fastSplit(); anagrams.fastPush(this.toString()); for(var j = 0; j < N; j++) p.fastPush(0); while(i < N) { if(p[i] < i) { k = 0; k = (i & 1 != 0) & p[i]; anagrams.fastPush(word.swap(k, i).fastJoin()); p[i]++; i = 1; } else { p[i] = 0; i++ } } return anagrams; }; console.log("asdasdfghj".anagrams());
とにかく、この最初のプログラムには制限があり、10文字を超えて試行するとページが壊れます。
2番目のプログラム(以下の1つ)は、自己呼び出し関数を使用して4e4要素のコンソールチャンクにログインし、チャンクを出力するたびにanagrams配列をクリアし、coもクリアします。 nsole.log各5チャンク(これらの組み合わせを確認するのに十分)。関数を自己呼び出すために、プログラムはsetTimeoutの代わりにsetZeroTimeout( David Baronのブログ)を使用します。これは、setTimeoutがはるかに遅いためです。
(function() { var timeouts = []; var messageName = "zero-timeout-message"; // Like setTimeout, but only takes a function argument. There"s // no time argument (always zero) and no arguments (you have to // use a closure). function setZeroTimeout(fn) { timeouts.fastPush(fn); window.postMessage(messageName, "*"); } function handleMessage(event) { if (event.source == window && event.data == messageName) { event.stopPropagation(); if (timeouts.length > 0) { var fn = timeouts.shift(); fn(); } } } window.addEventListener("message", handleMessage, true); // Add the one thing we want added to the window object. window.setZeroTimeout = setZeroTimeout; })(); Array.prototype.swap = function(i, j) { var el1 = this[i], el2 = this[j]; this[i] = el2; this[j] = el1; return this; }; Array.prototype.fastPush = function(element) { this[this.length] = element; }; String.prototype.fastSplit = function() { var array = []; for(var i = 0; i < this.length; i++) array.fastPush(this[i]); return array; }; Array.prototype.fastJoin = function() { var string = ""; for(var i = 0; i < this.length; i++) string += this[i]; return string; }; var i = 1, p = [], N, k, chars, anagrams = [], r = 0; function anagramsGen(word) { N = word.length; chars = word.fastSplit(); anagrams.fastPush(word); for(var j = 0; j < N; j++) p.fastPush(0); noLimitWhile(); } function noLimitWhile() { if(i < N) { if(p[i] < i) { k = 0; k = (i & 1 != 0) & p[i]; anagrams.fastPush(chars.swap(k, i).fastJoin()); if(anagrams.length === 4e4) { if(r === 5) { console.clear(); r = 0; } r++; console.log(anagrams); anagrams = []; } p[i]++; i = 1; } else { p[i] = 0; i++ } setZeroTimeout(noLimitWhile); } else { console.log(anagrams); console.log("Finished"); } } anagramsGen("qwertyuiopas");
回答
すべての文字列のべき集合。
#include <string.h> #include <stdio.h> int main(int argc, char* argv[]) { if (argc < 2) return (1); char* arg = argv[1]; int n = 1 << strlen(arg); for (int i = 1; i < n; i++) { for (int j = 1, l = 0; j <= i; j <<= 1, l++) { if (i & j) printf("%c", arg[l]); } printf("\n"); } return (0); }
すべての文字列の印刷で実行:
$ time anagrams codereview > log real 0m0.002s user 0m0.000s sys 0m0.000s
コメント
- すべての文字列の”印刷とはどういう意味ですか”?関数は単語の一部を出力するだけです。たとえば、run` anagramsabc`が
a,b,ab,c,ac,bc,abc
divを出力する場合>
回答
パフォーマンスを向上させるための型付き配列。
承認された回答方法を使用すると、パフォーマンスを3倍にすることができます。
私のマシンで勝者の答えを実行すると、ベンチマークが得られました
Mean : 5,564,999µs ±182,246µs 11 samples
tを使用する場合yped配列
Mean : 1,514,112µs ±72,000µs (*) 13 samples
3.68のパフォーマンス向上のため。
var knownPermutations = [0,0,0,24,120,720,5040,40320,362880,3628800]; function anagramer(input) { var counter, chars,length,i,index,tc,n,arraySize,anagrams; index = 0; length = input.length; arraySize = knownPermutations[length-1] * length; anagrams = new Uint8Array(arraySize); chars = new Uint8Array(length); counter = new Uint8Array(length); for( i = 0; i < length; i ++){ chars[i] = input.charCodeAt(i); } anagrams.set(chars) index += length; i = 0; while (i < length) { if (counter[i] < i) { tc = chars[i]; chars[i] = chars[n= (i%2?counter[i]:0)]; chars[n] = tc; counter[i]++; i = 0; anagrams.set(chars,index) index += length; } else { counter[i] = 0; i++; } } return anagrams; }
もう少しエッジングする結果の配列を事前に定義して、取得することができます
Mean : 1,408,974µs ±25,173µs (*) 24 samples
勝者の答えの最大3.95倍の速さで改善します。
allanagrams
->all_anagrams
、shorterword
->shorter_word
など。 2:適切にインデントします。インデントなし、2つのスペースインデント、4つのスペースインデントが混在しています。それは’ダメです。