[C#]Stringに正規表現の拡張メソッドを追加してLINQで使う

System::String に正規表現が使えたらいいなぁと思いつつ、書いてみたのがこれです。

■用法

        [TestMethod]
        public void TestMethod1()
        {
            string[] lst = {
                "masuda",
                "tomoaki",
                "yamasaki",
                "yumi" };

            var q = from t in lst
                    where t.IsMatch("y.*i")
                    select t;

            Assert.AreEqual(2, q.Count());
            Assert.AreEqual("yamasaki", q.First());
        }

        [TestMethod]
        public void TestMethod2()
        {
            string[] lst = {
                "masuda",
                "tomoaki",
                "yamasaki",
                "yumi" };

            var q = from t in lst
                    where t.IsMatch("ki$")
                    select new { match = t.Match("..ki$") };

            Assert.AreEqual(2, q.Count());
            Assert.AreEqual("oaki", q.First().match);
        }

単純に、Regex の Match, IsMatch メソッドを String クラスにくっつけただけなのですが、LINQ 内で直接使えるというのがメリットですね。別途、new Regex(…) を使えば、LINQ 内でも書けるのすが、それだと文が長くなってしまうしというパターンです。

■実装

実装はおそろしく簡単で以下な感じ。単純に Match, IsMatch をラップします。

using System.Text.RegularExpressions;

namespace Moonmile.StringRegex
{
    public static class StringRegexExtentions
    {
        public static string Match( this string target, string pattern )
        {
            var rx = new Regex(pattern);
            var mc = rx.Matches(target);
            return (mc.Count == 0) ? "" : mc[0].Value;
        }
        public static bool IsMatch(this string target, string pattern)
        {
            var rx = new Regex(pattern);
            return rx.IsMatch( target );
        }
    }
}

■利用方法

で、最終的には何をしたいのかという、以下な感じで2つのファイルの DIFF を調べたかったのです。
2つのファイルに、ファイル名の羅列があって、それを使って同期をするツールですね。マッチさせたい拡張子が限られているのでそれを内部で指定するという現場の即したツールです。

static void Main(string[] args)
{
#if false
	if (args.Length != 2)
	{
	    Console.WriteLine("usage: difffolders [src] [dest]");
	    return;
	}
	var lsrc = new List();
	var ldst = new List();

	StreamReader sr = File.OpenText(src);
	while (!sr.EndOfStream)
	    lsrc.Add(sr.ReadLine());
	sr.Close();
	sr = File.OpenText(dest);
	while (!sr.EndOfStream)
	    ldst.Add(sr.ReadLine());
	sr.Close();

	var llsrc = new List();
	var lldst = new List();

	string[] exts = {
	                    "\\.cpp", "\\.h", "\\.hpp", "\\.rc", "\\.h", "\\.f90", "\\.fi", "\\.for", "\\.inc", "\\.cmn"
	                    };
	foreach (var ext in exts)
	{
	    string ext1 = ext + "$";
	    llsrc.AddRange(lsrc.Where(n => n.IsMatch(ext1) == true).Select(n => n.Replace("C:\\Develop\\Main-branch-0","")));
	    lldst.AddRange(ldst.Where(n => n.IsMatch(ext1) == true).Select(n => n.Replace("C:\\Develop\\Main-branch-1","")));
	}
	llsrc.RemoveAll(n => n.IsMatch("\\Debug")); ;
	lldst.RemoveAll(n => n.IsMatch("\\Debug"));
	llsrc.Sort();
	lldst.Sort();

	// 差分を計算する
	Debug.Print("src count: {0}", llsrc.Count);
	Debug.Print("dst count: {0}", lldst.Count);

	var onlysrc = llsrc.Where(n => !lldst.Contains(n)).Select(n => n );
	var onlydst = lldst.Where(n => !llsrc.Contains(n)).Select(n => n );
	var andlst = llsrc.Where(n => lldst.Contains(n)).Select(n => n);

	Debug.Print("only/and {0} {1} {2}", onlysrc.Count(), onlydst.Count(), andlst.Count());

	StreamWriter sw = new StreamWriter(File.OpenWrite("out-diff.txt"));
	foreach( var f in onlysrc )
	    sw.WriteLine("- {0}", f );
	foreach (var f in onlydst)
	    sw.WriteLine("+ {0}", f);
	foreach (var f in andlst)
	    sw.WriteLine("m {0}", f);
	sw.Close();
}

で、これを作るうえで困ったのが、.NET の正規表現のまずさ(Regexの実装かな?)ですね。本来ならば、

var q = llsrc.Where( n => n.IsMatch("\\.(cpp|h|hpp|rc|f98|for|fi|inc|cmn)").Select( n => n );

のように一発で書きたいところなのですが、.NET 正規表現では、OR 演算子が無い模様。Group を使うんですかね?

2012/09/18 追記

とか思ったら、

代替構成体
http://msdn.microsoft.com/ja-jp/library/36xybswe(v=vs.100).aspx

に「|」が使えるやんッ!!! ってことで、多分先のコードは書けるハズ。後で書き直しますか。

 

 

 

正規表現クラス
http://msdn.microsoft.com/ja-jp/library/30wbz966(v=vs.80)

仕方がないので array で用意して foreach で廻しています。Perl 互換の正規表現ライブラリがあればそれを使いたいなと。

ちなみに、ファイル名から拡張子を取る Path.GetExtension を使っても良いんですけどね。

	string[] exts = {
            ".cpp", ".h", ".hpp", ".rc", ".h", ".f90", ".fi", ".for", ".inc", ".cmn"
            };

	foreach ( var n in lsrc ) {
		if (exts.Contains(Path.GetExtension(n)))
		{
			llsrc.Add(n.Replace("C:\\Develop\\Main-branch-0", ""));
		}
	}
	foreach ( var n in ldst ) {
		if (exts.Contains(Path.GetExtension(n)))
		{
			lldst.Add(n.Replace("C:\\Develop\\Main-branch-1", ""));
		}
	}
カテゴリー: C# パーマリンク

[C#]Stringに正規表現の拡張メソッドを追加してLINQで使う への3件のフィードバック

  1. ピンバック: .NET Clips

  2. neuecc のコメント:

    単純な正規表現の場合はRegex.IsMatchなどの静的メソッドを使うと良いです。
    というのも、それらはキャッシュされているため、
    短期間に繰り返し使う場合は、キャッシュから取り出され高速に動作するためです。
    # Regex.CacheSizeで設定可能で、デフォルトは15です

    とはともかくとして
    > .NET 正規表現では、OR 演算子が無い模様
    というのが分かりません。
    あるのでは?
    しいて
    “\\.(cpp|h|hpp|rc|f98|for|fi|inc|cmn)”
    に関していうなら、拡張子の判定ならば末尾も含めなきゃダメなので$が足りないとか
    そういうことだとは思いますが。 #サンプルコードのループの例には含まれてますが

    あと、サンプルコード中の
    .Select(n => n )
    は完全に意味ないので、なしで良いかと。

    あと、この程度のリストのサイズだと微妙なところですが、
    含むかどうかのチェックならHashSetも。

    • masuda のコメント:

      コメント Thx です。
      「“\\.(cpp|h|hpp|rc|f98|for|fi|inc|cmn)”」に関して云えば、調査ミス…という訳で、MSDN に記述がありました。なんか、以前からこれに当たらなかったので、ないと思い込んでいたのです、申し訳ない。

      キャッシュ/高速化関係は、まぁ、おまけツール(30分で作って10分使って捨てるツール)なので。1万ファイルを比較して、50ファイル位の更新を視覚チェックしたのちに、自動でマージさせる中ツールでした。LINQ で biff もどき、とか考えていたのですが、winbiff がテキスト出力ができるのを知って、まあ、これで十分かということで放置なのです。

コメントは停止中です。