バイナリファイルの読み書き。PNG の縦横サイズを取得してみる
BinaryReader, BinaryWriter クラスを用いて、バイナリデータの読み書きを行うサンプルです。
using System; using System.IO; class Program { const string filename = "data.dat"; static void Read() { using (var reader = new BinaryReader(File.Open(filename, FileMode.Open))) { int x = reader.ReadInt32(); char c1 = reader.ReadChar(); char c2 = reader.ReadChar(); Console.WriteLine("x: 0x{0:X}", x); Console.WriteLine("c1: {0}", c1); Console.WriteLine("c2: {0}", c2); } } static void Write() { using (var writer = new BinaryWriter(File.Open(filename, FileMode.Create))) { writer.Write(0x11FF33cc); writer.Write('a'); writer.Write('あ'); } } static void Main() { Write(); Read(); } }
実行結果です。
x: 0x11FF33CC c1: a c2: あ
hexdump コマンドで作成したファイルの中身を確認してみます。
$ hexdump data.dat 0000000 cc 33 ff 11 61 e3 81 82 0000008
PNG ファイルの縦横サイズを取得してみる
PNGについて のページには、PNG のファイルフォーマットの解説があります。
ここでは BinaryReader クラスを用いて、PNG ファイルの縦横サイズを取得してみます。
using System; using System.Collections.Generic; using System.Linq; using System.IO; class Program { // BinaryReader の ReadInt32() はリトルエンディアンで読み込むので自作する static int Read32(BinaryReader reader) { int x = 0; for (int i = 0; i < 4; i++) { var b = reader.ReadByte(); x = (x << 8) | b; } return x; } // bytes バイト分読み込む static byte[] Eat(int bytes, BinaryReader reader) { var ret = new byte[bytes]; for (int i = 0; i < bytes; i++) { ret[i] = reader.ReadByte(); } return ret; } static void Read(string filename) { using (var reader = new BinaryReader(File.Open(filename, FileMode.Open))) { // PNG 識別部 var png = Eat(8, reader); Console.WriteLine("PNG 識別部"); Console.WriteLine(string.Join(" ", png.Select(e => string.Format("0x{0:X}", e)))); // --- 本体部 ---- Read32(reader); // データ長 // 名称 var name = Eat(4, reader); Console.WriteLine(string.Join("", name.Select(e => (char)e))); // IHDR が一番最初に必ず存在する int width = Read32(reader); int height = Read32(reader); Console.WriteLine("width = {0}", width); Console.WriteLine("height = {0}", height); } } static void Main() { Read("test.png"); // 横 320 縦 60 の画像を読み込む } }
4 バイトのデータ長を取得するときに、BinaryReader クラスの ReadInt32() メソッドではなく自作の Read32() メソッドを使用しています。これは、ReadInt32() メソッドはリトルエンディアンで読み込むためです。
実行結果です。
PNG 識別部 0x89 0x50 0x4E 0x47 0xD 0xA 0x1A 0xA IHDR width = 320 height = 60
リンク
インスタンスメソッドと静的メソッドに同じメソッド名をつける
インスタンスメソッドと静的メソッドのそれぞれに同じ名前を付けることが出来ます。ただし、メソッドの引数は異なるようにする必要があります。
たとえば、以下のサンプルコードでは、インスタンスメソッドと静的メソッドいずれも 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);
の簡易表現です。