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がサポートする正規表現クラスを徹底活用する