最終的にやりたいこと
・先頭に指定文字※があったら削除
前提条件
・対象文字列の先頭に※が1回含まれるか、全く含まれないの何れか
分からないこと
・下記何れで実装した方が良い?
案1.先頭一文字を取得後、条件分岐処理
案2.正規表現
質問背景
・正規表現は遅いからなるべく使用しないほうが良い、とどこかで聞いたことがあるため
・一般的にはどちらで実装するのでしょうか?
・あるいはこれら以外?
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答3件
0
コード
js
1'use strict'; 2 3[ 4 ['※abc', 'マッチする短い文字列'], 5 ['abc', 'マッチしない短い文字列'], 6 ['※' + new Array(10000).join('a'), 'マッチする長い文字列'], 7 ['' + new Array(10000).join('a'), 'マッチしない長い文字列'], 8].map(function (item) { 9 var i; 10 var str = item[0]; 11 12 console.time('indexOf-' + item[1]); 13 for (i = 0; i < 10000000; ++i) { 14 str.indexOf('※') === 0; 15 } 16 console.timeEnd('indexOf-' + item[1]); 17 18 console.time('RegExp-' + item[1]); 19 for (i = 0; i < 10000000; ++i) { 20 str.match(/^※/); 21 } 22 console.timeEnd('RegExp-' + item[1]); 23 24 console.time('startsWith-' + item[1]); 25 for (i = 0; i < 10000000; ++i) { 26 str.startsWith('※'); 27 } 28 console.timeEnd('startsWith-' + item[1]); 29 30 console.log(); 31 32});
結果
V8系
Node.js
indexOf-マッチする短い文字列: 451.954ms RegExp-マッチする短い文字列: 1168.502ms startsWith-マッチする短い文字列: 656.937ms indexOf-マッチしない短い文字列: 451.098ms RegExp-マッチしない短い文字列: 377.314ms startsWith-マッチしない短い文字列: 673.922ms indexOf-マッチする長い文字列: 458.273ms RegExp-マッチする長い文字列: 1188.736ms startsWith-マッチする長い文字列: 678.872ms indexOf-マッチしない長い文字列: 442.082ms RegExp-マッチしない長い文字列: 377.708ms startsWith-マッチしない長い文字列: 649.069ms
Chrome
indexOf-マッチする短い文字列: 676.688ms RegExp-マッチする短い文字列: 1360.670ms startsWith-マッチする短い文字列: 1462.866ms indexOf-マッチしない短い文字列: 541.262ms RegExp-マッチしない短い文字列: 617.367ms startsWith-マッチしない短い文字列: 851.028ms indexOf-マッチする長い文字列: 636.199ms RegExp-マッチする長い文字列: 1344.160ms startsWith-マッチする長い文字列: 1424.883ms indexOf-マッチしない長い文字列: 506.433ms RegExp-マッチしない長い文字列: 517.285ms startsWith-マッチしない長い文字列: 780.458ms
SpiderMonkey系
Firefox
indexOf-マッチする短い文字列: 284.28ms RegExp-マッチする短い文字列: 3639.21ms startsWith-マッチする短い文字列: 265.69ms indexOf-マッチしない短い文字列: 276.58ms RegExp-マッチしない短い文字列: 1038.85ms startsWith-マッチしない短い文字列: 285.4ms indexOf-マッチする長い文字列: 294.38ms RegExp-マッチする長い文字列: 3774.39ms startsWith-マッチする長い文字列: 303.2ms indexOf-マッチしない長い文字列: 26592.07ms RegExp-マッチしない長い文字列: 1386.18ms startsWith-マッチしない長い文字列: 285.8ms
考察
String#match
がマッチするときに遅いとなっているのは,返り値の生成にコストがかかるから.String#startsWith
は,V8においてはあまり最適化されておらず,String#indexOf
よりも低速.一方SpiderMonkeyにおいては最適化されており,安定して高速.String#indexOf
は,V8においては最適化されており,安定して高速.一方SpiderMonkeyにおいてはあまり最適化されておらず,「マッチしない長い文字列」において著しく動作が遅くなる傾向にある.
V8のString#indexOf
が**「マッチしない長い文字列」に対して高速**な理由が気になりますね.右辺の値を先に見て,それ以上無駄な走査を行わないようにする…という高度な最適化でもやってるんでしょうか.
投稿2016/03/26 11:22
編集2016/03/26 12:07総合スコア5223
0
ベストアンサー
比較するなら RegExp#test
にしないと不公平な気はしますね。
比較検証用コード
JavaScript
1'use strict'; 2function test (target, search) { 3 var i = 50000, 4 regExp = new RegExp('^' + search.replace(/(\W)/g, '\u005C$1')); 5 6 while (i--) { 7 regExp.test(target); 8 } 9 10 return regExp.test(target); 11} 12 13function propertyAccess (target, search) { 14 var i = 50000; 15 16 while (i--) { 17 target[0] === search; 18 } 19 20 return target[0] === search; 21} 22 23function charAt (target, search) { 24 var i = 50000; 25 26 while (i--) { 27 target.charAt(0) === search; 28 } 29 30 return target.charAt(0) === search; 31} 32 33function indexOf (target, search) { 34 var i = 50000; 35 36 while (i--) { 37 target.indexOf(search) === 0; 38 } 39 40 return target.indexOf(search) === 0; 41} 42 43function lastIndexOf (target, search) { 44 var i = 50000; 45 46 while (i--) { 47 target.lastIndexOf(search, 0) === 0; 48 } 49 50 return target.lastIndexOf(search, 0) === 0; 51} 52 53function startsWith (target, search) { 54 var i = 50000; 55 56 while (i--) { 57 target.startsWith(search); 58 } 59 60 return target.startsWith(search); 61} 62 63 64function benchmark (fn, name, target, search) { 65 console.time(name); 66 var result = fn(target, search); 67 console.timeEnd(name); 68 console.log(result); 69} 70 71var matchedString = '※' + Array(50000).join('a'), 72 noMatchString = Array(50001).join('a'); 73 74benchmark(test, 'RegExp#test (matched)', matchedString, '※'); 75benchmark(test, 'RegExp#test (no match)', noMatchString, '※'); 76benchmark(propertyAccess, 'propety access on strings (matched)', matchedString, '※'); 77benchmark(propertyAccess, 'propety access on strings (no match)', noMatchString, '※'); 78benchmark(charAt, 'String#charAt (matched)', matchedString, '※'); 79benchmark(charAt, 'String#charAt (no match)', noMatchString, '※'); 80benchmark(indexOf, 'String#indexOf (matched)', matchedString, '※'); 81benchmark(indexOf, 'String#indexOf (no match)', noMatchString, '※'); 82benchmark(lastIndexOf, 'String#lastIndexOf (matched)', matchedString, '※'); 83benchmark(lastIndexOf, 'String#lastIndexOf (no match)', noMatchString, '※'); 84benchmark(startsWith, 'String#startsWith (matched)', matchedString, '※'); 85benchmark(startsWith, 'String#startsWith (no match)', noMatchString, '※');
検証結果
Google Chrome 49.0.2623.87 m
RegExp#test (matched): 6.640ms true RegExp#test (no match): 3.348ms false propety access on strings (matched): 4.925ms true propety access on strings (no match): 1.420ms false String#charAt (matched): 5.177ms true String#charAt (no match): 2.596ms false String#indexOf (matched): 5.453ms true String#indexOf (no match): 5.050ms false String#lastIndexOf (matched): 5.788ms true String#lastIndexOf (no match): 3.901ms false String#startsWith (matched): 13.675ms true String#startsWith (no match): 4.301ms false
実装
「何を選択するか」はコードの設計指針によりますが、私自身は次の事に気をつけています。
- アルゴリズムに無駄が無い事
- 汎用性が高い事
例えば、String#indexOf
は対象文字列の先頭から検索してHITした時点で index
値を返す関数です。その為、検索でマッチしないと最後まで検索し続ける事になります。先頭1文字だけ探せば良いところを最後まで探し続けるのは無駄です。
String#indexOf
が候補から外れます。
汎用性とは今回の要件だけでなく、広範囲の要件を網羅できる機能を指します。
今回は先頭の1文字だけを検索すればすみますが、先頭の2文字を削除したい場合も対応できる方が汎用性が高いといえます。
string[0]
, String#charAt
は先頭1文字だけが対象なので汎用性が低いといえます。
JavaScript
1function removeStartsWith1 (targetString, searchString) { 2 if (targetString.startsWith(searchString)) { 3 targetString = targetString.slice(searchString.length); 4 } 5 6 return targetString; 7} 8 9function removeStartsWith2 (targetString, searchString) { 10 return targetString.replace(new RegExp('^' + searchString.replace(/(\W)/g, '\u005C$1'), 'g'), ''); 11} 12 13function removeStartsWith3 (textNode, searchString) { 14 if (textNode.data.startsWith(searchString)) { 15 textNode.deleteData(0, searchString.length); 16 } 17 18 return textNode; 19} 20 21console.log(removeStartsWith1('※aaa', '※')); // "aaa" 22console.log(removeStartsWith1('aaa', '※')); // "aaa" 23console.log(removeStartsWith2('※aaa', '※')); // "aaa" 24console.log(removeStartsWith2('aaa', '※')); // "aaa" 25console.log(removeStartsWith3(document.createTextNode('※aaa'), '※')); // "aaa" 26console.log(removeStartsWith3(document.createTextNode('aaa'), '※')); // "aaa"
String#startsWith
は IE11- に対応する為に Polyfill が必要なので後述します。
先頭文字列削除に特化するなら String#replace
の実装がお手軽だと思います。
対象がテキストノードなら CharacterData.prototype.deleteData
でテキストノード自身を操作できるのでお勧めです。
String.prototype.startsWith の Polyfill
String.prototype.startsWith
は ES6 規定の為、IE11- で使用できませんが、Polyfill を使うことで対応できます。
結論
いろいろ書きましたが、個人的には String#replace
と CharacterData#deleteData
の二択ですね。
テキストノードをゴリゴリ操作するなら CharacterData#deleteData
、文字列操作なら String#replace
というところでしょうか。
テキストノードも data
プロパティから String#replace
を使用してもいいのですが、画面の再描画コストが CharacterData#deleteData
の方が低そうな気がします(未検証)。
参考リンク
- ECMAScript 5 compatibility table
- ECMAScript 6 compatibility table
- String.prototype.startsWith() - JavaScript | MDN
- 1.1.3.18 String.prototype.startsWith – ECMA-262 6th Edition
(更新履歴)
- 2016/03/27 11:55
String#lastIndexOf
のコード追加 - 2016/03/28 10:21
String#lastIndexOf
のコードが追加できていなかったのを修正。jsperfが削除されていたので比較検証用コードを作成してjsfiddleにUP - 2016/03/29 18:30
String#startsWith
の Polyfill 追加。「実装」「結論」節の追加。
Re: re97 さん
投稿2016/03/26 12:46
編集2016/03/29 09:32総合スコア18164
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/03/26 14:32
2016/03/27 01:43
2016/03/27 03:06 編集
2016/03/27 07:06 編集
2016/03/28 03:35
2016/03/29 09:37
2016/03/29 11:11
0
こんなコードで試してみましたよ。
var arr=['※あいう', 'かきく']; var time; time = (new Date().valueOf()); for(var i = 0; i < 10000000; i++){ var str = arr[i%2]; var v = str.match(/^※.*/i); } console.log('regexp:' + (new Date().valueOf() - time)); time = (new Date().valueOf()); for(var i = 0; i < 10000000; i++){ var str = arr[i%2]; var v = str.indexOf('※') == 0; } console.log('indexOf:' + (new Date().valueOf() - time)); time = (new Date().valueOf()); for(var i = 0; i < 10000000; i++){ var str = arr[i%2]; var v = str.startsWith('※'); } console.log('startsWith:' + (new Date().valueOf() - time));
結果:
1回目
regexp:640
indexOf:701
startsWith:252
2回目
regexp:643
indexOf:678
startsWith:239
3回目
regexp:651
indexOf:671
startsWith:237
先頭文字限定ならstartsWithが速いみたい。
投稿2016/03/26 02:05
総合スコア5572
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/03/26 02:10
2016/03/26 11:22
2016/03/26 11:27
2016/03/26 11:40
2016/03/27 02:03 編集
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/03/26 12:04 編集
2016/03/26 12:11 編集
2016/03/26 12:17
2016/03/26 12:49
2016/03/26 13:04
2016/03/26 13:06
2016/03/26 14:27
2016/03/27 01:53
2016/03/27 03:33 編集
2016/03/28 02:50