C# における指定範囲の乱数生成と唯一性確保の実装手法

System.Random クラスの基本的な活用

C# において乱数を生成する際、標準ライブラリである System.Random クラスが主に使用されます。このクラスを利用することで、整数や小数など、さまざまなデータ型のランダムな値を取得することが可能です。ただし、短い時間間隔で複数回インスタンス化すると、同じ種子(シード)が使用され、結果として同一の値が生成されるリスクがあります。これを回避するには、インスタンスの使い回しや適切な種子の生成が不可欠です。

整数範囲の乱数生成

特定の上限値、または上下限値を持つ整数の乱数を取得するには、Next メソッドを利用します。

上限のみを指定する場合

0 から指定した値未満の整数を生成します。

var rng = new Random();
int value = rng.Next(100); // 0 以上 100 未満の整数

範囲を指定する場合

指定した最小値以上、最大値未満の整数を生成します。

var rng = new Random();
int value = rng.Next(100, 1000); // 100 以上 1000 未満の整数

配列およびコレクションからのランダム選択

連続しない特定の値セット(例えば、特定の文字列リストや布尔値)からランダムに 1 つを選ぶ場合、配列のインデックスを乱数として利用します。

文字列リストからの選択

あらかじめ定義された配列からランダムに要素を取得するメソッドです。

public static T SelectRandomItem<T>(T[] candidates, Random rng)
{
    int index = rng.Next(candidates.Length);
    return candidates[index];
}

// 使用例
string[] options = { "Red", "Green", "Blue", "White" };
var rng = new Random();
string color = SelectRandomItem(options, rng);

布尔値の生成

真偽値も配列として定義し、上記のロジックを適用することで簡潔に実装できます。

public static bool GetRandomBoolean(Random rng)
{
    bool[] values = { true, false };
    return SelectRandomItem(values, rng);
}

小数(double)の範囲指定生成

標準の NextDouble は 0.0 以上 1.0 未満の値を返すため、任意の範囲に変換するには計算が必要です。これを拡張メソッドとして実装すると、コードの可読性が向上します。

拡張メソッドによる実装

public static class RandomExtensions
{
    public static double NextDoubleRange(this Random rng, double min, double max)
    {
        return rng.NextDouble() * (max - min) + min;
    }

    public static double NextDoubleRange(this Random rng, double min, double max, int decimals)
    {
        double rawValue = rng.NextDouble() * (max - min) + min;
        return Math.Round(rawValue, decimals);
    }
}

// 使用例
var rng = new Random();
double num1 = rng.NextDoubleRange(1.5, 2.6);
double num2 = rng.NextDoubleRange(5.1, 8.6, 2); // 小数点以下 2 桁

重複を減らすための種子管理とスレッドセーフ

ループ処理内で頻繁に new Random() を実行すると、システム時計の分解能により同じ種子が使われる可能性があります。これを防ぐため、固定のインスタンスを使用するか、クリプトグラフィックな種子生成を利用します。

静的インスタンスの利用

クラス内で単一の Random インスタンスを保持し、ロック機構を用いてスレッドセーフ性を確保します。

public static class SafeRandom
{
    private static readonly Random _global = new Random();
    [ThreadStatic] private static Random _local;

    public static int Next(int min, int max)
    {
        Random inst = _local;
        if (inst == null)
        {
            int seed;
            lock (_global) seed = _global.Next();
            _local = inst = new Random(seed);
        }
        lock (inst) return inst.Next(min, max);
    }
}

重複しない一意の乱数序列の生成

指定された範囲内で、重複のない整数列を生成するには、いくつかのアルゴリズムが有効です。

LINQ を利用したシャッフル

範囲内の数字を生成し、GUID などで順序をランダムに並べ替える方法です。コードが簡潔ですが、大量のデータには向きません。

using System.Linq;

public static IEnumerable<int> GenerateUnique LINQ(int min, int max, int count)
{
    return Enumerable.Range(min, max - min)
        .OrderBy(x => Guid.NewGuid())
        .Take(count);
}

HashSet による重複チェック

生成された値を HashSet に追加し、既存の値との重複を検証しながら必要な数まで生成します。

public static int[] GenerateUniqueWithSet(int min, int max, int count)
{
    var result = new HashSet<int>();
    var rng = new Random();
    
    while (result.Count < count)
    {
        result.Add(rng.Next(min, max));
    }
    return result.ToArray();
}

フィッシャー・イェーツ法の応用

配列内の要素をランダムに交換していく手法です。範囲内のすべての数字を配列に格納し、そこから必要な数だけ取り出します。

public static int[] GenerateUniqueShuffle(int min, int max, int count)
{
    int range = max - min;
    if (count > range) count = range;

    var pool = Enumerable.Range(min, range).ToArray();
    var result = new int[count];
    var rng = new Random();

    for (int i = 0; i < count; i++)
    {
        int swapIndex = rng.Next(i, pool.Length);
        int temp = pool[i];
        pool[i] = pool[swapIndex];
        pool[swapIndex] = temp;
        result[i] = pool[i];
    }
    return result;
}

タグ: C# System.Random LINQ hashset ExtensionMethod

7月28日 16:34 投稿