2013年5月8日水曜日
2013年5月3日金曜日
Enumerable.Zipメソッド
2つのコレクションを、同時に列挙したいケースというのはたまにありますよね。たとえば、2つの異なるデータソース上のデータが、何らかのキーでJoinできるわけではなく、単純に格納順序で順番に取り出してなんかしらの処理をして出力したい。とか。
まぁ、こんなケースはたいていデザイン的に問題があったりするんですけど、とはいえリファクタリングするにはなんだか大掛かりになりすぎちゃって、「そこまでしたくないなぁ。」なとき。
こうなると、foreachで2つのコレクションから順次取り出すことはできないので、for文+インデクサを使ったりするわけですが、こんな感じのコードになりますね。
ところが、今となっては単純な列挙にforとかiとか[]とか、こういうのを見るとなんだか可読性が悪い(…様な)気がして、foreachで置き変えたくなっちゃう。こんなときに使えるのが「Enumerable.Zip」メソッドです。2つのコレクションを、まとめて一つにしちゃってくれる、「.net4」で追加されたメソッドです。たとえば、Tupleと組み合わせることで、こんなコードに置き換えできる。
やっぱりこっちのほうがすっきり見えるわけです。完全にLINQに毒されてますな。自覚あります。
Item1とかItem2とか、このコードくらいスコープが短ければ気にならないけど、もう少し複雑な処理で、名前にも気を使いたいような場合なら、Tupleの代わりに匿名クラスを使うことで、こうも書けますね。
どちらでも、ケースバイケースで。あるいはお好みで。
まぁ、こんなケースはたいていデザイン的に問題があったりするんですけど、とはいえリファクタリングするにはなんだか大掛かりになりすぎちゃって、「そこまでしたくないなぁ。」なとき。
こうなると、foreachで2つのコレクションから順次取り出すことはできないので、for文+インデクサを使ったりするわけですが、こんな感じのコードになりますね。
int[] numbers = new[] { 0, 1, 2, 3, 4 };
string[] names = new[] { "Zero", "One", "Two", "Three", "Four" };
for (int i = 0; i < numbers.Length; ++i)
{
Console.WriteLine("{0}:{1}", numbers[i], names[i]);
}
ところが、今となっては単純な列挙にforとかiとか[]とか、こういうのを見るとなんだか可読性が悪い(…様な)気がして、foreachで置き変えたくなっちゃう。こんなときに使えるのが「Enumerable.Zip」メソッドです。2つのコレクションを、まとめて一つにしちゃってくれる、「.net4」で追加されたメソッドです。たとえば、Tupleと組み合わせることで、こんなコードに置き換えできる。
int[] numbers = new[] { 0, 1, 2, 3, 4 };
string[] names = new[] { "Zero", "One", "Two", "Three", "Four" };
foreach (var x in numbers.Zip(names, Tuple.Create))
{
Console.WriteLine("{0}:{1}", x.Item1, x.Item2);
}
やっぱりこっちのほうがすっきり見えるわけです。完全にLINQに毒されてますな。自覚あります。
Item1とかItem2とか、このコードくらいスコープが短ければ気にならないけど、もう少し複雑な処理で、名前にも気を使いたいような場合なら、Tupleの代わりに匿名クラスを使うことで、こうも書けますね。
int[] numbers = new[] { 0, 1, 2, 3, 4 };
string[] names = new[] { "Zero", "One", "Two", "Three", "Four" };
foreach (var x in numbers.Zip(names, (number, name) => new { number, name }))
{
Console.WriteLine("{0}:{1}", x.number, x.name);
}
どちらでも、ケースバイケースで。あるいはお好みで。
ラベル:
C#
2013年4月25日木曜日
Comparison デリゲートを利用した IComparer の実装クラス
訳のわからないタイトル。
事の起こりは、ジェネリックの「SortedDictionary」クラスを使おうと思ったこと。
デフォルトの比較ルールだと、希望する順番にソートされないので、比較関数を置き変えたかった。そのためにはどうするのか?MSDNを引いてみると、
SortedDictionaryのコンストラクタのオーバーロードを確認しても、IComparer<TKey>を指定するものはあっても、Comparison<TKey>を指定できるものはない。えー?このためにクラス一個作んないとダメってことかい?めんどくせ。
List<T>とかのSortメソッドには、Comparison<T>もIComparer<T>もどっちも引き渡せるようになっているのに…。.net 2.0の時代ならいざ知らず…、ちょっと片手落ちじゃないすか?というか、手を入れ損なったのかなぁ。
ま、無いものはしょうがない。とはいえ、クラスを作るにしても似たようなものを何度も作りたくはない。なので、『Comparison<T> デリゲートを利用した IComparer<T> の実装クラス』を作り、汎用的に使いまわせるようにしようと考えた次第。
しかし、ここで悩んだのはそのクラス名。汎用的なIComparer実装クラスなので、「Comparer」クラスにしたかったのだけど、「System.Coolection.Generic.Comparer」クラスが既に存在しているので却下。
悩んだ挙句、「ComparisonComparer<T>」にしました。頭痛が痛いような名前。でもまぁ、名は体を表していることには違いないし、なによりIntellisenceで「Compar...」でリストアップされるので、まぁ、使い勝手は悪くないんじゃないかと思う。
およそ実装はこんな感じ。
念のため、ジェネリックじゃないIComparerも実装しておいたほうがいいかも。そして、こう使うと。
正直、それほど使い道があるわけじゃない(ざっと見た限り、SortedListやSortedSetではIComparer<T>が必要なので、使い道はありそうだけど、それくらい…)。だけど、いちいちクラス一個作るよりはいいでしょ。
事の起こりは、ジェネリックの「SortedDictionary」クラスを使おうと思ったこと。
デフォルトの比較ルールだと、希望する順番にソートされないので、比較関数を置き変えたかった。そのためにはどうするのか?MSDNを引いてみると、
SortedDictionaryのコンストラクタのオーバーロードを確認しても、IComparer<TKey>を指定するものはあっても、Comparison<TKey>を指定できるものはない。えー?このためにクラス一個作んないとダメってことかい?めんどくせ。
List<T>とかのSortメソッドには、Comparison<T>もIComparer<T>もどっちも引き渡せるようになっているのに…。.net 2.0の時代ならいざ知らず…、ちょっと片手落ちじゃないすか?というか、手を入れ損なったのかなぁ。
ま、無いものはしょうがない。とはいえ、クラスを作るにしても似たようなものを何度も作りたくはない。なので、『Comparison<T> デリゲートを利用した IComparer<T> の実装クラス』を作り、汎用的に使いまわせるようにしようと考えた次第。
しかし、ここで悩んだのはそのクラス名。汎用的なIComparer実装クラスなので、「Comparer」クラスにしたかったのだけど、「System.Coolection.Generic.Comparer」クラスが既に存在しているので却下。
悩んだ挙句、「ComparisonComparer<T>」にしました。頭痛が痛いような名前。でもまぁ、名は体を表していることには違いないし、なによりIntellisenceで「Compar...」でリストアップされるので、まぁ、使い勝手は悪くないんじゃないかと思う。
およそ実装はこんな感じ。
class ComparisonComparer<T> : IComparer<T>
{
private Comparison<T> comparison;
public ComparisonComparer(Comparison<T> comparison)
{
this.comparison = comparison;
}
public int Compare(T p1, T p2)
{
return comparison(p1, p2);
}
}
念のため、ジェネリックじゃないIComparerも実装しておいたほうがいいかも。そして、こう使うと。
var dic = new SortedDictionary<string, int>(
new ComparisonComparer<string>((p1, p2) => string.Compare(p1, p2)));
正直、それほど使い道があるわけじゃない(ざっと見た限り、SortedListやSortedSetではIComparer<T>が必要なので、使い道はありそうだけど、それくらい…)。だけど、いちいちクラス一個作るよりはいいでしょ。
ラベル:
C#
2013年4月22日月曜日
Lambdaの小ネタ / ハッシュ文字列(フィンガープリント)の生成
「Lambda の小ネタ / 文字列から特定種類の文字以外を除去したコピーを作る」でも使いましたが、string.Concat(IEnumerable<T>)。これを知ったのはつい最近。が、これが大変使い勝手がよく、昔書いたコードのいろいろを書き直したくなる衝動に駆られます。
たとえば、バイナリデータのMD5ハッシュ文字列(いわゆるフィンガープリント)を生成するのに、こんなコードを書いてました。
MSDNのサンプルもこんなコードになっています。しかし、ここにstring.Concatのメソッドを使うと、
おんなじことがこれでできます。なんて素敵な!
たとえば、バイナリデータのMD5ハッシュ文字列(いわゆるフィンガープリント)を生成するのに、こんなコードを書いてました。
static string MD5OldStyle(byte[] tgt)
{
var sb = new StringBuilder();
using (var md5 = MD5.Create())
{
foreach (var b in md5.ComputeHash(tgt))
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
}
MSDNのサンプルもこんなコードになっています。しかし、ここにstring.Concatのメソッドを使うと、
static string MD5NewStyle(byte[] tgt)
{
using(var md5 = MD5.Create())
{
return string.Concat(md5.ComputeHash(tgt).Select(b => b.ToString("x2")));
}
}
おんなじことがこれでできます。なんて素敵な!
ラベル:
C#
2013年4月19日金曜日
Lambda の小ネタ / 文字列から特定種類の文字以外を除去したコピーを作る
ラムダ式を使ったちょっとした小ネタ。
『文字列から、数字以外を除去したコピーの文字列を作る』処理を書きたい。
.net Framework 4.0以降であれば、入力文字列を「input」、出力文字列を「output」とすると、こんな風に書けます。なにしろstring は 「IEnumerable<char>」ですから!
シンプルでいいでしょ。
『文字列から、数字以外を除去したコピーの文字列を作る』処理を書きたい。
.net Framework 4.0以降であれば、入力文字列を「input」、出力文字列を「output」とすると、こんな風に書けます。なにしろstring は 「IEnumerable<char>」ですから!
var output = string.Concat(input.Where(c => Char.IsDigit(c)));
シンプルでいいでしょ。
ラベル:
C#
2013年4月18日木曜日
登録:
投稿 (Atom)

