先読み、戻り読み

viで正規表現を覚えたため、機能拡張されている部分は疎かったりします。特に(?で始まるものは苦手で精々(?:...)くらいしか知らなかったり。使える武器は多い方が良いのでちょっと勉強し直しています。

  • (?=...) : 肯定先読み
  • (?<=...) : 肯定戻り読み
  • (?!...) : 否定先読み
  • (?<!...) : 否定戻り読み

これらは文字じゃなくて位置でマッチするので文字を挿入する置換に便利です。例えば、

%s/(?<=\d)(?=(\d{3,3})+(?!\d))/,/g

と、すると1234567を1,234,567に変換するというようなことが出来ます。では、実際の動作を追ってみます。まずは(?<=\d)。\dは任意の数字一文字(つまり0-9)にマッチします。(?<=...)は肯定戻り読みなので...部分がマッチした右側の位置となります。

1^234567

数字1文字なので1にマッチし、その右側なので1と2の間(^の位置)となります。次に(?=(\d{3,3})+(?!\d))ですが、長いので分割して説明します。(\d{3,3})+は3つの数字の繰り返し、つまり3,6,9...個並ぶ数字にマッチします。(?!\d)は否定先読みで数字一文字とマッチしない文字の左側の位置になります。なので、(?=(\d{3,3})+(?!\d))は3つの数字が繰り返し、その最後に数字以外が来ているパターンの左側です。

1^234567

2以降をみると数字が6つ並んで文字列が終わっている(数字以外)なのでマッチしたことになります。マッチした位置を,に置換するので,が挿入されることになります。

1,234567

引き続き、パターンにマッチする位置を探します。現在位置は,と2の間(|の位置)なので、ここから再スタート。(?<=\d)により2と3の間となります。

1,|2^34567

(?=(\d{3,3})+(?!\d))によって3,6,9個並ぶ数字ですが、残っている数字は5つなので3つだけ読み込みます。

1,2|345^67

それに続く文字は数字以外で無ければなりませんが、6は数字なのでマッチは失敗となります。なので置換は行わず、次を探しに行きます。

1,2|3^4567

これも失敗。次。

1,23|4^567

これは成功なので置換。

1,234,567

もうマッチするところがないので置換終了となります。これをプログラムで書くと次のようになります。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main(string[] args)
    {
        Regex r = new Regex(@"(?<=\d)(?=(\d{3,3})+(?!\d))");
        Console.WriteLine(r.Replace("123456789ms", ","));
        Console.WriteLine(r.Replace("1234567890ms", ","));
        Console.ReadLine();
    }
}
/* 結果
123,456,789ms
1,234,567,890ms*/

実際のアルゴリズムは違うかも知れませんが、考え方はこんな感じ(のハズ)。(^^;