Ранее в курсе мы изучили метод перебора и сталкивались с задачей про европейские столицы. Снова попробуем решить эту задачу, но выберем новый, более продуктивный способ.
В задаче про европейские столицы в условиях задачи были указаны пары — город и дата основания. Зная дату, мы можем определить город. Для решения задачи используем следующую таблицу:
Пары с годом и городом в таблице расположены в произвольном порядке, поэтому поиск нужной столицы требует полного перебора.
Временная сложность такого алгоритма составляет O(n), что довольно медленно. Бинарный поиск позволяет сократить это время до O(logn). На большой таблице из миллиона записей среднее количество сравнений для линейного поиска равно 500000, а для бинарного — всего 10.
Но и это не предел. Если поиск нужно выполнять с максимальной скоростью, мы можем воспользоваться еще одной структурой данных. Это хэш-таблица, которая обеспечивает временную сложность O(1).
Хэш-таблица похожа на массив, потому что в ней тоже есть операция индексации. В JavaScript доступ к элементу массива осуществляется по индексу, записанному в квадратных скобках. В качестве индекса можно использовать последовательные целые числа. В случае с хэш-таблицей в качестве индекса можно брать любые структуры данных — строки, дату и время, массивы.
В этом уроке мы разберемся, как устроены хэш-таблицы и как их реализовывать.
Разреженные массивы
Простейший способ определить столицу по году основания — использовать массив:
Такой массив занимает много памяти. В нем хранится всего 15 городов, но при этом размер массива — это целых 1873 элемента:
Так получилось потому, что индексами в массиве могут быть только последовательные целые числа, начиная с 0. Если мы добавляем элемент с индексом 752, в массив добавляются и неопределенные элементы с индексами от 0 до 751. Длина массива получилась равной 1873, потому что:
- Наибольший год основания — 1872
- Первым в массиве всегда стоит элемент с индексом 0
Если мы применим массив, доступ к элементам выполнится очень быстро — за константное время O(1). Но при этом мы впустую расходуем память — в нашем примере массив заполнен всего на 0,8%.
Массивы, в которых большая часть элементов не определена, называются разреженными. Чтобы сэкономить память, программисты стараются уплотнить массив. Скажем, в нашем примере годы находятся достаточно далеко друг от друга — на каждые двадцать пустых элементов приходится только один элемент с данными. Мы можем уменьшить массив в двадцать раз с помощью простого трюка.
Возьмем числа 0, 1, 2 и 3, поделим их на 20 и получим такой результат:
- 0 делить на 20 = 0
- 1 делить на 20 = 0.05
- 2 делить на 20 = 0.1
- 3 делить на 20 = 0.15
Целая часть этих чисел будет равна 0, пока мы не доберемся до такой операции:
19 делить на 20 = 0.95
Начиная с 20, целая часть после деления будет равна 1:
- 20 делить на 20 = 1
- 21 делить на 20 = 1.05
Начиная с 40, целая часть будет равна 2.
Получается, что после преобразования числа схлопываются:
- От 0 до 19 → 0
- От 20 до 39 → 1
- От 40 до 59 → 2 и так далее
Чтобы получать целую часть, воспользуемся стандартной функцией Math.floor:
Чтобы узнать, какой город основан в 1191 году, мы проверяем элемент с индексом 1191/20:
У такого подхода есть недостаток — довольно трудно предсказать размер массива. Когда речь идет о годах основания, мы можем проанализировать данные и прикинуть, что каждые двадцать лет можно схлопнуть в один элемент.
Для чисел 0 до 1999 нам достаточно массива из ста элементов. Последний элемент такого уплотненного массива имеет индекс 99, а Math.floor(1999/20) как раз равно 99.
Но в другой задаче это может не сработать — например, если требуется хранить в массиве индексы, равные миллиону или миллиарду. Коэффициент 20 слишком мал для таких чисел, потому что даже уплотненный массив все равно будет слишком большим и слишком пустым.
Но мы не можем анализировать каждую возникающую задачу. Нам бы хотелось иметь универсальный способ, подходящий для разных случаев и позволяющий контролировать размер массива. Поэтому мы рассмотрим еще один способ.
Модульная арифметика
Предположим, мы фиксируем размер массива. Пусть у нас будет массив из ста элементов:
Мы хотим сохранить в него несколько элементов, но их индексы могут быть произвольными целыми числами. Чтобы преобразовать индекс в число от 0 до 99, мы должны вычислить остаток от деления индекса на 100.
Общее правило такое:
Остатки от деления любого числа на n всегда находятся в диапазоне от 0 до n-1
Это правило поможет нам работать с массивами любого размера.
Иногда вместо «остаток от деления на n» говорят «модуль по основанию n». В JavaScript и многих других языках программирования для вычисления модуля используют оператор %:
Решим нашу задачу со столицами с помощью модуля:
Теперь мы точно знаем, что наш массив не вырастет до больших размеров, как это может случиться с операцией деления. Но мы можем обнаружить другую проблему.
Попробуем узнать, какой город основан в 1167 году. Если верить нашей таблице, это Копенгаген. Но программа говорит совсем другое:
Минск основан в 1067 году, а Копенгаген — в 1167. Годы отличаются, но у них один и тот же остаток от деления на 100, а именно 67.
Ситуация, когда разные элементы после преобразования индекса попадают в одну и ту же ячейку массива, называется коллизией. На самом деле это не ошибка, а вполне вероятная ситуация, хотя и не очень частая.
Коллизии
Справиться с коллизиями нетрудно. Вместо того чтобы хранить в каждой ячейке массива простое значение, мы размещаем там односвязный список. При добавлении элемента мы добавляем в список пару из года и города:
- Год — это индекс, по которому мы сохраняем и извлекаем данные. Обычно его называют ключом
- Название — это значение
Попробуем реализовать в коде:
Мы уже разработали структуру данных LinkedList, поэтому мы можем просто импортировать ее.
Размер массива capitals всегда будет равен ста элементам.
По умолчанию все ячейки массива пусты, и связный список создается только при первой записи в ячейку. В каждом списке может храниться несколько элементов. Чтобы различать их, мы сохраняем не просто значение, а пару из ключа (key) и значения (value).
При поиске точно также вычисляем индекс:
Если в ячейке ничего нет, значит, мы ничего и не записывали.
Но если в ячейке что-то есть, то это список, по которому мы пробегаем и пытаемся найти пару с заданным ключом (key).
Обнаружив пару, возвращаем ее значение (value).
Алгоритмическая сложность этих операций зависит от того, насколько часто элементы попадают в одну и ту же ячейку — в один и тот же связный список.
Если у нас будет случайный набор из ста чисел, он достаточно равномерно распределится по массиву: в большинстве ячеек будет храниться одно число, в некоторых — два, и в некоторых — ни одного. Алгоритмическая сложность записи и чтения в таком случае будет близка к O(1) — это один из самых быстрых возможных алгоритмов.
Если числа не будут случайными, может получиться так, что в одном из списков их окажется слишком много — тогда скорость вставки и проверки значительно снизится. Подробнее мы обсудим эту ситуацию позже, а пока запомним, что предложенный нами алгоритм любит равномерные случайные ключи.
Хэш-функция
Теперь попробуем решить обратную задачу — определять год основания столицы, зная ее название. Заглянем в таблицу в начале урока и вспомним, что год основания Мадрида — 856, а Москвы — 1147.
Наша структура данных может хранить значения с любым целочисленным ключом, не только с годом. Но «Мадрид» и «Москва» — это не числа, а строки.
При этом компьютеры умеют работать только с числами, и любые объекты в них хранятся как последовательности чисел. Каждая буква хранится как одно число, которое называют кодом символа:
Слово «Мадрид» хранится как последовательность чисел 1052, 1072, 1076, 1088, 1080 и 1076. В простейшем случае мы могли бы использовать для вычисления индекса код первой буквы — в нашем примере 1052. Но это значит все города на М (Мадрид, Москва и Минск) попадут в один и тот же список. В таком случае скорость поиска будет не очень высокой.
Чтобы этого избежать, нам нужны ключи, которые очень похожи на случайные. Мы можем взять все наши числа и подвергнуть их какой-нибудь обработке — например, вычислить их сумму или произведение.
Однако сложение и умножение — слишком простые операции. Все слова, состоящие из одних и тех же букв (ведро, вроде и древо), будут иметь один и тот же ключ — сумму или произведение кодов символов.
Хороший результат в таком случае даст так называемая полиномиальная функция.
Попробуем применить ее к нашей задаче с городами. Пусть коды символов строки длины n будут равны c0, c1, ..., cn-1. Возьмем число k, которое будет больше кода любого символа — скажем, k = 65537.
У нас получилось такое выражение:
Число k достаточно большое. На каждом шаге нам приходится возводить его в возрастающую степень, поэтому выражение может оказаться просто огромным. Для слова "Мадрид" мы получим 1300923352423037265600321836.
На практике для ускорения вычислений и для удобства используют не все выражение, а только остаток от его деления на другое число — m:
Обычно модуль m делают равным большому простому числу или большому круглому числу — например, миллиону или миллиарду. Часто в качестве модуля выбирают степень числа 2 — к примеру, 2^32 или 2^64.
Чтобы промежуточные числа не становились слишком большими, все сложения и умножения выполняют по модулю m. Другими словами, остаток от деления вычисляется сразу после сложения или умножения.
Здесь в качестве модуля мы выбрали 2**20, то есть 2 в степени 20 (2^20). Это число равно 1048576 и часто используется программистами, потому что очень близко к миллиону. Слово мегабайт традиционно означает именно 1048576 байт, а не миллион, как можно было бы ожидать.
Сейчас комитет по стандартизации мер и весов рекомендует термины «мебибайт» для 1048576 байтов и «мегабайт» для 1000000 байтов, но большинство программистов все еще не используют эту новую рекомендацию.
Переменная k_i на каждом шаге цикла умножается на k. Она принимает значения 1, затем k, затем k умноженное на 2, и так далее. В переменной result накапливается результат вычислений.
Функция называется hash, и это неслучайно. В переводе с английского слово hash означает «мелко крошить, перемешивать».
Функция перемешивает исходные данные так, чтобы получилось число, похожее на случайное:
Эту функцию так и называют — хеш-функция. В подавляющем большинстве случаев нам не приходится писать хеш-функции, потому что они уже разработаны авторами фреймворков и стандартных библиотек. Тем не менее полезно понимать принцип работы хешей.
В Python для вычисления хешей можно использовать функцию hash(), в Java — метод hashCode(), в C# — метод GetHashCode(). В JavaScript нет готовой хеш-функции — считается, что все нужное уже есть в языке.
Название функции дало название всей структуре данных. Обычно ее называют хеш-таблицей (hash table или hashmap). Очень часто это название сокращают просто до хеша. Другие распространенные названия — словарь (dictionary) и ассоциативный массив (associative array).
Худший случай
Выше мы говорили, что обычно вставка и поиск в хеш-таблице выполняются за время O(1). Но если хеш-функция выбрана неудачно, все значения могут оказаться в одном связном списке во внутреннем массиве.
Тогда время поиска составит O(n), что обычно считается медленным. К счастью, эта ситуация практически невозможна. Тем не менее, если вы соберетесь писать собственную хеш-таблицу, посвятите время выбору хорошей хеш-функции.
Есть еще одна возможная сложность. В массиве, который мы использовали в примерах выше, мы резервировали сто элементов, потому что рассчитывали, что данные будут распределены равномерно. Однако если мы поместим в такой массив 10000 элементов, то даже при идеальном распределении в каждом связном списке окажется сто элементов. Это значит, что поиск и вставка все-таки будут медленными.
Чтобы справиться с этой проблемой, надо расширять хеш-таблицу — увеличивать внутренний массив по мере того, как в нее добавляются элементы. Конечно, это накладно делать при каждой вставке.
Обычно подсчитывают коэффициент заполнения хеш-таблицы (load factor) — это частное от деления количества вставленных элементов на размер внутреннего массива. Если он достигает заранее выбранного порога в 60–80%, внутренний массив увеличивают вдвое, а индексы всех элементов пересчитывают.
Точно также, если коэффициент заполнения становится слишком маленьким, 20–25%, массив уменьшают вдвое. У массива есть нижняя граница в 128 или 256 элементов, чтобы не перестраивать его слишком часто, пока он маленький.
Хэш-таблицы и функция вставки
Перепишем функцию вставки, опираясь на все новые знания. Вместе с этим реализуем хеш-таблицу в виде новой структуры данных, спрятав внутри класса массив с данными:
Весь код из этого примера мы уже обсуждали, кроме удвоения массива. Создавая новый массив, в два раза больше текущего, мы должны скопировать все элементы, пересчитав их индексы.
Именно за это отвечает вложенный цикл:
В самом начале внутренний массив имеет размер 0, чтобы не тратить память, если в хеш-таблице нет ни одного элемента. При вставке первого элемента мы увеличиваем внутренний массив сразу до 128 элементов.
Остальной код мы уже рассмотрели выше в этом уроке.
Выводы
В этом уроке мы изучили хэш-таблицы и научились с ними работать. Повторим основные тезисы:
-
Хэш-таблица обеспечивает временную сложность O(1). Она похожа на массив, потому что в ней тоже есть операция индексации. В качестве индекса можно использовать практически любые структуры данных — строки, дату и время, массивы.
- Массивы, в которых большая часть элементов не определена, называются разреженными. Чтобы сэкономить память, программисты стараются уплотнить массив, но тогда довольно трудно предсказать размер массива.
- Остатки от деления любого числа на n всегда находятся в диапазоне от 0 до n-1. Иногда вместо «остаток от деления на n» говорят «модуль по основанию n. В JavaScript и многих других языках программирования для вычисления модуля используют оператор %
- Ситуация, когда разные элементы после преобразования индекса попадают в одну и ту же ячейку массива, называется коллизией. Справиться с коллизиями нетрудно. Вместо того чтобы хранить в каждой ячейке массива простое значение, мы размещаем там односвязный список. При добавлении элемента мы добавляем в список пару из года и города:
- Для некоторых задач нужны ключи, которые очень похожи на случайные. Мы можем взять все наши числа и подвергнуть их какой-нибудь обработке — например, вычислить их сумму или произведение. Но сложение и умножение — слишком простые операции. Хороший результат в таком случае даст так называемая полиномиальная функция.
- Хэш-функция перемешивает исходные данные так, чтобы получилось число, похожее на случайное. В Python для вычисления хешей можно использовать функцию hash(), в Java — метод hashCode(), в C# — метод GetHashCode(). В JavaScript нет готовой хеш-функции — считается, что все нужное уже есть в языке.
<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 23:26:13 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="Gmq5CfWBfYzW4BKNvwQfUkC2vOjacUoHvFrvR9AD9uf1u3I-B__Q7GCjNhWzC-8lgL-RQtJGtKUBunUTggQRiQ";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Хэш | Основы алгоритмов и структур данных</title>
<meta name="description" content="Хэш / Основы алгоритмов и структур данных: Изучаем, как работает хэш">
<link rel="canonical" href="https://ru.hexlet.io/courses/basic-algorithms/lessons/hash/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Хэш">
<meta property="og:title" content="Основы алгоритмов и структур данных">
<meta property="og:description" content="Хэш / Основы алгоритмов и структур данных: Изучаем, как работает хэш">
<meta property="og:url" content="https://ru.hexlet.io/courses/basic-algorithms/lessons/hash/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="0VJXJS4QRz7KgfjzBAgsavula89kk4OIfTiK2bfOOZw-g5wS3G7qXnzC3GsIB9wdO6xGZWykfSrA2BCN5cne8g" />
<script src="/vite/assets/inertia-DfXos102.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-cb8xch9l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDAyOCwicHVyIjoiYmxvYl9pZCJ9fQ==--ae9eed98663dd1201759d042a5ba7ca790866156/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programming-bro.png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T23:26:13.183Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"GHyJtl9Jze4burWU-jHzgic-QO0ZijQFLFFGa3aQf0T3rUKBrTdgjq35kQz2PgP15zdtRxG9yqeRsdw_JJeYKg","topics":[{"id":99933,"title":"Решение из подсказки для Java не проходит валидацию","plain_title":"Решение из подсказки для Java не проходит валидацию ","creator":{"public_name":"Dmitry Ким","id":697764,"is_tutor":false},"comments":[{"creator":{"public_name":"Dmitry Ким","id":697764,"is_tutor":false},"id":191495,"body":"и, кажется, в этом блоке кода ошибка\n```\n while (current.next != null) {\n var nextPair = (Object[]) current.next.value;\n if(nextPair[0].equals(key)) {\n var resultPair = (Object[]) current.value;\n var result = resultPair[1];\n current.next = current.next.next;\n hash.count--;\n return result;\n }\n\n current = current.next;\n }\n```\nна 4 строке вместо `var resultPair = (Object[]) current.value;` должен быть `var resultPair = (Object[]) current.next.value;`\n\nлибо полностью убрать 4 строку и 5 строку заменить на `var result = nextPair[1]`","topic_id":99933},{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":191534,"body":"Дмитрий, проверил, решение из подсказки проходит проверку тестами. Попробуйте нажать на Сброс и повторить процесс. Технически устроено так, что решение учителя, прежде чем попасть к вам, проходит проверку теми же тестам","topic_id":99933}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Хэш","entity_url":null,"active":true}},{"id":88018,"title":"> AttributeError: 'Hash' object has no attribute 'items'\nв строке 4 функции `solution`\nПосмотрел в класс `Hash` , действительно нет такого\n\n`Python`","plain_title":"AttributeError: 'Hash' object has no attribute 'items' в строке 4 функции solution Посмотрел в класс Hash , действительно нет такого Python ","creator":{"public_name":"Юрий","id":348954,"is_tutor":false},"comments":[{"creator":{"public_name":"Artyom Kropp","id":381127,"is_tutor":true},"id":176183,"body":"Добрый день! Так метод `items()`, вызывается на словаре переданном в функцию. В самой реализации `Hash` этого метода и не должно быть.","topic_id":88018}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Хэш","entity_url":null,"active":true}},{"id":87460,"title":"Привет!\nПо коду на Пайтоне:\n1) current.next = current.next.next -> у current.next может не быть next, кажется в решении учителя этот кейс не предусмотрен\n2) странно класть словарь в связный список в реализации словаря - по идее должен быть кортеж \n3) уменьшение count не проверяется в тестах - я не уменьшил, решение прошло ","plain_title":"Привет! По коду на Пайтоне: 1) current.next = current.next.next -> у current.next может не быть next, кажется в решении учителя этот кейс не предусмотрен 2) странно класть словарь в связный список в реализации словаря - по идее должен быть кортеж 3) уменьшение count не проверяется в тестах - я не уменьшил, решение прошло ","creator":{"public_name":"Станислав Глазко","id":252357,"is_tutor":false},"comments":[{"creator":{"public_name":"Artyom Kropp","id":381127,"is_tutor":true},"id":175552,"body":"Спасибо, что обратили на это внимание! Завел тикет на это дело.","topic_id":87460}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Хэш","entity_url":null,"active":true}},{"id":87259,"title":"Привет! Хотел написать об опечатке в тексте упражнения - в главе описания задания к solution.pu написано : \"Создайте функцию remove(), которая удаляет их хеша \". Предполагаю,что удаляет она ИЗ хеша) И хотел добавить пожелание по улучшению тестов - тесты не проверяют изменился ли счетчик hash.count у обьекта, поэтому на данный момент можно реализовать алгоритм, который не убавляя счетчик, пройдет все тесты","plain_title":"Привет! Хотел написать об опечатке в тексте упражнения - в главе описания задания к solution.pu написано : \"Создайте функцию remove(), которая удаляет их хеша \". Предполагаю,что удаляет она ИЗ хеша) И хотел добавить пожелание по улучшению тестов - тесты не проверяют изменился ли счетчик hash.count у обьекта, поэтому на данный момент можно реализовать алгоритм, который не убавляя счетчик, пройдет все тесты ","creator":{"public_name":"Богдан Кустов","id":536145,"is_tutor":false},"comments":[{"creator":{"public_name":"Богдан Кустов","id":536145,"is_tutor":false},"id":174974,"body":"**Roman Ashikov**, Да , я иногда забываю ,что этот курс мультиязыковой) ","topic_id":87259},{"creator":{"public_name":"Roman Ashikov","id":226258,"is_tutor":true},"id":174916,"body":"Приветствую! Спасибо за фидбек! Опечатки исправил. Реализовать проверку счётчика не получится. Тут уже особенности реализации мультиязыковых практик сказываются, но спасибо, что обратили внимание.","topic_id":87259}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Хэш","entity_url":null,"active":true}},{"id":89171,"title":"Если решать на java, то `hash.get()` всегда выдает `null`","plain_title":"Если решать на java, то hash.get() всегда выдает null ","creator":{"public_name":"Axes Thump","id":175090,"is_tutor":false},"comments":[{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":177716,"body":"Спасибо, поправил! Нужно будет нажать Сброс, чтобы получить новую версию упражнения. Не забудьте сохранить свой код, так как при сбросе он удалится","topic_id":89171}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Хэш","entity_url":null,"active":true}},{"id":90622,"title":"Почему в примерах в PHP где нужен массив не используется SplFixedArray() ?\nА используется [] - хэш таблица. Из-за этого пример с городами не корректный:\n\n```php\n<?php\n\n$capitals = [];\n\n$capitals[881] = 'Вена';\n$capitals[1237] = 'Берлин';\n$capitals[1323] = 'Вильнюс';\n$capitals[1614] = 'Тирана';\n$capitals[1167] = 'Копенгаген';\n$capitals[963] = 'Люксембург';\n$capitals[1067] = 'Минск';\n$capitals[1191] = 'Дублин';\n$capitals[1275] = 'Амстердам';\n$capitals[752] = 'Ватикан';\n$capitals[1786] = 'Рейкьявик';\n$capitals[1200] = 'Варшава';\n$capitals[1872] = 'Будапешт';\n$capitals[856] = 'Мадрид';\n$capitals[1147] = 'Москва';\n\n\necho count($capitals); //15\n```","plain_title":"Почему в примерах в PHP где нужен массив не используется SplFixedArray() ? А используется [] - хэш таблица. Из-за этого пример с городами не корректный: <?php $capitals = []; $capitals[881] = 'Вена'; $capitals[1237] = 'Берлин'; $capitals[1323] = 'Вильнюс'; $capitals[1614] = 'Тирана'; $capitals[1167] = 'Копенгаген'; $capitals[963] = 'Люксембург'; $capitals[1067] = 'Минск'; $capitals[1191] = 'Дублин'; $capitals[1275] = 'Амстердам'; $capitals[752] = 'Ватикан'; $capitals[1786] = 'Рейкьявик'; $capitals[1200] = 'Варшава'; $capitals[1872] = 'Будапешт'; $capitals[856] = 'Мадрид'; $capitals[1147] = 'Москва'; echo count($capitals); //15 ","creator":{"public_name":"Ильдар","id":362775,"is_tutor":false},"comments":[{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":179772,"body":"Приветствую, Ильдар! А почему вы считаете, что этот пример не корректный?","topic_id":90622},{"creator":{"public_name":"Ильдар","id":362775,"is_tutor":false},"id":179776,"body":"Приветствую Максим! Так я привел пример кода. См выше.\nКогда используем array() - то длина его echo `count($capitals); //15`, а по теме урока должно быть `1873`. Всё из-за того что в PHP array() - это хэш таблицы.\n","topic_id":90622},{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":179788,"body":"Да, вы правы. Обычный массив в php по сути есть ассоциативный массив. Спасибо, поправил пример","topic_id":90622}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Хэш","entity_url":null,"active":true}},{"id":90459,"title":"В коде реализации Hash.py есть вот такой метод:\n\n` def get(self, key):\n index = self.calculate_index(self.table, key)\n if self.table[index] is None:\n return None\n\n for pair in self.table[index]:\n if pair['key'] == key:\n return pair['value']\n\n return None`\n\nГде переменная `pair` внутри цикла по сути является LinkedList состоящий из pair{key:value}\nИз метода `get(key)` мы возвращаем не пару, а весь чанк, в котором хранится все пары соответствующие этому хешу. На сколько это правильно? Нет ли тут ошибки? ","plain_title":"В коде реализации Hash.py есть вот такой метод: ` def get(self, key): index = self.calculate_index(self.table, key) if self.table[index] is None: return None for pair in self.table[index]: if pair['key'] == key: return pair['value'] return None` Где переменная pair внутри цикла по сути является LinkedList состоящий из pair{key:value} Из метода get(key) мы возвращаем не пару, а весь чанк, в котором хранится все пары соответствующие этому хешу. На сколько это правильно? Нет ли тут ошибки? ","creator":{"public_name":"","id":635017,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":179626,"body":"Добрый день, ошибки нет. Мы записываем в ячейку хеш-таблицы связный список, а при извлечении данных, дотсаем список и пробегаемся по его содержимому.","topic_id":90459}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Хэш","entity_url":null,"active":true}},{"id":90883,"title":"Странное упражнение.\n\n```\nЯ воспользовался стандартным методом связанного списка и решил задачу, добавив всего одну строчку кода к методу хэш-функции. \nУчитель как-то написал много кода, с обращением к next и head связанного списка. \nЕсли в этом была цель, то может как-то описать это в задании или тесты подкорректировать?\n\n```\nОсталось какое-то чувство неудовлетворенности\nhttps://ru.hexlet.io/code_reviews/1099236\n","plain_title":"Странное упражнение. Я воспользовался стандартным методом связанного списка и решил задачу, добавив всего одну строчку кода к методу хэш-функции. Учитель как-то написал много кода, с обращением к next и head связанного списка. Если в этом была цель, то может как-то описать это в задании или тесты подкорректировать? Осталось какое-то чувство неудовлетворенности https://ru.hexlet.io/code_reviews/1099236 ","creator":{"public_name":"Иван Рябкин","id":342289,"is_tutor":false},"comments":[{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":180142,"body":"Добрый день.\n\nВы могли заметить, что в этом курсе решение можно написать на нескольких языках. Особенность тестов такая, что они не проверяют реализацию, а только конечный результат.\n\nВы можете изучить код учителя и увидеть, что используется один и тот же алгоритм. В большинстве случаем он мало завязан на реализации в конкретных языках. \n\nВ качестве тренировки, вы можете написать новое решение с новой реализацией :)","topic_id":90883}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Хэш","entity_url":null,"active":true}},{"id":90881,"title":"у вас в hash-функции для python значение power = 0, и таким образом все хэши равны нулю.","plain_title":"у вас в hash-функции для python значение power = 0, и таким образом все хэши равны нулю. ","creator":{"public_name":"Иван Рябкин","id":342289,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":180152,"body":"Добрый день, к сожалению не могу найти где в теории power = 0.","topic_id":90881}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Хэш","entity_url":null,"active":true}},{"id":91840,"title":"Добрый день. Решал задачу на Java. Столкнулся с проблемой: если я делаю Object тип возвращаемого значения метода remove, то сталкиваюсь с ошибкой при запуске тестов:\n\n```Caused by: java.lang.NoSuchMethodError: 'java.lang.String solutions.Solution.remove(hash.Hash, java.lang.String)'```\n\nПри изменении типа на String, тесты перестают проходить из-за несоответствия типов значений при сравнении:\n\n```\n test reverse › checkWhenKeyExists2\n\n expect(received).toEqual(expected) // deep equality\n\n Expected: 25\n Received: \"25\"\n\n 24 |\n 25 | const actual1 = await solution(user, 'age');\n > 26 | expect(actual1).toEqual(25);\n | ^\n 27 |\n 28 | const actual2 = await solution(user, 'hasGithubAccount');\n 29 | expect(actual2).toEqual(true);\n\n at Object.toEqual (__tests__/index.test.js:26:21)\n```\n\nПодскажите, пожалуйста, это я неверно задание понял или в тестах ошибка?\n\n","plain_title":"Добрый день. Решал задачу на Java. Столкнулся с проблемой: если я делаю Object тип возвращаемого значения метода remove, то сталкиваюсь с ошибкой при запуске тестов: Caused by: java.lang.NoSuchMethodError: 'java.lang.String solutions.Solution.remove(hash.Hash, java.lang.String)' При изменении типа на String, тесты перестают проходить из-за несоответствия типов значений при сравнении: test reverse › checkWhenKeyExists2 expect(received).toEqual(expected) // deep equality Expected: 25 Received: \"25\" 24 | 25 | const actual1 = await solution(user, 'age'); > 26 | expect(actual1).toEqual(25); | ^ 27 | 28 | const actual2 = await solution(user, 'hasGithubAccount'); 29 | expect(actual2).toEqual(true); at Object.toEqual (__tests__/index.test.js:26:21) Подскажите, пожалуйста, это я неверно задание понял или в тестах ошибка? ","creator":{"public_name":"Иван","id":671361,"is_tutor":false},"comments":[{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":181414,"body":"Иван, а можете сохранить свое решение и сбросить ссылку на него? \n\nhttps://help.hexlet.io/ru/articles/282174-kak-soxranit-svoe-resenie","topic_id":91840},{"creator":{"public_name":"Иван","id":671361,"is_tutor":false},"id":181510,"body":"Ссылка на решение:\nhttps://ru.hexlet.io/code_reviews/1125776\nВ нём оба варианта(один закомментированный), которыми я пробовал решать задачу.","topic_id":91840},{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":181515,"body":"Иван, метод должен Object возвращать, тут все верно. А код в Helper.java не трогали случайно? Попробуйте скопировать свое решение и сбросить упражнение, чтобы вернуть его в первоначальную форму","topic_id":91840},{"creator":{"public_name":"Иван","id":671361,"is_tutor":false},"id":181533,"body":"**Maksim Litvinov**, Helper.java не трогал, насколько помню. Но Ваша рекомендация со сбросом упражнения помогла, тесты прошли. Спасибо!","topic_id":91840}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Хэш","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":2305,"slug":"js_basic_algorithms_hash_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"В этом задании вам предстоит создать функцию, которая будет удалять из хеша запись по указанному ключу\n\n## solutions/solution.js\n\n* Импортируйте в файле класс `Hash`, как показано ниже\n\n```javascript\nimport Hash from '../hash/Hash.js';\n```\n\n* Определите вспомогательную функцию `solution()`. Она нужна для проверки вашего решения автоматизированными тестами\n\n```javascript\nexport default (map, key) => {\n\n const hash = new Hash();\n\n for (const [key, value] of Object.entries(map)) {\n hash.set(key, value);\n }\n\n return remove(hash, key);\n}\n```\n\n* Создайте функцию `remove()`, которая удаляет из хеша запись по переданному ключу. Функция принимает два параметра – хэш и ключ, который нужно удалить. Функция удаляет из переданного хеша значение по указанному ключу и возвращает удаленное значение. Если такого ключа в хеше нет, то функция должна вернуть `null`\n\n```javascript\nconst table = new Hash();\ntable.set(\"key\", \"value\");\ntable.set(\"key1\", \"value1\");\n\nconst removed = remove(table, \"key\");\nconsole.log(removed); // => value\n\n// В хеше ключа больше нет\ntable.get(\"key\"); // undefined\n```\n\n## solutions/solution.py\n\nИмпортируйте в solution.py класс `Hash` следующим образом:\n\n```python\nimport os\nimport sys\n\nsys.path.append(os.path.abspath('/usr/src/app/'))\n\nfrom hash.Hash import Hash # noqa: E402\n```\n\nРеализуйте функцию `remove()`, которая удаляет из хеша запись по переданному ключу. Функция принимает два параметра – хэш и ключ, который нужно удалить. Функция удаляет из переданного хеша значение по указанному ключу и возвращает удаленное значение. Если такого ключа в хеше нет, то функция должна вернуть `None`\n\n```python\ntable = Hash()\ntable.set(\"key\", \"value\")\ntable.set(\"key1\", \"value1\")\n\nremoved = solution(table, \"key\")\nprint(removed) # => value\n\n# В хеше ключа больше нет\ntable.get(\"key\") # None\n```\n\nДобавьте в файл функцию `solution`, необходимую для проверки вашего решения автоматизированными тестами. Данную функцию разместите в конце файла.\n\n```python\ndef solution(map, key):\n hash = Hash()\n\n for key_, value in map.items():\n hash.set(key_, value)\n\n return remove(hash, key)\n```\n\n## solutions/Solution.java\n\n* В файле определите пакет `solutions` и создайте публичный класс `Solution`\n* В классе реализуйте публичный статический метод `remove()`, который удаляет из хеша запись по переданному ключу. Метод принимает два параметра – хэш `Hash` и ключ, запись по которому нужно удалить. Метод удаляет из переданного хеша запись по указанному ключу и возвращает удаленное значение. Если такого ключа в хеше нет, то метод должен вернуть `null`\n\n```java\nimport hash.Hash;\n\nHash table = new Hash();\ntable.set(\"key\", \"value\");\ntable.set(\"key1\", \"value1\");\n\nvar removed = remove(table, \"key\");\nSystem.out.println(removed); // => value\n\n// В хеше ключа больше нет\ntable.get(\"key\"); // null\n```\n\n* Добавьте в класс `Solution` метод `run()` со следующим кодом:\n\n```java\npublic static Object run(java.util.Map<String, Object> coll, String key) {\n return helpers.Helper.run(coll, key);\n }\n```\n\nЭтот метод нужен для проверки вашего решения автоматизированными тестами\n\n## solutions/solution.php\n\n* Импортируйте в файле класс `Hash`, как показано ниже\n\n```php\n<?php\n\nrequire_once(__DIR__ . '/../hash/Hash.php');\n```\n\n* Определите вспомогательную функцию `solution()`. Она нужна для проверки вашего решения автоматизированными тестами\n\n```php\n<?php\n\nfunction solution($map, $key)\n{\n $hash = new Hash();\n foreach ($map as $k => $v) {\n $hash->set($k, $v);\n }\n\n return remove($hash, $key);\n}\n```\n\n* Создайте функцию `remove()`, которая удаляет из хеша запись по переданному ключу. Функция принимает два параметра – хэш и ключ, который нужно удалить. Функция удаляет из переданного хеша значение по указанному ключу и возвращает удаленное значение. Если такого ключа в хеше нет, то функция должна вернуть `null`\n\n```php\n<?php\n\n$table = new Hash();\n$table->set(\"key\", \"value\");\n$table->set(\"key1\", \"value1\");\n\n$removed = remove($table, \"key\");\nprint_r($removed); // => value\n\n// В хеше ключа больше нет\n$table->get(\"key\"); // null\n```\n","prepared_readme":"В этом задании вам предстоит создать функцию, которая будет удалять из хеша запись по указанному ключу\n\n## solutions/solution.js\n\n* Импортируйте в файле класс `Hash`, как показано ниже\n\n```javascript\nimport Hash from '../hash/Hash.js';\n```\n\n* Определите вспомогательную функцию `solution()`. Она нужна для проверки вашего решения автоматизированными тестами\n\n```javascript\nexport default (map, key) => {\n\n const hash = new Hash();\n\n for (const [key, value] of Object.entries(map)) {\n hash.set(key, value);\n }\n\n return remove(hash, key);\n}\n```\n\n* Создайте функцию `remove()`, которая удаляет из хеша запись по переданному ключу. Функция принимает два параметра – хэш и ключ, который нужно удалить. Функция удаляет из переданного хеша значение по указанному ключу и возвращает удаленное значение. Если такого ключа в хеше нет, то функция должна вернуть `null`\n\n```javascript\nconst table = new Hash();\ntable.set(\"key\", \"value\");\ntable.set(\"key1\", \"value1\");\n\nconst removed = remove(table, \"key\");\nconsole.log(removed); // => value\n\n// В хеше ключа больше нет\ntable.get(\"key\"); // undefined\n```\n\n## solutions/solution.py\n\nИмпортируйте в solution.py класс `Hash` следующим образом:\n\n```python\nimport os\nimport sys\n\nsys.path.append(os.path.abspath('/usr/src/app/'))\n\nfrom hash.Hash import Hash # noqa: E402\n```\n\nРеализуйте функцию `remove()`, которая удаляет из хеша запись по переданному ключу. Функция принимает два параметра – хэш и ключ, который нужно удалить. Функция удаляет из переданного хеша значение по указанному ключу и возвращает удаленное значение. Если такого ключа в хеше нет, то функция должна вернуть `None`\n\n```python\ntable = Hash()\ntable.set(\"key\", \"value\")\ntable.set(\"key1\", \"value1\")\n\nremoved = solution(table, \"key\")\nprint(removed) # => value\n\n# В хеше ключа больше нет\ntable.get(\"key\") # None\n```\n\nДобавьте в файл функцию `solution`, необходимую для проверки вашего решения автоматизированными тестами. Данную функцию разместите в конце файла.\n\n```python\ndef solution(map, key):\n hash = Hash()\n\n for key_, value in map.items():\n hash.set(key_, value)\n\n return remove(hash, key)\n```\n\n## solutions/Solution.java\n\n* В файле определите пакет `solutions` и создайте публичный класс `Solution`\n* В классе реализуйте публичный статический метод `remove()`, который удаляет из хеша запись по переданному ключу. Метод принимает два параметра – хэш `Hash` и ключ, запись по которому нужно удалить. Метод удаляет из переданного хеша запись по указанному ключу и возвращает удаленное значение. Если такого ключа в хеше нет, то метод должен вернуть `null`\n\n```java\nimport hash.Hash;\n\nHash table = new Hash();\ntable.set(\"key\", \"value\");\ntable.set(\"key1\", \"value1\");\n\nvar removed = remove(table, \"key\");\nSystem.out.println(removed); // => value\n\n// В хеше ключа больше нет\ntable.get(\"key\"); // null\n```\n\n* Добавьте в класс `Solution` метод `run()` со следующим кодом:\n\n```java\npublic static Object run(java.util.Map<String, Object> coll, String key) {\n return helpers.Helper.run(coll, key);\n }\n```\n\nЭтот метод нужен для проверки вашего решения автоматизированными тестами\n\n## solutions/solution.php\n\n* Импортируйте в файле класс `Hash`, как показано ниже\n\n```php\n<?php\n\nrequire_once(__DIR__ . '/../hash/Hash.php');\n```\n\n* Определите вспомогательную функцию `solution()`. Она нужна для проверки вашего решения автоматизированными тестами\n\n```php\n<?php\n\nfunction solution($map, $key)\n{\n $hash = new Hash();\n foreach ($map as $k => $v) {\n $hash->set($k, $v);\n }\n\n return remove($hash, $key);\n}\n```\n\n* Создайте функцию `remove()`, которая удаляет из хеша запись по переданному ключу. Функция принимает два параметра – хэш и ключ, который нужно удалить. Функция удаляет из переданного хеша значение по указанному ключу и возвращает удаленное значение. Если такого ключа в хеше нет, то функция должна вернуть `null`\n\n```php\n<?php\n\n$table = new Hash();\n$table->set(\"key\", \"value\");\n$table->set(\"key1\", \"value1\");\n\n$removed = remove($table, \"key\");\nprint_r($removed); // => value\n\n// В хеше ключа больше нет\n$table->get(\"key\"); // null\n```\n","has_solution":true,"entity_name":"Хэш"},"units":[{"id":6566,"name":"theory","url":"/courses/basic-algorithms/lessons/hash/theory_unit"},{"id":7963,"name":"quiz","url":"/courses/basic-algorithms/lessons/hash/quiz_unit"},{"id":7958,"name":"exercise","url":"/courses/basic-algorithms/lessons/hash/exercise_unit"}],"links":[],"ordered_units":[{"id":6566,"name":"theory","url":"/courses/basic-algorithms/lessons/hash/theory_unit"},{"id":7963,"name":"quiz","url":"/courses/basic-algorithms/lessons/hash/quiz_unit"},{"id":7958,"name":"exercise","url":"/courses/basic-algorithms/lessons/hash/exercise_unit"}],"id":2876,"slug":"hash","state":"approved","name":"Хэш","course_order":930,"goal":"Изучаем, как работает хэш","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Ранее в курсе мы изучили метод перебора и сталкивались с задачей про европейские столицы. Снова попробуем решить эту задачу, но выберем новый, более продуктивный способ.\n\nВ задаче про европейские столицы в условиях задачи были указаны пары — город и дата основания. Зная дату, мы можем определить город. Для решения задачи используем следующую таблицу:\n\n| | |\n|------|------------|\n| 1804 | Вена |\n| 1614 | Тирана |\n| 1067 | Минск |\n| 752 | Ватикан |\n| 1872 | Будапешт |\n| 1237 | Берлин |\n| 1167 | Копенгаген |\n| 1191 | Дублин |\n| 1786 | Рейкьявик |\n| 856 | Мадрид |\n| 1323 | Вильнюс |\n| 963 | Люксембург |\n| 1275 | Амстердам |\n| 1200 | Варшава |\n| 1147 | Москва |\n\nПары с годом и городом в таблице расположены в произвольном порядке, поэтому поиск нужной столицы требует полного перебора.\n\nВременная сложность такого алгоритма составляет O(n), что довольно медленно. Бинарный поиск позволяет сократить это время до O(logn). На большой таблице из миллиона записей среднее количество сравнений для линейного поиска равно 500000, а для бинарного — всего 10.\n\nНо и это не предел. Если поиск нужно выполнять с максимальной скоростью, мы можем воспользоваться еще одной структурой данных. Это **хэш-таблица**, которая обеспечивает временную сложность O(1).\n\nХэш-таблица похожа на массив, потому что в ней тоже есть операция индексации. В JavaScript доступ к элементу массива осуществляется по индексу, записанному в квадратных скобках. В качестве индекса можно использовать последовательные целые числа. В случае с хэш-таблицей в качестве индекса можно брать любые структуры данных — строки, дату и время, массивы.\n\nВ этом уроке мы разберемся, как устроены хэш-таблицы и как их реализовывать.\n\n## Разреженные массивы\n\nПростейший способ определить столицу по году основания — использовать массив:\n\n```javascript\nlet capitals = []\n\ncapitals[881] = 'Вена'\ncapitals[1237] = 'Берлин'\ncapitals[1323] = 'Вильнюс'\ncapitals[1614] = 'Тирана'\ncapitals[1167] = 'Копенгаген'\ncapitals[963] = 'Люксембург'\ncapitals[1067] = 'Минск'\ncapitals[1191] = 'Дублин'\ncapitals[1275] = 'Амстердам'\ncapitals[752] = 'Ватикан'\ncapitals[1786] = 'Рейкьявик'\ncapitals[1200] = 'Варшава'\ncapitals[1872] = 'Будапешт'\ncapitals[856] = 'Мадрид'\ncapitals[1147] = 'Москва'\n```\n\n```python\ncapitals = [None for _ in range(1873)]\n\ncapitals[1804] = \"Вена\"\ncapitals[1237] = \"Берлин\"\ncapitals[1323] = \"Вильнюс\"\ncapitals[1614] = \"Тирана\"\ncapitals[1167] = \"Копенгаген\"\ncapitals[963] = \"Люксембург\"\ncapitals[1067] = \"Минск\"\ncapitals[1191] = \"Дублин\"\ncapitals[1275] = \"Амстердам\"\ncapitals[752] = \"Ватикан\"\ncapitals[1786] = \"Рейкьявик\"\ncapitals[1200] = \"Варшава\"\ncapitals[1872] = \"Будапешт\"\ncapitals[856] = \"Мадрид\"\ncapitals[1147] = \"Москва\"\n```\n\n```java\nString[] capitals = new String[1873];\n\ncapitals[881] = \"Вена\";\ncapitals[1237] = \"Берлин\";\ncapitals[1323] = \"Вильнюс\";\ncapitals[1614] = \"Тирана\";\ncapitals[1167] = \"Копенгаген\";\ncapitals[963] = \"Люксембург\";\ncapitals[1067] = \"Минск\";\ncapitals[1191] = \"Дублин\";\ncapitals[1275] = \"Амстердам\";\ncapitals[752] = \"Ватикан\";\ncapitals[1786] = \"Рейкьявик\";\ncapitals[1200] = \"Варшава\";\ncapitals[1872] = \"Будапешт\";\ncapitals[856] = \"Мадрид\";\ncapitals[1147] = \"Москва\";\n```\n\n```php\n<?php\n\n$capitals = new SplFixedArray(1873);\n\n$capitals[881] = 'Вена';\n$capitals[1237] = 'Берлин';\n$capitals[1323] = 'Вильнюс';\n$capitals[1614] = 'Тирана';\n$capitals[1167] = 'Копенгаген';\n$capitals[963] = 'Люксембург';\n$capitals[1067] = 'Минск';\n$capitals[1191] = 'Дублин';\n$capitals[1275] = 'Амстердам';\n$capitals[752] = 'Ватикан';\n$capitals[1786] = 'Рейкьявик';\n$capitals[1200] = 'Варшава';\n$capitals[1872] = 'Будапешт';\n$capitals[856] = 'Мадрид';\n$capitals[1147] = 'Москва';\n```\n\nТакой массив занимает много памяти. В нем хранится всего 15 городов, но при этом размер массива — это целых 1873 элемента:\n\n```javascript\nconsole.log(capitals.length) // => 1873\n```\n\n```python\nprint(len(capitals)) # => 1873\n```\n\n```java\nSystem.out.println(capitals.length); // => 1873\n```\n\n```php\n<?php\n\nprint_r(count($capitals)); // => 1873\n```\n\nТак получилось потому, что индексами в массиве могут быть только последовательные целые числа, начиная с 0. Если мы добавляем элемент с индексом 752, в массив добавляются и неопределенные элементы с индексами от 0 до 751. Длина массива получилась равной 1873, потому что:\n\n* Наибольший год основания — 1872\n* Первым в массиве всегда стоит элемент с индексом 0\n\nЕсли мы применим массив, доступ к элементам выполнится очень быстро — за константное время O(1). Но при этом мы впустую расходуем память — в нашем примере массив заполнен всего на 0,8%.\n\nМассивы, в которых большая часть элементов не определена, называются **разреженными**. Чтобы сэкономить память, программисты стараются уплотнить массив. Скажем, в нашем примере годы находятся достаточно далеко друг от друга — на каждые двадцать пустых элементов приходится только один элемент с данными. Мы можем уменьшить массив в двадцать раз с помощью простого трюка.\n\nВозьмем числа 0, 1, 2 и 3, поделим их на 20 и получим такой результат:\n\n* 0 делить на 20 = 0\n* 1 делить на 20 = 0.05\n* 2 делить на 20 = 0.1\n* 3 делить на 20 = 0.15\n\nЦелая часть этих чисел будет равна 0, пока мы не доберемся до такой операции:\n\n19 делить на 20 = 0.95\n\nНачиная с 20, целая часть после деления будет равна 1:\n\n* 20 делить на 20 = 1\n* 21 делить на 20 = 1.05\n\nНачиная с 40, целая часть будет равна 2.\n\nПолучается, что после преобразования числа схлопываются:\n\n* От 0 до 19 → 0\n* От 20 до 39 → 1\n* От 40 до 59 → 2 и так далее\n\nЧтобы получать целую часть, воспользуемся стандартной функцией `Math.floor`:\n\n```javascript\nlet capitals = []\n\ncapitals[Math.floor(881 / 20)] = 'Вена'\ncapitals[Math.floor(1237 / 20)] = 'Берлин'\ncapitals[Math.floor(1323 / 20)] = 'Вильнюс'\ncapitals[Math.floor(1614 / 20)] = 'Тирана'\ncapitals[Math.floor(1167 / 20)] = 'Копенгаген'\ncapitals[Math.floor(963 / 20)] = 'Люксембург'\ncapitals[Math.floor(1067 / 20)] = 'Минск'\ncapitals[Math.floor(1191 / 20)] = 'Дублин'\ncapitals[Math.floor(1275 / 20)] = 'Амстердам'\ncapitals[Math.floor(752 / 20)] = 'Ватикан'\ncapitals[Math.floor(1786 / 20)] = 'Рейкьявик'\ncapitals[Math.floor(1200 / 20)] = 'Варшава'\ncapitals[Math.floor(1872 / 20)] = 'Будапешт'\ncapitals[Math.floor(856 / 20)] = 'Мадрид'\ncapitals[Math.floor(1147 / 20)] = 'Москва'\n```\n\n```python\n# В Python для целых чисел используется целочисленное деление\ncapitals = [None for _ in range(100)];\n\ncapitals[1804 // 20] = \"Вена\"\ncapitals[1237 // 20] = \"Берлин\"\ncapitals[1323 // 20] = \"Вильнюс\"\ncapitals[1614 // 20] = \"Тирана\"\ncapitals[1167 // 20] = \"Копенгаген\"\ncapitals[963 // 20] = \"Люксембург\"\ncapitals[1067 // 20] = \"Минск\"\ncapitals[1191 // 20] = \"Дублин\"\ncapitals[1275 // 20] = \"Амстердам\"\ncapitals[752 // 20] = \"Ватикан\"\ncapitals[1786 // 20] = \"Рейкьявик\"\ncapitals[1200 // 20] = \"Варшава\"\ncapitals[1872 // 20] = \"Будапешт\"\ncapitals[856 // 20] = \"Мадрид\"\ncapitals[1147 // 20] = \"Москва\"\n```\n\n```java\n// В java для целых чисел используется целочисленное деление\nString[] capitals = new String[100];\n\ncapitals[881 / 20] = \"Вена\";\ncapitals[1237 / 20] = \"Берлин\";\ncapitals[1323 / 20] = \"Вильнюс\";\ncapitals[1614 / 20] = \"Тирана\";\ncapitals[1167 / 20] = \"Копенгаген\";\ncapitals[963 / 20] = \"Люксембург\";\ncapitals[1067 / 20] = \"Минск\";\ncapitals[1191 / 20] = \"Дублин\";\ncapitals[1275 / 20] = \"Амстердам\";\ncapitals[752 / 20] = \"Ватикан\";\ncapitals[1786 / 20] = \"Рейкьявик\";\ncapitals[1200 / 20] = \"Варшава\";\ncapitals[1872 / 20] = \"Будапешт\";\ncapitals[856 / 20] = \"Мадрид\";\ncapitals[1147 / 20] = \"Москва\";\n```\n\n```php\n<?php\n\n$capitals = new SplFixedArray(100);\n\n$capitals[round(881 / 20)] = 'Вена';\n$capitals[round(1237 / 20)] = 'Берлин';\n$capitals[round(1323 / 20)] = 'Вильнюс';\n$capitals[round(1614 / 20)] = 'Тирана';\n$capitals[round(1167 / 20)] = 'Копенгаген';\n$capitals[round(963 / 20)] = 'Люксембург';\n$capitals[round(1067 / 20)] = 'Минск';\n$capitals[round(1191 / 20)] = 'Дублин';\n$capitals[round(1275 / 20)] = 'Амстердам';\n$capitals[round(752 / 20)] = 'Ватикан';\n$capitals[round(1786 / 20)] = 'Рейкьявик';\n$capitals[round(1200 / 20)] = 'Варшава';\n$capitals[round(1872 / 20)] = 'Будапешт';\n$capitals[round(856 / 20)] = 'Мадрид';\n$capitals[round(1147 / 20)] = 'Москва';\n```\n\nЧтобы узнать, какой город основан в 1191 году, мы проверяем элемент с индексом 1191/20:\n\n```javascript\nconst getCity = year => capitals[Math.floor(year / 20)]\n\nlet city = getCity(1191) // Дублин\n```\n\n```python\ndef get_city(year):\n return capitals[year // 20]\n\ncity = get_city(1191) # Дублин\n```\n\n```java\nclass App {\n public static String getCity(String[] capitals, int year) {\n return capitals[year / 20];\n }\n}\n```\n\n```php\n<?php\n\nfunction getCity($year, $capitals)\n{\n return $capitals[round($year / 20)];\n}\n```\n\nУ такого подхода есть недостаток — довольно трудно предсказать размер массива. Когда речь идет о годах основания, мы можем проанализировать данные и прикинуть, что каждые двадцать лет можно схлопнуть в один элемент.\n\nДля чисел 0 до 1999 нам достаточно массива из ста элементов. Последний элемент такого уплотненного массива имеет индекс 99, а `Math.floor(1999/20)` как раз равно 99.\n\nНо в другой задаче это может не сработать — например, если требуется хранить в массиве индексы, равные миллиону или миллиарду. Коэффициент 20 слишком мал для таких чисел, потому что даже уплотненный массив все равно будет слишком большим и слишком пустым.\n\nНо мы не можем анализировать каждую возникающую задачу. Нам бы хотелось иметь универсальный способ, подходящий для разных случаев и позволяющий контролировать размер массива. Поэтому мы рассмотрим еще один способ.\n\n## Модульная арифметика\n\nПредположим, мы фиксируем размер массива. Пусть у нас будет массив из ста элементов:\n\n```javascript\nlet capitals = new Array(100)\n```\n\n```python\ncapitals = [None for _ in range(100)]\n```\n\n```java\nString[] capitals = new String[100];\n```\n\n```php\n<?php\n\n$capitals = array_fill(0, 100, null);\n```\n\nМы хотим сохранить в него несколько элементов, но их индексы могут быть произвольными целыми числами. Чтобы преобразовать индекс в число от 0 до 99, мы должны вычислить остаток от деления индекса на 100.\n\nОбщее правило такое:\n\nОстатки от деления любого числа на n всегда находятся в диапазоне от 0 до n-1\n\nЭто правило поможет нам работать с массивами любого размера.\n\nИногда вместо «остаток от деления на n» говорят «модуль по основанию n». В JavaScript и многих других языках программирования для вычисления модуля используют оператор `%`:\n\n```javascript\nconsole.log(1999 % 20) // => 19\n```\n\n```python\nprint(1999 % 20) # => 19\n```\n\n```java\nSystem.out.println(1999 % 20); // => 19\n```\n\n```php\n<?php\n\nprint_r(1999 % 20); // => 19\n```\n\nРешим нашу задачу со столицами с помощью модуля:\n\n```javascript\nlet capitals = new Array(100)\n\ncapitals[881 % 100] = 'Вена'\ncapitals[1237 % 100] = 'Берлин'\ncapitals[1323 % 100] = 'Вильнюс'\ncapitals[1614 % 100] = 'Тирана'\ncapitals[1167 % 100] = 'Копенгаген'\ncapitals[963 % 100] = 'Люксембург'\ncapitals[1067 % 100] = 'Минск'\ncapitals[1191 % 100] = 'Дублин'\ncapitals[1275 % 100] = 'Амстердам'\ncapitals[752 % 100] = 'Ватикан'\ncapitals[1786 % 100] = 'Рейкьявик'\ncapitals[1200 % 100] = 'Варшава'\ncapitals[1872 % 100] = 'Будапешт'\ncapitals[856 % 100] = 'Мадрид'\ncapitals[1147 % 100] = 'Москва'\n\nconst getCity = year => capitals[year % 100]\n\nlet city = getCity(1167) // Копенгаген\n```\n\n```python\ncapitals = [None for _ in range(100)]\n\ncapitals[1804 % 100] = 'Вена'\ncapitals[1237 % 100] = 'Берлин'\ncapitals[1323 % 100] = 'Вильнюс'\ncapitals[1614 % 100] = 'Тирана'\ncapitals[1167 % 100] = 'Копенгаген'\ncapitals[963 % 100] = 'Люксембург'\ncapitals[1067 % 100] = 'Минск'\ncapitals[1191 % 100] = 'Дублин'\ncapitals[1275 % 100] = 'Амстердам'\ncapitals[752 % 100] = 'Ватикан'\ncapitals[1786 % 100] = 'Рейкьявик'\ncapitals[1200 % 100] = 'Варшава'\ncapitals[1872 % 100] = 'Будапешт'\ncapitals[856 % 100] = 'Мадрид'\ncapitals[1147 % 100] = 'Москва'\n\ndef get_city(year):\n return capitals[year % 100]\n\ncity = get_city(1167) # Копенгаген\n```\n\n```java\nString[] capitals = new String[100];\n\ncapitals[881 % 100] = \"Вена\";\ncapitals[1237 % 100] = \"Берлин\";\ncapitals[1323 % 100] = \"Вильнюс\";\ncapitals[1614 % 100] = \"Тирана\";\ncapitals[1167 % 100] = \"Копенгаген\";\ncapitals[963 % 100] = \"Люксембург\";\ncapitals[1067 % 100] = \"Минск\";\ncapitals[1191 % 100] = \"Дублин\";\ncapitals[1275 % 100] = \"Амстердам\";\ncapitals[752 % 100] = \"Ватикан\";\ncapitals[1786 % 100] = \"Рейкьявик\";\ncapitals[1200 % 100] = \"Варшава\";\ncapitals[1872 % 100] = \"Будапешт\";\ncapitals[856 % 100] = \"Мадрид\";\ncapitals[1147 % 100] = \"Москва\";\n\nclass App {\n public static String getCity(String[] capitals, int year) {\n return capitals[year % 100];\n }\n}\n```\n\n```php\n<?php\n\n$capitals = array_fill(0, 100, null);\n\n$capitals[881 % 100] = 'Вена';\n$capitals[1237 % 100] = 'Берлин';\n$capitals[1323 % 100] = 'Вильнюс';\n$capitals[1614 % 100] = 'Тирана';\n$capitals[1167 % 100] = 'Копенгаген';\n$capitals[963 % 100] = 'Люксембург';\n$capitals[1067 % 100] = 'Минск';\n$capitals[1191 % 100] = 'Дублин';\n$capitals[1275 % 100] = 'Амстердам';\n$capitals[752 % 100] = 'Ватикан';\n$capitals[1786 % 100] = 'Рейкьявик';\n$capitals[1200 % 100] = 'Варшава';\n$capitals[1872 % 100] = 'Будапешт';\n$capitals[856 % 100] = 'Мадрид';\n$capitals[1147 % 100] = 'Москва';\n\nconst getCity = (year) => $capitals[year % 100];\n\nfunction getCity($capitals, $year)\n{\n return $capitals[$year % 100];\n}\n\n$city = getCity(1167); // 'Копенгаген'\n```\n\nТеперь мы точно знаем, что наш массив не вырастет до больших размеров, как это может случиться с операцией деления. Но мы можем обнаружить другую проблему.\n\nПопробуем узнать, какой город основан в 1167 году. Если верить нашей таблице, это Копенгаген. Но программа говорит совсем другое:\n\n```javascript\nconsole.log(getCity(1167)) // => Минск\n```\n\n```python\nprint(get_city(1167)) # => Минск\n```\n\n```java\nSystem.out.println(App.getCity(1167)); // => Минск\n```\n\n```php\n<?php\n\nprint_r(getCity(1167)); // => Минск\n```\n\nМинск основан в 1067 году, а Копенгаген — в 1167. Годы отличаются, но у них один и тот же остаток от деления на 100, а именно 67.\n\nСитуация, когда разные элементы после преобразования индекса попадают в одну и ту же ячейку массива, называется **коллизией**. На самом деле это не ошибка, а вполне вероятная ситуация, хотя и не очень частая.\n\n## Коллизии\n\nСправиться с коллизиями нетрудно. Вместо того чтобы хранить в каждой ячейке массива простое значение, мы размещаем там односвязный список. При добавлении элемента мы добавляем в список пару из года и города:\n\n* Год — это индекс, по которому мы сохраняем и извлекаем данные. Обычно его называют **ключом**\n* Название — это значение\n\nПопробуем реализовать в коде:\n\n```javascript\nimport LinkedList from './LinkedList.js'\n\nlet capitals = new Array(100)\n\nconst setCapital = (year, city) => {\n let index = year % capitals.length\n if (typeof (capitals[index]) === 'undefined') {\n capitals[index] = new LinkedList()\n }\n\n capitals[index].add({ key: year, value: city })\n}\n```\n\n```python\nfrom LinkedList import LinkedList\n\ncapitals = [None for _ in range(100)]\n\ndef set_capital(year, city):\n index = year % len(capitals)\n\n if capitals[index] is None:\n capitals[index] = LinkedList()\n\n capitals[index].add({'key': year, 'value': city})\n```\n\n```java\nclass App {\n private LinkedList[] capitals = new LinkedList[100];\n\n public void setCapital(int year, String city) {\n var index = year % capitals.length;\n if (capitals[index] == null) {\n capitals[index] = new LinkedList<Object[]>();\n }\n\n capitals[index].add(new Object[] {year, city});\n }\n}\n```\n\n```php\n<?php\n\nuse LinkedList;\n\n$capitals = array_fill(0, 100, null);\n\nfunction setCapital($year, $city, &$capitals)\n{\n $index = $year % count(capitals);\n if (!isset($capitals[$index])) {\n $capitals[$index] = new LinkedList();\n }\n\n $capitals[$index]->add(['key' => $year, 'value' => $city]);\n}\n```\n\nМы уже разработали структуру данных `LinkedList`, поэтому мы можем просто импортировать ее.\n\nРазмер массива capitals всегда будет равен ста элементам.\n\nПо умолчанию все ячейки массива пусты, и связный список создается только при первой записи в ячейку. В каждом списке может храниться несколько элементов. Чтобы различать их, мы сохраняем не просто значение, а пару из ключа (`key`) и значения (`value`).\n\nПри поиске точно также вычисляем индекс:\n\n```javascript\nconst getCapital = (year) => {\n let index = year % capitals.length\n if (typeof (capitals[index]) === 'undefined') {\n return undefined\n }\n\n for (let pair of capitals[index]) {\n if (pair.key === year) {\n return pair.value\n }\n }\n\n return undefined\n}\n```\n\n```python\ndef get_capital(year):\n index = year % len(capitals)\n\n if capitals[index] is None:\n return None\n\n for pair in capitals[index]:\n if pair['key'] == year:\n return pair['value']\n\n return None\n```\n\n```java\nlass App {\n private LinkedList[] capitals = new LinkedList[100];\n\n public void setCapital(int year, String city) {\n // ...\n }\n\n public String getCapital(int year) {\n var index = year % capitals.length;\n\n if (capitals[index] == null) {\n return null;\n }\n\n for (var pair : capitals[index]) {\n var casted = (Object[]) pair;\n if ((int) casted[0] == year) {\n return (String) casted[1];\n }\n }\n\n return null;\n }\n}\n```\n\n```php\n<?php\n\nfunction getCapital($capitals, $year)\n{\n $index = $year % count($capitals);\n if (!isset($capitals[$year])) {\n return null;\n }\n\n foreach($capitals[$index] as $pair) {\n if ($pair['key'] === $year) {\n return $pair['value'];\n }\n }\n\n return null;\n}\n```\n\nЕсли в ячейке ничего нет, значит, мы ничего и не записывали.\n\nНо если в ячейке что-то есть, то это список, по которому мы пробегаем и пытаемся найти пару с заданным ключом (_key_).\n\nОбнаружив пару, возвращаем ее значение (_value_).\n\nАлгоритмическая сложность этих операций зависит от того, насколько часто элементы попадают в одну и ту же ячейку — в один и тот же связный список.\n\nЕсли у нас будет случайный набор из ста чисел, он достаточно равномерно распределится по массиву: в большинстве ячеек будет храниться одно число, в некоторых — два, и в некоторых — ни одного. Алгоритмическая сложность записи и чтения в таком случае будет близка к O(1) — это один из самых быстрых возможных алгоритмов.\n\nЕсли числа не будут случайными, может получиться так, что в одном из списков их окажется слишком много — тогда скорость вставки и проверки значительно снизится. Подробнее мы обсудим эту ситуацию позже, а пока запомним, что предложенный нами алгоритм любит равномерные случайные ключи.\n\n## Хэш-функция\n\nТеперь попробуем решить обратную задачу — определять год основания столицы, зная ее название. Заглянем в таблицу в начале урока и вспомним, что год основания Мадрида — 856, а Москвы — 1147.\n\nНаша структура данных может хранить значения с любым целочисленным ключом, не только с годом. Но «Мадрид» и «Москва» — это не числа, а строки.\n\nПри этом компьютеры умеют работать только с числами, и любые объекты в них хранятся как последовательности чисел. Каждая буква хранится как одно число, которое называют **кодом символа**:\n\n```javascript\nconst capital = 'Мадрид'\nfor (let i = 0; i < capital.length; i++) {\n console.log(capital.charCodeAt(i))\n}\n// => 1052\n// => 1072\n// => 1076\n// => 1088\n// => 1080\n// => 1076\n```\n\n```python\ncapital = 'Мадрид'\n\nfor i in range(len(capital)):\n print(ord(capital[i]))\n# => 1052\n# => 1072\n# => 1076\n# => 1088\n# => 1080\n# => 1076\n```\n\n```java\nvar capital = \"Мадрид\";\nfor (var i = 0; i < capital.length; i++) {\n System.out.println((int) capital.charAt(i));\n}\n// => 1052\n// => 1072\n// => 1076\n// => 1088\n// => 1080\n// => 1076\n\n```\n\n```php\n<?php\n\n$capital = 'Мадрид';\nfor ($i = 0; $i < count($capital); i += 1) {\n print_r(mb_ord($capital[$i]));\n}\n// => 1052\n// => 1072\n// => 1076\n// => 1088\n// => 1080\n// => 1076\n```\n\nСлово «Мадрид» хранится как последовательность чисел 1052, 1072, 1076, 1088, 1080 и 1076. В простейшем случае мы могли бы использовать для вычисления индекса код первой буквы — в нашем примере 1052. Но это значит все города на М (Мадрид, Москва и Минск) попадут в один и тот же список. В таком случае скорость поиска будет не очень высокой.\n\nЧтобы этого избежать, нам нужны ключи, которые очень похожи на случайные. Мы можем взять все наши числа и подвергнуть их какой-нибудь обработке — например, вычислить их сумму или произведение.\n\nОднако сложение и умножение — слишком простые операции. Все слова, состоящие из одних и тех же букв (ведро, вроде и древо), будут иметь один и тот же ключ — сумму или произведение кодов символов.\n\nХороший результат в таком случае даст так называемая **полиномиальная функция**.\n\nПопробуем применить ее к нашей задаче с городами. Пусть коды символов строки длины n будут равны c0, c1, ..., cn-1. Возьмем число k, которое будет больше кода любого символа — скажем, k = 65537.\n\nУ нас получилось такое выражение:\n\n```text\nc0 + c1 * k + c2 * k^2 + c3 * k^3 + ... + cn-1 * k^(n-1)\n```\n\nЧисло k достаточно большое. На каждом шаге нам приходится возводить его в возрастающую степень, поэтому выражение может оказаться просто огромным. Для слова \"Мадрид\" мы получим 1300923352423037265600321836.\n\nНа практике для ускорения вычислений и для удобства используют не все выражение, а только остаток от его деления на другое число — m:\n\n```text\n(c0 + c1 * k + c2 * k^2 + c3 * k^3 + ... + cn-1 * k^(n-1)) mod m\n```\n\nОбычно модуль m делают равным большому простому числу или большому круглому числу — например, миллиону или миллиарду. Часто в качестве модуля выбирают степень числа 2 — к примеру, 2\\^32 или 2\\^64.\n\nЧтобы промежуточные числа не становились слишком большими, все сложения и умножения выполняют по модулю m. Другими словами, остаток от деления вычисляется сразу после сложения или умножения.\n\n```javascript\nconst hash = (s) => {\n const k = 65537\n const m = 2 ** 20\n\n let result = 0\n let k_i = 1\n for (let i = 0; i < s.length; i++) {\n result = (result + k_i * s.charCodeAt(i)) % m\n k_i = (k_i * k) % m\n }\n\n return result\n}\n```\n\n```python\ndef hash(s):\n k = 65537\n m = 2**20\n\n result = 0\n k_i = 1\n\n for i in range(len(s)):\n result = (result + k_i * ord(s[i])) % m\n k_i = (k_i * k) % m\n\n return result\n```\n\n```java\nclass App {\n private int hash(String s) {\n var k = 65537;\n var m = (int) Math.pow(2, 20);\n\n var result = 0;\n var k_i = 1;\n\n for (var i = 0; i < s.length(); i++) {\n result = (result + k_i * s.charAt(i)) % m;\n k_i = (k_i * k) % m;\n }\n\n return result;\n }\n}\n```\n\n```php\n<?php\n\nfunction hash($s)\n{\n $k = 65537;\n $m = 2 ** 20;\n\n $result = 0;\n $k_i = 1;\n for ($i = 0; $i < mb_strlen($s); $i += 1) {\n $result = ($result + $k_i * mb_ord($s[$i])) % $m;\n $k_i = ($k_i * $k) % $m;\n }\n\n return $result;\n}\n```\n\nЗдесь в качестве модуля мы выбрали `2**20`, то есть 2 в степени 20 (2\\^20). Это число равно 1048576 и часто используется программистами, потому что очень близко к миллиону. Слово мегабайт традиционно означает именно 1048576 байт, а не миллион, как можно было бы ожидать.\n\nСейчас комитет по стандартизации мер и весов рекомендует термины «мебибайт» для 1048576 байтов и «мегабайт» для 1000000 байтов, но большинство программистов все еще не используют эту новую рекомендацию.\n\nПеременная `k_i` на каждом шаге цикла умножается на `k`. Она принимает значения 1, затем `k`, затем `k` умноженное на 2, и так далее. В переменной `result` накапливается результат вычислений.\n\nФункция называется `hash`, и это неслучайно. В переводе с английского слово hash означает «мелко крошить, перемешивать».\n\nФункция перемешивает исходные данные так, чтобы получилось число, похожее на случайное:\n\n```javascript\nhash('Мадрид') // 792876\nhash('Москва') // 399671\nhash('Минск') // 857356\n```\n\n```python\nhash('Мадрид') # 792876\nhash('Москва') # 399671\nhash('Минск') # 857356\n```\n\n```java\nApp.hash(\"Мадрид\") // 792876\nApp.hash(\"Москва\") // 399671\nApp.hash(\"Минск\") // 857356\n```\n\n```php\n<?php\n\nhash('Мадрид') // 792876\nhash('Москва') // 399671\nhash('Минск') // 857356\n```\n\nЭту функцию так и называют — **хеш-функция**. В подавляющем большинстве случаев нам не приходится писать хеш-функции, потому что они уже разработаны авторами фреймворков и стандартных библиотек. Тем не менее полезно понимать принцип работы хешей.\n\nВ Python для вычисления хешей можно использовать функцию `hash()`, в Java — метод `hashCode()`, в C# — метод `GetHashCode()`. В JavaScript нет готовой хеш-функции — считается, что все нужное уже есть в языке.\n\nНазвание функции дало название всей структуре данных. Обычно ее называют хеш-таблицей (hash table или hashmap). Очень часто это название сокращают просто до хеша. Другие распространенные названия — словарь (dictionary) и ассоциативный массив (associative array).\n\n## Худший случай\n\nВыше мы говорили, что обычно вставка и поиск в хеш-таблице выполняются за время O(1). Но если хеш-функция выбрана неудачно, все значения могут оказаться в одном связном списке во внутреннем массиве.\n\nТогда время поиска составит O(n), что обычно считается медленным. К счастью, эта ситуация практически невозможна. Тем не менее, если вы соберетесь писать собственную хеш-таблицу, посвятите время выбору хорошей хеш-функции.\n\nЕсть еще одна возможная сложность. В массиве, который мы использовали в примерах выше, мы резервировали сто элементов, потому что рассчитывали, что данные будут распределены равномерно. Однако если мы поместим в такой массив 10000 элементов, то даже при идеальном распределении в каждом связном списке окажется сто элементов. Это значит, что поиск и вставка все-таки будут медленными.\n\nЧтобы справиться с этой проблемой, надо расширять хеш-таблицу — увеличивать внутренний массив по мере того, как в нее добавляются элементы. Конечно, это накладно делать при каждой вставке.\n\nОбычно подсчитывают **коэффициент заполнения хеш-таблицы** (load factor) — это частное от деления количества вставленных элементов на размер внутреннего массива. Если он достигает заранее выбранного порога в 60–80%, внутренний массив увеличивают вдвое, а индексы всех элементов пересчитывают.\n\nТочно также, если коэффициент заполнения становится слишком маленьким, 20–25%, массив уменьшают вдвое. У массива есть нижняя граница в 128 или 256 элементов, чтобы не перестраивать его слишком часто, пока он маленький.\n\n## Хэш-таблицы и функция вставки\n\nПерепишем функцию вставки, опираясь на все новые знания. Вместе с этим реализуем хеш-таблицу в виде новой структуры данных, спрятав внутри класса массив с данными:\n\n```javascript\nimport LinkedList from './LinkedList.js'\n\nclass Hash {\n table = []\n count = 0\n\n hash(s) {\n const k = 65537\n const m = 2 ** 20\n\n let result = 0, power = 1\n for (let i = 0; i < s.length; i++) {\n result = (result + power * s.charCodeAt(i)) % m\n power = (power * k) % m\n }\n\n return result\n }\n\n calculateIndex(table, key) {\n return this.hash(String(key)) % table.length\n }\n\n rebuildTableIfNeed() {\n if (this.table.length == 0) {\n this.table.length = 128\n }\n else {\n const loadFactor = this.count / this.table.length\n if (loadFactor >= 0.8) {\n let newTable = new Array(this.table.length * 2)\n for (let list in this.table) {\n for (let pair of list) {\n const newIndex = this.calculateIndex(newTable, pair.key)\n if (typeof (newTable[newIndex]) === 'undefined') {\n newTable[newIndex] = new LinkedList()\n }\n\n newTable[newIndex].add(pair)\n }\n }\n\n this.table = newTable\n }\n }\n }\n\n set(key, value) {\n this.rebuildTableIfNeed()\n\n const index = this.calculateIndex(this.table, key)\n if (typeof (this.table[index]) === 'undefined') {\n this.table[index] = new LinkedList()\n }\n\n this.table[index].add({ key: key, value: value })\n this.count += 1\n }\n\n get(key) {\n const index = this.calculateIndex(this.table, key)\n if (typeof (this.table[index].fore()) === 'undefined') {\n return undefined\n }\n\n for (let pair of this.table[index]) {\n if (pair.key === key) {\n return pair.value\n }\n }\n\n return undefined\n }\n}\n```\n\n```python\nclass Hash:\n table = []\n count = 0\n\n def hash(self, s):\n k = 65537\n m = 2**20\n\n result = 0\n power = 1\n\n for i in range(len(s)):\n result = (result + power * ord(s[i])) % m\n power = (power * k) % m\n\n return result\n\n def calculate_index(self, table, key):\n try:\n return self.hash(str(key)) % len(table)\n except ZeroDivisionError:\n return self.hash(str(key))\n\n def rebuild_table_if_need(self):\n if len(self.table) == 0:\n table = [None for _ in range(128)]\n else:\n load_factor = self.count / len(self.table)\n\n if load_factor >= 0.8:\n new_table = [None for _ in range(len(self.table) * 2)]\n\n for list_ in self.table:\n for pair in list_:\n new_index = self.calculate_index(new_table, pair['key'])\n if new_table[new_index] is None:\n new_table[new_index] = LinkedList()\n\n new_table[new_index].add(pair)\n\n self.table = new_table\n\n def set(self, key, value):\n self.rebuild_table_if_need()\n index = self.calculate_index(self.table, key)\n\n if self.table[index] is None:\n self.table[index] = LinkedList()\n\n self.table[index].add(\n {'key': key, 'value': value}\n )\n self.count += 1\n\n def get(self, key):\n index = self.calculate_index(self.table, key)\n\n if self.table[index] is None:\n return None\n\n for pair in self.table[index]:\n if pair['key'] == key:\n return pair['value']\n\n return None\n```\n\n```java\npublic class Hash {\n\n public LinkedList[] table = new LinkedList[128];\n public int count = 0;\n\n private int hash(String s) {\n var k = 65537;\n var m = (int) Math.pow(2, 20);\n\n var result = 0;\n var k_i = 1;\n\n for (var i = 0; i < s.length(); i++) {\n result = (result + k_i * s.charAt(i)) % m;\n k_i = (k_i * k) % m;\n }\n\n return result;\n }\n\n public int calculateIndex(LinkedList[] table, String key) {\n return hash(key) % table.length;\n }\n\n private void rubuildTableIfNeed() {\n var loadFactor = (double) count / table.length;\n if (loadFactor >= 0.8) {\n LinkedList[] newTable = new LinkedList[table.length * 2];\n for (LinkedList list : table) {\n for (var pair : list) {\n Object[] casted = (Object[]) pair;\n var newIndex = calculateIndex(newTable, (String) casted[0]);\n if (newTable[newIndex] == null) {\n newTable[newIndex] = new LinkedList();\n }\n\n newTable[newIndex].append(pair);\n }\n }\n\n table = newTable;\n }\n }\n\n public void set(String key, Object value) {\n this.rubuildTableIfNeed();\n var index = calculateIndex(table, key);\n\n if (table[index] == null) {\n table[index] = new LinkedList();\n }\n\n table[index].append(new Object[] {key, value});\n count++;\n }\n\n public Object get(String key) {\n var index = calculateIndex(table, key);\n if (table[index] == null) {\n return null;\n }\n\n for (var current : table[index]) {\n LinkedListNode node = (LinkedListNode) current;\n var pair = node.value;\n var casted = (Object[]) pair;\n if (casted[0].equals(key)) {\n return casted[1];\n }\n }\n\n return null;\n }\n}\n```\n\n```php\n<?php\n\nclass Hash\n{\n public function __construct()\n {\n $this->table = [];\n $this->count = 0;\n }\n\n public function hash($s)\n {\n $k = 65537;\n $m = 2 ** 20;\n\n $result = 0;\n $power = 1;\n for ($i = 0; $i < strlen($s); $i += 1) {\n $result = ($result + $power * ord($s[$i])) % $m;\n $power = ($power * $k) % $m;\n }\n\n return $result;\n }\n\n public function calculateIndex($table, $key)\n {\n return $this->hash((string) $key) % count($table);\n }\n\n public function rebuildTableIfNeed()\n {\n if (count($this->table) === 0) {\n $this->table = array_fill(0, 128, null);\n } else {\n $loadFactor = $this->count / count($this->table);\n if ($loadFactor >= 0.8) {\n $newTable = array_fill(0, count($this->table) * 2, null);\n foreach ($this->table as $list) {\n foreach ($list as $pair) {\n $newIndex = $this->calculateIndex($newTable, $pair['key']);\n if (!isset($newTable[$newIndex])) {\n $newTable[$newIndex] = new LinkedList();\n }\n\n $newTable[$newIndex]->append($pair);\n }\n }\n\n $this->table = $newTable;\n }\n }\n }\n\n public function set($key, $value)\n {\n $this->rebuildTableIfNeed();\n\n $index = $this->calculateIndex($this->table, $key);\n if (!isset($this->table[$index])) {\n $this->table[$index] = new LinkedList();\n }\n\n $this->table[$index]->append(['key' => $key, 'value' => $value ]);\n $this->count += 1;\n }\n\n public function get($key)\n {\n $index = $this->calculateIndex($this->table, $key);\n if (!isset($this->table[$index])) {\n return null;\n }\n\n foreach ($this->table[$index] as $pair1) {\n $pair = (array) $pair1;\n if ($pair['value']['key'] === $key) {\n return $pair['value']['value'];\n }\n }\n\n return null;\n }\n}\n```\n\nВесь код из этого примера мы уже обсуждали, кроме удвоения массива. Создавая новый массив, в два раза больше текущего, мы должны скопировать все элементы, пересчитав их индексы.\n\nИменно за это отвечает вложенный цикл:\n\n```javascript\nlet newTable = new Array(this.table.length * 2)\nfor (let list in this.table) {\n for (let pair of list) {\n const newIndex = this.calculateIndex(newTable, pair.key)\n if (typeof (newTable[newIndex]) === 'undefined') {\n newTable[newIndex] = new LinkedList()\n }\n\n newTable[newIndex].add(pair)\n }\n}\n```\n\n```python\nnew_table = [None for _ in range(len(self.table) * 2)]\n\nfor list_ in self.table:\n for pair in list_:\n new_index = self.calculate_index(new_table, pair['key'])\n if new_table[new_index] is None:\n new_table[new_index] = LinkedList()\n\n new_table[new_index].add(pair)\n```\n\n```java\nLinkedList[] newTable = new LinkedList[table.length * 2];\n\nfor (LinkedList list : table) {\n for (var pair : list) {\n Object[] casted = (Object[]) pair;\n var newIndex = calculateIndex(newTable, (String) casted[0]);\n if (newTable[newIndex] == null) {\n newTable[newIndex] = new LinkedList();\n }\n\n newTable[newIndex].add(pair);\n }\n}\n```\n\n```php\n<?php\n\n$newTable = array_fill(0, count($this->table) * 2, null);\nforeach ($this->table as $list) {\n foreach ($list as $pair) {\n $newIndex = $this->calculateIndex($newTable, $pair['key'])\n if (!isset($newTable[$newIndex])) {\n $newTable[$newIndex] = new LinkedList();\n }\n\n $newTable[$newIndex]->add($pair);\n }\n}\n```\n\nВ самом начале внутренний массив имеет размер 0, чтобы не тратить память, если в хеш-таблице нет ни одного элемента. При вставке первого элемента мы увеличиваем внутренний массив сразу до 128 элементов.\nОстальной код мы уже рассмотрели выше в этом уроке.\n\n## Выводы\n\nВ этом уроке мы изучили хэш-таблицы и научились с ними работать. Повторим основные тезисы:\n\n* **Хэш-таблица** обеспечивает временную сложность O(1). Она похожа на массив, потому что в ней тоже есть операция индексации. В качестве индекса можно использовать практически любые структуры данных — строки, дату и время, массивы.\n* Массивы, в которых большая часть элементов не определена, называются **разреженными**. Чтобы сэкономить память, программисты стараются уплотнить массив, но тогда довольно трудно предсказать размер массива.\n* Остатки от деления любого числа на n всегда находятся в диапазоне от 0 до n-1. Иногда вместо «остаток от деления на n» говорят «модуль по основанию n. В JavaScript и многих других языках программирования для вычисления модуля используют оператор `%`\n* Ситуация, когда разные элементы после преобразования индекса попадают в одну и ту же ячейку массива, называется **коллизией**. Справиться с коллизиями нетрудно. Вместо того чтобы хранить в каждой ячейке массива простое значение, мы размещаем там односвязный список. При добавлении элемента мы добавляем в список пару из года и города:\n* Для некоторых задач нужны ключи, которые очень похожи на случайные. Мы можем взять все наши числа и подвергнуть их какой-нибудь обработке — например, вычислить их сумму или произведение. Но сложение и умножение — слишком простые операции. Хороший результат в таком случае даст так называемая **полиномиальная функция**.\n* Хэш-функция перемешивает исходные данные так, чтобы получилось число, похожее на случайное. В Python для вычисления хешей можно использовать функцию `hash()`, в Java — метод `hashCode()`, в C# — метод `GetHashCode()`. В JavaScript нет готовой хеш-функции — считается, что все нужное уже есть в языке.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":4581,"name":"theory","url":"/courses/basic-algorithms/lessons/intro/theory_unit"}],"links":[],"ordered_units":[{"id":4581,"name":"theory","url":"/courses/basic-algorithms/lessons/intro/theory_unit"}],"id":2044,"slug":"intro","state":"approved","name":"Введение","course_order":100,"goal":"Знакомимся с темой курса","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"> «Плохие программисты думают о коде. Хорошие программисты думают о структурах данных и их взаимосвязях», — Линус Торвальдс, создатель Linux.\n\nАлгоритмы и структуры данных — основа Computer Science. Даже если вы не собираетесь писать алгоритмы или реализовывать структуры, их знание потребуется вам для понимания почему, как, и где использовать уже имеющиеся инструменты (библиотеки, фреймворки, базы данных). А хорошее владение этой темой открывает путь в мир сложных задач (поисковые системы, карты, компиляторы, нейронные сети, UI-фреймворки), и именно поэтому она должна входить в базовый набор знаний программиста.\n\nВ этом курсе рассмотрим следующие темы:\n\n- Алгоритмы поиска и сортировки.\n- Оценка сложности алгоритмов (сколько нужно сделать шагов и сколько памяти потребуется для решения задачи).\n- Реализация структур данных, стека, очереди, связного списка.\n- Жадные алгоритмы.\n- Динамическое программирование.\n\n## Зачем это нужно?\n\n- Получить необходимые знания перед изучением графов, деревьев, машинного обучения.\n- Для развития в качестве профессионального разработчика.\n- Для развития алгоритмического мышления.\n- Для подготовки к собеседованию.\n"},"id":222,"slug":"basic-algorithms","challenges_count":5,"name":"Основы алгоритмов и структур данных","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"Курс посвящен знакомству со структурами данных, алгоритмами поиска и сортировки. Здесь мы на практике разберем, в каких ситуациях подходит тот или иной алгоритм. Вы научитесь оценивать сложность алгоритмов с помощью нотации «О-большое» — узнавать их сложность, скорость и затраты памяти. За время курса вы напишете свою реализацию структур данных.","kind":"basic","updated_at":"2026-02-12T14:03:31.332Z","language":"javascript","duration_cache":38520,"skills":["Определять эффективность алгоритмов","Выбирать подходящую структуру данных в зависимости от ситуации","Определять NP-полные задачи и находить приближенное решение"],"keywords":["Алгоритмы сортировки","Структуры данных","Бинарный поиск","Жадные алгоритмы","Асимптотический анализ"],"lessons_count":9,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQzMDksInB1ciI6ImJsb2JfaWQifX0=--7159d348a0cee778c91291e2f50f87b02ac3dcc6/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJwbmciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--6067466c2912ca31a17eddee04b8cf2a38c6ad17/image.png"},"recommendedLandings":[{"stack":{"id":34,"slug":"algorithms","title":"Алгоритмы и структуры данных","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"finished","order":4000,"duration_in_months":2},"id":56,"slug":"algorithms","title":"Алгоритмы и структуры данных","subtitle":"Навык, который увеличит ваши шансы пройти алгоритмическое интервью в международные компании на 80%","subtitle_for_lists":"Алгоритмы для собеседований","locale":"ru","current":true,"duration_in_months_text":"2 месяца","stack_slug":"algorithms","price_text":"от 3 900 ₽","duration_text":"2 месяца","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDAyOCwicHVyIjoiYmxvYl9pZCJ9fQ==--ae9eed98663dd1201759d042a5ba7ca790866156/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programming-bro.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/basic-algorithms/lessons/hash/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Основы алгоритмов и структур данных</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Хэш</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Хэш","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"Основы алгоритмов и структур данных"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>Ранее в курсе мы изучили метод перебора и сталкивались с задачей про европейские столицы. Снова попробуем решить эту задачу, но выберем новый, более продуктивный способ.</p>
<p>В задаче про европейские столицы в условиях задачи были указаны пары — город и дата основания. Зная дату, мы можем определить город. Для решения задачи используем следующую таблицу:</p>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td>1804</td><td>Вена</td></tr><tr><td>1614</td><td>Тирана</td></tr><tr><td>1067</td><td>Минск</td></tr><tr><td>752</td><td>Ватикан</td></tr><tr><td>1872</td><td>Будапешт</td></tr><tr><td>1237</td><td>Берлин</td></tr><tr><td>1167</td><td>Копенгаген</td></tr><tr><td>1191</td><td>Дублин</td></tr><tr><td>1786</td><td>Рейкьявик</td></tr><tr><td>856</td><td>Мадрид</td></tr><tr><td>1323</td><td>Вильнюс</td></tr><tr><td>963</td><td>Люксембург</td></tr><tr><td>1275</td><td>Амстердам</td></tr><tr><td>1200</td><td>Варшава</td></tr><tr><td>1147</td><td>Москва</td></tr></tbody></table></div></div></div></div>
<p>Пары с годом и городом в таблице расположены в произвольном порядке, поэтому поиск нужной столицы требует полного перебора.</p>
<p>Временная сложность такого алгоритма составляет O(n), что довольно медленно. Бинарный поиск позволяет сократить это время до O(logn). На большой таблице из миллиона записей среднее количество сравнений для линейного поиска равно 500000, а для бинарного — всего 10.</p>
<p>Но и это не предел. Если поиск нужно выполнять с максимальной скоростью, мы можем воспользоваться еще одной структурой данных. Это <strong>хэш-таблица</strong>, которая обеспечивает временную сложность O(1).</p>
<p>Хэш-таблица похожа на массив, потому что в ней тоже есть операция индексации. В JavaScript доступ к элементу массива осуществляется по индексу, записанному в квадратных скобках. В качестве индекса можно использовать последовательные целые числа. В случае с хэш-таблицей в качестве индекса можно брать любые структуры данных — строки, дату и время, массивы.</p>
<p>В этом уроке мы разберемся, как устроены хэш-таблицы и как их реализовывать.</p>
<h2 id="heading-2-1">Разреженные массивы</h2>
<p>Простейший способ определить столицу по году основания — использовать массив:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">let capitals = []
capitals[881] = 'Вена'
capitals[1237] = 'Берлин'
capitals[1323] = 'Вильнюс'
capitals[1614] = 'Тирана'
capitals[1167] = 'Копенгаген'
capitals[963] = 'Люксембург'
capitals[1067] = 'Минск'
capitals[1191] = 'Дублин'
capitals[1275] = 'Амстердам'
capitals[752] = 'Ватикан'
capitals[1786] = 'Рейкьявик'
capitals[1200] = 'Варшава'
capitals[1872] = 'Будапешт'
capitals[856] = 'Мадрид'
capitals[1147] = 'Москва'</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>Такой массив занимает много памяти. В нем хранится всего 15 городов, но при этом размер массива — это целых 1873 элемента:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">console.log(capitals.length) // => 1873</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>Так получилось потому, что индексами в массиве могут быть только последовательные целые числа, начиная с 0. Если мы добавляем элемент с индексом 752, в массив добавляются и неопределенные элементы с индексами от 0 до 751. Длина массива получилась равной 1873, потому что:</p>
<ul>
<li>Наибольший год основания — 1872</li>
<li>Первым в массиве всегда стоит элемент с индексом 0</li>
</ul>
<p>Если мы применим массив, доступ к элементам выполнится очень быстро — за константное время O(1). Но при этом мы впустую расходуем память — в нашем примере массив заполнен всего на 0,8%.</p>
<p>Массивы, в которых большая часть элементов не определена, называются <strong>разреженными</strong>. Чтобы сэкономить память, программисты стараются уплотнить массив. Скажем, в нашем примере годы находятся достаточно далеко друг от друга — на каждые двадцать пустых элементов приходится только один элемент с данными. Мы можем уменьшить массив в двадцать раз с помощью простого трюка.</p>
<p>Возьмем числа 0, 1, 2 и 3, поделим их на 20 и получим такой результат:</p>
<ul>
<li>0 делить на 20 = 0</li>
<li>1 делить на 20 = 0.05</li>
<li>2 делить на 20 = 0.1</li>
<li>3 делить на 20 = 0.15</li>
</ul>
<p>Целая часть этих чисел будет равна 0, пока мы не доберемся до такой операции:</p>
<p>19 делить на 20 = 0.95</p>
<p>Начиная с 20, целая часть после деления будет равна 1:</p>
<ul>
<li>20 делить на 20 = 1</li>
<li>21 делить на 20 = 1.05</li>
</ul>
<p>Начиная с 40, целая часть будет равна 2.</p>
<p>Получается, что после преобразования числа схлопываются:</p>
<ul>
<li>От 0 до 19 → 0</li>
<li>От 20 до 39 → 1</li>
<li>От 40 до 59 → 2 и так далее</li>
</ul>
<p>Чтобы получать целую часть, воспользуемся стандартной функцией <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Math.floor</code>:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">let capitals = []
capitals[Math.floor(881 / 20)] = 'Вена'
capitals[Math.floor(1237 / 20)] = 'Берлин'
capitals[Math.floor(1323 / 20)] = 'Вильнюс'
capitals[Math.floor(1614 / 20)] = 'Тирана'
capitals[Math.floor(1167 / 20)] = 'Копенгаген'
capitals[Math.floor(963 / 20)] = 'Люксембург'
capitals[Math.floor(1067 / 20)] = 'Минск'
capitals[Math.floor(1191 / 20)] = 'Дублин'
capitals[Math.floor(1275 / 20)] = 'Амстердам'
capitals[Math.floor(752 / 20)] = 'Ватикан'
capitals[Math.floor(1786 / 20)] = 'Рейкьявик'
capitals[Math.floor(1200 / 20)] = 'Варшава'
capitals[Math.floor(1872 / 20)] = 'Будапешт'
capitals[Math.floor(856 / 20)] = 'Мадрид'
capitals[Math.floor(1147 / 20)] = 'Москва'</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>Чтобы узнать, какой город основан в 1191 году, мы проверяем элемент с индексом 1191/20:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">const getCity = year => capitals[Math.floor(year / 20)]
let city = getCity(1191) // Дублин</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>У такого подхода есть недостаток — довольно трудно предсказать размер массива. Когда речь идет о годах основания, мы можем проанализировать данные и прикинуть, что каждые двадцать лет можно схлопнуть в один элемент.</p>
<p>Для чисел 0 до 1999 нам достаточно массива из ста элементов. Последний элемент такого уплотненного массива имеет индекс 99, а <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Math.floor(1999/20)</code> как раз равно 99.</p>
<p>Но в другой задаче это может не сработать — например, если требуется хранить в массиве индексы, равные миллиону или миллиарду. Коэффициент 20 слишком мал для таких чисел, потому что даже уплотненный массив все равно будет слишком большим и слишком пустым.</p>
<p>Но мы не можем анализировать каждую возникающую задачу. Нам бы хотелось иметь универсальный способ, подходящий для разных случаев и позволяющий контролировать размер массива. Поэтому мы рассмотрим еще один способ.</p>
<h2 id="heading-2-2">Модульная арифметика</h2>
<p>Предположим, мы фиксируем размер массива. Пусть у нас будет массив из ста элементов:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">let capitals = new Array(100)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>Мы хотим сохранить в него несколько элементов, но их индексы могут быть произвольными целыми числами. Чтобы преобразовать индекс в число от 0 до 99, мы должны вычислить остаток от деления индекса на 100.</p>
<p>Общее правило такое:</p>
<p>Остатки от деления любого числа на n всегда находятся в диапазоне от 0 до n-1</p>
<p>Это правило поможет нам работать с массивами любого размера.</p>
<p>Иногда вместо «остаток от деления на n» говорят «модуль по основанию n». В JavaScript и многих других языках программирования для вычисления модуля используют оператор <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">%</code>:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">console.log(1999 % 20) // => 19</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>Решим нашу задачу со столицами с помощью модуля:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">let capitals = new Array(100)
capitals[881 % 100] = 'Вена'
capitals[1237 % 100] = 'Берлин'
capitals[1323 % 100] = 'Вильнюс'
capitals[1614 % 100] = 'Тирана'
capitals[1167 % 100] = 'Копенгаген'
capitals[963 % 100] = 'Люксембург'
capitals[1067 % 100] = 'Минск'
capitals[1191 % 100] = 'Дублин'
capitals[1275 % 100] = 'Амстердам'
capitals[752 % 100] = 'Ватикан'
capitals[1786 % 100] = 'Рейкьявик'
capitals[1200 % 100] = 'Варшава'
capitals[1872 % 100] = 'Будапешт'
capitals[856 % 100] = 'Мадрид'
capitals[1147 % 100] = 'Москва'
const getCity = year => capitals[year % 100]
let city = getCity(1167) // Копенгаген</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>Теперь мы точно знаем, что наш массив не вырастет до больших размеров, как это может случиться с операцией деления. Но мы можем обнаружить другую проблему.</p>
<p>Попробуем узнать, какой город основан в 1167 году. Если верить нашей таблице, это Копенгаген. Но программа говорит совсем другое:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">console.log(getCity(1167)) // => Минск</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>Минск основан в 1067 году, а Копенгаген — в 1167. Годы отличаются, но у них один и тот же остаток от деления на 100, а именно 67.</p>
<p>Ситуация, когда разные элементы после преобразования индекса попадают в одну и ту же ячейку массива, называется <strong>коллизией</strong>. На самом деле это не ошибка, а вполне вероятная ситуация, хотя и не очень частая.</p>
<h2 id="heading-2-3">Коллизии</h2>
<p>Справиться с коллизиями нетрудно. Вместо того чтобы хранить в каждой ячейке массива простое значение, мы размещаем там односвязный список. При добавлении элемента мы добавляем в список пару из года и города:</p>
<ul>
<li>Год — это индекс, по которому мы сохраняем и извлекаем данные. Обычно его называют <strong>ключом</strong></li>
<li>Название — это значение</li>
</ul>
<p>Попробуем реализовать в коде:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">import LinkedList from './LinkedList.js'
let capitals = new Array(100)
const setCapital = (year, city) => {
let index = year % capitals.length
if (typeof (capitals[index]) === 'undefined') {
capitals[index] = new LinkedList()
}
capitals[index].add({ key: year, value: city })
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>Мы уже разработали структуру данных <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">LinkedList</code>, поэтому мы можем просто импортировать ее.</p>
<p>Размер массива capitals всегда будет равен ста элементам.</p>
<p>По умолчанию все ячейки массива пусты, и связный список создается только при первой записи в ячейку. В каждом списке может храниться несколько элементов. Чтобы различать их, мы сохраняем не просто значение, а пару из ключа (<code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">key</code>) и значения (<code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">value</code>).</p>
<p>При поиске точно также вычисляем индекс:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">const getCapital = (year) => {
let index = year % capitals.length
if (typeof (capitals[index]) === 'undefined') {
return undefined
}
for (let pair of capitals[index]) {
if (pair.key === year) {
return pair.value
}
}
return undefined
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>Если в ячейке ничего нет, значит, мы ничего и не записывали.</p>
<p>Но если в ячейке что-то есть, то это список, по которому мы пробегаем и пытаемся найти пару с заданным ключом (<em>key</em>).</p>
<p>Обнаружив пару, возвращаем ее значение (<em>value</em>).</p>
<p>Алгоритмическая сложность этих операций зависит от того, насколько часто элементы попадают в одну и ту же ячейку — в один и тот же связный список.</p>
<p>Если у нас будет случайный набор из ста чисел, он достаточно равномерно распределится по массиву: в большинстве ячеек будет храниться одно число, в некоторых — два, и в некоторых — ни одного. Алгоритмическая сложность записи и чтения в таком случае будет близка к O(1) — это один из самых быстрых возможных алгоритмов.</p>
<p>Если числа не будут случайными, может получиться так, что в одном из списков их окажется слишком много — тогда скорость вставки и проверки значительно снизится. Подробнее мы обсудим эту ситуацию позже, а пока запомним, что предложенный нами алгоритм любит равномерные случайные ключи.</p>
<h2 id="heading-2-4">Хэш-функция</h2>
<p>Теперь попробуем решить обратную задачу — определять год основания столицы, зная ее название. Заглянем в таблицу в начале урока и вспомним, что год основания Мадрида — 856, а Москвы — 1147.</p>
<p>Наша структура данных может хранить значения с любым целочисленным ключом, не только с годом. Но «Мадрид» и «Москва» — это не числа, а строки.</p>
<p>При этом компьютеры умеют работать только с числами, и любые объекты в них хранятся как последовательности чисел. Каждая буква хранится как одно число, которое называют <strong>кодом символа</strong>:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">const capital = 'Мадрид'
for (let i = 0; i < capital.length; i++) {
console.log(capital.charCodeAt(i))
}
// => 1052
// => 1072
// => 1076
// => 1088
// => 1080
// => 1076</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>Слово «Мадрид» хранится как последовательность чисел 1052, 1072, 1076, 1088, 1080 и 1076. В простейшем случае мы могли бы использовать для вычисления индекса код первой буквы — в нашем примере 1052. Но это значит все города на М (Мадрид, Москва и Минск) попадут в один и тот же список. В таком случае скорость поиска будет не очень высокой.</p>
<p>Чтобы этого избежать, нам нужны ключи, которые очень похожи на случайные. Мы можем взять все наши числа и подвергнуть их какой-нибудь обработке — например, вычислить их сумму или произведение.</p>
<p>Однако сложение и умножение — слишком простые операции. Все слова, состоящие из одних и тех же букв (ведро, вроде и древо), будут иметь один и тот же ключ — сумму или произведение кодов символов.</p>
<p>Хороший результат в таком случае даст так называемая <strong>полиномиальная функция</strong>.</p>
<p>Попробуем применить ее к нашей задаче с городами. Пусть коды символов строки длины n будут равны c0, c1, ..., cn-1. Возьмем число k, которое будет больше кода любого символа — скажем, k = 65537.</p>
<p>У нас получилось такое выражение:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">c0 + c1 * k + c2 * k^2 + c3 * k^3 + ... + cn-1 * k^(n-1)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Число k достаточно большое. На каждом шаге нам приходится возводить его в возрастающую степень, поэтому выражение может оказаться просто огромным. Для слова "Мадрид" мы получим 1300923352423037265600321836.</p>
<p>На практике для ускорения вычислений и для удобства используют не все выражение, а только остаток от его деления на другое число — m:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">(c0 + c1 * k + c2 * k^2 + c3 * k^3 + ... + cn-1 * k^(n-1)) mod m</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Обычно модуль m делают равным большому простому числу или большому круглому числу — например, миллиону или миллиарду. Часто в качестве модуля выбирают степень числа 2 — к примеру, 2^32 или 2^64.</p>
<p>Чтобы промежуточные числа не становились слишком большими, все сложения и умножения выполняют по модулю m. Другими словами, остаток от деления вычисляется сразу после сложения или умножения.</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">const hash = (s) => {
const k = 65537
const m = 2 ** 20
let result = 0
let k_i = 1
for (let i = 0; i < s.length; i++) {
result = (result + k_i * s.charCodeAt(i)) % m
k_i = (k_i * k) % m
}
return result
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>Здесь в качестве модуля мы выбрали <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">2**20</code>, то есть 2 в степени 20 (2^20). Это число равно 1048576 и часто используется программистами, потому что очень близко к миллиону. Слово мегабайт традиционно означает именно 1048576 байт, а не миллион, как можно было бы ожидать.</p>
<p>Сейчас комитет по стандартизации мер и весов рекомендует термины «мебибайт» для 1048576 байтов и «мегабайт» для 1000000 байтов, но большинство программистов все еще не используют эту новую рекомендацию.</p>
<p>Переменная <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">k_i</code> на каждом шаге цикла умножается на <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">k</code>. Она принимает значения 1, затем <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">k</code>, затем <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">k</code> умноженное на 2, и так далее. В переменной <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">result</code> накапливается результат вычислений.</p>
<p>Функция называется <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">hash</code>, и это неслучайно. В переводе с английского слово hash означает «мелко крошить, перемешивать».</p>
<p>Функция перемешивает исходные данные так, чтобы получилось число, похожее на случайное:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">hash('Мадрид') // 792876
hash('Москва') // 399671
hash('Минск') // 857356</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>Эту функцию так и называют — <strong>хеш-функция</strong>. В подавляющем большинстве случаев нам не приходится писать хеш-функции, потому что они уже разработаны авторами фреймворков и стандартных библиотек. Тем не менее полезно понимать принцип работы хешей.</p>
<p>В Python для вычисления хешей можно использовать функцию <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">hash()</code>, в Java — метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">hashCode()</code>, в C# — метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">GetHashCode()</code>. В JavaScript нет готовой хеш-функции — считается, что все нужное уже есть в языке.</p>
<p>Название функции дало название всей структуре данных. Обычно ее называют хеш-таблицей (hash table или hashmap). Очень часто это название сокращают просто до хеша. Другие распространенные названия — словарь (dictionary) и ассоциативный массив (associative array).</p>
<h2 id="heading-2-5">Худший случай</h2>
<p>Выше мы говорили, что обычно вставка и поиск в хеш-таблице выполняются за время O(1). Но если хеш-функция выбрана неудачно, все значения могут оказаться в одном связном списке во внутреннем массиве.</p>
<p>Тогда время поиска составит O(n), что обычно считается медленным. К счастью, эта ситуация практически невозможна. Тем не менее, если вы соберетесь писать собственную хеш-таблицу, посвятите время выбору хорошей хеш-функции.</p>
<p>Есть еще одна возможная сложность. В массиве, который мы использовали в примерах выше, мы резервировали сто элементов, потому что рассчитывали, что данные будут распределены равномерно. Однако если мы поместим в такой массив 10000 элементов, то даже при идеальном распределении в каждом связном списке окажется сто элементов. Это значит, что поиск и вставка все-таки будут медленными.</p>
<p>Чтобы справиться с этой проблемой, надо расширять хеш-таблицу — увеличивать внутренний массив по мере того, как в нее добавляются элементы. Конечно, это накладно делать при каждой вставке.</p>
<p>Обычно подсчитывают <strong>коэффициент заполнения хеш-таблицы</strong> (load factor) — это частное от деления количества вставленных элементов на размер внутреннего массива. Если он достигает заранее выбранного порога в 60–80%, внутренний массив увеличивают вдвое, а индексы всех элементов пересчитывают.</p>
<p>Точно также, если коэффициент заполнения становится слишком маленьким, 20–25%, массив уменьшают вдвое. У массива есть нижняя граница в 128 или 256 элементов, чтобы не перестраивать его слишком часто, пока он маленький.</p>
<h2 id="heading-2-6">Хэш-таблицы и функция вставки</h2>
<p>Перепишем функцию вставки, опираясь на все новые знания. Вместе с этим реализуем хеш-таблицу в виде новой структуры данных, спрятав внутри класса массив с данными:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">import LinkedList from './LinkedList.js'
class Hash {
table = []
count = 0
hash(s) {
const k = 65537
const m = 2 ** 20
let result = 0, power = 1
for (let i = 0; i < s.length; i++) {
result = (result + power * s.charCodeAt(i)) % m
power = (power * k) % m
}
return result
}
calculateIndex(table, key) {
return this.hash(String(key)) % table.length
}
rebuildTableIfNeed() {
if (this.table.length == 0) {
this.table.length = 128
}
else {
const loadFactor = this.count / this.table.length
if (loadFactor >= 0.8) {
let newTable = new Array(this.table.length * 2)
for (let list in this.table) {
for (let pair of list) {
const newIndex = this.calculateIndex(newTable, pair.key)
if (typeof (newTable[newIndex]) === 'undefined') {
newTable[newIndex] = new LinkedList()
}
newTable[newIndex].add(pair)
}
}
this.table = newTable
}
}
}
set(key, value) {
this.rebuildTableIfNeed()
const index = this.calculateIndex(this.table, key)
if (typeof (this.table[index]) === 'undefined') {
this.table[index] = new LinkedList()
}
this.table[index].add({ key: key, value: value })
this.count += 1
}
get(key) {
const index = this.calculateIndex(this.table, key)
if (typeof (this.table[index].fore()) === 'undefined') {
return undefined
}
for (let pair of this.table[index]) {
if (pair.key === key) {
return pair.value
}
}
return undefined
}
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>Весь код из этого примера мы уже обсуждали, кроме удвоения массива. Создавая новый массив, в два раза больше текущего, мы должны скопировать все элементы, пересчитав их индексы.</p>
<p>Именно за это отвечает вложенный цикл:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>javascript</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>java</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>php</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code">let newTable = new Array(this.table.length * 2)
for (let list in this.table) {
for (let pair of list) {
const newIndex = this.calculateIndex(newTable, pair.key)
if (typeof (newTable[newIndex]) === 'undefined') {
newTable[newIndex] = new LinkedList()
}
newTable[newIndex].add(pair)
}
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>В самом начале внутренний массив имеет размер 0, чтобы не тратить память, если в хеш-таблице нет ни одного элемента. При вставке первого элемента мы увеличиваем внутренний массив сразу до 128 элементов.
Остальной код мы уже рассмотрели выше в этом уроке.</p>
<h2 id="heading-2-7">Выводы</h2>
<p>В этом уроке мы изучили хэш-таблицы и научились с ними работать. Повторим основные тезисы:</p>
<ul>
<li><strong>Хэш-таблица</strong> обеспечивает временную сложность O(1). Она похожа на массив, потому что в ней тоже есть операция индексации. В качестве индекса можно использовать практически любые структуры данных — строки, дату и время, массивы.</li>
<li>Массивы, в которых большая часть элементов не определена, называются <strong>разреженными</strong>. Чтобы сэкономить память, программисты стараются уплотнить массив, но тогда довольно трудно предсказать размер массива.</li>
<li>Остатки от деления любого числа на n всегда находятся в диапазоне от 0 до n-1. Иногда вместо «остаток от деления на n» говорят «модуль по основанию n. В JavaScript и многих других языках программирования для вычисления модуля используют оператор <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">%</code></li>
<li>Ситуация, когда разные элементы после преобразования индекса попадают в одну и ту же ячейку массива, называется <strong>коллизией</strong>. Справиться с коллизиями нетрудно. Вместо того чтобы хранить в каждой ячейке массива простое значение, мы размещаем там односвязный список. При добавлении элемента мы добавляем в список пару из года и города:</li>
<li>Для некоторых задач нужны ключи, которые очень похожи на случайные. Мы можем взять все наши числа и подвергнуть их какой-нибудь обработке — например, вычислить их сумму или произведение. Но сложение и умножение — слишком простые операции. Хороший результат в таком случае даст так называемая <strong>полиномиальная функция</strong>.</li>
<li>Хэш-функция перемешивает исходные данные так, чтобы получилось число, похожее на случайное. В Python для вычисления хешей можно использовать функцию <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">hash()</code>, в Java — метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">hashCode()</code>, в C# — метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">GetHashCode()</code>. В JavaScript нет готовой хеш-функции — считается, что все нужное уже есть в языке.</li>
</ul></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/algorithms?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">2 месяца</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Алгоритмы и структуры данных</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Алгоритмы для собеседований</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDAyOCwicHVyIjoiYmxvYl9pZCJ9fQ==--ae9eed98663dd1201759d042a5ba7ca790866156/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programming-bro.png" alt="Алгоритмы и структуры данных" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 900 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/basic-algorithms/lessons/hash/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 9</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/basic-algorithms/lessons/hash/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-Bukl1lYy.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>