Хэш-таблица
Описание
Хэш-таблица (hash table) - это структура данных, которая реализует абстрактный тип данных "ассоциативный массив" и позволяет хранить пары "ключ-значение". Хэш-таблица использует так называемую хэш-функцию (hash function), которая принимает ключ и возвращает индекс массива, по которому будет храниться значение (см. хорошее видео про хэширование). Пример хэш-таблицы, в которой ключом выступает имя человека, а значением адрес его электронной почты:

В идеале хэш-функция должна генерировать уникальный индекс для каждого ключа. Однако реальные хэш-таблицы используют несовершенные хэш-функции, генерирующие одинаковые индексы для разных ключей. Такие ситуации называются коллизиями (collisions). Существует несколько способов их решения, наиболее популярными из которых являются: хэш-таблица с цепочками и хэш-таблица с открытой адресацией.
Метод цепочек подразумевает хранение значений, соответствующих одному индексу в виде связного списка (linked list) (см. часть 1, раздел 1):

Метод открытой адресации помещает значение по совпадающему индексу в первую свободную ячейку:

Реализация
Приступаем к реализации хэш-таблицы:
// data-structures/hash-table.js
// Импортируем конструктор связного списка
// (мы будем использовать метод цепочек для разрешения коллизий)
import LinkedList from './linked-list'
// Дефолтный размер таблицы
// (в реальности размер будет намного больше)
const defaultHashTableSize = 32
// Хэш-таблица
export default class HashTable {
constructor(size = defaultHashTableSize) {
// Создаем таблицу указанного размера и
// заполняем ее пустыми связными списками
this.buckets = new Array(size).fill(null).map(() => new LinkedList())
// Хранилище ключей
this.keys = {}
}
}
Реализуем простейшую хэш-функцию:
// Преобразует ключ в хэшированное значение
// (хэш-функция)
hash(key) {
// Для простоты в качестве хэша используется сумма кодов символов ключа
// https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt
const hash = [...key].reduce((acc, char) => acc + char.charCodeAt(0), 0)
// Хэш (индекс) не должен превышать размера таблицы
return hash % this.buckets.length
}
Метод установки значения по ключу:
// Устанавливает значение по ключу
set(key, value) {
// Хэшируем ключ
// (получаем индекс массива)
const index = this.hash(key)
// Сохраняем хэш по ключу
this.keys[key] = index
// Извлекаем нужный список
const bucket = this.buckets[index]
// Извлекаем узел
// (значением узла является объект)
const node = bucket.find({ cb: (value) => value.key === key })
// Если узел не найден
if (!node) {
// Добавляем новый узел
bucket.append({ key, value })
} else {
// Иначе, обновляем значение узла
node.value.value = value
}
}
Метод удаления значения по ключу:
// Удаляет значение по ключу
remove(key) {
// Хэшируем ключ
const index = this.hash(key)
// Удаляем хэш по ключу
delete this.keys[key]
// Извлекаем нужный список
const bucket = this.buckets[index]
// Извлекаем узел
const node = bucket.find({ cb: (value) => value.key === key })
// Возвращаем удаленный узел или `null`,
// если узел отсутствует
return node ? bucket.remove(node.value) : null
}
Метод извлечения значения по ключу:
// Возвращает значение по ключу
get(key) {
// Хэшируем ключ
const index = this.hash(key)
// Извлекаем нужный список
const bucket = this.buckets[index]
// Извлекаем узел
const node = bucket.find({ cb: (value) => value.key === key })
// Возвращаем значение узла или `null`,
// если узел отсутствует
return node ? node.value.value : null
}
Напоследок реализуем несколько вспомогательных методов:
// Определяет наличие ключа
has(key) {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn
return Object.hasOwn(this.keys, key)
}
// Возвращает все ключи
getKeys() {
return Object.keys(this.keys)
}
// Возвращает все значения
getValues() {
// Перебираем списки и возвращаем значения всех узлов
return this.buckets.reduce((acc, bucket) => {
return acc.concat(
// Метод `toArray` преобразует связный список в массив
bucket.toArray().map((node) => node.value.value),
)
}, [])
}
Полный код хэш-таблицы
Тесты
Запускаем тесты:
npm run test ./data-structures/__tests__/hash-table
