先読み
X(?=Y) はX の後にYが続けばXにマッチすると説明されていました。
間違いではありませんが、正確でもありません。
「先読みは文字と文字の境界にマッチする」と覚えてください。
String.prototype.split
String.prototype.split
と先読みを併用すると、挙動を理解しやすいと思います。
JavaScript
1console.log('a,b,c'.split(/(?=,)/)); // ["a", ",b", ",c"]
/(?=,)/
はカンマ(,)が後続する文字間の境界にマッチしています。
(?!^)
(?!^)の対象は何になるのでしょうか?
まず、(?!^)
は文字列の先端にカンマが挿入されないようにする為のものです。
JavaScript
1console.log('100000000'.replace(/(?=(?:\d{3})+$)/g, ',')); // ",100,000,000"
2console.log('100000000'.replace(/(?!^)(?=(?:\d{3})+$)/g, ',')); // "100,000,000"
^
は「文字列の先端」を表し、1文字目の一つ手間の境界にマッチします。
これを先読みしますので、「1文字目の一つ手間の境界(^
)」から更に一つ手前の境界にマッチしているのでしょう。
下記理由でお勧めし難い正規表現ではありますが…。
- 境界の手前に境界があるのは非論理的に感じます
- ECMAScript 仕様は未確認です
- 複数ブラウザで期待通りに動くか分かりません
別解
後読みを使うと、部分一致で置換する正規表現を実装できるようになります。
JavaScript
1console.log('100000000'.replace(/(?<=\d)(?=(?:\d{3})+(?!\d))/g, ',')); // "100,000,000"
2console.log('abc100000000def'.replace(/(?<=\d)(?=(?:\d{3})+(?!\d))/g, ',')); // "abc100,000,000def"
こちらは、(?<=\d)
によって数字が前述することを保証するので、^
にまつわる不自然さは解消されています。
変数にキャプチャすることをいとわなければ、先読みだけでも同じことが可能です。
JavaScript
1console.log('100000000'.replace(/(\d)(?=(?:\d{3})+(?!\d))/g, '$1,')); // "100,000,000"
2console.log('abc100000000def'.replace(/(\d)(?=(?:\d{3})+(?!\d))/g, '$1,')); // "abc100,000,000def"
先読みは文字を消費しない
「先読み」の正規表現を説明する時、
という表現が良く使われます。
手元の「詳説 正規表現 第三版」でも次の一文がありました。
2.3.5 先後読みによって数値にカンマを付け加える
...
先後読み構文は、テキストではなく、テキストの中の位置にマッチするという点で、「\b」などの単語境界メタ文字、「^」や「$」などのアンカーに似ている。
これはこれで納得できる説明ですが、次節の
テキストを "消費" しない先後読み
「消費」の概念が本質なのかもしれません。
正規表現を実装する立場で考えた場合、「^
」を「1文字も消費していない状態」と定義する事が出来ます。
その考えで正規表現を読み解くと、
上記正規表現は、「1文字も消費していない状態にはマッチせず、この検索で文字列を消費しない」という事になります。
※「先端を否定先読みする」という正規表現は先端(^)の手前があるかのようで不思議に感じられましたが、「消費」の概念を人間に分かりやすく説明する為に「位置」や「文字と文字の間」という説明が生まれた、と考えると、この挙動の辻褄が合うように思います。
Re: takahashi-one さん