Advent LINQ (7): Count

Enumerable.Count()メソッドの特徴を考えてみる。ただ要素数を数えるだけなら、以下のような実装で良い筈だ。

public static class LinqExtensions
{
    public static int Count<T>(this IEnumerable<T> enumerable)
    {
        var count = 0;
        foreach (var value in enumerable)
        {
            count++;
        }
        return count;
    }
}

当たり前だが、これでは要素を全て列挙しなければ個数が分からない。例えば:

// 100000000個の値が入ったリスト
var values = Enumerable.Range(0, 100000000).ToList();

// 有意に時間がかかる
var count1 = LinqExtensions.Count(values);

// こちらは一瞬
var count2 = Enumerable.Count(values);

何故、Enumerable.Count()は速いのだろうか。それは、列挙子の実行時の型を見て最適化を行っているからだ。ICollectionインターフェイスを実装しているクラスであれば、Countプロパティを参照するだけで個数が識別出来る。この方法を利用するには、以下のようにキャストで判定する。

public static class LinqExtensions
{
    public static int Count<T>(this IEnumerable<T> enumerable)
    {
        // 列挙子がICollectionを実装していれば
        var collection = enumerable as ICollection;
        if (collection != null)
        {
            // 直接Countプロパティを参照する
            return collection.Count;
        }

        var count = 0;
        foreach (var value in enumerable)
        {
            count++;
        }
        return count;
    }
}

列挙子として配列を使用する場合、配列の長さを調べるにはLengthプロパティを参照する。しかし、配列は暗黙にICollectionインターフェイスを実装しているため、わざわざ配列かどうかを確認しなくても、上記コードがそのまま使える。

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

Advent LINQ (5): Flatten

Advent LINQ (3)で、フォルダを探索しながらファイルを列挙するFilesAsEnumerableを作った。このような、木構造のシーケンスを探索するというシチュエーションは、色々ありそうだ。なので、このアルゴリズムを一般化する事を考える。

public static class LinqExtensions
{
    public static IEnumerable<U> Flatten<T, U>(
        this T node,
        Func<T, IEnumerable<T>> predictBySubNode,
        Func<T, IEnumerable<U>> predictByNode)
    {
        var bySubNode =
            from subNode in predictBySubNode(node)
            from childNode in Flatten(subNode, predictBySubNode, predictByNode)
            select childNode;

        var byNode =
            from childNode in predictByNode(node)
            select childNode;

        return bySubNode.Concat(byNode);
    }
}

これを使ってファイルの探索を行うには、以下のように記述する。

var baseDirectory = new DirectoryInfo(@"C:project");
foreach (var file in baseDirectory.Flatten(
    directory => directory.GetDirectories(),
    directory => directory.GetFiles("*.cs")))
{
    Console.WriteLine(file.FullName);
}

XmlDocumentから、テキストノードを抜き出してみる。

var document = new XmlDocument();
document.Load(@"C:projectdata.xml");
foreach (var text in document.DocumentElement.Flatten(
    element => element.SelectNodes("*").Cast<XmlElement>(),
    element => element.SelectNodes("text()").Cast<XmlText>()))
{
    Console.WriteLine(text.Value);
}

もちろん、XDocumentにも応用可能。

Advent LINQ (4): Buffering

列挙子を非同期で実行して、可能なら結果をキューに蓄積したい場合がある。列挙子の要素生成速度が十分に早ければ、並列実行出来ることになる。
並列実行コレクションに、丁度この目的に使えるBlockingCollectionクラスがある。

public static class LinqExtensions
{
    public static IEnumerable<T> Buffering<T>(this IEnumerable<T> enumerable, int queueCount = 10)
    {
        var queue = new BlockingCollection<T>(queueCount);
        Task.Factory.StartNew(() =>
            {
                try
                {
                    foreach (var value in enumerable)
                    {
                        queue.Add(value);
                    }
                }
                finally
                {
                    queue.CompleteAdding();
                }
            });

        return queue.GetConsumingEnumerable();
    }
}

使うときは、非同期化したい列挙子の直後に指定するだけだ。

var r = new Random();
foreach (var value in
    Enumerable.Range(0, 1000000).
    Select(index => r.Next()).
    Buffering(1000))
{
    Console.WriteLine(value);
}

これで、乱数の生成は最大1000個まで非同期で実行されてバッファリングされる。コンシューマー側(foreach)の処理が遅く、乱数の生成が早ければ、効率よく動作する。

Advent LINQ (3): FilesAsEnumerable

Directory.GetFilesが配列を返すので、列挙子にしてみる。特にSearchOption.AllDirectoriesが指定されると、処理が返って来るまでにかなり時間がかかることがある。
フォルダーは再帰探索する必要があるので、実装は少し捻る必要がある。

public static class LinqExtensions
{
    public static IEnumerable<string> FilesAsEnumerable(string path, string patterns)
    {
        foreach (var directoryPath in
            Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly))
        {
            foreach (var filePath in
                FilesAsEnumerable(directoryPath, patterns))
            {
                yield return filePath;
            }
        }

        foreach (var filePath in
            Directory.GetFiles(path, patterns, SearchOption.TopDirectoryOnly))
        {
            yield return filePath;
        }
    }
}

これでもいいのだが、foreachだらけで見通しが悪い。全てクエリに置き換える。

public static class LinqExtensions
{
    public static IEnumerable<string> FilesAsEnumerable(string path, string patterns)
    {
        var byDirectory =
            from directoryPath in Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly)
            from filePath in FilesAsEnumerable(directoryPath, patterns)
            select filePath;

        var byFile =
            from filePath in Directory.GetFiles(path, patterns, SearchOption.TopDirectoryOnly)
            select filePath;

        return byDirectory.Concat(byFile);
    }
}

これでファイルの列挙は以下のように記述できる。

foreach (var path in LinqExtensions.FilesAsEnumerable(@"C:project", "*.cs"))
{
    Console.WriteLine(path);
}

ファイルの逐次探索が可能になったので、大量のファイルをパイプライン処理できるようになった。
なお、このコードは.NET 4.0以降では、Directory.EnumerateFilesとほぼ同じ動作となる。

Advent LINQ (2): TextReader.AsEnumerable

TextReaderで読み取れる行を、列挙子に変えてみる。

public static class LinqExtensions
{
    public static IEnumerable<string> AsEnumerable(this TextReader tr)
    {
        try
        {
            while (true)
            {
                var line = tr.ReadLine();
                if (line == null)
                {
                    break;
                }
                yield return line;
            }
        }
        finally
        {
            tr.Dispose();
        }
    }
}

TextReaderは全て読み取った時点でDisposeを呼び出している。こうする事で、

foreach (var line in File.OpenText("VeryLongTextFile.txt").AsEnumerable())
{
    Console.WriteLine(line);
}

という使い方をしても、列挙が終わればリーダーがクローズされるようになる。
一旦列挙子になってしまえば、

// フォルダ内のすべてのテキストファイルの、先頭・終端の空白を削除し、
// 空行や#で始まる行を除外して、ソートして表示
foreach (var line in
    from path in
        Directory.GetFiles(".", "*.txt", SearchOption.AllDirectories).
        AsParallel()
    from line in
        File.OpenText(path).
        AsEnumerable()
    let trim = line.Trim()
    where (trim.Length >= 1) && (trim.StartsWith("#") == false)
    orderby trim
    select trim)
{
    Console.WriteLine(line);
}

こんなことも自在に行える。

Advent LINQ (1): SplitAsEnumerable

System.Stringクラスには、Splitというメソッドがある。引数で指定された文字で文字列を分割するメソッドなのだが、返却される結果は配列になっている。配列と言う事は、非常に多くの文字列が分割されると、それだけメモリを消費する事になる。
そんなわけで、このメソッドを逐次実行可能な列挙子を返すLINQメソッドとして定義してみる。

public static class LinqExtensions
{
    public static IEnumerable<string> SplitAsEnumerable(this string target, params char[] separators)
    {
        var startIndex = 0;
        while (true)
        {
            var index = target.IndexOfAny(separators, startIndex);
            if (index == -1)
            {
                yield return target.Substring(startIndex);
                break;
            }

            yield return target.Substring(startIndex, index - startIndex);
            startIndex = index + 1;
        }
    }
}

System.String.Splitはインスタンスメンバーなので、Splitというメソッド名にすると呼び出せなくなってしまう。仕方がないので、SplitAsEnumerableという名前で定義した。

var veryLongStringValue = "...";
foreach (var value in veryLongStringValue.SplitAsEnumerable(','))
{
    Console.WriteLine("Value={0}", value);
}

これで逐次分割できるようになった。ロジックを拡張して、ダブルクオートで囲まれた文字列を切り出す等の応用が出来ると思う。

Visual Studio Coverage file to Emma converter

CodePlex now!
And 1.0.1.0 released!!

Visual Studio Coverage file to Emma converter : CodePlex


Visual Studio Coverage file to Emma converter.
Simple solution, can apply only one tool to five Visual Studio versions.
Fast multicore processing.

Included:

  • Command line executable.
  • MSBuild custom task.

Thank you choosing this tool, probably use with jenkins.
(I’m not jenkins professional ops, may not operate this tool…)

Require:
.NET Framework 4.5.1 Runtime environment.
Visual Studio 2005, 2008, 2010, 2012 and/or 2013 versions (Require code coverage option)

Setup:
1. Copy VSCoverageToEmma.exe, VSCoverageToEmma.dll to your tool folder.
2. Copy VS
folders (ex:VS2013) to your tool folder.
3. Copy your Visual Studio’s “Microsoft.VisualStudio.Coverage..dll”
(In %VSROOTFOLDER%Common7IDEPrivateAssemblies) into VS
folders.

Tool folder example:

  Tool
    +--- VSCoverageToEmma.exe
    +--- VSCoverageToEmma.Interfaces.dll
    +--- VSCoverageToEmma.Core.dll
    +--- VSCoverageToEmma.VS2005Converter.dll
    +--- VSCoverageToEmma.VS2008Converter.dll
    +--- VSCoverageToEmma.VS2010Converter.dll
    +--- VSCoverageToEmma.VS2012Converter.dll
    +--- VSCoverageToEmma.VS2013Converter.dll
    +--- VS2005
    |       +--- Microsoft.VisualStudio.Coverage.Analysis.dll (From Visual Studio 2005, if use)
    +--- VS2008
    |       +--- Microsoft.VisualStudio.Coverage.Analysis.dll (From Visual Studio 2008, if use)
    +--- VS2010
    |       +--- Microsoft.VisualStudio.Coverage.Analysis.dll (From Visual Studio 2010, if use)
    |       +--- Microsoft.VisualStudio.Coverage.Interop.dll  (From Visual Studio 2010, if use)
    |       +--- Microsoft.VisualStudio.Coverage.Symbols.dll  (From Visual Studio 2010, if use)
    +--- VS2012
    |       +--- Microsoft.VisualStudio.Coverage.Analysis.dll (From Visual Studio 2012, if use)
    |       +--- Microsoft.VisualStudio.Coverage.Interop.dll  (From Visual Studio 2012, if use)
    |       +--- Microsoft.VisualStudio.Coverage.Symbols.dll  (From Visual Studio 2012, if use)
    +--- VS2013
            +--- Microsoft.VisualStudio.Coverage.Analysis.dll (From Visual Studio 2013, if use)
            +--- Microsoft.VisualStudio.Coverage.Interop.dll  (From Visual Studio 2013, if use)
            +--- Microsoft.VisualStudio.Coverage.Symbols.dll  (From Visual Studio 2013, if use)

Command line usage (VS2013):

  C:TEMPVSCoverageToEmmaDebug&gt;VSCoverageToEmma.exe VS2013 &quot;C:TEMPCoverageTargetTestResults&quot; &quot;C:TEMPCoverageTargetbinDebug&quot; &quot;C:TEMPCoverageTargetbinDebug&quot; &quot;C:TEMPCoverageTargetTestResults&quot;

If use VS2010/VS2012/VS2013 converter, automatically recursive search binary/symbol files in nested folders.

MSBuild usage:
Target assembly is “VSCoverageToEmma.Core.dll”, task name is “VSCoverageToEmma”.
ConverterName: required (ex: “VS2005”)
VSCoverageFolderPath: required (path)
BinariesFolderPath: optional (path)
SymbolsFolderPath: optional (path)
EmmaFolderPath: optional (path)
VSCoverageFiles: output (path list)
EmmaFiles: output (path list)
Methods: output (number)

MSBuild limitation: Tool execution failed in 64bit process. (VS Coverage library required 32bit mode)

Download from SkyDrive: VSCoverageToEmma-1.0.zip

Bar Windows 8.1 – BluetoothでGO!

Bar Windows 8 in 名古屋 with 8.1に登壇して、「BluetoothでGO!」というタイトルでセッションしてきました。

ご清聴ありがとうございました。プレゼンを置いておきます。
最後のデモで見せたコードはあまり整理されていません。反響があれば公開したいと思います。→ GitHubで公開しています。

BluetoothでGo!

次回も頑張ります (^^)

名古屋MS秋祭り – LINQソースでGO!

名古屋MS秋祭りにて登壇して、「LINQソースでGO!」というお題目でセッションをさせて頂きました。

詰め込み過ぎなのに時間が短すぎという結果で、大変分かりにくい解説になってしまったようで、またしても反省です m(_ _)m
参加者の皆様、ご静聴いただき、ありがとうございました。次回登壇の際には、これに懲りずに、宜しくお願いします。

ここにオリジナルプレゼンを置いておきます(アニメーションを使用しているので、オリジナルを見た方が良いです)。早すぎて理解不能だった場合の足しにしてください (^^;;