Advent LINQ (6): Match

Regexクラスを使うと正規表現を使って文字列のキャプチャが出来る。多少なりとも簡単に連続判定できるようにしてみる。

public static class LinqExtensions
{
    public static IEnumerable<Match> Match(
        this IEnumerable<string> sentences,
        string pattern,
        RegexOptions options = RegexOptions.Compiled | RegexOptions.Multiline)
    {
        var regex = new Regex(pattern, options);
        foreach (var sentence in sentences)
        {
            foreach (Match match in regex.Matches(sentence))
            {
                yield return match;
            }
        }
    }
}

これで、引数に正規表現パターンを指定するだけで、連続する文字列に処理が可能となった。

// 文字列中に存在するIPv4アドレスを抽出する
var sampleDatas = new[] { "IP:133.0.0.0", "IP:133.255.255.255 primary", "[192.50.0.0]", "IPn192.50.255.255n" };
foreach (var match in
    sampleDatas.Match(@"(?<1>d+)(?:.(?<1>d+)){3}"))
{
    Console.WriteLine(match);
}

上記の例では、ハードコードされた文字列を扱っているが、Advent LINQ (2)で示したTextReader.AsEnumerableと組み合わせれば、LINQクエリ一文でファイルからの正規表現検索が実現する。壮大な時計仕掛けをやってみよう。

// 指定されたフォルダ配下のテキストファイル群からIPv4アドレスを抽出する(並列実行)
foreach (var match in
    from path in Extensions.FilesAsEnumerable(@"C:project", "*.txt").AsParallel()
    from match in File.OpenText(path).AsEnumerable().Match(@"(?<1>d+)(?:.(?<1>d+)){3}")
    select match)
{
    Console.WriteLine(match);
}

MatchはIEnumerable<string>を受け取るようになっているので、列挙子ではなく単一の文字列を受けるには、1要素の配列を作る必要がある。この部分が分かりにくいのであれば、Match(this string sentence, …) のような、単一の文字列だけを受け取るようなメソッドに変更し、明示的にselect句で射影するようにしてもよい。ただ、その場合は、正規表現をコンパイルするメリットが失われるかもしれない。
参考: .NET Frameworkがサポートする正規表現クラスを徹底活用する