読み込み中...

Pythonで使う重複なしの乱数生成方法と活用10選

重複なしの乱数生成 徹底解説 Python
この記事は約21分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

●Pythonで重複なし乱数を極めよう

Pythonで、重複のない乱数生成は非常に重要な技術です。

データサイエンスや機械学習、さらにはゲーム開発など、様々な分野で活用される基礎的かつ強力な手法です。

始めたばかりの方から経験豊富なエンジニアまで、誰もが身につけておくべきスキルと言えるでしょう。

今回は、Pythonを使って重複のない乱数を生成する方法について、基礎から応用まで詳しく解説します。

初心者の方でも理解しやすいよう、ステップバイステップで進めていきますので、安心して読み進めてください。

○重複なし乱数とは?初心者でもわかる基礎知識

重複なし乱数とは、一度選ばれた数値が二度と選ばれないように生成される乱数のことです。

通常の乱数生成では、同じ数値が何度も出現する可能性がありますが、重複なし乱数ではそれが起こりません。

例えば、1から10までの数字から3つの数を選ぶ場合、普通の乱数では「2, 5, 2」のように同じ数字が選ばれる可能性がありますが、重複なし乱数では「2, 5, 8」のように必ず異なる数字が選ばれます。

重複なし乱数は、ユニークな要素を選択する必要がある場合に特に有用です。

例えば、くじ引きやカードゲーム、データのランダムサンプリングなど、多岐にわたる場面で活躍します。

○Pythonの標準ライブラリで乱数生成!

Pythonには、乱数を生成するための標準ライブラリとして「random」モジュールが用意されています。

このモジュールを使うことで、簡単に重複のない乱数を生成できます。

まずは、randomモジュールをインポートしましょう。

次のようにコードを書きます。

import random

このコードを実行すると、randomモジュールの機能が使えるようになります。

Pythonの標準ライブラリなので、追加でインストールする必要はありません。

○サンプルコード1:random.sampleを使った基本的な重複なし乱数生成

random.sample()関数は、重複のない乱数を生成するための最も基本的な方法です。

この関数を使うと、指定した範囲から指定した数だけ、重複のない要素をランダムに選択できます。

次のサンプルコードを見てみましょう。

import random

# 1から10までの整数から、3つの重複しない乱数を生成
result = random.sample(range(1, 11), 3)
print(result)

このコードを実行すると、1から10までの整数から3つの重複しない乱数が生成されます。

実行結果は次のようになります。

[7, 2, 9]

注意点として、選択する要素の数が元のリストの要素数を超えてはいけません。

例えば、1から5までの整数から6つの重複しない乱数を生成しようとすると、エラーが発生します。

random.sample()関数は非常に便利ですが、大量のデータを扱う場合はメモリ使用量に注意が必要です。

大規模なデータセットを扱う場合は、後述するnumpy.random.choiceなどの方法を検討するとよいでしょう。

●プロ級エンジニアも驚く!5つの重複なし乱数生成法

基本的な方法を押さえたところで、より高度な重複なし乱数生成の手法を見ていきましょう。

ここでは、プロのエンジニアも活用する5つの方法を紹介します。

○サンプルコード2:numpy.random.choiceによる効率的なサンプリング

NumPyライブラリのnumpy.random.choice関数を使用すると、より効率的に重複のない乱数を生成できます。

特に、大規模なデータセットを扱う場合に有用です。

まず、NumPyをインストールしていない場合は、次のコマンドでインストールしてください。

pip install numpy

ここではnumpy.random.choiceを使用したサンプルコードを紹介します。

import numpy as np

# 0から99までの整数から、10個の重複しない乱数を生成
result = np.random.choice(100, 10, replace=False)
print(result)

実行結果は次のようになります。

[62 41 93 31 57 82  8 21 98 76]

numpy.random.choice関数では、replace=Falseを指定することで重複を避けています。

この方法は、大量のデータを扱う際にメモリ効率が良く、処理速度も速いという利点があります。

○サンプルコード3:リストシャッフルを活用した独自の乱数生成

リストをシャッフルして、その一部を取り出すことで重複のない乱数を生成する方法もあります。

この方法は、全ての要素を一度シャッフルするため、大規模なデータセットには向いていませんが、中小規模のデータに対しては効果的です。

次のサンプルコードを見てみましょう。

import random

# 1から20までの整数のリストを作成
numbers = list(range(1, 21))

# リストをシャッフル
random.shuffle(numbers)

# シャッフルしたリストから最初の5要素を取得
result = numbers[:5]
print(result)

実行結果は次のようになります。

[14, 7, 20, 3, 11]

この方法の利点は、元のリスト全体を保持しながら、その一部をランダムに選択できることです。

ただし、メモリ使用量が増加するため、大規模なデータセットには適していません。

○サンプルコード4:range()と組み合わせた整数範囲の乱数生成

特定の範囲の整数から重複のない乱数を生成したい場合、range()関数とrandom.sample()を組み合わせる方法が効果的です。

この方法は、メモリ効率が良く、大きな範囲の整数からサンプリングする際に特に有用です。

次のサンプルコードを見てみましょう。

import random

# 1000から9999までの整数から、100個の重複しない乱数を生成
result = random.sample(range(1000, 10000), 100)
print(result[:10])  # 最初の10個だけ表示

実行結果は次のようになります(結果は毎回異なります)。

[3458, 8731, 5062, 1279, 9574, 2186, 7940, 4615, 6803, 1957]

この方法の利点は、大きな範囲の整数から効率的にサンプリングできることです。

range()関数はメモリを効率的に使用するため、1000から9999までの10000個の整数を実際にリストとして生成することなく、その中から重複なしでサンプリングできます。

○サンプルコード5:numpy.random.uniformを使った浮動小数点数の乱数生成

整数だけでなく、浮動小数点数の重複のない乱数を生成したい場合があります。

そのような場合、numpy.random.uniform関数を使用すると、指定した範囲内で均一に分布する浮動小数点数の乱数を生成できます。

次のサンプルコードを見てみましょう。

import numpy as np

# 0.0から1.0の範囲で、5個の重複しない浮動小数点数を生成
result = np.random.uniform(0, 1, 5)
print(result)

実行結果は次のようになります(結果は毎回異なります)。

[0.23651224 0.78542319 0.46273891 0.12394567 0.95678123]

この方法の特徴は、連続的な値から重複のない乱数を生成できることです。

浮動小数点数は無限に存在するため、厳密な意味での「重複なし」は保証されませんが、実用上は問題ないレベルの精度で重複のない乱数を生成できます。

●乱数生成の落とし穴!回避すべき3つの注意点

重複なし乱数の生成は、一見単純に見えますが、実際には注意すべき点がいくつか存在します。

プログラミング初心者からベテランまで、思わぬ落とし穴にはまることがあります。

ここでは、よくある3つの問題点とその対策を詳しく解説します。

○シード設定のミスによる再現性の欠如

乱数生成において、シードの設定は非常に重要です。

シードとは、乱数生成のスタート地点を決める値のことです。

同じシードを使用すれば、同じ乱数列が生成されます。

再現性を確保するためには、シードを適切に設定する必要があります。

シード設定を忘れたり、不適切な値を使用したりすると、毎回異なる結果が得られてしまい、デバッグや実験の再現が困難になります。

対策として、次のようなコードを使用しましょう。

import random
import numpy as np

# シードを設定
seed_value = 42
random.seed(seed_value)
np.random.seed(seed_value)

# 乱数生成
result1 = random.sample(range(1, 100), 5)
result2 = np.random.choice(100, 5, replace=False)

print("Random結果:", result1)
print("NumPy結果:", result2)

実行結果

Random結果: [37, 13, 95, 4, 68]
NumPy結果: [51 92 14 71 60]

シードを固定することで、何度実行しても同じ結果が得られます。

この方法は、特にデバッグや実験結果の再現性が求められる場面で非常に有効です。

○大規模データでのパフォーマンス低下

大規模なデータセットから重複なし乱数を生成する場合、パフォーマンスの低下が問題となることがあります。

特に、random.sample()関数は、元のリスト全体をメモリに保持する必要があるため、大量のデータを扱う際には非効率的です。

対策としては、ジェネレータを使用する方法があります。

次のコードは、大規模なデータセットから効率的に重複なし乱数を生成する例です。

import random

def efficient_sampling(population_size, sample_size):
    seen = set()
    for _ in range(sample_size):
        while True:
            random_num = random.randrange(population_size)
            if random_num not in seen:
                seen.add(random_num)
                yield random_num
                break

# 10億の範囲から100万個の重複なし乱数を生成
population_size = 1_000_000_000
sample_size = 1_000_000

sampled_numbers = list(efficient_sampling(population_size, sample_size))
print(f"生成された乱数の数: {len(sampled_numbers)}")
print(f"最初の10個の乱数: {sampled_numbers[:10]}")

実行結果

生成された乱数の数: 1000000
最初の10個の乱数: [305308945, 823758598, 798563513, 84353286, 164228718, 656821772, 326491876, 489816769, 708986395, 320783500]

このアプローチを使用すると、メモリ使用量を抑えつつ、効率的に大規模な重複なし乱数を生成できます。

○不適切なエラーハンドリング

乱数生成時のエラーハンドリングは、しばしば見落とされがちな部分です。

特に、指定した範囲外の値を要求したり、重複を許さない条件下で要求数が多すぎたりする場合、適切なエラー処理が必要です。

次のコードは、エラーハンドリングを適切に行う例です。

import random

def safe_sample(population, sample_size):
    try:
        return random.sample(population, sample_size)
    except ValueError as e:
        print(f"エラーが発生しました: {e}")
        return None

# 正常なケース
result1 = safe_sample(range(1, 11), 5)
print("正常なケース:", result1)

# エラーケース:サンプルサイズが母集団より大きい
result2 = safe_sample(range(1, 6), 10)
print("エラーケース:", result2)

実行結果

正常なケース: [7, 2, 9, 3, 5]
エラーが発生しました: Sample larger than population or is negative
エラーケース: None

このように、try-except文を使用することで、エラーが発生した際にプログラムが突然停止することを防ぎ、適切なエラーメッセージを表示できます。

●実践で差がつく!乱数の活用事例4選

重複なし乱数の生成方法を学んだところで、実際の活用例を見ていきましょう。

ここでは、4つの実践的な活用事例を紹介します。

○サンプルコード6:ゲーム開発でのランダムアイテム生成

ゲーム開発において、プレイヤーにランダムなアイテムを提供することは、ゲームの面白さを高める重要な要素です。

次のコードは、重複なしでランダムなアイテムを生成する例です。

import random

items = ["剣", "盾", "弓", "魔法の杖", "回復薬", "毒消し", "爆弾", "鍵", "地図", "お守り"]

def generate_random_items(num_items):
    if num_items > len(items):
        raise ValueError("要求されたアイテム数が多すぎます")
    return random.sample(items, num_items)

# プレイヤーに3つのランダムアイテムを付与
player_items = generate_random_items(3)
print("プレイヤーが獲得したアイテム:", player_items)

実行結果

プレイヤーが獲得したアイテム: ['魔法の杖', '爆弾', '鍵']

このコードを使用すると、プレイヤーに重複のないランダムなアイテムを簡単に付与できます。

ゲームの多様性と予測不可能性を高めることができるでしょう。

○サンプルコード7:モンテカルロシミュレーションにおける乱数活用

モンテカルロシミュレーションは、乱数を使用して複雑な問題を解決する強力な手法です。

次の例では、円周率πの値を推定するシミュレーションを行っています。

import random
import math

def estimate_pi(num_points):
    inside_circle = 0
    total_points = num_points

    for _ in range(total_points):
        x = random.uniform(-1, 1)
        y = random.uniform(-1, 1)
        if math.sqrt(x**2 + y**2) <= 1:
            inside_circle += 1

    pi_estimate = 4 * inside_circle / total_points
    return pi_estimate

# シミュレーション実行
num_simulations = 1000000
estimated_pi = estimate_pi(num_simulations)

print(f"推定されたπの値: {estimated_pi}")
print(f"実際のπとの差: {abs(math.pi - estimated_pi)}")

実行結果

推定されたπの値: 3.141148
実際のπとの差: 0.0004062858163088319

このシミュレーションでは、重複のない乱数を大量に生成することで、高精度な円周率の推定が可能になります。

○サンプルコード8:機械学習のためのランダムデータ分割

機械学習では、データセットをトレーニング用とテスト用に分割する必要があります。

次のコードは、重複なしでランダムにデータを分割する例です。

import numpy as np

# サンプルデータセット
data = np.array([i for i in range(100)])

def split_data(data, train_ratio=0.8):
    data_size = len(data)
    train_size = int(data_size * train_ratio)

    # インデックスをシャッフル
    indices = np.random.permutation(data_size)

    train_indices = indices[:train_size]
    test_indices = indices[train_size:]

    train_data = data[train_indices]
    test_data = data[test_indices]

    return train_data, test_data

# データ分割の実行
train_set, test_set = split_data(data)

print(f"トレーニングセットのサイズ: {len(train_set)}")
print(f"テストセットのサイズ: {len(test_set)}")
print(f"トレーニングセットの最初の10要素: {train_set[:10]}")
print(f"テストセットの最初の10要素: {test_set[:10]}")

実行結果

トレーニングセットのサイズ: 80
テストセットのサイズ: 20
トレーニングセットの最初の10要素: [42 90 60 17 95 25 22 80 72 62]
テストセットの最初の10要素: [70 39 19 85 40 27 35 75 43 11]

このように、重複なしの乱数生成を活用することで、偏りのないデータ分割が可能になります。

機械学習モデルの性能評価の信頼性向上に貢献します。

○サンプルコード9:統計的検定のためのブートストラップサンプリング

統計学では、ブートストラップ法というリサンプリング手法がよく使用されます。

次のコードは、ブートストラップサンプリングを使って平均値の信頼区間を推定する例です。

import numpy as np

def bootstrap_mean(data, num_bootstrap_samples=10000, confidence_level=0.95):
    bootstrap_means = []
    for _ in range(num_bootstrap_samples):
        sample = np.random.choice(data, size=len(data), replace=True)
        bootstrap_means.append(np.mean(sample))

    confidence_interval = np.percentile(bootstrap_means, [(1-confidence_level)/2*100, (1+confidence_level)/2*100])
    return np.mean(data), confidence_interval

# サンプルデータ
data = np.random.normal(loc=10, scale=2, size=100)

# ブートストラップ法による平均値と信頼区間の推定
sample_mean, (ci_lower, ci_upper) = bootstrap_mean(data)

print(f"サンプル平均: {sample_mean:.2f}")
print(f"95%信頼区間: ({ci_lower:.2f}, {ci_upper:.2f})")

実行結果

サンプル平均: 9.91
95%信頼区間: (9.53, 10.28)

このように、重複を許す乱数生成(リサンプリング)を活用することで、データの統計的性質をより正確に把握できます。

ブートストラップ法は、サンプルサイズが小さい場合や、理論的な分布が不明な場合に特に有用です。

●パフォーマンスと信頼性の向上

重複なし乱数生成の基本を押さえ、実践的な活用法を学んだ今、さらに一歩進んだ技術について探求しましょう。

大規模なデータ処理や高速な計算が必要な場面では、パフォーマンスの向上が鍵となります。

並列処理を用いた高速乱数生成は、そんな要求に応える強力な手法です。

○サンプルコード10:並列処理を用いた高速乱数生成

大量の重複なし乱数を生成する必要がある場合、処理時間が問題になることがあります。

並列処理を活用すれば、複数のCPUコアを同時に使用して乱数生成を行うことができ、処理速度を大幅に向上させることが可能です。

Pythonのmultiprocessingモジュールを使用して、並列処理による高速な重複なし乱数生成を実装してみましょう。

import multiprocessing
import random
import time

def generate_unique_random(start, end, count):
    return random.sample(range(start, end + 1), count)

def parallel_unique_random(total_range, total_count, num_processes):
    pool = multiprocessing.Pool(processes=num_processes)

    chunk_size = total_count // num_processes
    remaining = total_count % num_processes

    tasks = []
    start = 1
    for i in range(num_processes):
        count = chunk_size + (1 if i < remaining else 0)
        end = start + total_range // num_processes - 1
        tasks.append((start, end, count))
        start = end + 1

    results = pool.starmap(generate_unique_random, tasks)
    pool.close()
    pool.join()

    return [num for sublist in results for num in sublist]

if __name__ == "__main__":
    total_range = 1000000
    total_count = 100000
    num_processes = 4

    start_time = time.time()
    parallel_result = parallel_unique_random(total_range, total_count, num_processes)
    end_time = time.time()

    print(f"並列処理による生成時間: {end_time - start_time:.4f}秒")
    print(f"生成された乱数の数: {len(parallel_result)}")
    print(f"重複チェック: {len(set(parallel_result)) == len(parallel_result)}")

    start_time = time.time()
    sequential_result = random.sample(range(1, total_range + 1), total_count)
    end_time = time.time()

    print(f"\n逐次処理による生成時間: {end_time - start_time:.4f}秒")
    print(f"生成された乱数の数: {len(sequential_result)}")
    print(f"重複チェック: {len(set(sequential_result)) == len(sequential_result)}")

実行結果

並列処理による生成時間: 0.2353秒
生成された乱数の数: 100000
重複チェック: True

逐次処理による生成時間: 0.3747秒
生成された乱数の数: 100000
重複チェック: True

並列処理を用いることで、処理速度が大幅に向上していることがわかります。

総数100万の範囲から10万個の重複なし乱数を生成する作業が、4つのプロセスを使用することで約37%高速化されました。

並列処理の仕組みについて詳しく説明しましょう。

まず、全体の範囲と生成する乱数の数を複数のプロセスに分割します。

各プロセスは割り当てられた範囲内で重複なし乱数を生成し、最後に全てのプロセスの結果を結合します。

プログラムの流れは次の通りです。

  1. multiprocessing.Poolを使用して、指定した数のプロセスを作成します。
  2. 全体の範囲と生成する乱数の数を各プロセスに均等に分配します。
  3. 各プロセスでgenerate_unique_random関数を実行し、重複なし乱数を生成します。
  4. 全プロセスの結果を収集し、1つのリストにまとめます。
  5. 並列処理と逐次処理の実行時間を比較し、性能向上を確認します。

並列処理を活用することで、大規模なデータセットや複雑な計算を含む機械学習プロジェクトでも、効率的に重複なし乱数を生成することが可能になります。

ただし、注意点もあります。

並列処理は常に効果的とは限りません。

小規模なデータセットや単純な計算の場合、プロセス間の通信オーバーヘッドが処理時間を上回ることがあります。

また、使用可能なCPUコア数やメモリ容量にも制限があるため、システムリソースを考慮して適切なプロセス数を選択する必要があります。

並列処理を用いた高速乱数生成は、大規模なシミュレーションや機械学習の前処理など、高い計算能力が求められる場面で特に有効です。

適切に活用することで、データサイエンスやAI開発プロジェクトの効率を大幅に向上させることができるでしょう。

まとめ

Pythonにおける重複なし乱数生成について、基礎から応用まで幅広く解説してきました。

重複なし乱数の概念を理解し、標準ライブラリやNumPyを使った基本的な生成方法から、並列処理を用いた高度な技術まで、段階的に知識を深めてきました。

今回学んだ知識を活かし、実際のプロジェクトで重複なし乱数を効果的に活用してください。

基本的な使い方から高度な最適化テクニックまで、状況に応じて適切な方法を選択することが大切です。