色んな型の配列をGenericsで操作する

C#で配列中で最も大きな要素を得るためのメソッドを書いたときに、配列がintとdoubleとか複数の型を持っているといちいちオーバーロードしなきゃいけなくて面倒くさい! C++なら std::max_element でなんでも扱えるのに……。

ということで少し考えてみた。intもdoubleもIComparableから派生しているのでこれを使えばひとつのメソッドにまとめられそうだ。

static T Max<T>(IList<T> array) where T : IComparable
{
  T max = array[0];
  for (int i = 1, n = array.Count; i < n; ++i)
  {
    if (max.CompareTo(array[i]) < 0)
    {
      max = array[i];
    }
  }
  return max;
}

// 使ってみる
const int MAX_COUNT = 10000000;
Random rand = new Random();
double[] array = new double[MAX_COUNT];
for (int i = 0; i < MAX_COUNT; ++i)
{
  array[i] = rand.NextDouble();
}
Console.WriteLine(Max(array));

目的は達成できたけれど、普通に配列を引数にとって最大値を得るのに比べると20倍前後遅くなる。array[i]という形の参照方法が遅いようなので、一度無駄なループが回るけれどforeachに置き換えてみた。

static T Max2<T>(IList<T> array) where T : IComparable
{
  T max = array[0];
  foreach (T tmp in array)
  {
    if (max.CompareTo(tmp) < 0)
    {
      max = tmp;
    }
  }
  return max;
}

若干マシになったけどそれでも遅いなぁ。普通の配列バージョンをforeachに置き換えたらそちらも遅くなったのでforeachもそんなに良くないのだろう。

速度が必要な場面ではコードジェネレータみたいなの書いて型ごとのオーバーロードをすぐに作れるようにしておく方が良いかなぁ。まぁ1000万点で1sかからないので、たいていの場合はこれで十分な気もするけれど。