Python
 Computer >> コンピューター >  >> プログラミング >> Python

組み込みライブラリを使わずに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として必要な機能をすべて満たしています。

  1. Pythonで二分木の直径を求める方法【DFSを使った実装解説】

    二分木の直径とは二分木が与えられたとき、その木の直径(diameter)を計算することを考えます。二分木の直径とは、木の中の任意の2つのノードをつなぐ最長経路の長さのことです。重要なポイントとして、この経路は必ずしも根(ルート)を通るとは限りません。例えば、次のような木を考えてみましょう。この場合、経路 [4, 2, 1, 3] または [5, 2, 1, 3] の長さが3本の辺で構成されているため、直径は3となります。解法のアプローチこの問題はDFS(深さ優先探索)を使うことで効率的に解くことができます。手順は以下の通りです。DFSで各ノードを訪問しながら直径を求めます。まず答えを格納する変

  2. Pythonの継承とは?単一継承と階層継承の基本をサンプルコードで解説

    本記事では、Python 3.xにおける継承(インヘリタンス)とクラスの拡張方法について詳しく解説します。 継承とは、現実世界のモノや概念の関係性を自然に表現できる、オブジェクト指向プログラミングの中核となる仕組みです。継承を活用すると、次のようなメリットが得られます。 再利用性:すでに書いたコードを流用でき、重複を削減できる 推移性:クラス間の関係を連鎖的に引き継げる 開発速度の向上:ゼロから書かずに済むため、短期間で開発できる 保守性・拡張性:既存クラスを壊さずに機能を追加しやすい 継承の5つの種類 Pythonの継承は、その構造によって主に以下の5種類に分類されます。 単一継承(