Re-translate the Japanese version (#1871)

* Retranslate Japanese docs with GPT-5.4

* Retranslate Japanese code with GPT-5.4
This commit is contained in:
Yudong Jin
2026-03-30 07:30:15 +08:00
committed by GitHub
parent fe6443235b
commit d7b2277d2b
1444 changed files with 83312 additions and 8363 deletions
+259 -185
View File
@@ -1,238 +1,238 @@
# ハッシュ
# ハッシュテーブル
<u>ハッシュ</u>は<u>ハッシュマップ</u>とも呼ばれ、キーと値の間のマッピングを確立し、効率な要素の取得を可能にするデータ構造です。具体的には、ハッシュ表に`key`を入力すると、$O(1)$の時間計算量で対応する`value`取得できます。
<u>ハッシュテーブル(hash table</u>は<u>散列表</u>とも呼ばれ、キー `key` と値 `value` の対応関係を構築することで、高効率な要素検索を実現します。具体的には、ハッシュテーブルにキー `key` を入力すると、対応する`value` を $O(1)$ 時間で取得できます。
図に示すように、$n$人の学生がい、各学生は「名前」と「学籍番号」の2つのデータフィールドがあるとします。学籍番号を入力として対応する名前を返すクエリ機能を実したい場合、下図に示すハッシュ表を使用できます。
以下の図に示すように、$n$ 人の学生がいるとし、各学生は「名前」と「学籍番号」の 2 つの情報を持っています。もし「学籍番号を入力すると対応する名前を返す」という検索機能を実したいなら、下図のようなハッシュテーブルを用いることができます。
![ハッシュの抽象的な表現](hash_map.assets/hash_table_lookup.png)
![ハッシュテーブルの抽象表現](hash_map.assets/hash_table_lookup.png)
ハッシュ表に加えて、配列や連結リストもクエリ機能の実装に使用できますが、時間計算量が異なります。効率は以下の表で比較されています
ハッシュテーブルのほかに、配列や連結リストでも検索機能を実現できます。それらの効率比較を次の表に示します
- **要素の挿入**: 配列(または連結リスト)の末尾に要素を追加するだけです。この操作の時間計算量は$O(1)$です。
- **要素の検索**: 配列(または連結リスト)がソートされていないため、要素を検索するにはすべての要素を走査する必要があります。この操作の時間計算量は$O(n)$です。
- **要素の削除**: 要素を削除するには、まずその要素を見つけてから配列(または連結リスト)から削除します。この操作の時間計算量は$O(n)$です。
- **要素の追加**:要素を配列(連結リスト)の末尾に追加するだけでよく、$O(1)$ 時間です。
- **要素の検索**配列(連結リスト)は無秩序なので、すべての要素を走査する必要があり、$O(n)$ 時間かかります。
- **要素の削除**:先に要素を検索してから配列(連結リスト)から削除する必要があり、$O(n)$ 時間かかります。
<p align="center"> 表 <id> &nbsp; 一般的な操作の時間効率の比較 </p>
<p align="center"> 表 <id> &nbsp; 要素検索効率の比較 </p>
| | 配列 | 連結リスト | ハッシュ |
| -------------- | ------ | ----------- | ---------- |
| 要素の検索 | $O(n)$ | $O(n)$ | $O(1)$ |
| 要素の挿入 | $O(1)$ | $O(1)$ | $O(1)$ |
| 要素の削除 | $O(n)$ | $O(n)$ | $O(1)$ |
| | 配列 | 連結リスト | ハッシュテーブル |
| -------- | ------ | ------ | ------ |
| 要素の検索 | $O(n)$ | $O(n)$ | $O(1)$ |
| 要素の追加 | $O(1)$ | $O(1)$ | $O(1)$ |
| 要素の削除 | $O(n)$ | $O(n)$ | $O(1)$ |
観察されるように、**ハッシュ表における操作(挿入、削除検索、変更)の時間計算量は$O(1)$**、非常に効率です。
以上から分かるように、**ハッシュテーブルにおける追加・削除検索・更新の時間計算量はいずれも $O(1)$** であり、非常に効率です。
## ハッシュ表の一般的な操作
## ハッシュテーブルの基本操作
ハッシュの一般的な操作には、初期化、クエリ、キー値ペアの追加、キーペアの削除があります。以下はコード例です:
ハッシュテーブルの一般的な操作には、初期化、検索、キーと値のペアの追加、キーと値のペアの削除などがあります。コード例は以下のとおりです:
=== "Python"
```python title="hash_map.py"
# ハッシュを初期化
# ハッシュテーブルを初期化
hmap: dict = {}
# 追加操作
# ハッシュ表にキー値ペア (key, value) を追加
hmap[12836] = "小哈"
hmap[15937] = "小啰"
hmap[16750] = "小算"
hmap[13276] = "小法"
hmap[10583] = "小鸭"
# ハッシュテーブルにキーと値のペア (key, value) を追加
hmap[12836] = "シャオハ"
hmap[15937] = "シャオルオ"
hmap[16750] = "シャオスワン"
hmap[13276] = "シャオファ"
hmap[10583] = "シャオヤー"
# クエリ操作
# ハッシュ表にキーを入力し、値を取得
# 検索操作
# ハッシュテーブルにキー key を入力し、値 value を取得
name: str = hmap[15937]
# 削除操作
# ハッシュからキーペア (key, value) を削除
# ハッシュテーブルからキーと値のペア (key, value) を削除
hmap.pop(10583)
```
=== "C++"
```cpp title="hash_map.cpp"
/* ハッシュを初期化 */
/* ハッシュテーブルを初期化 */
unordered_map<int, string> map;
/* 追加操作 */
// ハッシュ表にキー値ペア (key, value) を追加
map[12836] = "小哈";
map[15937] = "小啰";
map[16750] = "小算";
map[13276] = "小法";
map[10583] = "小鸭";
// ハッシュテーブルにキーと値のペア (key, value) を追加
map[12836] = "シャオハ";
map[15937] = "シャオルオ";
map[16750] = "シャオスワン";
map[13276] = "シャオファ";
map[10583] = "シャオヤー";
/* クエリ操作 */
// ハッシュ表にキーを入力し、値を取得
/* 検索操作 */
// ハッシュテーブルにキー key を入力し、値 value を取得
string name = map[15937];
/* 削除操作 */
// ハッシュからキーペア (key, value) を削除
// ハッシュテーブルからキーと値のペア (key, value) を削除
map.erase(10583);
```
=== "Java"
```java title="hash_map.java"
/* ハッシュを初期化 */
/* ハッシュテーブルを初期化 */
Map<Integer, String> map = new HashMap<>();
/* 追加操作 */
// ハッシュ表にキー値ペア (key, value) を追加
map.put(12836, "小哈");
map.put(15937, "小啰");
map.put(16750, "小算");
map.put(13276, "小法");
map.put(10583, "小鸭");
// ハッシュテーブルにキーと値のペア (key, value) を追加
map.put(12836, "シャオハ");
map.put(15937, "シャオルオ");
map.put(16750, "シャオスワン");
map.put(13276, "シャオファ");
map.put(10583, "シャオヤー");
/* クエリ操作 */
// ハッシュ表にキーを入力し、値を取得
/* 検索操作 */
// ハッシュテーブルにキー key を入力し、値 value を取得
String name = map.get(15937);
/* 削除操作 */
// ハッシュからキーペア (key, value) を削除
// ハッシュテーブルからキーと値のペア (key, value) を削除
map.remove(10583);
```
=== "C#"
```csharp title="hash_map.cs"
/* ハッシュを初期化 */
/* ハッシュテーブルを初期化 */
Dictionary<int, string> map = new() {
/* 追加操作 */
// ハッシュ表にキー値ペア (key, value) を追加
{ 12836, "小哈" },
{ 15937, "小啰" },
{ 16750, "小算" },
{ 13276, "小法" },
{ 10583, "小鸭" }
// ハッシュテーブルにキーと値のペア (key, value) を追加
{ 12836, "シャオハ" },
{ 15937, "シャオルオ" },
{ 16750, "シャオスワン" },
{ 13276, "シャオファ" },
{ 10583, "シャオヤー" }
};
/* クエリ操作 */
// ハッシュ表にキーを入力し、値を取得
/* 検索操作 */
// ハッシュテーブルにキー key を入力し、値 value を取得
string name = map[15937];
/* 削除操作 */
// ハッシュからキーペア (key, value) を削除
// ハッシュテーブルからキーと値のペア (key, value) を削除
map.Remove(10583);
```
=== "Go"
```go title="hash_map_test.go"
/* ハッシュを初期化 */
/* ハッシュテーブルを初期化 */
hmap := make(map[int]string)
/* 追加操作 */
// ハッシュ表にキー値ペア (key, value) を追加
hmap[12836] = "小哈"
hmap[15937] = "小啰"
hmap[16750] = "小算"
hmap[13276] = "小法"
hmap[10583] = "小鸭"
// ハッシュテーブルにキーと値のペア (key, value) を追加
hmap[12836] = "シャオハ"
hmap[15937] = "シャオルオ"
hmap[16750] = "シャオスワン"
hmap[13276] = "シャオファ"
hmap[10583] = "シャオヤー"
/* クエリ操作 */
// ハッシュ表にキーを入力し、値を取得
/* 検索操作 */
// ハッシュテーブルにキー key を入力し、値 value を取得
name := hmap[15937]
/* 削除操作 */
// ハッシュからキーペア (key, value) を削除
// ハッシュテーブルからキーと値のペア (key, value) を削除
delete(hmap, 10583)
```
=== "Swift"
```swift title="hash_map.swift"
/* ハッシュを初期化 */
/* ハッシュテーブルを初期化 */
var map: [Int: String] = [:]
/* 追加操作 */
// ハッシュ表にキー値ペア (key, value) を追加
map[12836] = "小哈"
map[15937] = "小啰"
map[16750] = "小算"
map[13276] = "小法"
map[10583] = "小鸭"
// ハッシュテーブルにキーと値のペア (key, value) を追加
map[12836] = "シャオハ"
map[15937] = "シャオルオ"
map[16750] = "シャオスワン"
map[13276] = "シャオファ"
map[10583] = "シャオヤー"
/* クエリ操作 */
// ハッシュ表にキーを入力し、値を取得
/* 検索操作 */
// ハッシュテーブルにキー key を入力し、値 value を取得
let name = map[15937]!
/* 削除操作 */
// ハッシュからキーペア (key, value) を削除
// ハッシュテーブルからキーと値のペア (key, value) を削除
map.removeValue(forKey: 10583)
```
=== "JS"
```javascript title="hash_map.js"
/* ハッシュを初期化 */
/* ハッシュテーブルを初期化 */
const map = new Map();
/* 追加操作 */
// ハッシュ表にキー値ペア (key, value) を追加
map.set(12836, '小哈');
map.set(15937, '小啰');
map.set(16750, '小算');
map.set(13276, '小法');
map.set(10583, '小鸭');
// ハッシュテーブルにキーと値のペア (key, value) を追加
map.set(12836, 'シャオハ');
map.set(15937, 'シャオルオ');
map.set(16750, 'シャオスワン');
map.set(13276, 'シャオファ');
map.set(10583, 'シャオヤー');
/* クエリ操作 */
// ハッシュ表にキーを入力し、値を取得
/* 検索操作 */
// ハッシュテーブルにキー key を入力し、値 value を取得
let name = map.get(15937);
/* 削除操作 */
// ハッシュからキーペア (key, value) を削除
// ハッシュテーブルからキーと値のペア (key, value) を削除
map.delete(10583);
```
=== "TS"
```typescript title="hash_map.ts"
/* ハッシュを初期化 */
/* ハッシュテーブルを初期化 */
const map = new Map<number, string>();
/* 追加操作 */
// ハッシュ表にキー値ペア (key, value) を追加
map.set(12836, '小哈');
map.set(15937, '小啰');
map.set(16750, '小算');
map.set(13276, '小法');
map.set(10583, '小鸭');
console.info('\n追加後ハッシュ表は\nKey -> Value');
// ハッシュテーブルにキーと値のペア (key, value) を追加
map.set(12836, 'シャオハ');
map.set(15937, 'シャオルオ');
map.set(16750, 'シャオスワン');
map.set(13276, 'シャオファ');
map.set(10583, 'シャオヤー');
console.info('\n追加後ハッシュテーブルは次のとおりです\nKey -> Value');
console.info(map);
/* クエリ操作 */
// ハッシュ表にキーを入力し、値を取得
/* 検索操作 */
// ハッシュテーブルにキー key を入力し、値 value を取得
let name = map.get(15937);
console.info('\n学籍番号15937を入力、名前を問い合わせ ' + name);
console.info('\n学籍番号 15937 を入力、名前を検索: ' + name);
/* 削除操作 */
// ハッシュからキーペア (key, value) を削除
// ハッシュテーブルからキーと値のペア (key, value) を削除
map.delete(10583);
console.info('\n10583を削除後、ハッシュ表は\nKey -> Value');
console.info('\n10583 を削除した後のハッシュテーブル\nKey -> Value');
console.info(map);
```
=== "Dart"
```dart title="hash_map.dart"
/* ハッシュを初期化 */
/* ハッシュテーブルを初期化 */
Map<int, String> map = {};
/* 追加操作 */
// ハッシュ表にキー値ペア (key, value) を追加
map[12836] = "小哈";
map[15937] = "小啰";
map[16750] = "小算";
map[13276] = "小法";
map[10583] = "小鸭";
// ハッシュテーブルにキーと値のペア (key, value) を追加
map[12836] = "シャオハ";
map[15937] = "シャオルオ";
map[16750] = "シャオスワン";
map[13276] = "シャオファ";
map[10583] = "シャオヤー";
/* クエリ操作 */
// ハッシュ表にキーを入力し、値を取得
/* 検索操作 */
// ハッシュテーブルにキー key を入力し、値 value を取得
String name = map[15937];
/* 削除操作 */
// ハッシュからキーペア (key, value) を削除
// ハッシュテーブルからキーと値のペア (key, value) を削除
map.remove(10583);
```
@@ -241,51 +241,95 @@
```rust title="hash_map.rs"
use std::collections::HashMap;
/* ハッシュを初期化 */
/* ハッシュテーブルを初期化 */
let mut map: HashMap<i32, String> = HashMap::new();
/* 追加操作 */
// ハッシュ表にキー値ペア (key, value) を追加
map.insert(12836, "小哈".to_string());
map.insert(15937, "小啰".to_string());
map.insert(16750, "小算".to_string());
map.insert(13279, "小法".to_string());
map.insert(10583, "小鸭".to_string());
// ハッシュテーブルにキーと値のペア (key, value) を追加
map.insert(12836, "シャオハ".to_string());
map.insert(15937, "シャオルオ".to_string());
map.insert(16750, "シャオスワン".to_string());
map.insert(13279, "シャオファ".to_string());
map.insert(10583, "シャオヤー".to_string());
/* クエリ操作 */
// ハッシュ表にキーを入力し、値を取得
/* 検索操作 */
// ハッシュテーブルにキー key を入力し、値 value を取得
let _name: Option<&String> = map.get(&15937);
/* 削除操作 */
// ハッシュからキーペア (key, value) を削除
// ハッシュテーブルからキーと値のペア (key, value) を削除
let _removed_value: Option<String> = map.remove(&10583);
```
=== "C"
```c title="hash_map.c"
// Cには組み込みのハッシュ表が提供されていません
// C には組み込みのハッシュテーブルはありません
```
=== "Kotlin"
```kotlin title="hash_map.kt"
/* ハッシュテーブルを初期化 */
val map = HashMap<Int,String>()
/* 追加操作 */
// ハッシュテーブルにキーと値のペア (key, value) を追加
map[12836] = "シャオハ"
map[15937] = "シャオルオ"
map[16750] = "シャオスワン"
map[13276] = "シャオファ"
map[10583] = "シャオヤー"
/* 検索操作 */
// ハッシュテーブルにキー key を入力し、値 value を取得
val name = map[15937]
/* 削除操作 */
// ハッシュテーブルからキーと値のペア (key, value) を削除
map.remove(10583)
```
ハッシュ表を走査する一般的な方法は3つあります:キー値ペアの走査、キーの走査、値の走査。以下はコード例です:
=== "Ruby"
```ruby title="hash_map.rb"
# ハッシュテーブルを初期化
hmap = {}
# 追加操作
# ハッシュテーブルにキーと値のペア (key, value) を追加
hmap[12836] = "シャオハ"
hmap[15937] = "シャオルオ"
hmap[16750] = "シャオスワン"
hmap[13276] = "シャオファ"
hmap[10583] = "シャオヤー"
# 検索操作
# ハッシュテーブルにキー key を入力し、値 value を取得
name = hmap[15937]
# 削除操作
# ハッシュテーブルからキーと値のペア (key, value) を削除
hmap.delete(10583)
```
??? pythontutor "可視化実行"
https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%90%91%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E8%BE%93%E5%85%A5%E9%94%AE%20key%20%EF%BC%8C%E5%BE%97%E5%88%B0%E5%80%BC%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E5%88%A0%E9%99%A4%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap.pop%2810583%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
ハッシュテーブルには、キーと値のペア、キー、値を走査する 3 つの一般的な方法があります。コード例は以下のとおりです:
=== "Python"
```python title="hash_map.py"
# ハッシュを走査
# キーペア key->value を走査
# ハッシュテーブルを走査
# キーと値のペア key->value を走査
for key, value in hmap.items():
print(key, "->", value)
# キーのみを走査
# キー key のみを走査
for key in hmap.keys():
print(key)
# 値のみを走査
# 値 value のみを走査
for value in hmap.values():
print(value)
```
@@ -293,12 +337,12 @@
=== "C++"
```cpp title="hash_map.cpp"
/* ハッシュを走査 */
// キーペア key->value を走査
/* ハッシュテーブルを走査 */
// キーと値のペア key->value を走査
for (auto kv: map) {
cout << kv.first << " -> " << kv.second << endl;
}
// イテレータを使用してキー値ペア key->value を走査
// イテレータを使って key->value を走査
for (auto iter = map.begin(); iter != map.end(); iter++) {
cout << iter->first << "->" << iter->second << endl;
}
@@ -307,16 +351,16 @@
=== "Java"
```java title="hash_map.java"
/* ハッシュを走査 */
// キーペア key->value を走査
for (Map.Entry<Integer, String> kv: map.entrySet()) {
/* ハッシュテーブルを走査 */
// キーと値のペア key->value を走査
for (Map.Entry <Integer, String> kv: map.entrySet()) {
System.out.println(kv.getKey() + " -> " + kv.getValue());
}
// キーのみを走査
// キー key のみを走査
for (int key: map.keySet()) {
System.out.println(key);
}
// 値のみを走査
// 値 value のみを走査
for (String val: map.values()) {
System.out.println(val);
}
@@ -325,16 +369,16 @@
=== "C#"
```csharp title="hash_map.cs"
/* ハッシュを走査 */
// キーペア Key->Value を走査
/* ハッシュテーブルを走査 */
// キーと値のペア Key->Value を走査
foreach (var kv in map) {
Console.WriteLine(kv.Key + " -> " + kv.Value);
}
// キーのみを走査
// キー key のみを走査
foreach (int key in map.Keys) {
Console.WriteLine(key);
}
// 値のみを走査
// 値 value のみを走査
foreach (string val in map.Values) {
Console.WriteLine(val);
}
@@ -343,16 +387,16 @@
=== "Go"
```go title="hash_map_test.go"
/* ハッシュを走査 */
// キーペア key->value を走査
/* ハッシュテーブルを走査 */
// キーと値のペア key->value を走査
for key, value := range hmap {
fmt.Println(key, "->", value)
}
// キーのみを走査
// キー key のみを走査
for key := range hmap {
fmt.Println(key)
}
// 値のみを走査
// 値 value のみを走査
for _, value := range hmap {
fmt.Println(value)
}
@@ -361,16 +405,16 @@
=== "Swift"
```swift title="hash_map.swift"
/* ハッシュを走査 */
// キーペア Key->Value を走査
/* ハッシュテーブルを走査 */
// キーと値のペア Key->Value を走査
for (key, value) in map {
print("\(key) -> \(value)")
}
// キーのみを走査
// キー Key のみを走査
for key in map.keys {
print(key)
}
// 値のみを走査
// 値 Value のみを走査
for value in map.values {
print(value)
}
@@ -379,16 +423,16 @@
=== "JS"
```javascript title="hash_map.js"
/* ハッシュを走査 */
console.info('\nキーペア Key->Value を走査');
/* ハッシュテーブルを走査 */
console.info('\nキーと値のペア Key->Value を走査');
for (const [k, v] of map.entries()) {
console.info(k + ' -> ' + v);
}
console.info('\nキーのみを走査 Key');
console.info('\nキー Key のみを走査');
for (const k of map.keys()) {
console.info(k);
}
console.info('\n値のみを走査 Value');
console.info('\n値 Value のみを走査');
for (const v of map.values()) {
console.info(v);
}
@@ -397,16 +441,16 @@
=== "TS"
```typescript title="hash_map.ts"
/* ハッシュを走査 */
console.info('\nキーペア Key->Value を走査');
/* ハッシュテーブルを走査 */
console.info('\nキーと値のペア Key->Value を走査');
for (const [k, v] of map.entries()) {
console.info(k + ' -> ' + v);
}
console.info('\nキーのみを走査 Key');
console.info('\nキー Key のみを走査');
for (const k of map.keys()) {
console.info(k);
}
console.info('\n値のみを走査 Value');
console.info('\n値 Value のみを走査');
for (const v of map.values()) {
console.info(v);
}
@@ -415,38 +459,38 @@
=== "Dart"
```dart title="hash_map.dart"
/* ハッシュを走査 */
// キーペア Key->Value を走査
/* ハッシュテーブルを走査 */
// キーと値のペア Key->Value を走査
map.forEach((key, value) {
print('$key -> $value');
print('$key -> $value');
});
// キーのみを走査 Key
// キー Key のみを走査
map.keys.forEach((key) {
print(key);
print(key);
});
// 値のみを走査 Value
// 値 Value のみを走査
map.values.forEach((value) {
print(value);
print(value);
});
```
=== "Rust"
```rust title="hash_map.rs"
/* ハッシュを走査 */
// キーペア Key->Value を走査
/* ハッシュテーブルを走査 */
// キーと値のペア Key->Value を走査
for (key, value) in &map {
println!("{key} -> {value}");
}
// キーのみを走査 Key
// キー Key のみを走査
for key in map.keys() {
println!("{key}");
}
// 値のみを走査 Value
// 値 Value のみを走査
for value in map.values() {
println!("{value}");
}
@@ -455,63 +499,93 @@
=== "C"
```c title="hash_map.c"
// Cには組み込みのハッシュ表が提供されていません
// C には組み込みのハッシュテーブルはありません
```
=== "Kotlin"
```kotlin title="hash_map.kt"
/* ハッシュテーブルを走査 */
// キーと値のペア key->value を走査
for ((key, value) in map) {
println("$key -> $value")
}
// キー key のみを走査
for (key in map.keys) {
println(key)
}
// 値 value のみを走査
for (_val in map.values) {
println(_val)
}
```
## ハッシュ表の簡単な実装
=== "Ruby"
まず、最も簡単なケースを考えてみましょう:**配列のみを使ってハッシュ表を実装すること**。ハッシュ表において、配列の各空きスロットは<u>バケット</u>と呼ばれ、各バケットはキー値ペアを格納できます。したがって、クエリ操作は`key`に対応するバケットを見つけ、そこから`value`を取得することになります。
```ruby title="hash_map.rb"
# ハッシュテーブルを走査
# キーと値のペア key->value を走査
hmap.entries.each { |key, value| puts "#{key} -> #{value}" }
では、`key`に基づいて対応するバケットをどのように特定するのでしょうか?これは<u>ハッシュ関数</u>によって実現されます。ハッシュ関数の役割は、より大きな入力空間をより小さな出力空間にマッピングすることです。ハッシュ表では、入力空間はすべてのキーで構成され、出力空間はすべてのバケット(配列インデックス)で構成されます。つまり、`key`が与えられた場合、**ハッシュ関数を使用して対応するキー値ペアの配列内の格納位置を決定できます**。
# キー key のみを走査
hmap.keys.each { |key| puts key }
与えられた`key`に対して、ハッシュ関数の計算は2つのステップで構成されます:
# 値 value のみを走査
hmap.values.each { |val| puts val }
```
1. 特定のハッシュアルゴリズム`hash()`を使用してハッシュ値を計算します。
2. ハッシュ値をバケット数(配列長)`capacity`で剰余を取り、キーに対応する配列`index`を取得します。
??? pythontutor "可視化実行"
https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E9%94%AE%E5%80%BC%E5%AF%B9%20key-%3Evalue%0A%20%20%20%20for%20key,%20value%20in%20hmap.items%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key,%20%22-%3E%22,%20value%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E9%94%AE%20key%0A%20%20%20%20for%20key%20in%20hmap.keys%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E5%80%BC%20value%0A%20%20%20%20for%20value%20in%20hmap.values%28%29%3A%0A%20%20%20%20%20%20%20%20print%28value%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
## ハッシュテーブルの簡単な実装
まずは最も単純なケースとして、**1 つの配列だけでハッシュテーブルを実装する**ことを考えます。ハッシュテーブルでは、配列中の各空き位置を<u>バケット(bucket</u>と呼び、各バケットには 1 つのキーと値のペアを格納できます。したがって、検索操作とは `key` に対応するバケットを見つけ、そのバケットから `value` を取得することです。
では、`key` に基づいて対応するバケットをどのように特定するのでしょうか。これは<u>ハッシュ関数(hash function</u>によって実現されます。ハッシュ関数の役割は、大きな入力空間をより小さな出力空間に写像することです。ハッシュテーブルでは、入力空間はすべての `key` 、出力空間はすべてのバケット(配列インデックス)です。言い換えると、`key` を入力すると、**ハッシュ関数によってその `key` に対応するキーと値のペアの配列内での格納位置を求められます**。
`key` を入力したとき、ハッシュ関数の計算過程は次の 2 段階に分かれます。
1. あるハッシュアルゴリズム `hash()` を用いてハッシュ値を計算します。
2. ハッシュ値をバケット数(配列長)`capacity` で剰余し、その `key` に対応するバケット(配列インデックス)`index` を求めます。
```shell
index = hash(key) % capacity
```
その後、`index`を使用してハッシュ内の対応するバケットにアクセスし、`value`を取得できます。
その後、`index` を使てハッシュテーブル内の対応するバケットにアクセスし、`value` を取得できます。
配列長`capacity = 100`、ハッシュアルゴリズム`hash(key) = key`として定義されているとします。したがって、ハッシュ関数は`key % 100`として表現できます。以下の図は、`key`を学籍番号、`value`を名前として、ハッシュ関数の動作原理を示しています。
配列長`capacity = 100` 、ハッシュアルゴリズム`hash(key) = key` とすると、ハッシュ関数は `key % 100` となります。の図は、`key` を学籍番号、`value` を名前の例として、ハッシュ関数の動作原理を示します。
![ハッシュ関数の動作原理](hash_map.assets/hash_function.png)
以下のコードは簡単なハッシュ表を実装しています。ここでは、`key``value``Pair`クラスにカプセル化してキー値ペアを表現しています。
以下のコードは、単純なハッシュテーブルを実装したものです。ここでは、キーと値のペアを表すために `key``value` をクラス `Pair` にまとめています。
```src
[file]{array_hash_map}-[class]{array_hash_map}-[func]{}
```
## ハッシュ衝突とリサイズ
## ハッシュ衝突と拡張
本質的に、ハッシュ関数の役割は、すべてのキーの入力空間全体を、すべての配列インデックス出力空間にマッピングすることです。しかし、入力空間は出力空間よりはるかに大きいことがよくあります。したがって、**理論的には、「複数の入力が同じ出力に対応する」ケースが常に存在します**。
本質的に、ハッシュ関数の役割は、すべての `key` からなる入力空間を、配列のすべてのインデックスからなる出力空間に写像することです。しかし、入力空間は多くの場合、出力空間よりはるかに大きいため、**理論上は必ず「複数の入力が同じ出力に対応する」状況が存在します**。
の例では、与えられたハッシュ関数で、入力`key`の下桁が同じ場合、ハッシュ関数は同じ出力を生成します。えば、学籍番号1283620336の2人の学生をクエリすると、以下のことがわかります:
上の例ハッシュ関数で、入力 `key` の下 2 桁が同じであれば、出力結果も同じになります。たとえば、学籍番号 1283620336 の 2 人の学生を検索すると、次の結果を得ます:
```shell
12836 % 100 = 36
20336 % 100 = 36
```
図に示すように、両方の学籍番号が同じ名前を指しており、これは明らかに間違っています。この複数の入力が同じ出力に対応する状況を<u>ハッシュ衝突</u>と呼びます。
次の図に示すように、2 つの学籍番号が同じ名前を指してしまっており、これは明らかに誤りです。このような、複数の入力が同じ出力に対応する状況を<u>ハッシュ衝突hash collision</u>と呼びます。
![ハッシュ衝突の例](hash_map.assets/hash_collision.png)
ハッシュ表の容量$n$が増加するにつれて、複数のキーが同じバケットに割り当てられる確率が減少し、衝突少なくなることは理解しやすいです。したがって、**ハッシュ表をリサイズすることでハッシュ衝突を減らすことができます**。
容易に分かるように、ハッシュテーブルの容量 $n$ が大きいほど、複数の `key` が同じバケットに割り当てられる確率は低くなり、衝突少なくなります。したがって、**ハッシュテーブルを拡張することでハッシュ衝突を減らます**。
図に示すように、リサイズ前はキーペア`(136, A)``(236, D)`が衝突していました。しかし、リサイズ後は衝突が解されています。
次の図に示すように、拡張前はキーと値のペア `(136, A)``(236, D)` が衝突していますが、拡張後は衝突が解されます。
![ハッシュ表のリサイズ](hash_map.assets/hash_table_reshash.png)
![ハッシュテーブルの拡張](hash_map.assets/hash_table_reshash.png)
配列の拡張と同様に、ハッシュ表のリサイズにはすべてのキーペアを元のハッシュから新しいものに移行する必要があり、時間がかかります。さらに、ハッシュ表の`capacity`が変更されるため、ハッシュ関数を使用してすべてのキーペアの格納位置を再計算する必要があり、リサイズプロセスの計算オーバーヘッドがさらに増加します。したがって、プログラミング言語は頻繁なリサイズを防ぐために、ハッシュ表に十分大きな容量を割り当てることがよくあります。
配列の拡張と同様に、ハッシュテーブルの拡張ではすべてのキーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移し替える必要があり、非常に時間がかかります。また、ハッシュテーブルの容量 `capacity` が変るため、ハッシュ関数を使てすべてのキーと値のペアの格納位置を再計算しなければならず、これによって拡張過程の計算コストがさらに増加します。そのため、プログラミング言語では通常、頻繁な拡張を防ぐために十分大きなハッシュテーブル容量をあらかじめ確保します。
<u>負荷率</u>はハッシュ表の重要な概念です。ハッシュ内の要素数バケット数の比率として定義されます。ハッシュ衝突の深刻を測定するために使用され、**しばしばハッシュ表のリサイズのトリガーとしても機能します**。例えばJavaでは、負荷率が$0.75$を超えると、システムはハッシュ表を元のサイズの2倍にリサイズします。
<u>負荷率load factor</u>はハッシュテーブルにおける重要な概念であり、ハッシュテーブル内の要素数バケット数で割ったものとして定義されハッシュ衝突の深刻を測るために用いられます。**また、ハッシュテーブル拡張の発動条件としてもよく使われます**。例えば Java では、負荷率が $0.75$ を超えると、システムはハッシュテーブルを元の $2$ 倍に拡張します。