JavaScriptでの**ハッシュテーブル(Hash Table)**について詳しく説明します。
ハッシュテーブルは、キー(key)と値(value)のペアを効率的に保存・検索するためのデータ構造です。
ハッシュ関数を用いてキーを一意のインデックスに変換し、配列の特定の位置に値を格納することで、O(1)(平均ケース) の時間でデータを検索・挿入・削除できます。
JavaScriptでは、オブジェクト(Object)やMapをハッシュテーブルとして使用できます。
JavaScriptのObjectは、キーと値のペアを格納するハッシュテーブルとして動作します。
const hashTable = {};
// データを追加
hashTable['name'] = 'Alice';
hashTable['age'] = 25;
// データを取得
console.log(hashTable['name']); // "Alice"
// データの削除
delete hashTable['age'];
console.log(hashTable); // { name: 'Alice' }特長:
- 文字列キーの使用が可能(数値キーも可能だが文字列に変換される)。
Object.prototypeの影響を受けるため、シンプルなハッシュテーブルとして使うには注意が必要。
欠点:
hasOwnPropertyを使わないと、toStringなどの組み込みプロパティと衝突する可能性がある。- キーは必ず文字列として扱われる。
const hashTable = Object.create(null);
hashTable['name'] = 'Alice';
console.log(hashTable['name']); // "Alice"これにより、プロトタイプの影響を受けないクリーンなハッシュテーブルが作れます。
ECMAScript 6(ES6)では、Mapが導入され、より強力なハッシュテーブルを利用できるようになりました。
const hashTable = new Map();
// データを追加
hashTable.set('name', 'Alice');
hashTable.set('age', 25);
// データを取得
console.log(hashTable.get('name')); // "Alice"
// データの存在確認
console.log(hashTable.has('age')); // true
// データの削除
hashTable.delete('age');
// すべてのデータを削除
hashTable.clear();メリット:
- どんな型でもキーにできる(オブジェクト、配列、関数も可)
sizeプロパティで要素数を取得可能(Objectにはない)forEachやfor...ofでイテレーションが可能- 挿入順を保持する
hashTable.set(42, 'Number key');
hashTable.set({ key: 'obj' }, 'Object key');
console.log(hashTable.get(42)); // "Number key"キー(文字列)を数値のインデックスに変換する簡単なハッシュ関数を作成します。
function hashStringToIndex(key, size) {
let hash = 0;
for (let i = 0; i < key.length; i++) {
hash += key.charCodeAt(i);
}
return hash % size; // 配列のサイズで割ることで範囲を制限
}この関数は、keyの各文字のUnicode値を合計し、配列サイズで割った余りを返します。
配列をベースに、キー・値ペアを管理するハッシュテーブルを作成します。
class HashTable {
constructor(size = 50) {
this.table = new Array(size);
this.size = size;
}
// ハッシュ関数
_hash(key) {
let hash = 0;
for (let i = 0; i < key.length; i++) {
hash += key.charCodeAt(i);
}
return hash % this.size;
}
// データの追加
set(key, value) {
const index = this._hash(key);
if (!this.table[index]) {
this.table[index] = [];
}
this.table[index].push([key, value]); // キー・値ペアを配列で保存
}
// データの取得
get(key) {
const index = this._hash(key);
if (this.table[index]) {
for (let pair of this.table[index]) {
if (pair[0] === key) {
return pair[1];
}
}
}
return undefined;
}
// データの削除
remove(key) {
const index = this._hash(key);
if (this.table[index]) {
this.table[index] = this.table[index].filter((pair) => pair[0] !== key);
}
}
// 表示
display() {
this.table.forEach((pairs, index) => {
if (pairs) {
console.log(index, pairs);
}
});
}
}
// ハッシュテーブルの使用
const myHashTable = new HashTable();
myHashTable.set('name', 'Alice');
myHashTable.set('age', 25);
console.log(myHashTable.get('name')); // "Alice"
myHashTable.remove('age');
myHashTable.display();同じインデックスに複数のキーが割り当てられる問題。
- チェイン法(Chaining): 配列の各インデックスにリスト(配列)を格納する(上のコードで採用)。
- オープンアドレス法(Open Addressing): すでにデータがある場合、次の空きスロットを探す。
配列のサイズが小さいと衝突が増え、大きすぎるとメモリの無駄が発生。
- 負荷率(Load Factor) を監視し、一定以上になったらサイズを増やす。
| 手法 | 特徴 |
|---|---|
Object |
文字列キーのみ。プロトタイプの影響あり。 |
Object.create(null) |
プロトタイプなしの純粋なハッシュテーブル。 |
Map |
任意の型のキーが使用可能。順序も保持。 |
| 自作ハッシュテーブル | Arrayとhash関数を使って実装可能。 |
実際の開発では、基本的に Mapを推奨