抽象的な話ばかり続いたので、今回は、実用的な例を示そう。
.NETでCSVファイルを読み取るとき、まさか自分でパースしたりしていないと思うが、知っていると便利なクラスが「VB.NET」のライブラリに存在する。TextFieldParserクラスだ。VB向けの実装の割には、Streamからの読み取りに対応しているなど、割としっかり作ってある。
今回はこのクラスをLINQで「楽に」使えるようにする。
public static class TextField
{
// 指定されたCSVファイルへのコンテキストを生成する
public static IEnumerable<string[]> Context(
string path, string separator = ",", Encoding encoding = null)
{
using (Stream stream =
new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (TextFieldParser parser =
new TextFieldParser(stream, encoding ?? Encoding.UTF8, true, false))
{
parser.TextFieldType = FieldType.Delimited;
parser.Delimiters = new[] { separator };
parser.HasFieldsEnclosedInQuotes = true;
parser.TrimWhiteSpace = true;
while (parser.EndOfData == false)
{
string[] fields = parser.ReadFields();
yield return fields;
}
}
}
}
}
このクラスのアイデアは、LINQ to Entitiesだ。Contextメソッドで一旦「コンテキスト」を作ってしまえば、面倒な事を一切考えなくても読み取りが可能になる。これでロジックから解放だ。
郵便局 郵便番号データ(全国)を使う。
static void Main(string[] args)
{
// 郵便番号データのコンテキストを生成
IEnumerable<string[]> context = TextField.Context(
@"KEN_ALL.CSV", ",", Encoding.GetEncoding(932));
// (クエリは遅延実行なので、以下の式ではまだ処理を開始していない)
IEnumerable<string> results =
// コンテキストからフィールド群を取得
from fields in
context.
AsParallel() // 並列化
// 郵便番号データの住所部分に「字」と「条」が含まれている行を抽出し、
where fields[8].Contains("字") || fields[8].Contains("条")
// 郵便番号の降順でソートし、
orderby fields[2] descending
// 文字列形式にフォーマットする
select string.Format("〒{0} {1}{2}{3}", fields[2], fields[6], fields[7], fields[8]);
// 取りあえず、結果をコンソールに出してみた(ここでGetEnumeratorが呼ばれて実行が開始される)
foreach (string result in results)
{
Console.WriteLine(result);
}
}
データ量が少ないので、並列化の恩恵はあまりないが、AsParallelしてみた。AsParallelを付けたり外したりしてみて、タスクマネージャのCPUリソースの具合を確認して見ると良い。
前回、LINQのパイプライン実行について説明したが、上記のコードの良い点は、コンテキストからデータが流れるようにやってきて、クエリで加工されて結果が出力される(コンソールに表示される)というところだ。実は、orderby句(OrderByDescending拡張メソッド)は、内部でバッファリングを行っているので、件数に応じてメモリ使用量が増加してしまうが、orderbyが無ければゴミをGCが回収するため、メモリ使用量が増え続けてしまう事はない。
(orderbyはソートを行うのだが、ソートを行うためにはすべてのデータが揃わなければならない。いくらパイプライン実行でも、不可能なことはある。その代わり、AsParallelしていれば、スレッド数に応じてソートが高速化される。)
さあ、仕上げだ。目の前にCSVファイルの山があったとしよう。
static void Main(string[] args)
{
IEnumerable<string> results =
from path in
Directory.GetFiles("CSV_FILES", "*.csv", SearchOption.AllDirectories).
AsParallel()
from fields in
TextField.Context(path, ",", Encoding.GetEncoding(932))
where fields[8].Contains("字") || fields[8].Contains("条")
orderby fields[2] descending
select string.Format("〒{0} {1}{2}{3}", fields[2], fields[6], fields[7], fields[8]);
foreach (string result in results)
{
Console.WriteLine(result);
}
}
涙が出るほど簡単で、しかも速い。鳥肌が立つね :-)
1 thoughts on “LINQは本当に強力だ (6) TextFieldContext”
コメントは停止中です。