【Python】なぜ set は高速なのか? 〜listとの違いとハッシュテーブルの仕組み〜

はじめに

Python を使っていて、リストに対して「特定の要素が含まれているかどうか」を調べる場面は多くあります。たとえば、次のような処理です。

if value in my_list:
    # 処理

このようなコードは一見シンプルですが、データ量が多くなると処理が極端に遅くなることがあります。

しかし、listset に変えただけで、実行時間は数百秒からわずか 数秒程度まで短縮されました。

この記事では、なぜ set によってこのような劇的な高速化が可能になるのか、技術的な背景を交えて解説します。


listset の違い

まずは基本的な違いを整理しましょう。

特徴listset
データ構造配列ハッシュテーブル
順序保持する保持しない
重複許可する許可しない
探索速度(平均)O(n)O(1)

list はなぜ遅いのか?

Python の list は、単純な「配列構造」で実装されています。

そのため、value in my_list のような処理では、先頭から1つずつ全ての要素を順番に確認する必要があります。

たとえば以下のようなコードでは:

a = [1, 3, 5, 7, 9]
print(5 in a)

Python は 1 → 3 → 5 というふうに、順番に走査していきます。

このような検索は、要素数が少ないうちは問題になりませんが、10万件、100万件と増えると一気に処理が重くなるのです。


set はなぜ高速なのか?

一方で set は、「ハッシュテーブル」というデータ構造で実装されています。

これは、値を内部的に「ハッシュ値」という一意な数値に変換して管理**する仕組みです。

ハッシュテーブルのイメージ

  • "apple" → ハッシュ関数 → 42
  • 42 番目のバケツに "apple" を格納
  • 検索時も "apple"42 → 該当データに即アクセス

このように、リストのように全件を走査する必要がなく、平均して1回のアクセスで目的の要素にたどり着くことができます。

そのため、value in my_set は非常に高速です。


実験:リストとセットの探索速度を比較してみた

大量データの中から「この値があるかどうか?」を調べる処理、Python では in 演算子で簡単に書けますよね。

でも、データの持ち方list にするか set にするかで、処理時間は劇的に変わります。

今回は、わかりやすく「孫悟空、ブルマがドラゴンボールを探す」を例に、listset の違いを比較してみました。

import time
from random import randint

# ----------- 世界に散らばるドラゴンボール(データ生成)-----------
print("ドラゴンボール探索実験開始!\n")

world_dragonballs = [randint(1, 10_000_000) for _ in range(100_000)]

radar_signals = [randint(1, 10_000_000) for _ in range(100_000)]

# ----------- 悟空の気合い探索(list)-----------
print("悟空「よーし!全部のドラゴンボールをくまなく探すぞ!」(list)")

start_time = time.time()
found_count_list = 0
for signal in radar_signals:
    if signal in world_dragonballs:
        found_count_list += 1
elapsed_list = time.time() - start_time

print(f"[リスト探索] 見つけた数:{found_count_list}個, 探索時間:{elapsed_list:.3f}秒")

# ----------- ブルマのドラゴンレーダー(set)-----------
print("\nブルマ「悟空、ドラゴンレーダーで探せば一瞬よ!」(set)")

capsule_corp_scouter = set(world_dragonballs)

start_time = time.time()
found_count_set = 0
for signal in radar_signals:
    if signal in capsule_corp_scouter:
        found_count_set += 1
elapsed_set = time.time() - start_time

print(f"[ドラゴンレーダー探索] 見つけた数:{found_count_set}個, 探索時間:{elapsed_set:.3f}秒")

# ----------- 結果発表 -----------
print("\n結果発表:")
if elapsed_list > elapsed_set:
    print(f"ブルマのドラゴンレーダーは悟空の気合い探索の {round(elapsed_list / elapsed_set)} 倍速かった!")
else:
    print("まさかの…悟空の気合い勝ち!?")

パターン①:悟空が地道に探す(list)

まずは悟空。世界中を飛び回って、1個ずつドラゴンボールを探していくスタイルです。これはまさに list で探索している状態。1つずつチェックするため、要素数が多いと時間がかかります。

ドラゴンボール探索実験開始!
悟空「よーし!全部のドラゴンボールをくまなく探すぞ!」(list)
[リスト探索] 見つけた数:1010個, 探索時間:80.632秒

力任せに頑張ってくれる悟空は頼もしいですが、10万件のデータを10万回調べるのはさすがに時間がかかります。


パターン②:ブルマがスカウターで瞬時に発見(set)

次にブルマ。さすがカプセルコーポレーション、ハッシュテーブル搭載のドラゴンレーダー(= set)**を使って一瞬でチェック!

ブルマ「悟空、ドラゴンレーダーで探せば一瞬よ!」(set)
[スカウター探索] 見つけた数:1010個, 探索時間:0.012秒
結果発表:
ブルマのスカウターは悟空の気合い探索の 6976 倍速かった!

なんと、処理時間は約6976分の1。科学の力、恐るべし。


まとめ

Python の set は、以下のようなシチュエーションに非常に有効です。

  • データの 存在確認 を高速に行いたいとき
  • 重複を排除したいとき
  • 順番を気にしない処理

逆に、

  • 順番を保持したい
  • 要素の順番を意識して処理したい

という場合には list が適しています。


おわりに

Python の set は、ただの「重複を許さないデータ型」ではなく、データ構造レベルで大きな性能差を生み出す強力なツールです。

「とりあえず list で処理を書いていたけど遅いな…」と思ったら、ぜひ一度 set を使ってみてください。

それだけで、数十倍〜数百倍の高速化につながることもあります。


参考文献

コメントを送信

You May Have Missed