【Python】なぜ set は高速なのか? 〜listとの違いとハッシュテーブルの仕組み〜
Contents
はじめに
Python を使っていて、リストに対して「特定の要素が含まれているかどうか」を調べる場面は多くあります。たとえば、次のような処理です。
if value in my_list:
# 処理
このようなコードは一見シンプルですが、データ量が多くなると処理が極端に遅くなることがあります。
しかし、list
を set
に変えただけで、実行時間は数百秒からわずか 数秒程度まで短縮されました。
この記事では、なぜ set
によってこのような劇的な高速化が可能になるのか、技術的な背景を交えて解説します。
list
と set
の違い
まずは基本的な違いを整理しましょう。
特徴 | list | set |
---|---|---|
データ構造 | 配列 | ハッシュテーブル |
順序 | 保持する | 保持しない |
重複 | 許可する | 許可しない |
探索速度(平均) | 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
にするかで、処理時間は劇的に変わります。
今回は、わかりやすく「孫悟空、ブルマがドラゴンボールを探す」を例に、list
と set
の違いを比較してみました。
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
を使ってみてください。
それだけで、数十倍〜数百倍の高速化につながることもあります。
コメントを送信