2015年11月5日木曜日

今週のモーニング

『ピアノの森』が最終回を迎えました。

連載開始から18年とのこと。ものすごい長期連載が大団円を迎えました。
最初の連載はアッパーズで、当時掲載誌で読んでました。アッパーズは好きだったなぁ。
長らく楽しませていただきました。最終回は、ありゃ泣くよ。

お疲れ様でした。次回作はどんなお話でしょうか。楽しみにしてます。

それと、『決してマネしないでください』が次回で最終回とのこと。これも楽しみだっただけに、正直もう少し続けてほしかったなぁ。

2015年10月22日木曜日

SJISのCSVファイルを各カラムの操作と条件による絞り込みを行い、UTF-8のCSVファイルを出力する処理をLINQで。(改)

もともとは「ラムダ式を利用したリファクタリングの例 その2」で扱った後、さらに「SJISのCSVファイルを各カラムの操作と条件による絞り込みを行い、UTF-8のCSVファイルを出力する処理をLINQで。」で修正版を示したネタです。改二ですな。

こんな要求に対する処理を書いていました。
  • 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なので、少し工夫する必要がありそう。ググってみると、以下のようなコードが一般的なようだ。

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を使ったほうが実はよかった。とかいうケースがあったらいやだなぁ。と。

なので、とりあえず処理時間を計ってみました。使ったのはこんなコード。

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~』

イブニングで5週掲載された、ウチヤマユージさんの『月光~the lunatic~』の単行本が発売されたようなので、おススメを。



表題作と他一遍の中編集。イブニング掲載時に読みましたが、すごく良かった。絵も話もとても好み。

静かに始まる第一話。一見他愛ない日常に見えて、ちらほらと異常がちりばめられる。
話が進むと時間軸を行ったり来たりしながら展開していき、少しずつ全容が見えてくる。
すべてが明らかになった後、結末は「んなあほな」と思いつつ、なんだか妙に晴れやかな気分。

この作者さんは知りませんでしたが、今後に期待大です。

2015年6月25日木曜日

『僕だけがいない街』アニメ化決定

『僕だけがいない街』の最新刊の発売はいつかな?と、調べていたらこんなのを見つけました。

『僕だけがいない街』アニメ化決定&最新刊発売!各界の著名人も絶賛

え?アニメ化?まじで?だって、未完結の状態でこれアニメになんかできないでしょ?と思いつつ、記事を読むと、『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)をスローします。例えば、以下のようなコード。

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日木曜日

来週のサンデー

銀の匙が載りますか!戻ってきてくれてよかった。
BIRDMENも掲載される号だし。楽しみだー。

2015年1月14日水曜日

Import時の重複チェックにLINQ(GroupBy)を利用する

外部のシステムからデータをインポートする機能は、多くのシステムで必要になってきます。CSVなりXMLなりの標準化された形式で送られたデータを、一括でシステムに取り込む機能です。

この場合、普通の入力とは異なり、一括で処理するゆえのメンドクササがありますよね。

たとえば、ユーザ情報を外部システムからインポートする場合。想定するケースによりますが、ユーザ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」句をイメージしてもらうとほぼ間違いなしで、つまりは「コレクション内の各要素を、ある条件でグループ分けする」メソッドです。ぱっと見面倒そうですが、使い方はいたってシンプルです。

サンプルを書いてみましょう。

  • データメンバとして、日付(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です。