質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.87%

  • JavaScript

    8766questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

先頭に指定文字があったら削除を実装する際、正規表現を使用した方が良いか、計測する方法を知りたい

解決済

回答 3

投稿 2016/03/26 10:40

  • 評価
  • クリップ 0
  • VIEW 739

re97

score 177

最終的にやりたいこと
・先頭に指定文字※があったら削除

前提条件
・対象文字列の先頭に※が1回含まれるか、全く含まれないの何れか

分からないこと
・下記何れで実装した方が良い?
案1.先頭一文字を取得後、条件分岐処理
案2.正規表現

質問背景
・正規表現は遅いからなるべく使用しないほうが良い、とどこかで聞いたことがあるため
・一般的にはどちらで実装するのでしょうか?
・あるいはこれら以外?

  • 気になる質問をクリップする

    クリップした質問に回答があった場合に通知・メールを受け取ることができます。

    クリップした質問はマイページの「クリップ」タブからいつでも見ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+3

 コード

'use strict';

[
    ['※abc', 'マッチする短い文字列'],
    ['abc', 'マッチしない短い文字列'],
    ['※' + new Array(10000).join('a'), 'マッチする長い文字列'],
    ['' + new Array(10000).join('a'), 'マッチしない長い文字列'],
].map(function (item) {
    var i;
    var str = item[0];

    console.time('indexOf-' + item[1]);
    for (i = 0; i < 10000000; ++i) {
        str.indexOf('※') === 0;
    }
    console.timeEnd('indexOf-' + item[1]);

    console.time('RegExp-' + item[1]);
    for (i = 0; i < 10000000; ++i) {
        str.match(/^※/);
    }
    console.timeEnd('RegExp-' + item[1]);

    console.time('startsWith-' + item[1]);
    for (i = 0; i < 10000000; ++i) {
        str.startsWith('※');
    }
    console.timeEnd('startsWith-' + item[1]);

    console.log();

});

 結果

 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 20:22

編集 2016/03/26 21:07

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    以下のような回答は評価を下げられます

    • 間違っている回答
    • 質問の回答になっていない投稿
    • 不快な投稿

    評価を下げる際はその理由をコメントに書き込んでください。

  • 2016/03/26 21:04 編集

    関連検証: https://paiza.io/projects/HbiQUMxm_OOY2zZYsS8LBg

    V8ではCharCodeAtが飛び抜けて速そうです.1文字の判定しか行わない場合はこれでいいかも.

    キャンセル

  • 2016/03/26 21:11 編集

    でも速度気にするよりもこの場合は短くコードを書けるかどうかを優先したほうがいいと思いますよ.正規表現使うのが一番シンプルです.if文の分岐が要りませんから.

    '※abc'.replace(/^※/, '')

    (何も考えずに先客の真似したけどString#matchじゃなくてString#replaceで検証すべきだった

    キャンセル

  • 2016/03/26 21:17

    うちんところでも同じような結果(nodejs,chrome,firefox)がでてて、結構差がでますねぇ。
    Win10+Edge(Chakra)の結果をのせておきます。

    indexOf 0: 2,950.67 ミリ秒
    RegExp 0: 3,006.58 ミリ秒
    startsWith 0: 2,326 ミリ秒

    indexOf 1: 2,766.215 ミリ秒
    RegExp 1: 2,851.62 ミリ秒
    startsWith 1: 2,333.92 ミリ秒

    indexOf 2: 1,311.535 ミリ秒
    RegExp 2: 1,363.3 ミリ秒
    startsWith 2: 992.755 ミリ秒

    indexOf 3: 69,527.255 ミリ秒
    RegExp 3: 1,349.935 ミリ秒
    startsWith 3: 1,065.795 ミリ秒

    キャンセル

  • 2016/03/26 21:49

    > - String#matchがマッチするときに遅いとなっているのは,返り値の生成にコストがかかるから.
    そんな時の為に `RegExp#test` があります。

    キャンセル

  • 2016/03/26 22:04

    単にマッチしているかどうかだけ見るだけなら、RegExp.prototype.test() も比較対象に追加して欲しいです。

    キャンセル

  • 2016/03/26 22:06

    あう、think49さんと被った…
    リロードしてなかったから…

    キャンセル

  • 2016/03/26 23:27

    RegExp#testに関しては「関連検証」のほうにデータがあるので参考にしてください.

    キャンセル

  • 2016/03/27 10:53

    ・みなさん回答ありがとうございます

    ・そもそもの疑問なのですが、マッチしない文字列も計測しているのはなぜでしょうか?
    ・正規表現速度を考える場合、マッチしない文字列がある場合も考慮しなければいけない、ということでしょうか?

    ・もしそうなら、判断基準は「マッチする文字列」と「マッチしない文字列」の合計計測時間がその正規表現に対する性能評価ということになるのでしょうか?
    ・そんな単純な話ではない?

    キャンセル

  • 2016/03/27 12:33 編集

    To: re97 さん
    「各関数がどのように動作しているのか」を考えてみてください。
    例えば、indexOfは文字列を前方から走査し続ける為、文字列が前方一致しなくとも処理を続けます。
    コードの書き方によっては与えられた文字列によっては無駄が出来てしまいます。
    ですので、「re97さんの要件」と「選択肢となるコード」を比較して出来るだけ要件に合致したコードを選択する必要があります。

    キャンセル

  • 2016/03/28 11:50

    ・コメントありがとうございました
    ・質問した時点では単純に一般的な記述方法を知りたかったのですが、正規表現を使用する場合においては、要件(対象文字列)に応じて結果は変動するということですね
    ・「先頭に指定文字があったら削除」という同じ条件でも、渡す文字列内容によって結果は異なることがあり得ると
    ・なるほど、と思いました

    キャンセル

checkベストアンサー

+1

比較するなら RegExp#test にしないと不公平な気はしますね。

 比較検証用コード

'use strict';
function test (target, search) {
  var i = 50000,
      regExp = new RegExp('^' + search.replace(/(\W)/g, '\u005C$1'));

  while (i--) {
    regExp.test(target);
  }

  return regExp.test(target);
}

function propertyAccess (target, search) {
  var i = 50000;

  while (i--) {
    target[0] === search;
  }

  return target[0] === search;
}

function charAt (target, search) {
  var i = 50000;

  while (i--) {
    target.charAt(0) === search;
  }

  return target.charAt(0) === search;
}

function indexOf (target, search) {
  var i = 50000;

  while (i--) {
    target.indexOf(search) === 0;
  }

  return target.indexOf(search) === 0;
}

function lastIndexOf (target, search) {
  var i = 50000;

  while (i--) {
    target.lastIndexOf(search, 0) === 0;
  }

  return target.lastIndexOf(search, 0) === 0;
}

function startsWith (target, search) {
  var i = 50000;

  while (i--) {
    target.startsWith(search);
  }

  return target.startsWith(search);
}


function benchmark (fn, name, target, search) {
  console.time(name);
  var result = fn(target, search);
  console.timeEnd(name);
  console.log(result);
}

var matchedString = '※' + Array(50000).join('a'),
    noMatchString = Array(50001).join('a');

benchmark(test, 'RegExp#test (matched)', matchedString, '※');
benchmark(test, 'RegExp#test (no match)', noMatchString, '※');
benchmark(propertyAccess, 'propety access on strings (matched)', matchedString, '※');
benchmark(propertyAccess, 'propety access on strings (no match)', noMatchString, '※');
benchmark(charAt, 'String#charAt (matched)', matchedString, '※');
benchmark(charAt, 'String#charAt (no match)', noMatchString, '※');
benchmark(indexOf, 'String#indexOf (matched)', matchedString, '※');
benchmark(indexOf, 'String#indexOf (no match)', noMatchString, '※');
benchmark(lastIndexOf, 'String#lastIndexOf (matched)', matchedString, '※');
benchmark(lastIndexOf, 'String#lastIndexOf (no match)', noMatchString, '※');
benchmark(startsWith, 'String#startsWith (matched)', matchedString, '※');
benchmark(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文字だけが対象なので汎用性が低いといえます。

function removeStartsWith1 (targetString, searchString) {
  if (targetString.startsWith(searchString)) {
    targetString = targetString.slice(searchString.length);
  }

  return targetString;
}

function removeStartsWith2 (targetString, searchString) {
  return targetString.replace(new RegExp('^' + searchString.replace(/(\W)/g, '\u005C$1'), 'g'), '');
}

function removeStartsWith3 (textNode, searchString) {
  if (textNode.data.startsWith(searchString)) {
    textNode.deleteData(0, searchString.length);
  }

  return textNode;
}

console.log(removeStartsWith1('※aaa', '※'));  // "aaa"
console.log(removeStartsWith1('aaa', '※'));    // "aaa"
console.log(removeStartsWith2('※aaa', '※'));  // "aaa"
console.log(removeStartsWith2('aaa', '※'));    // "aaa"
console.log(removeStartsWith3(document.createTextNode('※aaa'), '※'));  // "aaa"
console.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 の方が低そうな気がします(未検証)。

 参考リンク

(更新履歴)

  • 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 21:46

編集 2016/03/29 18:32

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    以下のような回答は評価を下げられます

    • 間違っている回答
    • 質問の回答になっていない投稿
    • 不快な投稿

    評価を下げる際はその理由をコメントに書き込んでください。

  • 2016/03/26 23:31

    今回は目的が「先頭文字を条件に応じて置換する」ことなので,一番シンプルにString#replaceで正規表現を使うのが適任かと思います.「^」による言明を行っておけば,String#indexOfのように極端に遅くなることは無いですし.

    キャンセル

  • 2016/03/26 23:32

    関連検証その2: https://paiza.io/projects/VJkUd_-y-dj441WpkdUWNw

    キャンセル

  • 2016/03/27 10:43

    ・回答内容難しかったです

    >比較するなら RegExp#test にしないと不公平な気はしますね
    ・使用すると、なぜ公平になるのでしょうか?

    >Polyfill を適用して汎用性の高い String#startWith を使うか
    ・String#startWithは標準で使用不可?
    ・先行実装しているライブラリを読み込めば、使用できるということでしょうか?
    ・「汎用性の高い」という意味は、String#startWithを使用すれば正規表現を使用しなくても先頭1文字一致を確認できる、という意味でしょうか?

    キャンセル

  • 2016/03/27 12:06 編集

    To: CertaiN さん
    削除目的なら仰る通りだと思います。ところで、String#indexOf を String#lastIndexOf に置き換えれば走査速度の問題が解決される事に気が付きました。

    To: re97 さん
    String#startsWith は IE11- で使用できません。Polyfillについては後程、追記します。
    http://kangax.github.io/compat-table/es6/#test-String.prototype_methods_String.prototype.startsWith
    汎用性も後で追記しますが、私は速度よりも「re97さんが求める要件にどれだけマッチしているか」が重要だと考えています。

    キャンセル

  • 2016/03/27 16:06 編集

    lastIndexOfの第2引数は有用ですね,勉強になりました.ところが…

    http://www.axlight.com/mt/sundayhacking/2013/03/javascriptstringstartswith.html

    ここに書かれているように,実際に自分で試してみてもlastIndexOfがV8においてはあまり速度が出ませんでした…安定してindexOfのほうが速かったです.(SpiderMonkeyだと予想通りな感じになったんですが,V8の最適化がかなり特殊なようです…)

    キャンセル

  • 2016/03/28 12:35

    Re: CertaiN さん
    私の環境ではマッチしない場合に lastIndexOf が速い結果となりました(親記事参照)。
    マッチする場合においては同等速度となり、これは期待通りの結果ですね。
    indexOf はマッチする場合とマッチしない場合で速度差がない為、総合的には lastIndexOf が速いと判断出来る結果となりました。

    Re: re97 さん
    比較検証コードの修正まで出来ましたが、他についてはもう少し時間がかかりそうです。すみません。

    キャンセル

  • 2016/03/29 18:37

    遅くなりましたが、親記事を修正しました。
    startsWith の Polyfill について。私の環境(GC49)では lastIndexOf がなかなかの速さだったので lastindexOf を採用しました。
    下記コードのような while で1文字ずつ照合する組み方も個人的には嫌いではないので迷いましたがコーディング時間優先で lastIndexOf 採用に。
    https://github.com/mathiasbynens/String.prototype.startsWith/blob/master/startswith.js
    lastIndexOf の仕様を読み進められていないのでもう少しコード量を減らせるような気はしています。

    キャンセル

  • 2016/03/29 20:11

    ・回答&コメント&比較検証コードを作成いただき、ありがとうございました
    ・割と軽い気持ちで質問したのですが、自分が想定していた以上に色々なやり方があることを知り、かなり驚いています
    ・大変参考になりました

    キャンセル

+1

こんなコードで試してみましたよ。

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 11:05

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    以下のような回答は評価を下げられます

    • 間違っている回答
    • 質問の回答になっていない投稿
    • 不快な投稿

    評価を下げる際はその理由をコメントに書き込んでください。

  • 2016/03/26 11:10

    charAtを忘れてたので、こっちでも試してみましたが、regexpやindexOfよりも輪をかけて遅かったのでびっくり。いあ!いあ!

    キャンセル

  • 2016/03/26 20:22

    「.*」のマッチは無駄だと思います.

    キャンセル

  • 2016/03/26 20:27

    何故か私の環境でstartsWithが異様に遅いんですがこれは何故だろう…

    キャンセル

  • 2016/03/26 20:40

    V8(Node.js, Chrome)とSpiderMonkey(Firefox)で全然結果が違うようです…

    キャンセル

  • 2016/03/27 11:03 編集

    ・回答ありがとうございました

    ・indexOf や startsWith を初めて知りました
    ・色々なやり方があるんですね

    ・Chromeで試したら、startsWithが一番遅かったです
    ・環境によってこんなにも変わるんですね
    ・びっくりしました
    ・どういう風に評価したら良いか分からないです

    ・Chrome バージョン 49.0.2623.87 m
    ・regexp:707
    ・indexOf:660
    ・startsWith:1284

    キャンセル

teratailには29人のエキスパートがいます

今すぐはじめる

もっと詳しく

関連した質問

同じタグがついた質問を見る

  • JavaScript

    8766questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

閲覧数の多いJavaScriptの質問