JavaScript
正規表現

正規表現を使った金額のカンマ編集の解読

どうも、超スーパー初心者ビギナーエンジニアだよ。
何回解読しても毎回忘れてしまうのでここに書くことにしたよ。

カーソルを外したときに金額にカンマがつくようにしたい!と思ったときにググると出てくるアレ。

amountStr.replace( /(\d)(?=(\d\d\d)+(?!\d))/g, '$1,');

いや、なんでよ。
なんでこれで3桁毎ににカンマつくのよ。?! じゃないよ、こっちが「!?」だよ。
というわけで解読していくよ。

正規表現

ここでは実際にここで出てくる正規表現しか出さないので注意してね。

正規表現では、パターンの各要素を 1 対の区切り記号で囲むよ。
JScript では、1 対のスラッシュ文字 (/) を区切り記号として使用するよ。
つまりは、正規表現を使用するときは / で囲んでねってことみたい。

(~~)

~~と一致した文字列を記憶するよ。\$1,$2…に順番に代入されていくみたいだよ。

\d

任意の10進文字と一致。[0-9] と同じ意味らしいよ。
yyyyMMddって書いてあったら、なんとなく「日付の並び、20180330って並びなんだろうな」って思うよね。
そんな感じで\d\d\dって書いてあったら、「3文字の数値が並んでいるんだろうな」って思えればいいかな?

?=

後ろに指定した文字列が続く場合に一致と見なされるらしいよ。

+

直前のサブ式と1回以上一致するよ。

?!

後ろに指定した文字列が続かない場合に一致と見なされるらしいよ。

g

正規表現の後の g はグローバルサーチを行うオプション/フラグで、全体の文字列を見てすべてのマッチを返すらしいよ。
gをつけないと最初の1回で処理を終えちゃうよ。

'$1,'

(~~)で保存した文字列だよ。

解読するぞ!

まずはstr.replace(A,B)だよ。
「strにAがあったらBに変えるよ」ということだから、
「amountStrっていう文字列の中に、/(\d)(?=(\d\d\d)+(?!\d))/g ってのがあったら、'$1,'に書き換えるよ!」という意味だね。

次に/~~/gだよ。
正規表現を使うときは / で囲む!
正規表現のあとの g はマッチしなくなるまで繰り返すよ!ってことだね。

いよいよ正規表現使用箇所だよ。
落ち着いて少しずつ行こう。そうしないと僕が分からないから。

まずはこいつだよ。
(\d)
1文字の数字だよ!終わり。簡単だね!

次はこいつだよ。
(?=(\d\d\d)+(?!\d))

ハイ分かんなーい。なのでもっと分割するよ。
?=(\d\d\d)
+
(?!\d)

一個ずつ見ていくよ。

?=(\d\d\d)
?= は「後ろに指定した文字列が続く場合一致」ってヤツ。
後ろの文字の \d\d\d は3文字の数字って意味だね。
つまり、「後ろに3文字の数字が出た時に一致」ってことだね。

次いこう!
+
+は直前のサブ式と1回以上一致するよってヤツ。
つまり、さっきのと合わせると
?=(\d\d\d)+
で「後ろに3文字の数字が1回以上出た時に一致」ってことだね。

次!
(?!\d)
?! は「後ろに指定した文字列が続かない場合に一致」ってヤツ。
後ろの文字の \d は1文字の数字って意味だね。
つまり、「後ろに数字が続かなかったときに一致」ってことだね。

さっきのと合わせると
?=(\d\d\d)+(?!\d)
で「後ろに3文字の数字が1回以上出て、後ろに数字が続かなかったときに一致」ってことだね。

正規表現のところを全部くっつけて
/(\d)(?=(\d\d\d)+(?!\d))/

「(数字)(後ろに3文字の数字が1回以上出て、後ろに数字が続かなかったところ)」ってことだね!

最後に全部くっつけて!
str.replace( /(\d)(?=(\d\d\d)+(?!\d))/g, '\$1,')

「(数字)(後ろに3文字の数字が1回以上出て、後ろに数字が続かなかったところ)となるところを '$1,' に変えます!!!」
ってことだね!
解読完了!おめでとう僕。

実際に処理を確認

数字の文字列 12345678 で考えてみよう。ゴールは 12,345,678

1文字目の1が(\d)(?=(\d\d\d)+(?!\d))の最初の(\d)だとして考えると
1 \d
234 \d\d\d
567 \d\d\d (「+」で説明した通り3文字の数字は何回出てもいい。)
8 後ろが\d(数字)になってしまった!
これは条件に適さないのでreplaceしないよ。次いこう。

2文字目の2が(\d)(?=(\d\d\d)+(?!\d))の最初の(\d)だとして考えると
2 \d
345 \d\d\d
678 \d\d\d
(なし) 後ろが\d(数字)ではない!
これは条件に適してるね!さあreplaceしよう!
$1は最初の(\d)だから2だね!'2'を'2,'に置き換えよう。
すると、
12345678 → 12,345678
ようやく変換できたよ!

でもまだ終わらないよ。
何故なら該当するところは全て置き換えるって /~~/g が言ってるから。

3文字目の3が(\d)(?=(\d\d\d)+(?!\d))の最初の(\d)だとして考えると
3 \d
456 \d\d\d
78 後ろが\d(数字)
条件を満たさない!次!

4文字目の4が(\d)(?=(\d\d\d)+(?!\d))の最初の(\d)だとして考えると
4 \d
567 \d\d\d
8 後ろが\d(数字)
次次!

5文字目の5が(\d)(?=(\d\d\d)+(?!\d))の最初の(\d)だとして考えると
5 \d
678 \d\d\d
(空白) 後ろが\d(数字)じゃない!
条件を満たす!
$1は最初の(\d)だから5!'5'を'5,'に変換!
12,345678 → 12,345,678

6,7,8の後ろには3桁の数字が出ないので省略!

できた~~~~!無事12345678を12,345,678に変換できたね!
解読はできたけど、自分でこういうの作れる人ってヤバいなあ……。