// ============================================================ // Реализация хеш-таблицы с открытой адресацией // ============================================================ #включить "руси.з" #включить // стандартная библиотека #включить // стандартная библиотека #включить // стандартная библиотека #включить // стандартная библиотека #включить // стандартная библиотека // ============================================================ // Константы // ============================================================ #определить НАЧАЛЬНЫЙ_РАЗМЕР 16 // Начальный размер таблицы #определить ВЕРХНИЙ_ПОРОГ 70 // Перестройка при заполнении > 70% #определить НИЖНИЙ_ПОРОГ 50 // Целевое заполнение после перестройки < 50% #определить УДАЛЕНО ((пусто *)-1) // Маркер удаленного элемента // ============================================================ // Хеш-функция FNV-1a // ============================================================ стат uint64_t хеш_fnv(конст символ *данные, цел длина) { uint64_t хеш = 0xcbf29ce484222325ULL; // FNV offset basis для (цел ш = 0; ш < длина; ш++) { хеш *= 0x100000001b3ULL; // FNV prime хеш ^= (беззнак символ)данные[ш]; } возврат хеш; } // ============================================================ // Проверка совпадения ключей // ============================================================ стат бул ключи_совпадают(ЗаписьХеша *запись, конст символ *ключ, цел длина_ключа) { если (!запись->ключ || запись->ключ == УДАЛЕНО) { возврат ложь; } возврат (запись->длина_ключа == длина_ключа) && (memcmp(запись->ключ, ключ, длина_ключа) == 0); } // ============================================================ // Перестройка хеш-таблицы // ============================================================ стат пусто перестроить(ХешТаблица *таблица) { если (!таблица) возврат; // Подсчитываем количество активных записей цел активных = 0; для (цел ш = 0; ш < таблица->вместимость; ш++) { пусто *ключ = таблица->корзины[ш].ключ; если (ключ && ключ != УДАЛЕНО) { активных++; } } // Вычисляем новый размер цел новая_вместимость = таблица->вместимость; пока ((активных * 100) / новая_вместимость >= НИЖНИЙ_ПОРОГ) { новая_вместимость *= 2; } assert(новая_вместимость > 0); // Создаем новую таблицу ХешТаблица новая = {0}; новая.корзины = (ЗаписьХеша *)calloc(новая_вместимость, размер(ЗаписьХеша)); новая.вместимость = новая_вместимость; // Копируем все активные записи для (цел ш = 0; ш < таблица->вместимость; ш++) { ЗаписьХеша *запись = &таблица->корзины[ш]; если (запись->ключ && запись->ключ != УДАЛЕНО) { хеш_вставить2(&новая, запись->ключ, запись->длина_ключа, запись->значение); } } assert(новая.использовано == активных); // Освобождаем старую таблицу если (таблица->корзины) { free(таблица->корзины); } // Копируем новую таблицу *таблица = новая; } // ============================================================ // Поиск записи по ключу // ============================================================ стат ЗаписьХеша *найти_запись(ХешТаблица *таблица, конст символ *ключ, цел длина_ключа) { если (!таблица || !таблица->корзины) { возврат NULL; } uint64_t хеш = хеш_fnv(ключ, длина_ключа); для (цел ш = 0; ш < таблица->вместимость; ш++) { цел индекс = (хеш + ш) % таблица->вместимость; ЗаписьХеша *запись = &таблица->корзины[индекс]; если (ключи_совпадают(запись, ключ, длина_ключа)) { возврат запись; } если (запись->ключ == NULL) { возврат NULL; } } unreachable(); возврат NULL; } // ============================================================ // Поиск или создание записи // ============================================================ стат ЗаписьХеша *найти_или_создать(ХешТаблица *таблица, конст символ *ключ, цел длина_ключа) { // Инициализация при первом использовании если (!таблица->корзины) { таблица->корзины = (ЗаписьХеша *)calloc(НАЧАЛЬНЫЙ_РАЗМЕР, размер(ЗаписьХеша)); таблица->вместимость = НАЧАЛЬНЫЙ_РАЗМЕР; если (!таблица->корзины) { возврат NULL; } } // Перестройка при переполнении если ((таблица->использовано * 100) / таблица->вместимость >= ВЕРХНИЙ_ПОРОГ) { перестроить(таблица); } uint64_t хеш = хеш_fnv(ключ, длина_ключа); для (цел ш = 0; ш < таблица->вместимость; ш++) { цел индекс = (хеш + ш) % таблица->вместимость; ЗаписьХеша *запись = &таблица->корзины[индекс]; // Нашли существующую запись если (ключи_совпадают(запись, ключ, длина_ключа)) { возврат запись; } // Нашли tombstone - используем его если (запись->ключ == УДАЛЕНО) { запись->ключ = (символ *)ключ; запись->длина_ключа = длина_ключа; возврат запись; } // Нашли пустую запись если (запись->ключ == NULL) { запись->ключ = (символ *)ключ; запись->длина_ключа = длина_ключа; таблица->использовано++; возврат запись; } } unreachable(); возврат NULL; } // ============================================================ // Публичные функции // ============================================================ пусто *хеш_получить(ХешТаблица *таблица, символ *ключ) { если (!таблица || !ключ) возврат NULL; возврат хеш_получить2(таблица, ключ, (цел)strlen(ключ)); } пусто *хеш_получить2(ХешТаблица *таблица, символ *ключ, цел длина_ключа) { если (!таблица || !ключ || длина_ключа <= 0) возврат NULL; ЗаписьХеша *запись = найти_запись(таблица, ключ, длина_ключа); возврат запись ? запись->значение : NULL; } пусто хеш_вставить(ХешТаблица *таблица, символ *ключ, пусто *значение) { если (!таблица || !ключ) возврат; хеш_вставить2(таблица, ключ, (цел)strlen(ключ), значение); } пусто хеш_вставить2(ХешТаблица *таблица, символ *ключ, цел длина_ключа, пусто *значение) { если (!таблица || !ключ || длина_ключа <= 0) возврат; ЗаписьХеша *запись = найти_или_создать(таблица, ключ, длина_ключа); если (запись) { запись->значение = значение; } } пусто хеш_удалить(ХешТаблица *таблица, символ *ключ) { если (!таблица || !ключ) возврат; хеш_удалить2(таблица, ключ, (цел)strlen(ключ)); } пусто хеш_удалить2(ХешТаблица *таблица, символ *ключ, цел длина_ключа) { если (!таблица || !ключ || длина_ключа <= 0) возврат; ЗаписьХеша *запись = найти_запись(таблица, ключ, длина_ключа); если (запись) { запись->ключ = УДАЛЕНО; } } // ============================================================ // Тестирование хеш-таблицы // ============================================================ пусто хеш_тест(пусто) { ХешТаблица *таблица = (ХешТаблица *)calloc(1, размер(ХешТаблица)); если (!таблица) { fprintf(stderr, "Failed to allocate hashmap for testing\n"); возврат; } printf("Testing hashmap...\n"); // Вставка 5000 записей для (цел ш = 0; ш < 5000; ш++) { символ *ключ = формат("key %d", ш); хеш_вставить(таблица, ключ, (пусто *)(intptr_t)ш); } // Удаление записей 1000-1999 для (цел ш = 1000; ш < 2000; ш++) { символ *ключ = формат("key %d", ш); хеш_удалить(таблица, ключ); } // Повторная вставка 1500-1599 для (цел ш = 1500; ш < 1600; ш++) { символ *ключ = формат("key %d", ш); хеш_вставить(таблица, ключ, (пусто *)(intptr_t)ш); } // Вставка 6000-6999 для (цел ш = 6000; ш < 7000; ш++) { символ *ключ = формат("key %d", ш); хеш_вставить(таблица, ключ, (пусто *)(intptr_t)ш); } // Проверка записей 0-999 для (цел ш = 0; ш < 1000; ш++) { символ *ключ = формат("key %d", ш); пусто *значение = хеш_получить(таблица, ключ); assert((intptr_t)значение == ш); } // Проверка удаленных записей 1000-1499 для (цел ш = 1000; ш < 1500; ш++) { символ *ключ = формат("key %d", ш); пусто *значение = хеш_получить(таблица, ключ); assert(значение == NULL); } // Проверка восстановленных записей 1500-1599 для (цел ш = 1500; ш < 1600; ш++) { символ *ключ = формат("key %d", ш); пусто *значение = хеш_получить(таблица, ключ); assert((intptr_t)значение == ш); } // Проверка удаленных записей 1600-1999 для (цел ш = 1600; ш < 2000; ш++) { символ *ключ = формат("key %d", ш); пусто *значение = хеш_получить(таблица, ключ); assert(значение == NULL); } // Проверка записей 2000-4999 для (цел ш = 2000; ш < 5000; ш++) { символ *ключ = формат("key %d", ш); пусто *значение = хеш_получить(таблица, ключ); assert((intptr_t)значение == ш); } // Проверка несуществующих записей 5000-5999 для (цел ш = 5000; ш < 6000; ш++) { символ *ключ = формат("key %d", ш); пусто *значение = хеш_получить(таблица, ключ); assert(значение == NULL); } // Обновление записей 6000-6999 для (цел ш = 6000; ш < 7000; ш++) { символ *ключ = формат("key %d", ш); хеш_вставить(таблица, ключ, (пусто *)(intptr_t)(ш * 2)); } // Проверка обновленных записей для (цел ш = 6000; ш < 7000; ш++) { символ *ключ = формат("key %d", ш); пусто *значение = хеш_получить(таблица, ключ); assert((intptr_t)значение == ш * 2); } // Проверка несуществующего ключа assert(хеш_получить(таблица, "no such key") == NULL); // Освобождаем память если (таблица->корзины) { free(таблица->корзины); } free(таблица); printf("All hashmap tests passed!\n"); }