組み込みライブラリを使わずにPythonでHashSet(ハッシュセット)を実装する方法
本記事では、Pythonの組み込みハッシュテーブルライブラリを使用せずに、HashSet(ハッシュセット)データ構造をゼロから設計・実装する方法を解説します。実装すべき主な操作は以下の3つです。
- add(x) ― 値 x をHashSetに挿入します
- contains(x) ― 値 x がHashSetに存在するかどうかを判定します
- remove(x) ― 値 x をHashSetから削除します。値が存在しない場合は何もしません
動作確認のシナリオ
まずHashSetを初期化し、その後 add(1) → add(3) → contains(1) → contains(2) → add(2) → contains(2) → remove(2) → contains(2) の順に呼び出してテストします。期待される出力は次のとおりです。
contains(1)→ True(1は存在する)contains(2)→ False(2はまだ存在しない)add(2)後のcontains(2)→ True(2は存在する)remove(2)後のcontains(2)→ False(2は削除された)
設計のアプローチ
この問題を解くためには、「バケット方式」と呼ばれる手法を用います。大きな配列(ハッシュテーブル)を用意し、キーをハッシュ関数で振り分けることで、各バケット内に格納される要素数を抑えられます。具体的な手順は以下のとおりです。
1. Bucketクラスの定義
bucketとして新しいリストを初期化します
update(key) メソッド:
found := Falseとして初期化- bucket内の各インデックス i と要素 k について走査し、key と k が一致すれば
bucket[i] = keyとして found を True に設定しループを抜けます - 見つからなかった場合は、bucketの末尾に key を追加します
get(key) メソッド:
- bucket内の各要素 k を走査し、k が key と一致すれば True を返します
- 最後まで一致しなければ False を返します
remove(key) メソッド:
- bucket内の各インデックス i と要素 k を走査し、key と一致したら
del bucket[i]で削除します
2. MyHashSetクラスの定義
key_space := 2096(バケットの総数)hash_tableとして、Bucketオブジェクトを key_space 個格納したリストを作成します
add(key) メソッド:
hash_key = key % key_spaceでハッシュ値を計算hash_table[hash_key]の update(key) を呼び出します
remove(key) メソッド:
hash_key = key % key_spaceでハッシュ値を計算hash_table[hash_key]から key を削除します
contains(key) メソッド:
hash_key = key % key_spaceでハッシュ値を計算hash_table[hash_key]の get(key) の結果を返します
実装例
それでは、実際のコードを見てみましょう。
class Bucket:
def __init__(self):
self.bucket=[]
def update(self, key):
found=False
for i,k in enumerate(self.bucket):
if key==k:
self.bucket[i]=key
found=True
break
if not found:
self.bucket.append(key)
def get(self, key):
for k in self.bucket:
if k==key:
return True
return False
def remove(self, key):
for i,k in enumerate(self.bucket):
if key==k:
del self.bucket[i]
class MyHashSet:
def __init__(self):
self.key_space = 2096
self.hash_table=[Bucket() for i in range(self.key_space)]
def add(self, key):
hash_key=key%self.key_space
self.hash_table[hash_key].update(key)
def remove(self, key):
hash_key=key%self.key_space
self.hash_table[hash_key].remove(key)
def contains(self, key):
hash_key=key%self.key_space
return self.hash_table[hash_key].get(key)
ob = MyHashSet()
ob.add(1)
ob.add(3)
print(ob.contains(1))
print(ob.contains(2))
ob.add(2)
print(ob.contains(2))
ob.remove(2)
print(ob.contains(2))
入力
ob = MyHashSet()
ob.add(1)
ob.add(3)
print(ob.contains(1))
print(ob.contains(2))
ob.add(2)
print(ob.contains(2))
ob.remove(2)
print(ob.contains(2))
出力
True
False
True
False
まとめ
この実装では、モジュロ演算(剰余計算)による単純なハッシュ関数を使ってキーを2096個のバケットに振り分けています。これにより、各バケットに含まれる要素数が平均的に少なくなり、検索・挿入・削除の処理を高速に行えます。ハッシュの衝突が起きても、バケット内部では線形探索によって正しく処理されるため、HashSetとして必要な機能をすべて満たしています。
-
Pythonで二分木の直径を求める方法【DFSを使った実装解説】
二分木の直径とは二分木が与えられたとき、その木の直径(diameter)を計算することを考えます。二分木の直径とは、木の中の任意の2つのノードをつなぐ最長経路の長さのことです。重要なポイントとして、この経路は必ずしも根(ルート)を通るとは限りません。例えば、次のような木を考えてみましょう。この場合、経路 [4, 2, 1, 3] または [5, 2, 1, 3] の長さが3本の辺で構成されているため、直径は3となります。解法のアプローチこの問題はDFS(深さ優先探索)を使うことで効率的に解くことができます。手順は以下の通りです。DFSで各ノードを訪問しながら直径を求めます。まず答えを格納する変
-
Pythonの継承とは?単一継承と階層継承の基本をサンプルコードで解説
本記事では、Python 3.xにおける継承(インヘリタンス)とクラスの拡張方法について詳しく解説します。 継承とは、現実世界のモノや概念の関係性を自然に表現できる、オブジェクト指向プログラミングの中核となる仕組みです。継承を活用すると、次のようなメリットが得られます。 再利用性:すでに書いたコードを流用でき、重複を削減できる 推移性:クラス間の関係を連鎖的に引き継げる 開発速度の向上:ゼロから書かずに済むため、短期間で開発できる 保守性・拡張性:既存クラスを壊さずに機能を追加しやすい 継承の5つの種類 Pythonの継承は、その構造によって主に以下の5種類に分類されます。 単一継承(