あまりないコーディングパターンとLINQ to Objectの対応付け
今日も今日とてテライケメン!と叫んでいる方/叫ばれている方がTLにいました。と、見てみるとLINQ to Objectがどうのこうの、と。
あると便利ですよね、ということで書いてみた。
2011-02-10
よくあるコーディングパターンには yield とか使ってないです。
こっちの方がよくありそうでしょ?
まあyieldなんて例によって未確認飛行Cでこないだ初めて知ったぐらいですし。
LINQ to Objectでおなじみの拡張メソッドとよくあるループとか使ったパターンとの対応付けが書かれています。
Skip
先頭から n 個無視したい場合に使います。
後で書く。
・・・先に書いちゃる。
と言っても”よくあるパターン”の知識は少ないので、実践的に。マニアックとも言う。
Skip
先頭からn個無視したい場合に。
// たぶんあまりないコーディングパターン1 // 最初の要素だけ除外 // ほらコマンドライン引数の処理とかなんかあるでしょ・・・ public IEnumerable<string> SkipFirst(string[] target) { if (target.Length < 1) // 空の配列かもよ! return new string[0]; else { var result = new string[target.Length - 1]; Array.Copy(target, 1, result, 0, target.Length - 1); return result; } }
// Skipで書き直し public IEnumerable<string> SkipFirst(string[] target) { return target.Skip(1); }
必要なこと("1個"除外ということ)だけ書けばいいというのはイイネ!
SkipWhile
先頭から条件を満たす要素だけ無視。
// たぶんあまりないコーディングパターン2 // -で始まる要素が続く限り除外 // ほらコマンドライン引数の処理とか(ry public IEnumerable<string> SkipOptions(string[] target) { var result = new List<string>(); foreach (string s in target) { if (!s.StartsWith("-")) result.Add(s); } return result; }
// SkipWhileを使って書き直し public IEnumerable<string> SkipOptions(string[] target) { return target.SkipWhile(s => s.StartsWith("-")); }
Take
Skipとは逆に、先頭n要素を取り出す。
// たぶんあまりないコーディングパターン3 // 先頭10要素までを取得 // ほらheadコマンドとかあるじゃない・・・ public IEnumerable<string> ArrayHead(string[] target) { if (target.Length < 10) // 10要素もないかもしれない return target; else { var result = new string[10]; Array.Copy(target, result, 10); return result; } }
まあLength<10の時にもコピーすればいい気はするけど。
// Takeを使って書き直し public IEnumerable<string> ArrayHead(string[] target) { return target.Take(10); }
やっぱりスマート。
TakeWhile
SkipWhileと逆に、条件を満たさないものまでを取得したいとき。
// たぶんあまりないコーディングパターン4 // 先頭から、-で始まる要素を取得 // ほらコマンドライン引数の処理とか(ry public IEnumerable<string> GetOptions(string[] target) { var result = new List<string>(); foreach (string s in target) { if (s.StartsWith("-")) result.Add(s); } return result; }
// TakeWhileを使って書き直し public IEnumerable<string> GetOptions(string[] target) { return target.TakeWhile(s => s.StartsWith("-")); }
なに、デジャブ?気のせいだ。
First
条件を満たす最初の要素を返す・・・って要は検索か。
// たぶんあまりないコーディングパターン5 // 10文字より長い要素を探す // 誰とk public string FindLongStr(string[] target) { foreach (var s in target) if (s.Length > 10) return s; throw new InvalidOperationException(); }
// Firstを使って書き直し public string FindLongStr(string[] target) { return target.First(s => s.Length > 10); }
条件を指定しないと単純に最初の要素を返します。
FirstOrDefault
Firstと違うのは見つからなかったときに既定値を返すこと。
参照型・null許容型の既定値はnull、値型は・・・MSDNにも書いてないんだが察すればいいんだろうか。
// たぶんあまりないコーディングパターン6 // 10文字より長い要素を探す。見つからなければnull。 // 誰とk public string FindLongStr(string[] target) { foreach (var s in target) if (s.Length > 10) return s; return null; }
// Firstを使って書き直し public string FindLongStr(string[] target) { return target.FirstOrDefault(s => s.Length > 10); }
これも条件指定されなければ最初の要素を返す。
Single
条件を満たす"唯一"の要素を返す。複数あれば例外が飛んでくる。
// たぶんあまりないコーディングパターン7 // 値が100を超える要素を返す。複数あれば例外スロー。 // 誰とk public int GetBigValue(int[] target) { int result = 0; bool found = false; foreach (var n in target) if (n > 100) if (!found) result = n; else throw new InvalidOperationException(); if (found) return result; else throw new InvalidOperationException(); }
// Singleを使って書き直し public int GetBigValue(int[] target) { return target.Single(n => n > 100); }
これも条件指定なしで、要素が一つかチェックするのにも使える。
SingleOrDefault
Singleの例外回避版。要素が見つからない・複数見つかった時に既定値を返す。
// たぶんあまりないコーディングパターン8 // 値が100を超える要素を返す。複数あれば0を返す。 // 誰とk public int GetBigValueOrDefault(int[] target) { int result = 0; bool found = false; foreach (var n in target) if (n > 100) if (!found) result = n; else return 0; if (found) return result; else return 0; }
public int GetBigValueOrDefault(int[] target) { return target.SingleOrDefault(n => n > 100); }
Aggregate
アキュムレート・・・?*1
要素をまとめるのに使う・・・ぽい。
// たぶんあまりないコーディングパターン9 // 配列をカンマ区切りの文字列にまとめる public string ToCSV(string[] target) { if (target.Length == 0) throw new InvalidOperationException(); var result = target[0]; foreach (var s in target.Skip(1)) result += ", " + s; return result; }
// Aggregateを使って書き直し public string ToCSV(string[] target) { return target.Aggregate((acc, next) => acc + ", " + next); }
このメソッド、オーバーロードが3つある。
- TSource TSource.Aggregate(アキュムレータ関数)
- この時最初の要素は最初のアキュムレート値になる=アキュムレータ関数は呼ばれない
- TAccmulate TSource.Aggregate(TAccumlate シード値, アキュムレータ関数)
- シード値が最初のアキュムレート値になる。この場合要素の型と結果の型は異なってもいい。
- TResult TSource.Aggregate(TAccumlate シード値, アキュムレータ関数, 結果値への変換関数)
- 2番目のに加え、最後のアキュムレート値をさらに変換して結果にできる。
・・・配列をまとめるぐらいの感覚でいいよね。うん。
まとめ
どこが実践的なんだっていう。
ただAggregateの例でもSkipを使いましたが、単独より他のと組み合わせて使う事が多いような。
あとインテリセンスでエラーが出てないだけなので実行結果は確認してないとかどうとか(ぇ
*1:累積とか蓄積とか積もるとかいう意味らしい