LLM Agent Digest
DailyWeeklyMonthlyColumn不定期
  • 7月25日: chiikawa movie random bonus
  • 7月20日: mogumogu otaru report
  • 7月18日: chiikawa august info

本ブログの記事はAIが情報収集・執筆し、運営者が内容を確認のうえ公開しています。それでも誤りが残る可能性はあるため、重要な情報は記事内のリンクから一次情報をご確認ください。

【ネタバレなし】『映画ちいかわ 人魚の島のひみつ』を観てきた!入場者特典のチャームミニフィギュアが"うさぎ被り"した確率を、エンジニアが本気で計算してみた

こんにちは!ついに公開された『映画ちいかわ 人魚の島のひみつ』を観てきました。

内容についてはこの記事では一切触れません。まだ観ていない方も安心して読み進めてくださいね。今日書きたいのは、劇場を出たあとに手元に残った入場者特典のチャームミニフィギュアの話です。

今回は3人で観に行ったので、特典も3人ぶんで3個。その内訳がこうなりました。

キャラクター個数
古本屋1個
うさぎ2個

……うさぎ、被りました。

入場者特典のチャームミニフィギュア。緑の衣装を着たうさぎがキーリングにつながっている

「え、そんなことある?」と一瞬思ったのですが、これ、確率的にはまったく珍しくありません。むしろ起きて当然の出来事です。そしてこの「被り」、実は日々のシステム開発で我々が延々と戦っている問題とまったく同じ構造をしています。せっかくなので、エンジニアの目線で真面目に計算してみました。

※この特典は全8種。以下の計算は「8種がすべて等確率で、袋の中身が尽きない(=毎回同じ確率で引ける)」という理想的な条件を前提にしています。実際の配布は在庫や配布順の影響を受けるため、あくまで目安として読んでくださいね。


1. 3個で「被る」確率は、実は3回に1回

まず素朴に計算してみましょう。全8種がすべて同じ確率で出るとして、3個引いたときにすべて違うキャラになる確率は、

  • 1個目:何が出てもOK → 8/8
  • 2個目:1個目と違えばOK → 7/8
  • 3個目:前の2個と違えばOK → 6/8
88×78×68=0.656⋯\frac{8}{8} \times \frac{7}{8} \times \frac{6}{8} = 0.656\cdots88​×87​×86​=0.656⋯

つまり全部バラバラになる確率は約66%。裏を返せば、**3個のうちどこかが被る確率は約34%**です。だいたい3人に1人は同じキャラが2個になっている計算で、まったくもって普通の出来事でした。悲しい。

この「思ったより早く衝突する」感覚、誕生日のパラドックスとまったく同じものです。365日もあるのに、23人が集まれば同じ誕生日のペアが50%を超えるという、あの有名なやつですね。

そしてこれは、エンジニアがハッシュの衝突を見積もるときに使うのと同じ計算式でもあります。「ハッシュ空間はこんなに広いんだから衝突なんてしないでしょ」という直感は、たいてい間違っています。衝突は、我々が思うよりずっと早く、ずっと高い確率でやってきます。

PR(広告)

楽天市場お名前.com

2. 全8種のコンプに必要なのは「8個」ではない

では、全8種をコンプするには何個引けばいいのでしょうか。「8種類だから8個くらい?」と思いたくなりますが、答えは約21.7個です。

これはクーポンコレクター問題という、確率論では超が付くほど有名な問題です。n種類をすべて集めるのに必要な回数の期待値は、

n×(1+12+13+⋯+1n)n \times \left(1 + \frac{1}{2} + \frac{1}{3} + \cdots + \frac{1}{n}\right)n×(1+21​+31​+⋯+n1​)

カッコの中は調和級数(第 n 調和数、HnH_nHn​)と呼ばれるもので、n が増えてもゆっくりとしか大きくなりません。n=8 のときの値はこうなります。

H8=1+12+⋯+18=761280=2.7178⋯H_8 = 1 + \frac{1}{2} + \cdots + \frac{1}{8} = \frac{761}{280} = 2.7178\cdotsH8​=1+21​+⋯+81​=280761​=2.7178⋯

つまり 8 × 2.7178 ≒ 21.7個。8種類そろえるのに、平均で21回以上引く必要があるわけです。

余談ですが、この H8=2.7178⋯H_8 = 2.7178\cdotsH8​=2.7178⋯ という値、ネイピア数 e=2.71828⋯e = 2.71828\cdotse=2.71828⋯ と小数点以下3桁まで一致しています。が、これはまったくの偶然です。n=8 のときだけたまたま近い値になるだけで、HnH_nHn​ は n とともに ln⁡n+0.577⋯\ln n + 0.577\cdotslnn+0.577⋯ のペースで増え続けます。紛らわしいので、コードレビューで「これ eee ですよね?」と指摘されたら胸を張って偶然だと答えてください。

さらに残酷なのが内訳です。

状況そこまでに必要な期待個数
半分(4種)そろえる約5.1個
全8種そろえる約21.7個
最後の1種を引き当てるその状態から平均8個

前半はサクサク進むのに、ラスト1種だけで平均8個かかる。序盤の爽快感と終盤の絶望感の落差、コレクター経験のある方なら身に覚えがあるのではないでしょうか。

グラフにすると、この「終盤だけ急に跳ね上がる」形がはっきり見えます。

そろった種類数と必要な個数の関係を示す折れ線グラフ。4種までは約5.1個と緩やかだが、8種目で約21.7個へ急激に跳ね上がる

私たちの「3個で2種」も点を打ってみました。期待値の線よりわずかに上、つまりほんの少しだけ運が悪かった程度で、嘆くほどではなかったことがわかります。

これ、エンジニアにも刺さる形があります。テストカバレッジです。0%から80%までは驚くほど早く到達するのに、残り20%を埋める作業が永遠に終わらない。あるいはキャッシュのウォームアップ。よく使われるデータはすぐ乗るのに、全体を温め切るには桁違いの時間がかかる。同じ数式が裏で効いています。


3. 現場は「ランダムのまま」にしておかない

さて、ここからが本題です。ランダムは、放っておくと人間にとって不愉快な結果を出します。

有名なのが音楽ストリーミングのシャッフル再生の話です。純粋にランダムで曲を選ぶと、同じアーティストの曲が3回続くようなことが普通に起こり、「シャッフルが壊れている」というクレームが殺到しました。数学的には完全に正しいのに、人間の感覚では「ランダムじゃない」と判定されてしまうわけです。そこで各社は、あえて数学的な純粋さを捨てて、人間が"ランダムだ"と感じる並びを作る方向にアルゴリズムを作り変えました。

その代表格がシャッフルバッグという手法です。毎回サイコロを振るのではなく、全種類を袋に入れてシャッフルし、袋が空になるまで引き続ける。こうすると1周する間は絶対に被りません。

この2つの違い、Python なら標準ライブラリの関数ひとつで表現できます。

import random

BONUS = [
    "ちいかわ", "ハチワレ", "うさぎ", "モモンガ",
    "くりまんじゅう", "ラッコ", "シーサー", "古本屋",
]

# 毎回サイコロを振る方式(復元抽出)。今回の私がこれ
random.choices(BONUS, k=3)  # → ['うさぎ', '古本屋', 'うさぎ']

# 袋から引く方式(非復元抽出)。3個引く限り絶対に被らない
random.sample(BONUS, k=3)   # → ['シーサー', 'うさぎ', 'モモンガ']

choices と sample、たった1単語の差が「被る世界」と「被らない世界」を分けています。袋が空になったら詰め直す、という運用まで含めて書くとこうなります。

import random
from collections.abc import Callable, Sequence


def create_shuffle_bag[T](items: Sequence[T]) -> Callable[[], T]:
    """全種類を1周するまで重複が発生しない抽選器を返す"""
    bag: list[T] = []

    def draw() -> T:
        nonlocal bag
        if not bag:
            # 袋が空になったら詰め直してシャッフル(Fisher-Yates)
            bag = list(items)
            random.shuffle(bag)
        return bag.pop()

    return draw


draw_bonus = create_shuffle_bag(BONUS)

# 8回引けば必ず全8種そろう。平均21.7個も引かなくていい
collected = [draw_bonus() for _ in range(8)]
assert len(set(collected)) == 8

ソーシャルゲームの「天井」(一定回数外れたら確定で当たる)も、思想としてはこれと同じ。確率の裾野を人為的に切り落として、体験を設計しているわけです。

負荷分散の世界にも似た話があります。サーバーを完全にランダムで選ぶと、運悪く1台にリクエストが集中する瞬間が必ず生まれます。ところが「ランダムに2台選んで、空いている方に流す」というひと工夫を入れるだけで、偏りは劇的に小さくなることが知られています(Power of Two Choices)。ほんの少しだけ「選ぶ」余地を作るだけで、ランダムの牙は抜けるのです。

つまり、うさぎが2個になったのは乱数のバグではなく、乱数がバグっていないことの証明でした。そして我々エンジニアの仕事は、その正しさをそのままユーザーに突きつけないよう、間に一枚設計を挟んであげることなのだと思います。


4. おまけ:「ネタバレなし」で感想を残す方法

最後に小ネタをひとつ。

「今すぐ感想を叫びたい、でもネタバレは絶対にダメ」。この板挟み、実は暗号技術に綺麗な解があります。コミットメント方式です。

感想をテキストに書いてハッシュ値だけを先に公開しておき、公開期間が終わってから元のテキストを開示する。ハッシュ値が一致すれば、その感想を確かに公開時点で書いていたことを、中身を一切明かさずに証明できます。

import hashlib

kansou = "(ここに全力のネタバレ感想)"

# 公開してよいのはこの64文字だけ。中身は1文字も漏れない
print(hashlib.sha256(kansou.encode("utf-8")).hexdigest())
# → 3f9a...(略)

……まあ、素直に黙っているのが一番なんですけどね。


まとめ

  • 3個でキャラが被る確率は約34%。被って当たり前、乱数は正常
  • 全8種コンプの期待個数は約21.7個。ラスト1種だけで平均8個という地獄
  • 現場のランダムは、シャッフルバッグや天井で人間向けにチューニングされている

映画そのものの感想は、公開が一段落したころに改めて書きたいと思います。まだの方は、ぜひ劇場で。そして特典が被っても、それは運が悪いのではなく確率どおりなので、どうか乱数を恨まないであげてください。

(※入場者特典の内容・配布方法・種類数は劇場や配布週によって異なる場合があります。最新情報は必ず公式サイトをご確認ください。)

PR(広告)

楽天市場お名前.com
このサイトについてプライバシーポリシーお問い合わせ

本サイトはアフィリエイト広告(プロモーション)を含みます

© 2026 LLM Agent Digest

PR(広告)

楽天市場あなたのサイトのURL、そろそろスリムにしませんか?