1 次元配列の回転

1 次元配列を回転するサンプルです。

using System;
using System.Collections.Generic;
using System.Linq;

class Program {
    static T[] Rotate<T>(T[] xs, int n) {
        var ret = new T[xs.Length];
        if (ret.Length == 0) return ret;

        int m = n % ret.Length;
        for (int i = 0; i < ret.Length; i++) {
            ret[i] = xs[m++];
            if (m == ret.Length) m = 0;
        }
        return ret;
    }

    static void Main() {
        var xs = Enumerable.Range(1, 5).ToArray();
        for (int i = 0; i < 7; i++) {
            var ys = Rotate(xs, i);
            Console.WriteLine(string.Join(" ", ys));
        }
    }
}

実行結果です。

1 2 3 4 5
2 3 4 5 1
3 4 5 1 2
4 5 1 2 3
5 1 2 3 4
1 2 3 4 5
2 3 4 5 1

インスタンスが null の場合でも拡張メソッドの呼び出しは可能

インスタンスが null の場合にメソッド呼び出しを行うと、System.NullReferenceException 例外が生成されますが、拡張メソッドの場合は、インスタンスが null でも呼び出しが可能です。

using System;

public class Foo {
    public void Hello() {
        Console.WriteLine("Hello!!");
    }
}

public static class Extension {
    public static void Goodbye(this Foo foo) {
        Console.WriteLine("Goodbye!!");
    }
}

class Program {
    static void Main() {
        Foo foo = new Foo();
        foo.Hello();       // Hello
        foo.Goodbye();     // Goodbye!!

        Foo foo2 = null;
        // foo2.Hello();   // System.NullReferenceException
        foo2.Goodbye();    // Goodbye!!
    }
}

実行結果です。

Hello!!
Goodbye!!
Goodbye!!

Partition 関数

シーケンスの要素を、述語関数を満たす要素と満たさない要素に分割する Partition 関数(メソッド)を作ってみました。

using System;
using System.Collections.Generic;
using System.Linq;

class Program {
    static Tuple<List<T>, List<T>> Partition<T>(IEnumerable<T> xs, Predicate<T> pred) {
        var ok = new List<T>();
        var ng = new List<T>();
        foreach (var x in xs) {
            if (pred(x)) {
                ok.Add(x);
            }
            else {
                ng.Add(x);
            }
        }
        return Tuple.Create(ok, ng);
    }

    static void Main() {
        var xs = new[] { 1, 2, 3, 4, 5 };

        // 偶数と奇数に分割する
        var t = Partition(xs, e => e % 2 == 0);
        Console.WriteLine(string.Join(", ", t.Item1)); // 2, 4
        Console.WriteLine(string.Join(", ", t.Item2)); // 1, 3, 5

        // ゼロまたは正と負に分割する
        var t2 = Partition(new[] { 1, -3, 22, -90, -100 }, e => e >= 0);
        Console.WriteLine(string.Join(", ", t2.Item1)); // 1, 22
        Console.WriteLine(string.Join(", ", t2.Item2)); // -3, -90, -100
    }
}

実行結果です。

2, 4
1, 3, 5
1, 22
-3, -90, -100

リンク

Predicate(T) デリゲート (System)

Group 関数

シーケンス内の隣り合う要素のうち、同じ値のものを配列にまとめる関数 Group を作ってみました。

using System;
using System.Collections.Generic;
using System.Linq;

static class Iter {
    public static IEnumerable<T[]> Group<T>(IEnumerable<T> xs) {
        var ls = new List<T>();
        foreach (var x in xs) {
            if (ls.Count == 0 || ls[0].Equals(x)) {
                ls.Add(x);
            }
            else {
                yield return ls.ToArray();
                ls.Clear();
                ls.Add(x);
            }
        }
        if (ls.Count > 0) {
            yield return ls.ToArray();
        }
    }
}

class Program {
    static void Display<T>(IEnumerable<T[]> xxs) {
        var ls = new List<string>();
        foreach (var xs in xxs) {
            ls.Add(string.Format("[{0}]", string.Join(", ", xs)));
        }
        var s = string.Format("[{0}]", string.Join(", ", ls));
        Console.WriteLine(s);
    }

    static void Main() {
        Display(Iter.Group(new[] { 1 }));
        Display(Iter.Group(new[] { 1, 1 }));
        Display(Iter.Group(new int[0]));
        Display(Iter.Group(new[] { 1, 1, 0, 0, 1, 1, 1, 2, 2, 0, 0, 0 }));
        Display(Iter.Group(new[] { 1, 2, 3, 0, 0 }));
    }
}

実行結果です。

[[1]]
[[1, 1]]
[]
[[1, 1], [0, 0], [1, 1, 1], [2, 2], [0, 0, 0]]
[[1], [2], [3], [0, 0]]

対角線のどちら側か

f:id:noriok:20160218073255p:plain

R, C が与えられたとき、それぞれの格子点が対角線のどちら側にあるか判定するプログラムです。

  • 対角線の傾き (R-1)/(C - 1)
  • 格子点の傾き i/j

について、その大きさを比較します。もし傾きが等しければ格子点は対角線上にあることがわかります。 等しくなければ、格子点は対角線の左側(右側)のどちらかにあります。

大きさを比較するときは、割り算を避けるために通分して整数での比較をするようにします。

using System;
using System.Collections.Generic;
using System.Linq;

class Program {
    static void Diagonal(int R, int C) {
        if (R < 2 || C < 2) throw new ArgumentException();

        Console.WriteLine("R:{0} C:{1}", R, C);
        for (int i = 0; i < R; i++) {
            for (int j = 0; j < C; j++) {
                int a = (R-1) * j;
                int b = i * (C-1);
                if (a == b) {
                    Console.Write("o"); // 対角線上
                }
                else if (a < b) {
                    Console.Write("+"); // 対角線の左側
                }
                else {
                    Console.Write("-"); // 対角線の右側
                }
            }
            Console.WriteLine();
        }
        Console.WriteLine();
    }

    static void Main() {
        Diagonal(4, 4);
        Diagonal(3, 5);
        Diagonal(3, 6);
        Diagonal(3, 7);
        Diagonal(2, 4);
    }
}

実行結果です。対角線上にある格子点を o で表し、それ以外を領域ごとに +- で表しています。

R:4 C:4
o---
+o--
++o-
+++o

R:3 C:5
o----
++o--
++++o

R:3 C:6
o-----
+++---
+++++o

R:3 C:7
o------
+++o---
++++++o

R:2 C:4
o---
+++o

ラムダ式で再帰

ラムダ式再帰呼び出しのサンプルです。

using System;

class Program {
    static void Main() {
        Func<int, int> fact = null;
        fact = n => {
            if (n == 0)
                return 1;
            else
                return n * fact(n - 1);
        };

        for (int i = 1; i < 10; i++)
            Console.WriteLine(fact(i));
    }
}

実行結果です。

1
2
6
24
120
720
5040
40320
362880

fact 変数を null で初期化しています。 そうしないと、ラムダ式の中で自身の変数が参照できず、コンパイルエラーになります。

Func<int, int> fact = n => {
    if (n == 0)
        return 1;
    else
        return n * fact(n - 1); // コンパイルエラー: fact が見えない
};

コンパイルエラーメッセージ:

$ mcs test.cs
test.cs(9,28): error CS0165: Use of unassigned local variable `fact'
Compilation failed: 1 error(s), 0 warnings

バイナリファイルの読み書き。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

リンク