インスタンスメソッドと静的メソッドに同じメソッド名をつける
インスタンスメソッドと静的メソッドのそれぞれに同じ名前を付けることが出来ます。ただし、メソッドの引数は異なるようにする必要があります。
たとえば、以下のサンプルコードでは、インスタンスメソッドと静的メソッドいずれも Hello という名前で定義しています。いずれも引数を取りません。このコードはコンパイルできません。
// test.cs using System; class Foo { public void Hello() { Console.WriteLine("instance hello"); } public static void Hello() { Console.WriteLine("class hello"); } } class Program { static void Main() { new Foo().Hello(); Foo.Hello(); } }
$ mcs test.cs test.cs(9,24): error CS0111: A member `Foo.Hello()' is already defined. Rename this member or use different parameter types test.cs(5,17): (Location of the symbol related to previous error) Compilation failed: 1 error(s), 0 warnings
メソッド名を変更するか、もしくは引数が異なるように変更する必要があります。
クラスメソッドに引数を追加すると、今度はコンパイルが通ります。
// test.cs using System; class Foo { public void Hello() { Console.WriteLine("instance hello"); } public static void Hello(int n) { // <- 引数を追加した Console.WriteLine("class hello"); } } class Program { static void Main() { new Foo().Hello(); Foo.Hello(10); } }
コンパイルおよび実行結果です。
$ mcs test.cs $ mono test.exe instance hello class hello
リストの連結
リストを連結させるサンプルです。以下の 2 通りの方法で書いてみました。
- List
.AddRange で要素を追加する - Enumerable.Concat で二つのリストを連結する
using System; using System.Collections.Generic; using System.Linq; class Program { static List<int> Concat1(List<int> xs, List<int> ys) { // 新しいリストを作成して、xs, ys の要素を格納する var ret = new List<int>(xs); ret.AddRange(ys); return ret; } static List<int> Concat2(List<int> xs, List<int> ys) { // Enumerable.Concat でリストを連結する return xs.Concat(ys).ToList(); } static void Main() { var xs = new List<int>() { 1, 2, 3 }; var ys = new List<int>() { 4, 5, 6 }; Console.WriteLine(string.Join(" ", Concat1(xs, ys))); // 1 2 3 4 5 6 Console.WriteLine(string.Join(" ", Concat2(xs, ys))); // 1 2 3 4 5 6 } }
実行結果です。
1 2 3 4 5 6 1 2 3 4 5 6
Python の itertools.product を C# で作る(3 引数まで対応)
Python の itertools モジュールには product という関数が定義されています。
たとえば 1, 3, 4 の数字を使って 4 桁の数を作るには、Python では itertools.product([1, 3, 4], repeat=4)
と書くだけで実現できます。
配列を用意してバックトラックで桁を埋めていく、という処理が不要になるのは素晴らしいですね。
C# で product 関数を作ってみました(ただし、引数の要素数は 3 までの制限があります)。
using System; using System.Collections.Generic; using System.Linq; class Program { static IEnumerable<T[]> Product_1<T>(int p, T[] buf, List<List<T>> pools) { if (p == buf.Length) { yield return buf; } else { foreach (var e in pools[p]) { buf[p] = e; foreach (var ret in Product_1(p+1, buf, pools)) { yield return ret; } } } } static IEnumerable<T[]> Product<T>(IEnumerable<T> xs, int repeat = 1) { var pools = new List<List<T>>(Enumerable.Repeat(xs.ToList(), repeat)); return Product_1(0, new T[pools.Count], pools); } static IEnumerable<T[]> Product<T>(IEnumerable<T> xs, IEnumerable<T> ys, int repeat = 1) { var ls = new List<List<T>> { xs.ToList(), ys.ToList() }; var pools = new List<List<T>>(Enumerable.Repeat(ls, repeat).SelectMany(e => e)); return Product_1(0, new T[pools.Count], pools); } static IEnumerable<T[]> Product<T>(IEnumerable<T> xs, IEnumerable<T> ys, IEnumerable<T> zs, int repeat = 1) { var ls = new List<List<T>> { xs.ToList(), ys.ToList(), zs.ToList() }; var pools = new List<List<T>>(Enumerable.Repeat(ls, repeat).SelectMany(e => e)); return Product_1(0, new T[pools.Count], pools); } static void Display<T>(IEnumerable<T[]> xs) { var ys = xs.Select(e => string.Join("", e)); Console.WriteLine(string.Join(" ", ys)); Console.WriteLine(); } static void Main() { // 1, 2, 3 の数字を使って、2 桁の数をつくる Display(Product(new[] { 1, 2, 3 }, 2)); // { 1, 2, 3 } と { 4, 5 } を組み合わせて数字をつくる Display(Product(new[] { 1, 2, 3 }, new[] { 4, 5 })); // 1, 3, 4 の数字を使って 3 桁の数をつくる Display(Product(new[] { 1, 3, 4 }, 3)); } }
実行結果です。
11 12 13 21 22 23 31 32 33 14 15 24 25 34 35 111 113 114 131 133 134 141 143 144 311 313 314 331 333 334 341 343 344 411 413 414 431 433 434 441 443 444
再帰で順列をつくる
再帰で順列を作るサンプルです。
- ABCD の順列をつくりたい
- A を除いた BCD で順列を作り、先頭に A を付けると A が先頭の順列ができあがる
- B を除いた ACD で順列を作り、先頭に B を付けると B が先頭の順列ができあがる
- C を除いた ABD で……(以下同様)
- 1 文字の順列は、自身そのものである
ポイントは再帰呼び出しと、ベースケース(再帰呼び出しをせずに呼び出し元に戻る部分)の構造を見抜くことです。
- 自分自身から 1 文字取り除いた(1 文字少ない)文字列の順列を再帰呼び出しで作る
- 自分自身の文字列の長さが 1 のときは、自身をそのまま返す(ベースケース)
using System; using System.Collections.Generic; using System.Linq; class Program { static List<string> Perms(string s) { if (s.Length == 1) return new List<string>() { s }; // ↑は以下でもよい // if (s.Length == 0) return new List<string>() { "" }; var ret = new List<string>(); for (int i = 0; i < s.Length; i++) { // 1. s[i] を除いたグループで順列を作る(再帰) foreach (string t in Perms(s.Remove(i, 1))) { // 2. 先頭に s[i] をつけると // s[i] が先頭の順列が出来る(長さ s.Length の順列) ret.Add(s[i] + t); } } return ret; } static void Main() { foreach (var s in Perms("abcd")) { Console.WriteLine(s); } } }
実行結果です。
abcd abdc acbd acdb adbc adcb bacd badc bcad bcda bdac bdca cabd cadb cbad cbda cdab cdba dabc dacb dbac dbca dcab dcba
MinBy, MaxBy を Aggregate で代用する
リストの要素から、ある特定のキーで最小の値を持つものを取得したい場合があります。
Ruby では、Enumerable#min_by メソッドを使うと、そのような要素を簡単に取得できます。
C# には、そのものずばりのメソッドは標準ライブラリにはないのですが、Enumerable.Aggregate を使えば、 目的の機能を実装できます。
using System; using System.Collections.Generic; using System.Linq; class Program { static void Main() { var xs = new List<int[]> { new[] { 1, 2 }, new[] { 20, 50 }, new[] { 30, 22 }, new[] { 50, -100 }, }; // MinBy : 1 番目の要素が最小のものを返す。 var y = xs.Aggregate((a, b) => a[1] < b[1] ? a : b); Console.WriteLine(string.Join(" ", y)); // 50 -100 // MaxBy : 1 番目の要素が最大のものを返す。 var x = xs.Aggregate((a, b) => a[1] > b[1] ? a : b); Console.WriteLine(string.Join(" ", x)); // 20 50 } }
最小(最大)の要素を持ち回すイメージです。
実行結果です。
20 50 50 -100
Comparison<T> デリゲートの使い方
List<T> クラスの Sort メソッドは引数に Comparison<T> デリゲートを取ります。
Comparison<T> デリゲートの定義は次のようになっています。
public delegate int Comparison<in T>(T x, T y)
Comparison<T> デリゲートのインスタンスを作成し、List.Sort メソッドを呼び出すサンプルコードです。
using System; using System.Collections.Generic; using System.Linq; class C { public int Compare(int a, int b) { return a - b; } } class Program { static int Compare(int a, int b) { return a - b; } static void Main() { var xs = new List<int>() { 4, 3, 2, 7 }; // 静的メソッドを渡す xs.Sort(Compare); // インスタンスメソッドを渡す xs.Sort(new C().Compare); // 無名関数を渡す xs.Sort((a, b) => a - b); // Comparison<int> デリゲートを作成する Comparison<int> c = (a, b) => a - b; xs.Sort(c); // new キーワードで明示的に Comparison<int> デリゲートの // インスタンスを作成する Func<int, int, int> fn = (a, b) => a - b; xs.Sort(new Comparison<int>(fn)); } }
デリゲートインスタンスを作るときの
Comparison<int> c = Compare;
は、
Comparison<int> c = new Comparison<int>(Compare);
の簡易表現です。
リンク
インスタンスメソッドを変数に格納する
用途としては、いくつかのメソッドのうちからランダムなメソッドを呼び出したいときに、 一時的に配列に格納したいとき等に使います。
using System; class Test { public void Foo() { Console.WriteLine("Foo"); } public void Bar(int n) { Console.WriteLine("Bar {0}", n); } public int Baz(int n) { return n + 1; } } class Program { static void Main() { var t = new Test(); Action f = t.Foo; f(); // Foo Action<int> g = t.Bar; g(20); // Bar 20 Func<int, int> h = t.Baz; int ret = h(10); Console.WriteLine(ret); // 11 } }
インスタンスメソッドの型に応じて、Action
, Action<int>
, Func<int, int>
と変数の型が異なります。
Action は戻り値なし、Func は戻り値がある場合の型です。
実行結果です。
Foo Bar 20 11
インスタンスメソッドを配列に格納してランダムに呼び出す
じゃんけんの手をランダムに選ぶサンプルです。 インスタンスメソッドを配列に格納して、ランダムな要素を選択しています。
using System; class Hand { public void Guu() { Console.WriteLine("グー"); } public void Cho() { Console.WriteLine("チョキ"); } public void Paa() { Console.WriteLine("パー"); } } class Program { static void Main() { var hand = new Hand(); Action[] actions = { hand.Guu, hand.Cho, hand.Paa }; var rand = new Random(); for (int i = 0; i < 5; i++) { int p = rand.Next(actions.Length); actions[p](); } } }
実行結果です。
グー チョキ チョキ グー パー