平々毎々(アーカイブ)

はてなダイアリーのアーカイブです。

Eric Lippertのクイズ

A Simple Puzzle – Fabulous Adventures In Codingが面白かった。

度数分布表を作成するメソッドのバグを見つけられるか?というもの。

一応、度数分布表を説明しておくと、こういうやつね。

階級 度数 (人)
160cm未満 2
160〜164cm 4
164〜168cm 22
168〜172cm 17
172〜176cm 8
176〜180cm 6
180cm以上 1

ただし今回のメソッドでは、最小値未満のデータや最大値以上のデータは無視して、こんな感じの表を得るという仕様。

階級 度数 (人)
160〜164cm 4
164〜168cm 22
168〜172cm 17
172〜176cm 8
176〜180cm 6
/// <summary>
/// 指定した範囲内の度数分布表を作成します。
/// </summary>
/// <remarks>
/// <paramref name="buckets"/>は1以上の値が与えられるものとします。
/// <paramref name="max"/><paramref name="min"/>より大きな値が与えられるものとします。
/// </remarks>
/// <param name="data">度数分布表を作成するデータ。</param>
/// <param name="buckets">作成する度数分布表の階級数(区間の数)。</param>
/// <param name="min">作成する度数分布表の最小値。
/// この値以上のデータを集計し、この値未満のデータは無視します。</param>
/// <param name="max">作成する度数分布表の最大値。
/// この値未満のデータを集計し、この値以上のデータは無視します。</param>
/// <returns>累積度数の配列。長さは<paramref name="buckets"/></returns>
private static int[] CreateHistogram(IEnumerable<double> data, int buckets, double min, double max)
{
    // 累積度数の配列を作成。
    int[] results = new int[buckets];
    // 階級幅の逆数を計算。
    double multiplier = buckets / (max - min);
    foreach (double datum in data)
    {
        // 幅で割って(=幅の逆数を掛けて)整数化。
        int index = (int)((datum - min) * multiplier);
        // 度数分布表の範囲に収まっていたら度数をカウントアップ。
        if (0 <= index && index < buckets)
            results[index] += 1;
    }
    return results;
}

さて、どこにバグがあるでしょうか。

ヒント:ためしに実行してみればわかるかもよ。

static void Main()
{
    var r = new Random();
    // 0以上100未満のランダムな数を100000個生成。
    var data = Enumerable.Repeat(0, 100000).Select(x => 100.0d * r.NextDouble());
    // 10以上20未満の区間を10個に分割した度数分布表を作成。
    var hist = CreateHistogram(data, 10, 10.0d, 20.0d);
    for (var i = 0; i < hist.Length; i++)
        Console.WriteLine("{0,2}: {1}", i, hist[i]);
}

ideone.comで実行してみた結果がこちら。

追記:Javaでも同じ。