正規表現の「グループ化」のこと

きっとご存知な方にはナニを寝ぼけたことを、な話ですが。
正規表現にグループ化というのがあります。
使い道はイロイロあるようなのですが、今回欲しいのは「Rもしくはrをはさんだ数字2つ」。……ハイ、あいかわらずDX2ndのRダイスについてですw

グループ化を使うとこんなに変わる

Rダイスとは「n個の10面ダイスをクリティカル値mで振る」こと。詳しくは該当の日記をご参照ください。
ともかく、グループ化を使わなかった今まではえらい手間をかけてnやmを得ていました。

たとえば1回以上クリティカルする確率を算出する「いぬい」。

//nRmの1回以上のクリティカル率を計算して出力
function event::onChannelText(prefix, channel, text)
{

    if (text.match(/!\d+R\d+/i)){
    
        //該当文字列を配列matchedTextに格納
        var matchedText = text.match(/!\d+R\d+/i);
        
        //文字列からダイスの面数と個数を配列diceTextに格納
        var diceText = matchedText[0].match(/(\d+)/g);
        
        //個数とクリティカル値をそれぞれの変数に格納
        var diceNumber = parseInt(diceText[0]);	//個数(n)
        var numCritical = parseInt(diceText[1]); //クリティカル値(m)
        
(後略)

ご覧の通り、2つの配列を経由してようやくダイスの個数とクリティカル値を文字列から取り出してます。我ながらすごい手間をかけてました。でも他に思いつかなかったのですよ。


で、先日師匠的存在にその話をしたところ、こう書き換えることが出来る、といわれました。

//nRmの1回以上のクリティカル率を計算して出力
function event::onChannelText(prefix, channel, text)
{

    if (text.match(/!\d+R\d+/i)){
    
        //該当文字列を配列matchedTextに格納
        var matchedText = text.match(/!(\d+)R(\d+)/i);
        
        //個数とクリティカル値をそれぞれの変数に格納
        var diceNumber = parseInt(matchedText[0]);	//個数(n)
        var numCritical = parseInt(matchedText[1]); //クリティカル値(m)
        
(後略)

任意の桁数の数字を表す「\d+」を「()」でくくってます。Rの前と後ろと2ヶ所。
こうするとどうなるか。
正規表現によって抽出された文字列が格納される配列matchedTextの中に、次のように格納されるのだそうです。

  • 0番目は「!(\d+)R(\d+)/i」で抽出された文字列全部。
  • 1番目は「!(\d+)R(\d+)/i」の強調部分。
  • 2番目は「!(\d+)R(\d+)/i」のの強調部分。

このグループ化について一番分かりやすいと思った解説は以下のページです。
― .NET Frameworkがサポートする正規表現クラスを徹底活用する ― (1/3):基礎解説 スマートな文字列処理のための 正規表現入門(後編) - @IT
特にページ下部の図。

1つの文字列に、正規表現を満たす文字列が2個以上あったら?

たとえば以下のスクリプト

function event::onChannelText(prefix, channel, text)
{
    
    //ベタ出力
    send(channel, prefix.nick + ':' + text );

    if (text.match(/!\d+R\d+/i)){

        //該当文字列を配列matchedTextに格納
        var matchedText = text.match(/!(\d+)R(\d+)/i);
        
        //配列の中身を出力
        for (var i = 0; i < matchedText.length; i++){
            send(channel, '[' + i + '] = ' + matchedText[i] );
        }

    }
}

これを動かして「ああああ!2r6ああああああ」とIRCで発言すると、以下のようになります。

23:39 (ten-you) ああああ!2r6ああああああ
23:39 (ten-you) ten-you:ああああ!2r6ああああああ
23:39 (ten-you) [0] = !2r6
23:39 (ten-you) [1] = 2
23:39 (ten-you) [2] = 6


……さて問題。
上のスクリプトを動かして「!2r6!8r10!6r12」と発言するとどうなるか。
結果↓

23:40 (ten-you) !2r6!8r10!6r12
23:40 (ten-you) ten-you:!2r6!8r10!6r12
23:40 (ten-you) [0] = !2r6
23:40 (ten-you) [1] = 2
23:40 (ten-you) [2] = 6

最初の「!2r6」しか見てません。

23:27 (ten-you) !2r6!8r10!6r12
23:27 (ten-you) ten-you:!2r6!8r10!6r12
23:40 (ten-you) [0] = !2r6
23:40 (ten-you) [1] = 2
23:40 (ten-you) [2] = 6
23:40 (ten-you) [3] = !8r10
23:40 (ten-you) [4] = 8
23:40 (ten-you) [5] = 10
23:40 (ten-you) [6] = !6r12
23:40 (ten-you) [7] = 6
23:40 (ten-you) [8] = 12

とか

23:27 (ten-you) !2r6!8r10!6r12
23:27 (ten-you) ten-you:!2r6!8r10!6r12
23:40 (ten-you) [0] = !2r6
23:40 (ten-you) [1] = 2
23:40 (ten-you) [2] = 6
23:40 (ten-you) [3] = 8
23:40 (ten-you) [4] = 10
23:40 (ten-you) [5] = 6
23:40 (ten-you) [6] = 12

とかにはならないんですね。