Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Random Number Generator: Picking 10 Giveaway Winners Nobody Can Predict (Python, Node, and the Browser)

2026年10月29日に「Dev.to」が公開したITニュース「Random Number Generator: Picking 10 Giveaway Winners Nobody Can Predict (Python, Node, and the Browser)」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

抽選で予測不能かつ再現可能な乱数生成には工夫が必要だ。Pythonでは`secrets.randbits`で生成したシードを抽選後に公開し、`random.sample`で重複なく当選者を選ぶと両立できる。Node.jsは`crypto.randomInt`を使うが再現性は課題。日時シードやモジュロ演算による偏り、重複当選には注意が必要だ。

ITニュース解説

景品抽選のようなイベントで、当選者を公平に選ぶプログラムを作ることは、一見簡単そうに見えて実は奥深い課題を抱えている。特に、マーケティングチームが参加者から抽選で10名に景品を贈る際、法律や公平性の観点から「当選者が事前に誰にも予測できない」という条件と、「後で誰かに結果を疑われた場合、全く同じ結果を再現できる」という二つの条件が同時に求められることがある。これらは互いに矛盾するように見えるが、この記事ではITエンジニアがどのようにこの問題を解決したか、そしてその過程でどのような落とし穴があったかを解説している。

まず、コンピュータが生成する「乱数」の性質を理解することが重要だ。コンピュータは本当の意味でのランダムな数を作り出すことはできず、「擬似乱数」と呼ばれる、ある規則に基づいて作られた数列を生成する。この数列の出発点となる値が「シード(seed)」だ。同じシードを使えば、常に同じ擬似乱数列が生成されるため、抽選結果を再現できるという利点がある。しかし、もしシードが事前に分かってしまえば、当選者も事前に予測できてしまうという問題が生じる。

この記事の著者は、このジレンマを解決するためにPythonを使った巧妙な方法を採用した。抽選を行うその瞬間に、OS(オペレーティングシステム)が持つ「エントロピープール」という、予測不可能なデータ源から64ビットの秘密のシードを生成するのだ。これはsecrets.randbits(64)という関数を使って行われる。secretsモジュールは、暗号学的に安全な乱数を必要とする場面で使われることを想定しており、OSの持つ真に予測不能な乱数源を利用する。このシードを使ってPythonのrandom.Randomオブジェクトを初期化し、抽選を行う。抽選が終わって当選者が発表された後に、この生成されたシードを公開する。こうすることで、抽選前はシードが誰にも分からないため当選者を予測することはできず、抽選後はシードが公開されるため、誰でも同じシードと参加者リストを使って同じ抽選結果を再現できる、という両方の条件を満たせるわけだ。

実際の抽選処理では、まずCSV形式の参加者リストを読み込み、重複する登録者を排除する作業が行われる。例えば、同じメールアドレスで複数回登録した人がいた場合、最後の登録を有効とするなどして、一人の参加者につき一票となるように処理する。これはrandom.sampleという関数を使って抽選する前に重要な準備だ。random.sampleは、指定されたリストから重複なく(同じ人を二度選ばずに)指定された数の要素を抽出する機能を持つため、景品抽選には非常に適している。この一連の処理と抽選結果、そして生成されたシードは、後で検証できるようにJSON形式のファイルに記録される。

Pythonの他に、Node.jsを使った解決策も紹介されている。Node.jsではnode:cryptoモジュールが提供するcrypto.randomInt関数を利用する。この関数も暗号学的に安全な乱数を生成するもので、指定された範囲内で偏りなく乱数を生成できる。Node.jsの例では、当選者を選ぶためにFisher-Yatesシャッフルというアルゴリズムを部分的に適用している。これはリストから効率的に重複なく要素を選ぶ方法の一つだ。しかし、Node.jsの標準機能では、Pythonのように特定のシードを使って後から全く同じ乱数ストリームを再生できるような、シード設定可能な暗号学的に安全な擬似乱数生成器が提供されていないため、再現性の確保には工夫が必要となる点に注意が必要だ。

プログラミングをせずに手軽に抽選を行いたい場合は、ブラウザベースの抽選ツールを利用する方法もある。この記事で紹介されているようなツールは、クライアント側(ユーザーのブラウザ内)で処理が完結するため、機密性の高い参加者リストをサーバーに送信する必要がないという大きな利点がある。

この抽選プログラム開発の過程で著者が実際に遭遇したいくつかの落とし穴は、初心者が学ぶ上で非常に価値がある。

一つ目は「シードの漏洩」だ。Date.now()のように、実行時の時刻のような予測可能な値からシードを生成してしまうと、少しの情報から簡単にそのシードが特定され、将来の抽選結果まで予測されてしまう危険性がある。これは、ウェブブラウザでよく使われるMath.random()が暗号学的に安全ではないことを示している。安全な抽選には、secrets.randbitsやcrypto.randomIntのような、暗号学的に強力な乱数生成器を使うべきだ。

二つ目は「モジュロバイアス」という問題だ。これは、ある範囲の乱数を生成する際に、例えばos.urandom(1) % 100のように、乱数を特定の数値で割った余りを使うと、その数値の倍数ではない範囲の場合に、特定の数が他の数よりも出やすくなる偏りが生じる現象だ。例えば、0から255までの数値を100で割った余りを考えると、0から55までの数字は3通りの元の数値から生成される可能性があるが、56から99までの数字は2通りの元の数値からしか生成されないため、前者の数字が出やすくなる。大規模な抽選プールではその影響はごくわずかになるが、より正確な抽選のためにはsecrets.randbelowやcrypto.randomIntのように、この偏りを内部的に処理してくれるライブラリ関数を利用するのが最善だ。

三つ目は「重複当選」だ。Pythonのrandom.choices関数のように「重複あり」で抽選する機能を使うと、同じ人が複数回当選してしまう可能性がある。景品抽選では通常、一人の人が一度だけ当選することが期待されるため、random.sample関数のように「重複なし」で抽選する機能を使うべきだ。また、抽選を行う前に、参加者リスト自体から重複する登録者を事前に排除しておくことも非常に重要だ。そうでなければ、random.sampleを使っても、元のリストに同じ人が複数行登録されていれば、その人が複数回抽選プールに入り、結果的に複数回当選する可能性があるからだ。random.sampleにセット(集合)を直接渡すことはできないため、sorted(list(set(emails)))のように一度リストに変換し、ソートしてから渡す必要がある。

まとめると、予測不可能かつ再現可能な公平な抽選を実現するためには、抽選時にOS由来の予測不能なシードを生成し、抽選後にそのシードを公開する手法が有効である。また、暗号学的に安全な乱数生成器を使い、モジュロバイアスを回避すること、そして抽選前に参加者リストの重複を完全に排除し、重複なしで選ぶrandom.sampleのような関数を使うことが不可欠である。これらの技術的課題を理解し、適切に対処することで、信頼性の高い抽選システムを構築できる。

関連コンテンツ

関連IT用語