2020年12月10日

スティッキフラグ "y"、位置を検索中

フラグyを使用すると、ソース文字列の指定された位置で検索を実行できます。

yフラグのユースケースを理解し、正規表現の使い方をよりよく理解するために、実用的な例を見ていきましょう。

正規表現の一般的なタスクの1つに「字句解析」があります。プログラミング言語などのテキストを取得し、その構造要素を見つける必要があります。たとえば、HTMLにはタグと属性があり、JavaScriptコードには関数、変数などがあります。

字句解析器の記述は、独自のツールとアルゴリズムを持つ特殊な分野であるため、ここでは深く掘り下げませんが、指定された位置で何かを読み取るという一般的なタスクがあります。

たとえば、コード文字列let varName = "value"があり、位置4から始まる変数名を読み取る必要があります。

正規表現\w+を使用して変数名を検索します。実際、JavaScriptの変数名には正確に一致させるために少し複雑な正規表現が必要ですが、ここでは問題ありません。

  • str.match(/\w+/)を呼び出すと、行の最初の単語(let)のみが見つかります。それでは不十分です。
  • フラグgを追加できます。しかし、str.match(/\w+/g)を呼び出すと、テキスト内のすべての単語が検索されますが、位置4の1つの単語が必要なだけです。これも、必要なものではありません。

では、指定された位置で正規表現を正確に検索するにはどうすればよいでしょうか?

メソッドregexp.exec(str)を使用してみましょう。

フラグgyのないregexpの場合、このメソッドは最初のマッチのみを検索し、str.match(regexp)とまったく同じように機能します。

…しかし、フラグgがある場合、regexp.lastIndexプロパティに格納されている位置から始まるstrで検索を実行します。そして、一致が見つかった場合、regexp.lastIndexを一致の直後のインデックスに設定します。

言い換えると、regexp.lastIndexは検索の開始点として機能し、各regexp.exec(str)呼び出しで新しい値(「最後のマッチの後」)にリセットされます。もちろん、フラグgがある場合のみです。

したがって、regexp.exec(str)を連続して呼び出すと、マッチが次々と返されます。

このような呼び出しの例を以下に示します。

let str = 'let varName'; // Let's find all words in this string
let regexp = /\w+/g;

alert(regexp.lastIndex); // 0 (initially lastIndex=0)

let word1 = regexp.exec(str);
alert(word1[0]); // let (1st word)
alert(regexp.lastIndex); // 3 (position after the match)

let word2 = regexp.exec(str);
alert(word2[0]); // varName (2nd word)
alert(regexp.lastIndex); // 11 (position after the match)

let word3 = regexp.exec(str);
alert(word3); // null (no more matches)
alert(regexp.lastIndex); // 0 (resets at search end)

ループですべてのマッチを取得できます。

let str = 'let varName';
let regexp = /\w+/g;

let result;

while (result = regexp.exec(str)) {
  alert( `Found ${result[0]} at position ${result.index}` );
  // Found let at position 0, then
  // Found varName at position 4
}

このようなregexp.execの使い方は、プロセスをより詳細に制御できる、str.matchAllメソッドの代替手段です。

私たちのタスクに戻りましょう。

指定された位置から検索を開始するために、lastIndexを手動で4に設定できます!

このように

let str = 'let varName = "value"';

let regexp = /\w+/g; // without flag "g", property lastIndex is ignored

regexp.lastIndex = 4;

let word = regexp.exec(str);
alert(word); // varName

やった!問題解決!

位置regexp.lastIndex = 4から始まる\w+の検索を実行しました。

結果は正しいです。

…しかし、待ってください、そんなに速くはありません。

注意してください。regexp.exec呼び出しは、lastIndexの位置で検索を開始し、その後続行します。lastIndexの位置に単語がないが、その後に存在する場合は、見つかるでしょう。

let str = 'let varName = "value"';

let regexp = /\w+/g;

// start the search from position 3
regexp.lastIndex = 3;

let word = regexp.exec(str);
// found the match at position 4
alert(word[0]); // varName
alert(word.index); // 4

字句解析を含む一部のタスクでは、これは間違っています。テキストの指定された位置で正確にマッチを見つける必要があります。そのためにフラグyがあります。

フラグyは、regexp.execが「〜から始まる」のではなく、lastIndexの位置で正確に検索するようにします。

フラグyを使った同じ検索を以下に示します。

let str = 'let varName = "value"';

let regexp = /\w+/y;

regexp.lastIndex = 3;
alert( regexp.exec(str) ); // null (there's a space at position 3, not a word)

regexp.lastIndex = 4;
alert( regexp.exec(str) ); // varName (word at position 4)

ご覧のとおり、正規表現/\w+/yは位置3では一致しませんが(フラグgとは異なり)、位置4では一致します。

必要なものだけでなく、フラグyを使用すると、パフォーマンスが大幅に向上します。

長いテキストがあり、まったく一致がないと想像してみてください。フラグgを使った検索はテキストの最後まで進んで何も見つからず、これはフラグyを使った検索(正確な位置のみを確認する)よりもはるかに時間がかかります。

字句解析などのタスクでは、通常、正確な位置で多くの検索が行われ、そこで何が存在するかを確認します。フラグyを使用することは、正しい実装と良好なパフォーマンスの鍵となります。

チュートリアルマップ

コメント

コメントする前にこれを読んでください…
  • 改善点に関する提案がある場合は、コメントする代わりにGitHub issueまたはプルリクエストを送信してください。
  • 記事の内容が理解できない場合は、詳しく説明してください。
  • いくつかのコード行を挿入するには、<code>タグを使用し、複数行の場合は<pre>タグで囲み、10行を超える場合は、サンドボックス(plnkrjsbincodepen…)を使用してください。