『ピアノの森』が最終回を迎えました。
連載開始から18年とのこと。ものすごい長期連載が大団円を迎えました。
最初の連載はアッパーズで、当時掲載誌で読んでました。アッパーズは好きだったなぁ。
長らく楽しませていただきました。最終回は、ありゃ泣くよ。
お疲れ様でした。次回作はどんなお話でしょうか。楽しみにしてます。
それと、『決してマネしないでください』が次回で最終回とのこと。これも楽しみだっただけに、正直もう少し続けてほしかったなぁ。
2015年11月5日木曜日
2015年10月22日木曜日
SJISのCSVファイルを各カラムの操作と条件による絞り込みを行い、UTF-8のCSVファイルを出力する処理をLINQで。(改)
もともとは「ラムダ式を利用したリファクタリングの例 その2」で扱った後、さらに「SJISのCSVファイルを各カラムの操作と条件による絞り込みを行い、UTF-8のCSVファイルを出力する処理をLINQで。」で修正版を示したネタです。改二ですな。
こんな要求に対する処理を書いていました。
という処理になります。日本語とほぼ一対一にコードが対応してます。ちなみに、.net 2.0相当でコードを書くとこんな感じ。
こんな要求に対する処理を書いていました。
- Shift-JISのCSVファイルを入力し、UTF-8のCSVファイルを出力する。
- 入力したCSVの各カラムは、固定長で前後に空白が入る可能性があり、その空白は除去して出力する。
- 各行の先頭のカラムはIDになっていて、特定のIDのみ出力対象とする。
inFileが入力ファイルのパス、outFileが出力ファイルのパスとして、絞り込みの条件を「先頭カラムが奇数」とすると、以下のコードで拡張メソッドとか作らなくても要求が満たせてしまいますね。
File.WriteAllLines( outFile, File.ReadLines(inFile, Encoding.GetEncoding("shift-jis")) .Select(line => line.Split(',')) .Where(items => int.Parse(items[0]) % 2 != 0) .Select(items => string.Join(",", items.Select(item => item.Trim()))));
- inFileからShift-JISで各行を読み込み、
- ',' で分割し、
- 先頭カラムが奇数の行について、
- 各カラムの前後の空白を除去した上で再度 ',' で結合し、
- outFileに書き込む。
という処理になります。日本語とほぼ一対一にコードが対応してます。ちなみに、.net 2.0相当でコードを書くとこんな感じ。
var sb = new StringBuilder(); string line; using (var sr = new StreamReader(inFile, Encoding.GetEncoding("shift-jis"))) using (var sw = new StreamWriter(outFile)) { while ((line = sr.ReadLine()) != null) { var items = line.Split(','); sb.Length = 0; if (int.Parse(items[0]) % 2 != 0) { foreach (var item in items) { sb.Append(item.Trim()).Append(','); } sw.WriteLine(sb.ToString(0, sb.Length - 1)); } } }
雲泥の差がありますねぇ…。
2015年7月6日月曜日
C#でLEFT OUTER JOIN (左外部結合)
LINQを使って、「LEFT OUTER JOIN」をしたかった。普通のJoinだとINNER JOINなので、少し工夫する必要がありそう。ググってみると、以下のようなコードが一般的なようだ。
ちなみにこれは、MSDNの「方法 : 左外部結合を実行する (C# プログラミング ガイド)」から。
大体どこを見てもこんな感じで、ほとんど左外部結合をしたいときの慣用句(イディオム)みたいなもののよう。とはいえ、このコードを見て「あ、左外部結合させたいんだな」と思える人がどれくらいいるか。要は、コードから意図が解りづらいので、あまり好みじゃないなぁ。と。
もう少し、コードを見て左外部結合であることが解るように、拡張メソッドを作ってみた。こんなの。
Enumerable.Joinメソッドと比べると、最後のパラメータが余計についてます。これはOuterの要素に対して合致するInnerの要素がなかった時の代替要素を指定するもの。Null Objectだと思っておけばよいと思います。
あと、名前は長いのを嫌い、「OuterJoin」としています。タイプパラメータからも、左が外なのは明らかなので、特に問題はないと思っています。
ちなみに、これをライブラリ的に用意するなら、GroupJoinするときにEqautityComparerを指定するパターン、それと、DefaultIfEmptyでTInnerのデフォルト値を自動的に使うパターン、その組み合わせで4つのオーバーロードを、用意しておくのがよいと思います。
さて、テストします。
結果はこう。
…右外部結合?RIGHT OUTER JOINか。僕自身使ったことないですが、必要なら右左を入れ替えてあげればいいはず。上のコードの20行目から28行目を、こんな感じに置き換えてみる。
結果はこう。
うん。いんじゃないかな?
…完全外部結合?…FULL OUTER JOIN…。必要?それ。
使い道が解らなくてモチベーションゼロだけど、要するに左外部結合+右のみに存在するレコードを、編集してUnionすればいいんじゃないかな?多分。
var query = from person in people join pet in pets on person equals pet.Owner into gj from subpet in gj.DefaultIfEmpty() select new { person.FirstName, PetName = (subpet == null ? String.Empty : subpet.Name) };
ちなみにこれは、MSDNの「方法 : 左外部結合を実行する (C# プログラミング ガイド)」から。
大体どこを見てもこんな感じで、ほとんど左外部結合をしたいときの慣用句(イディオム)みたいなもののよう。とはいえ、このコードを見て「あ、左外部結合させたいんだな」と思える人がどれくらいいるか。要は、コードから意図が解りづらいので、あまり好みじゃないなぁ。と。
もう少し、コードを見て左外部結合であることが解るように、拡張メソッドを作ってみた。こんなの。
public static class EnumerableEx { public static IEnumerable<TResult> OuterJoin<TOuter, TInner, TKey, TResult> (this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector, TInner innerDefaultValue) { return outer .GroupJoin( inner, outerKeySelector, innerKeySelector, (o, i) => new { Out = o, Ins = i.DefaultIfEmpty(innerDefaultValue) }) .SelectMany(g => g.Ins, (g, i) => resultSelector(g.Out, i)); } }
Enumerable.Joinメソッドと比べると、最後のパラメータが余計についてます。これはOuterの要素に対して合致するInnerの要素がなかった時の代替要素を指定するもの。Null Objectだと思っておけばよいと思います。
あと、名前は長いのを嫌い、「OuterJoin」としています。タイプパラメータからも、左が外なのは明らかなので、特に問題はないと思っています。
ちなみに、これをライブラリ的に用意するなら、GroupJoinするときにEqautityComparerを指定するパターン、それと、DefaultIfEmptyでTInnerのデフォルト値を自動的に使うパターン、その組み合わせで4つのオーバーロードを、用意しておくのがよいと思います。
さて、テストします。
static void Main(string[] args) { var products = new[] { new { ProductId = 1, Name = "えんぴつ" }, new { ProductId = 2, Name = "けしごむ" }, new { ProductId = 3, Name = "コンパス" }, new { ProductId = 4, Name = "クレヨン" }, }; var sales = new[] { new { SaleId = 1, ProductId = 1, Buyer = "○○商会", Quantity = 2 }, new { SaleId = 2, ProductId = 1, Buyer = "××文具店", Quantity = 4 }, new { SaleId = 3, ProductId = 2, Buyer = "△屋", Quantity = 3 }, new { SaleId = 4, ProductId = 4, Buyer = "○○商会", Quantity = 1 }, new { SaleId = 5, ProductId = 5, Buyer = "○○商会", Quantity = 1 }, }; var nosaled = new { SaleId = 0, ProductId = 0, Buyer = "(no sales)", Quantity = 0 }; var salesInfo = products.OuterJoin( sales, p => p.ProductId, s => s.ProductId, (p, s) => new { p.Name, s.Buyer, s.Quantity }, nosaled); foreach (var i in salesInfo) { Console.WriteLine("{0}, {1}, {2}", i.Name, i.Buyer, i.Quantity); } }
結果はこう。
えんぴつ, ○○商会, 2 えんぴつ, ××文具店, 4 けしごむ, △屋, 3 コンパス, (no sales), 0 クレヨン, ○○商会, 1
…右外部結合?RIGHT OUTER JOINか。僕自身使ったことないですが、必要なら右左を入れ替えてあげればいいはず。上のコードの20行目から28行目を、こんな感じに置き換えてみる。
var noproduct = new { ProductId = 0, Name = "(no item)" }; var salesInfo = sales.OuterJoin( products, s => s.ProductId, p => p.ProductId, (s, p) => new { p.Name, s.Buyer, s.Quantity }, noproduct);
結果はこう。
えんぴつ, ○○商会, 2 えんぴつ, ××文具店, 4 けしごむ, △屋, 3 クレヨン, ○○商会, 1 (no item), ○○商会, 1
うん。いんじゃないかな?
…完全外部結合?…FULL OUTER JOIN…。必要?それ。
使い道が解らなくてモチベーションゼロだけど、要するに左外部結合+右のみに存在するレコードを、編集してUnionすればいいんじゃないかな?多分。
2015年7月3日金曜日
GroupByとToLookup
ここしばらく、C#関連記事ではGroupByを使ったものが続いています。
ところで、.net Frameworkには、GroupByと非常によく似た「ToLookup」というEnumerable拡張メソッドがあります。大体名前のイメージで、GroupByは遅延実行で、ToLookupは即時実行だと思い、基本的に使い分けていました。
もう少し調べてみると、ToLookupはその名の通りルックアップテーブルを作るメソッドで、戻されるLookupクラスは、キーからのルックアップを機能として有している。と。
ところがふと、過去にGroupByを使っていて、ToLookupを使ったほうがよいケースがあったりするんじゃないかと不安になってきまして。たとえば何も考えずにGroupByを使っていた箇所で、ToLookupを使ったほうが実はよかった。とかいうケースがあったらいやだなぁ。と。
なので、とりあえず処理時間を計ってみました。使ったのはこんなコード。
先日の『ToDictionaryで重複のない辞書を作る』と同様に、コレクションからキー重複を排除した辞書を作る処理にGroupByを使ってみて、単純にGroupBy→ToLookupに置き換えたときにどれくらい処理速度の差があるか?ほんの少しGroupByが早いんじゃないかな?と予想。
結果は、
GroupBy … 6155msec。
ToLookup … 6081msec。
となり、予想に反してほんの誤差レベルとはいえGroupByのほうが遅い結果に。なんでだろう?と思ってソースコードを調べてみた。
GroupByは大体こんな感じ。
それに対して、ToLookupはこんな感じ。
あー。これだとほぼ即時実行か遅延実行かの違いしかないですねぇ。これならToLookupが若干速いのも納得できる。
というか、そうするとGroupByの存在意義が解らなくなったので、ちょっと調べてみたら、Stackoverflowに「Why are ToLookup and GroupBy different?」こんな質問があって、その回答が以下。
気にしなければならない速度差では全くないですしね。
ところで、.net Frameworkには、GroupByと非常によく似た「ToLookup」というEnumerable拡張メソッドがあります。大体名前のイメージで、GroupByは遅延実行で、ToLookupは即時実行だと思い、基本的に使い分けていました。
もう少し調べてみると、ToLookupはその名の通りルックアップテーブルを作るメソッドで、戻されるLookupクラスは、キーからのルックアップを機能として有している。と。
ところがふと、過去にGroupByを使っていて、ToLookupを使ったほうがよいケースがあったりするんじゃないかと不安になってきまして。たとえば何も考えずにGroupByを使っていた箇所で、ToLookupを使ったほうが実はよかった。とかいうケースがあったらいやだなぁ。と。
なので、とりあえず処理時間を計ってみました。使ったのはこんなコード。
static void Main(string[] args) { var rnd = new Random(DateTime.Now.Millisecond); var items = Enumerable.Range(0, 10000000).Select(_ => new { Key1 = rnd.Next(10), Key2 = rnd.Next(100), Value = rnd.Next(10000) }); var sw = new Stopwatch(); sw.Start(); var dic = items.GroupBy(i => new { i.Key1, i.Key2 }) .ToDictionary(g => g.Key, g => g.First().Value); sw.Stop(); Console.WriteLine("Elapsed:{0}msec", sw.ElapsedMilliseconds); }
先日の『ToDictionaryで重複のない辞書を作る』と同様に、コレクションからキー重複を排除した辞書を作る処理にGroupByを使ってみて、単純にGroupBy→ToLookupに置き換えたときにどれくらい処理速度の差があるか?ほんの少しGroupByが早いんじゃないかな?と予想。
結果は、
GroupBy … 6155msec。
ToLookup … 6081msec。
となり、予想に反してほんの誤差レベルとはいえGroupByのほうが遅い結果に。なんでだろう?と思ってソースコードを調べてみた。
GroupByは大体こんな感じ。
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) { return new GroupedEnumerable<TSource, TKey, TSource>( source, keySelector, IdentityFunction<TSource>.Instance, null); } internal class GroupedEnumerable<TSource, TKey, TElement> : IEnumerable<IGrouping<TKey, TElement>> { public IEnumerator<IGrouping<TKey, TElement>> GetEnumerator() { return Lookup<TKey, TElement>.Create<TSource>( source, keySelector, elementSelector, comparer).GetEnumerator(); } }
それに対して、ToLookupはこんな感じ。
public static ILookup<TKey, TSource> ToLookup<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) { return Lookup<TKey, TSource>.Create( source, keySelector, IdentityFunction<TSource>.Instance, null); }
あー。これだとほぼ即時実行か遅延実行かの違いしかないですねぇ。これならToLookupが若干速いのも納得できる。
というか、そうするとGroupByの存在意義が解らなくなったので、ちょっと調べてみたら、Stackoverflowに「Why are ToLookup and GroupBy different?」こんな質問があって、その回答が以下。
What happens when you call ToLookup on an object representing a remote database table with a billion rows in it?なるほど。例えばデータソースがObjectではなくDatabaseだったりしたときに、即時実行しないで済むならそうしたい。LINQのメソッドはデータソースを問わないわけだから、基本的にはGroupByを使い、明示的に即時実行にしたい場合や、実行後のルックアップが必要なら、ToLookupを使うべし。と、解釈して納得できた。
気にしなければならない速度差では全くないですしね。
2015年6月30日火曜日
『月光~the lunatic~』
2015年6月25日木曜日
『僕だけがいない街』アニメ化決定
『僕だけがいない街』の最新刊の発売はいつかな?と、調べていたらこんなのを見つけました。
『僕だけがいない街』アニメ化決定&最新刊発売!各界の著名人も絶賛
え?アニメ化?まじで?だって、未完結の状態でこれアニメになんかできないでしょ?と思いつつ、記事を読むと、『2016年1月フジテレビ「ノイタミナ」枠でアニメスタート』とあった。
これは全部妄想だけど、アニメが2クールだとすると、2016年6月にアニメが最終回になるわけで。そうすると、2015年7月に単行本6巻が発売で、1冊出るのに半年かかるので、2016年1月に7巻、2016年7月に8巻が、最終巻として出るなら帳尻は合う。一回くらい休載しても何とか間に合うか。
『四月は君の嘘』もそんな感じじゃなかったっけ?あれもノイタミナ。
そーすると、あと12話で完結??
…なんてことを考えながら、はやく6巻読みたいな。単行本で追いかけてるので、続きが気になって。
『僕だけがいない街』アニメ化決定&最新刊発売!各界の著名人も絶賛
え?アニメ化?まじで?だって、未完結の状態でこれアニメになんかできないでしょ?と思いつつ、記事を読むと、『2016年1月フジテレビ「ノイタミナ」枠でアニメスタート』とあった。
これは全部妄想だけど、アニメが2クールだとすると、2016年6月にアニメが最終回になるわけで。そうすると、2015年7月に単行本6巻が発売で、1冊出るのに半年かかるので、2016年1月に7巻、2016年7月に8巻が、最終巻として出るなら帳尻は合う。一回くらい休載しても何とか間に合うか。
『四月は君の嘘』もそんな感じじゃなかったっけ?あれもノイタミナ。
そーすると、あと12話で完結??
…なんてことを考えながら、はやく6巻読みたいな。単行本で追いかけてるので、続きが気になって。
2015年6月22日月曜日
ToDictionaryで重複のない辞書を作る
シンプルなTipsをひとつ。
LINQのToDictionaryメソッドは、コレクションから辞書を簡単に作ってくれる、使用頻度の高い便利なメソッドです。ただ、指定したキーセレクタの結果、キーが重複していると例外(ArgumentException)をスローします。例えば、以下のようなコード。
この例では、文字列の最初の文字をキーにするので、キー'T'が重複します。しかし、キーに対して最初に現れた値を登録するような、単純なルールで重複を排除して辞書を作りたいケースがあります。
さて、どう解決しましょうか。ToDictionaryのメソッドで、重複を排除するようなオーバーロードがあれば簡単ですが、ないですね。なので、こんなコードではどうでしょうか?
ToDictionaryの前に、一旦最初の文字でグループ化しておいて、そのグループキーと最初の値を辞書のエントリとしてみました。
LINQのToDictionaryメソッドは、コレクションから辞書を簡単に作ってくれる、使用頻度の高い便利なメソッドです。ただ、指定したキーセレクタの結果、キーが重複していると例外(ArgumentException)をスローします。例えば、以下のようなコード。
var collection = new [] { "One", "Two", "Three", "Four" }; var dic = collection.ToDictionary(c => c.First());
この例では、文字列の最初の文字をキーにするので、キー'T'が重複します。しかし、キーに対して最初に現れた値を登録するような、単純なルールで重複を排除して辞書を作りたいケースがあります。
さて、どう解決しましょうか。ToDictionaryのメソッドで、重複を排除するようなオーバーロードがあれば簡単ですが、ないですね。なので、こんなコードではどうでしょうか?
var collection = new [] { "One", "Two", "Three", "Four" }; var dic = collection.GroupBy(c => c.First()) .ToDictionary(g => g.Key, g => g.First());
ToDictionaryの前に、一旦最初の文字でグループ化しておいて、そのグループキーと最初の値を辞書のエントリとしてみました。
2015年4月16日木曜日
2015年1月14日水曜日
Import時の重複チェックにLINQ(GroupBy)を利用する
外部のシステムからデータをインポートする機能は、多くのシステムで必要になってきます。CSVなりXMLなりの標準化された形式で送られたデータを、一括でシステムに取り込む機能です。
この場合、普通の入力とは異なり、一括で処理するゆえのメンドクササがありますよね。
たとえば、ユーザ情報を外部システムからインポートする場合。想定するケースによりますが、ユーザIDがインポートデータ中に重複して存在する可能性があったりすると、せめて取り込みの事前のチェックを行っておきたいケースもあると思います。が、これが結構面倒。
これをGroupByメソッドを活用して、なるべくシンプルに記述してみます。想定する前提はこんな感じだとします。
この場合、普通の入力とは異なり、一括で処理するゆえのメンドクササがありますよね。
たとえば、ユーザ情報を外部システムからインポートする場合。想定するケースによりますが、ユーザIDがインポートデータ中に重複して存在する可能性があったりすると、せめて取り込みの事前のチェックを行っておきたいケースもあると思います。が、これが結構面倒。
これをGroupByメソッドを活用して、なるべくシンプルに記述してみます。想定する前提はこんな感じだとします。
- インポートするユーザ情報データは、すでにオブジェクトへのマッピングがされており、コレクション化されている前提。
- ユーザ情報データは「UserInfo」クラスに格納されており、ユーザIDは文字列のメンバUserIdとして存在する。
- ユーザ情報データのコレクションを入力として、ユーザIDの重複チェックを行う静的メソッド「ValidateNotDuplicated」を作成する。
- このメソッドは、ユーザIDの重複があった場合、コンソールにエラーメッセージを出力し、メソッドの戻り値はfalseを返す。重複がなければそのままtrueを返す。
- エラーメッセージには、重複しているユーザIDを表示する。複数のユーザIDが重複していた場合は、そのすべてを表示する。
このような処理を書くと、こんな感じになりますね。
public static bool ValidateNotDuplicated(IEnumerable<UserInfo> users) { var dupusers = users.GroupBy(u => u.UserId) .Where(g => g.Count() > 1) .Select(g => g.Key) .ToArray(); if (dupusers.Length > 0) { Console.WriteLine("User:{0} was duplicated.", string.Join(",", dupusers); return false; } return true; }
つまり、「コレクションをUserIdでグループ化し(GroupBy)」、「そのうち2つ以上存在するものを選び出し(Where)」、「キーとなっているUserIdのみを対象として(Select)」、「配列に変換する(ToArray)」。という処理を行っています。
ちょっとだけバリエーションを考えて見ましょうか。たとえば、
ちょっとだけバリエーションを考えて見ましょうか。たとえば、
- 「UserInfo」クラスには、データが更新された時間を示す「Updated」メンバ(DateTime型)も持っている。
- このユーザ情報データのコレクションを入力として、ユーザIDが重複していた場合、「Updated」が最新のもの以外を取り除いたコレクションを返す、静的メソッド「SelectLatest」を作成する。
この場合ならもっとシンプルで、
public static IEnumerable<UserInfo> SelectLatest(IEnumerable<UserInfo> users) { return users.GroupBy(u => u.UserId) .Select(g => g.OrderByDescending(u => u.Updated).First()); }
これだけになりますね。
2015年1月12日月曜日
Enumerable.GroupByメソッドの威力
2015年も明けて間もなく2週間が経とうとしています。
おそくなりましたが、今年もよろしくお願いします。
今年の目標は「去年のエントリ数を超える」こと。2014年は5本だったようなので、達成できそうな目標にしてみました。
一発目のエントリは、「GroupBy」をテーマにしてみます。非常に強力なLINQのメソッドですが、なかなか使う機会が少ない人も多いのではないかと思います。
「GroupBy」メソッドは、SQLを触ったことのある人なら「group by」句をイメージしてもらうとほぼ間違いなしで、つまりは「コレクション内の各要素を、ある条件でグループ分けする」メソッドです。ぱっと見面倒そうですが、使い方はいたってシンプルです。
サンプルを書いてみましょう。
おそくなりましたが、今年もよろしくお願いします。
今年の目標は「去年のエントリ数を超える」こと。2014年は5本だったようなので、達成できそうな目標にしてみました。
一発目のエントリは、「GroupBy」をテーマにしてみます。非常に強力なLINQのメソッドですが、なかなか使う機会が少ない人も多いのではないかと思います。
「GroupBy」メソッドは、SQLを触ったことのある人なら「group by」句をイメージしてもらうとほぼ間違いなしで、つまりは「コレクション内の各要素を、ある条件でグループ分けする」メソッドです。ぱっと見面倒そうですが、使い方はいたってシンプルです。
サンプルを書いてみましょう。
- データメンバとして、日付(Date)と売上額(Amount)を持つSalesクラスがあります。
- Salesクラスは、ある指定期間の日別の売上額を列挙するEnumerate静的メソッドを持ちます。
- ここで、上記Salesクラスを使い、ある年(ここでは2014年)の曜日別の売り上げの総合計、平均値、中間値を算出します。
この処理は以下のコードになります。
static void ShowGroupedAmounts() { var from = new DateTime(2014, 1, 1); var to = new DateTime(2014, 12, 31); var group = Sales.Enumerate(from, to) .GroupBy(s => s.Date.DayOfWeek) .OrderBy(g => g.Key); Console.WriteLine("DayOfWeak,Total Amount,Average,Median"); foreach (var g in group) { var cnt = g.Count(); Console.WriteLine( "{0},{1},{2},{3}", g.Key, g.Sum(s => s.Amount), Math.Round(g.Average(s => s.Amount), 1), g.OrderBy(s => s.Amount).Skip(cnt / 2).First().Amount); } }
非常にシンプルなコードで、キー(ここでは曜日)ごとにグループ化できていると思います。なお、GroupByメソッドの戻り値は以下の形態になります。
IEnumerable<IGrouping<DayOfWeak, Sales>>
IGroupingは、IEnumerable<T>にキー情報を加えたもの。と考えればOKです。
登録:
投稿 (Atom)