Hatena::ブログ(Diary)

ten-youの日記 RSSフィード

2009-03-15

正規表現で「0回以上に一致」

正規表現については、そのとき必要なことだけをとりあえず付け焼刃で、というやり方ばっかりしててあんまり格闘したことなかったなあと思ったのでこのエントリ。

といってもやっぱり必要な分だけなのですが。


ダイス振る時に数値を省略すること、よくありますので、それをも受け付けるようにしてみたいと思います。


ベースになるスクリプト

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] );
        }

    }
}

以前もあげた奴です。これを改造しながら今回のお題を一つ。

これを実行した結果は以下の通り。

21:34 (ten-you) !1r10

21:34 (ten-you) [0] = !1r10

21:34 (ten-you) [1] = 1

21:34 (ten-you) [2] = 10

21:34 (ten-you) !r

ということで↓

お題

!以降、一部の数値を省略しても反応する正規表現をつくる

ろあだいすは「#d100」「#3r」のような入力をした場合、自動的に「1d100」「#3r10」と解釈して反応するのです。

これをまねしたい。


「+」を「*」に。

単純に。

正規表現において「+」は直前の文字の1回以上に一致、「*」は0回以上に一致なので。

これで実行した結果が以下に↓


●今までの入力フォーマットそのまま。

21:36 (ten-you) !1r10

21:36 (ten-you) [0] = !1r10

21:36 (ten-you) [1] = 1

21:36 (ten-you) [2] = 10


クリティカル値を省略。

21:38 (ten-you) !1r

21:38 (ten-you) [0] = !1r

21:38 (ten-you) [1] = 1

21:38 (ten-you) [2] =


●ダイス数を省略。

21:38 (ten-you) !r10

21:38 (ten-you) [0] = !r10

21:38 (ten-you) [1] =

21:38 (ten-you) [2] = 10


●全部省略。

21:38 (ten-you) !r

21:38 (ten-you) [0] = !r

21:38 (ten-you) [1] =

21:38 (ten-you) [2] =

「0回以上」って、こういうふうに反応するんですね。

私はてっきり、2つの数字のうち1つ目を省略したら、2つ目の数字が1つ目の数字として認識されるんだと思ってました。こんなふうに↓

21:38 (ten-you) !r10

21:38 (ten-you) [0] = !r10

21:38 (ten-you) [1] = 10


もうちょっと条件を増やしてみる

ではさらに。「いぬい」でやりたかった「途中経過の表示の有無と表示する場合の繰り返し回数の指定」のために、「@数字」をも拾うようにしてみます。

スクリプトを起動する発言に「@数字」がついてたら、数字回数分だけ繰り返しするように、ということ。

「@*\d*」を足してみた

ええと、正確には「@*\d*」と「@*(\d*)」。

スクリプトはこんな感じに。

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

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

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

    }
}

そしてこの結果は以下に↓

●今までの入力フォーマットそのまま。

(だから@以下は省略)

21:48 (ten-you) !1r10

21:48 (ten-you) [0] = !1r10

21:48 (ten-you) [1] = 1

21:48 (ten-you) [2] = 10

21:48 (ten-you) [3] =


●数値が入れられるところは全部入力。

21:48 (ten-you) !1r10@3

21:48 (ten-you) [0] = !1r10@3

21:48 (ten-you) [1] = 1

21:48 (ten-you) [2] = 10

21:48 (ten-you) [3] = 3

……なんか、一発で思い通りの結果が出てかえって不安になるんですが。


不安になったんでいろいろ意地悪入力もしてみました。


●@まで入れておいてその後ろの数字は省略。

21:50 (ten-you) !1r10@

21:50 (ten-you) [0] = !1r10@

21:50 (ten-you) [1] = 1

21:50 (ten-you) [2] = 10

21:50 (ten-you) [3] =


●クリティカル値だけを省略。

21:50 (ten-you) !1r@3

21:50 (ten-you) [0] = !1r@3

21:50 (ten-you) [1] = 1

21:50 (ten-you) [2] =

21:50 (ten-you) [3] = 3


●ダイス数だけ省略。

21:50 (ten-you) !r10@3

21:50 (ten-you) [0] = !r10@3

21:50 (ten-you) [1] =

21:50 (ten-you) [2] = 10

21:50 (ten-you) [3] = 3


●ダイス数とクリティカル値を省略して繰り返し回数だけ入力。

21:50 (ten-you) !r@3

21:50 (ten-you) [0] = !r@3

21:50 (ten-you) [1] =

21:50 (ten-you) [2] =

21:50 (ten-you) [3] = 3


●数値全部省略。

21:50 (ten-you) !r@

21:50 (ten-you) [0] = !r@

21:50 (ten-you) [1] =

21:50 (ten-you) [2] =

21:50 (ten-you) [3] =

太字が自分入力。その下が返ってきた結果です。……うーん。理想どおりだ。ほんとにこれでいいのか?


余談。

ところで正規表現はいわゆる『方言』が多いそうです。ということは今格闘しても、他の言語とかではやっぱり勉強しなおしなんだろうなあ。

2009-02-19 がりがりかいてます

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

正規表現の「グループ化」のこと - ten-youの日記にてFaceless様より頂いたコメントを参考に、スクリプトを以下のように書き換えました。


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

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

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

    }
}

その実行結果↓

20:54 (ten-you) !2r6!8r10!6r12

20:54 (ten-you) ten-you:!2r6!8r10!6r12

20:54 (ten-you) [0] = !2r6

20:54 (ten-you) [1] = !8r10

20:54 (ten-you) [2] = !6r12

「!nRm(n、mは任意)」という文字列を1つのグループとして配列に格納してました。面白い。


というわけで、

        //該当文字列を配列matchedTextに格納
        var matchedText = text.match(/!(\d+)R(\d+)/ig);

の部分を

        //該当文字列を配列matchedTextに格納
        var matchedText = text.match(/!\d+R\d+/ig);

と書き換えました。()とっちゃいました。

これの実行結果はさっきと同じ。

つまりJavaScript正規表現では『/gを使うと部分的なグループ化は無視してしまう』みたいです。


しかし……とすると、どうしたら「!で始まってrまたはRの両隣にある数字の固まりだけ」を検索するのでしょう?

正規表現は難問パズルのようです。

2009-02-18 確率を計算する前に

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

きっとご存知な方にはナニを寝ぼけたことを、な話ですが。

正規表現にグループ化というのがあります。

使い道はイロイロあるようなのですが、今回欲しいのは「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

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