mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-08 21:46:06 +00:00
Update ru translation
This commit is contained in:
@@ -1,60 +1,60 @@
|
||||
# Хеш-алгоритмы
|
||||
# Алгоритмы хеширования
|
||||
|
||||
В предыдущих двух разделах были рассмотрены принципы работы хеш-таблиц и методы обработки хеш-коллизий. Однако ни открытая адресация, ни цепная адресация **не могут уменьшить возникновение хеш-коллизий, они лишь обеспечивают нормальную работу хеш-таблицы при возникновении коллизий**.
|
||||
В предыдущих разделах были рассмотрены принципы работы хеш-таблиц и методы обработки хеш-конфликтов. Однако ни открытая, ни цепная адресация не могут уменьшить вероятность возникновения хеш-конфликтов, **они лишь обеспечивают корректную работу хеш-таблицы при их возникновении**.
|
||||
|
||||
Если хеш-коллизии возникают слишком часто, производительность хеш-таблицы резко ухудшается. Как показано на рисунке ниже, для хеш-таблицы с цепной адресацией в идеальном случае пары ключ-значение равномерно распределены по всем корзинам, достигая наилучшей эффективности поиска; в худшем случае все пары ключ-значение хранятся в одной корзине, и временная сложность деградирует до $O(n)$.
|
||||
Если хеш-конфликты происходят слишком часто, производительность хеш-таблицы резко снижается. Как показано на рис. 6.8, для хеш-таблицы с цепной адресацией в идеальном случае пары ключ--значение равномерно распределены по всем корзинам, что обеспечивает наилучшую эффективность поиска. В худшем случае все пары ключ--значение хранятся в одной корзине, и временная сложность повышается до $O(n)$.
|
||||
|
||||

|
||||

|
||||
|
||||
**Распределение пар ключ-значение определяется хеш-функцией**. Вспомним этапы вычисления хеш-функции: сначала вычисляется хеш-значение, затем берется остаток от деления на длину массива:
|
||||
**Распределение пар ключ--значение определяется хеш-функцией**. Вспомним этапы вычисления хеш-функции: сначала вычисляется хеш-значение, затем берется остаток от деления на длину массива.
|
||||
|
||||
```shell
|
||||
index = hash(key) % capacity
|
||||
```
|
||||
|
||||
Рассматривая приведенную выше формулу, когда емкость хеш-таблицы `capacity` фиксирована, **хеш-алгоритм `hash()` определяет выходное значение**, а следовательно, и распределение пар ключ-значение в хеш-таблице.
|
||||
Из этого выражения видно, что при фиксированной емкости хеш-таблицы `capacity` **алгоритм хеширования** `hash()` **определяет выходное значение**, которое, в свою очередь, определяет распределение пар ключ--значение в хеш-таблице.
|
||||
|
||||
Это означает, что для снижения вероятности возникновения хеш-коллизий следует сосредоточить внимание на разработке хеш-алгоритма `hash()`.
|
||||
Это означает, что для снижения вероятности возникновения хеш-конфликтов следует сосредоточиться на разработке алгоритма хеширования `hash()`.
|
||||
|
||||
## Цели хеш-алгоритма
|
||||
## Цели алгоритма хеширования
|
||||
|
||||
Для реализации структуры данных хеш-таблицы, которая является "быстрой и стабильной", хеш-алгоритм должен обладать следующими характеристиками.
|
||||
Для создания быстрой и надежной структуры данных хеш-таблицы алгоритм хеширования должен обладать следующими характеристиками.
|
||||
|
||||
- **Детерминированность**: для одного и того же входа хеш-алгоритм всегда должен выдавать один и тот же выход. Только так можно обеспечить надежность хеш-таблицы.
|
||||
- **Высокая эффективность**: процесс вычисления хеш-значения должен быть достаточно быстрым. Чем меньше вычислительные затраты, тем выше практичность хеш-таблицы.
|
||||
- **Равномерное распределение**: хеш-алгоритм должен обеспечивать равномерное распределение пар ключ-значение в хеш-таблице. Чем равномернее распределение, тем ниже вероятность хеш-коллизий.
|
||||
- **Детерминированность**: для одинакового ввода алгоритм хеширования должен всегда давать одинаковый вывод. Это необходимо для обеспечения надежности работы хеш-таблицы.
|
||||
- **Высокая эффективность**: процесс вычисления хеш-значения должен быть достаточно быстрым. Чем меньше вычислительные затраты, тем выше практическая ценность хеш-таблицы.
|
||||
- **Равномерное распределение**: алгоритм хеширования должен обеспечивать равномерное распределение пар ключ--значение в хеш-таблице. Чем равномернее распределение, тем ниже вероятность хеш-конфликтов.
|
||||
|
||||
На самом деле хеш-алгоритмы помимо реализации хеш-таблиц широко применяются и в других областях.
|
||||
На практике алгоритмы хеширования применяются не только для реализации хеш-таблиц, но и в других областях.
|
||||
|
||||
- **Хранение паролей**: для защиты паролей пользователей система обычно не хранит пароли в открытом виде, а хранит их хеш-значения. Когда пользователь вводит пароль, система вычисляет хеш-значение введенного пароля и сравнивает его с сохраненным хеш-значением. Если они совпадают, пароль считается правильным.
|
||||
- **Проверка целостности данных**: отправитель данных может вычислить хеш-значение данных и отправить его вместе с данными; получатель может заново вычислить хеш-значение полученных данных и сравнить его с полученным хеш-значением. Если они совпадают, данные считаются целостными.
|
||||
- **Хранение паролей**: для защиты паролей пользователей система обычно не хранит пароли в открытом виде, а сохраняет их хеш-значения. Когда пользователь вводит пароль, система вычисляет его хеш-значение и сравнивает с сохраненным. Если они совпадают, пароль считается правильным.
|
||||
- **Проверка целостности данных**: отправитель данных может вычислить хеш-значение данных и отправить его вместе с данными. Получатель может заново вычислить хеш-значение полученных данных и сравнить его с полученным. Если они совпадают, данные считаются неизмененными.
|
||||
|
||||
Для криптографических приложений, чтобы предотвратить обратное восстановление исходного пароля из хеш-значения и другие виды обратной инженерии, хеш-алгоритм должен обладать более высоким уровнем безопасности.
|
||||
В криптографических приложениях для предотвращения обратного вычисления исходного пароля из хеш-значения и других видов обратной инженерии алгоритм хеширования должен обладать дополнительными характеристиками.
|
||||
|
||||
- **Односторонность**: невозможно восстановить какую-либо информацию о входных данных из хеш-значения.
|
||||
- **Устойчивость к коллизиям**: должно быть крайне сложно найти два разных входа, дающих одинаковое хеш-значение.
|
||||
- **Эффект лавины**: небольшое изменение входа должно приводить к значительному и непредсказуемому изменению выхода.
|
||||
- **Необратимость**: невозможность извлечь какую-либо информацию о входных данных из хеш-значения.
|
||||
- **Устойчивость к коллизиям**: должно быть крайне сложно найти два различных входа, дающих одинаковое хеш-значение.
|
||||
- **Эффект лавины**: небольшие изменения на входе должны приводить к значительным и непредсказуемым изменениям на выходе.
|
||||
|
||||
Обратите внимание, что **"равномерное распределение" и "устойчивость к коллизиям" являются двумя независимыми понятиями**, удовлетворение равномерному распределению не обязательно означает устойчивость к коллизиям. Например, при случайном входе `key` хеш-функция `key % 100` может давать равномерно распределенный выход. Однако этот хеш-алгоритм слишком прост, все `key` с одинаковыми последними двумя цифрами дают одинаковый выход, поэтому мы можем легко восстановить подходящий `key` из хеш-значения и таким образом взломать пароль.
|
||||
Следует отметить, что **«равномерное распределение» и «устойчивость к коллизиям»** -- это два независимых понятия, и выполнение одного из них не обязательно означает выполнение другого. Например, хеш-функция `key % 100` при случайном вводе значения `key` может давать равномерное распределение. Однако этот алгоритм хеширования слишком прост, и все ключи с одинаковыми последними двумя цифрами будут иметь одинаковый вывод, что позволяет легко извлечь пригодные ключи из хеш-значения и взломать пароль.
|
||||
|
||||
## Разработка хеш-алгоритма
|
||||
## Разработка алгоритма хеширования
|
||||
|
||||
Разработка хеш-алгоритма — это сложная задача, требующая учета многих факторов. Однако для некоторых нетребовательных сценариев мы также можем разработать простые хеш-алгоритмы.
|
||||
Создание хеш-алгоритмов представляет собой сложную задачу, требующую учета множества факторов. Однако для некоторых несложных сценариев можно разработать простые хеш-алгоритмы.
|
||||
|
||||
- **Аддитивное хеширование**: складываются ASCII-коды каждого символа входа, полученная сумма используется как хеш-значение.
|
||||
- **Мультипликативное хеширование**: используя независимость умножения, на каждом шаге умножается на константу, ASCII-коды различных символов накапливаются в хеш-значении.
|
||||
- **XOR-хеширование**: каждый элемент входных данных накапливается в хеш-значении через операцию XOR.
|
||||
- **Ротационное хеширование**: ASCII-код каждого символа накапливается в хеш-значении, перед каждым накоплением выполняется операция вращения хеш-значения.
|
||||
- **Аддитивный хеш**: складываются ASCII-коды каждого символа входных данных, полученная сумма используется в качестве хеш-значения.
|
||||
- **Мультипликативный хеш**: используя свойство некоррелированности умножения, на каждом шаге значение хеша умножается на константу, и в результат добавляется ASCII-код очередного символа.
|
||||
- **Хеш с использованием операции XOR**: каждый элемент входных данных накапливается в хеш-значении с помощью операции XOR.
|
||||
- **Ротационный хеш**: ASCII-коды каждого символа накапливаются в хеш-значении, при этом перед каждым накоплением выполняется операция ротации хеш-значения.
|
||||
|
||||
```src
|
||||
[file]{simple_hash}-[class]{}-[func]{rot_hash}
|
||||
```
|
||||
|
||||
Можно заметить, что последним шагом каждого хеш-алгоритма является взятие остатка от деления на большое простое число $1000000007$, чтобы обеспечить нахождение хеш-значения в подходящем диапазоне. Стоит задуматься, почему важно брать остаток от деления именно на простое число, или каковы недостатки взятия остатка от деления на составное число? Это интересный вопрос.
|
||||
Можно заметить, что последним шагом в каждом из хеш-алгоритмов является взятие остатка от деления на большое простое число $1000000007$, чтобы гарантировать, что хеш-значение находится в допустимом диапазоне. Интересно, почему акцент делается на взятии остатка от деления именно на простое число, и какие недостатки могут быть при делении на составное число?
|
||||
|
||||
Сначала дадим вывод: **использование большого простого числа в качестве модуля может максимально обеспечить равномерное распределение хеш-значений**. Поскольку простое число не имеет общих делителей с другими числами, это может уменьшить периодические паттерны, возникающие из-за операции взятия остатка, тем самым избегая хеш-коллизий.
|
||||
Ответ: **использование большого простого числа в качестве модуля позволяет обеспечить максимально равномерное распределение хеш-значений**. Поскольку простое число не имеет общих делителей с другими числами, это позволяет уменьшить периодические закономерности, возникающие из-за операции взятия остатка, и избежать хеш-конфликтов.
|
||||
|
||||
Приведем пример: предположим, мы выбираем составное число $9$ в качестве модуля, оно делится на $3$, тогда все `key`, делящиеся на $3$, будут отображаться в три хеш-значения: $0$, $3$, $6$.
|
||||
Например, если выбрать в качестве модуля составное число $9$, которое делится на $3$, то все ключи, делящиеся на $3$, будут отображаться в хеш-значения $0$, $3$ и $6$:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
@@ -64,7 +64,7 @@ $$
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
Если входные `key` как раз удовлетворяют такому арифметическому распределению данных, то хеш-значения будут группироваться, усиливая хеш-коллизии. Теперь предположим, что `modulus` заменяется на простое число $13$. Поскольку между `key` и `modulus` нет общих делителей, равномерность выходных хеш-значений значительно улучшится.
|
||||
Если входные ключи имеют такую арифметическую прогрессию, то хеш-значения будут сгруппированы, что умножит хеш-конфликты. Теперь если заменить `modulus` на простое число $13$, то, поскольку между ключами и модулем нет общих делителей, равномерность распределения хеш-значений значительно улучшится:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
@@ -74,20 +74,337 @@ $$
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
Следует отметить, что если можно гарантировать, что `key` распределены случайно и равномерно, то выбор простого или составного числа в качестве модуля не имеет значения, оба могут выдавать равномерно распределенные хеш-значения. Однако когда распределение `key` имеет определенную периодичность, взятие остатка от составного числа с большей вероятностью приводит к группировке.
|
||||
Следует отметить, что если ключи распределены случайно и равномерно, то выбор простого или составного числа в качестве модуля не имеет значения -- оба варианта обеспечат равномерное распределение хеш-значений. Однако при наличии периодичности в распределении ключей использование составного числа в качестве модуля может привести к кластеризации.
|
||||
|
||||
В целом мы обычно выбираем простое число в качестве модуля, причем это простое число должно быть достаточно большим, чтобы максимально устранить периодические паттерны и повысить надежность хеш-алгоритма.
|
||||
В общем случае выбирается простое число в качестве модуля, и это простое число должно быть достаточно большим, чтобы максимально устранить периодические закономерности и повысить устойчивость хеш-алгоритма.
|
||||
|
||||
## Распространенные хеш-алгоритмы
|
||||
|
||||
Нетрудно заметить, что представленные выше простые хеш-алгоритмы довольно "хрупкие" и далеки от достижения целей разработки хеш-алгоритмов. Например, поскольку сложение и XOR удовлетворяют коммутативному закону, аддитивное хеширование и XOR-хеширование не могут различать строки с одинаковым содержимым, но разным порядком, что может усилить хеш-коллизии и вызвать некоторые проблемы безопасности.
|
||||
Нетрудно заметить, что описанные выше простые хеш-алгоритмы довольно хрупкие и далеки от достижения целей создания хеш-алгоритмов. Например, сложение и операция XOR удовлетворяют коммутативному закону, поэтому соответствующие хеш-алгоритмы не различают строки с одинаковым содержанием, но разным порядком символов, что может усилить хеш-конфликты и вызвать некоторые проблемы с безопасностью.
|
||||
|
||||
На практике обычно используются стандартные хеш-алгоритмы, такие как MD5, SHA-1, SHA-2 и SHA-3. Они могут отображать входные данные произвольной длины в хеш-значения фиксированной длины.
|
||||
|
||||
За последнее столетие хеш-алгоритмы находятся в процессе постоянного совершенствования и оптимизации. Часть исследователей стремится повысить производительность хеш-алгоритмов, другая часть исследователей и хакеров пытается найти проблемы безопасности хеш-алгоритмов. В таблице ниже представлены распространенные хеш-алгоритмы, используемые на практике.
|
||||
На протяжении почти ста лет хеш-алгоритмы постоянно обновляются и оптимизируются. Одни исследователи стремятся повысить производительность, другие исследователи и хакеры сосредоточены на поиске проблем с безопасностью. В табл. 6.2 представлены распространенные хеш-алгоритмы, используемые в реальных приложениях.
|
||||
|
||||
- MD5 и SHA-1 неоднократно подвергались успешным атакам, поэтому они отвергнуты различными приложениями безопасности.
|
||||
- SHA-256 из серии SHA-2 является одним из самых безопасных хеш-алгоритмов, для него до сих пор не было успешных атак, поэтому он часто используется в различных приложениях и протоколах безопасности.
|
||||
- SHA-3 по сравнению с SHA-2 имеет меньшие затраты на реализацию и более высокую вычислительную эффективность, но в настоящее время его охват использования не так широк, как у серии SHA-2.
|
||||
- В MD5 и SHA-1 были обнаружены многочисленные уязвимости, поэтому они не используются в сценариях, в которых требуется высокий уровень безопасности.
|
||||
- SHA-256 из серии SHA-2 является одним из самых безопасных хеш-алгоритмов, до сих пор не было обнаружено ни одной уязвимости, поэтому он часто используется в различных приложениях и протоколах безопасности.
|
||||
- SHA-3 имеет меньшие затраты на реализацию и более высокую вычислительную эффективность по сравнению с SHA-2, но в настоящее время его использование не так широко распространено, как серии SHA-2.
|
||||
|
||||
<p align="center"> Таблица <id
|
||||
<p align="center"> Таблица <id> Распространенные хеш-алгоритмы </p>
|
||||
|
||||
| | MD5 | SHA-1 | SHA-2 | SHA-3 |
|
||||
| -------- | ------------------------------ | ---------------- | ---------------------------- | ------------------- |
|
||||
| Год появления | 1992 | 1995 | 2002 | 2008 |
|
||||
| Длина вывода | 128 бит | 160 бит | 256/512 бит | 224/256/384/512 бит |
|
||||
| Хеш-конфликты | Много | Много | Мало | Мало |
|
||||
| Уровень безопасности | Низкий, есть известные уязвимости | Низкий, есть известные уязвимости | Высокий | Высокий |
|
||||
| Применение | Устарел, но еще используется для проверки целостности данных | Устарел | Проверка транзакций в криптовалюте, цифровые подписи и т. д. | Может использоваться в качестве замены SHA-2 |
|
||||
|
||||
## Хеш-значения для структур данных
|
||||
|
||||
Ключи в хеш-таблице могут быть представлены в виде целых чисел, дробей или строк. Языки программирования обычно предоставляют встроенные хеш-алгоритмы для своих типов данных, чтобы вычислять индексы корзин в хеш-таблице. Например, в Python можно вызвать функцию `hash()` для вычисления хеш-значений для различных типов данных.
|
||||
|
||||
- Хеш-значение целых чисел и булевых величин совпадает с их значением.
|
||||
- Хеш-значение дробных чисел и строк вычисляется по более сложному алгоритму, заинтересованные читатели могут изучить его самостоятельно.
|
||||
- Хеш-значение кортежа получается путем хеширования каждого элемента и объединения этих хеш-значений в одно.
|
||||
- Хеш-значение объекта генерируется на основе его адреса в памяти. Путем переопределения метода хеширования объекта можно реализовать генерацию хеша на основе его содержимого.
|
||||
|
||||
!!! tip
|
||||
|
||||
Обратите внимание, что в разных языках программирования встроенные функции вычисления хеш-значений определяются и реализуются по-разному.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title="built_in_hash.py"
|
||||
num = 3
|
||||
hash_num = hash(num)
|
||||
# Хеш-значение целого числа 3 равно 3
|
||||
|
||||
bol = True
|
||||
hash_bol = hash(bol)
|
||||
# Хеш-значение булевой величины True равно 1
|
||||
|
||||
dec = 3.14159
|
||||
hash_dec = hash(dec)
|
||||
# Хеш-значение дробного числа 3.14159 равно 326484311674566659
|
||||
|
||||
str = "Hello 算法"
|
||||
hash_str = hash(str)
|
||||
# Хеш-значение строки "Hello 算法" равно 4617003410720528961
|
||||
|
||||
tup = (12836, "小哈")
|
||||
hash_tup = hash(tup)
|
||||
# Хеш-значение кортежа (12836, '小哈') равно 1029005403108185979
|
||||
|
||||
obj = ListNode(0)
|
||||
hash_obj = hash(obj)
|
||||
# Хеш-значение объекта <ListNode object at 0x1058fd810> равно 274267521
|
||||
```
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title="built_in_hash.cpp"
|
||||
int num = 3;
|
||||
size_t hashNum = hash<int>()(num);
|
||||
// Хеш-значение целого числа 3 равно 3
|
||||
|
||||
bool bol = true;
|
||||
size_t hashBol = hash<bool>()(bol);
|
||||
// Хеш-значение булевой величины 1 равно 1
|
||||
|
||||
double dec = 3.14159;
|
||||
size_t hashDec = hash<double>()(dec);
|
||||
// Хеш-значение дробного числа 3.14159 равно 4614256650576692846
|
||||
|
||||
string str = "Hello 算法";
|
||||
size_t hashStr = hash<string>()(str);
|
||||
// Хеш-значение строки "Hello 算法" равно 15466937326284535026
|
||||
|
||||
// В C++ встроенная функция std:hash() предоставляет только вычисление хеш-значений базовых типов данных
|
||||
// Для массивов и объектов нужно реализовывать вычисление хеш-значений самостоятельно
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title="built_in_hash.java"
|
||||
int num = 3;
|
||||
int hashNum = Integer.hashCode(num);
|
||||
// Хеш-значение целого числа 3 равно 3
|
||||
|
||||
boolean bol = true;
|
||||
int hashBol = Boolean.hashCode(bol);
|
||||
// Хеш-значение булевой величины true равно 1231
|
||||
|
||||
double dec = 3.14159;
|
||||
int hashDec = Double.hashCode(dec);
|
||||
// Хеш-значение дробного числа 3.14159 равно -1340954729
|
||||
|
||||
String str = "Hello 算法";
|
||||
int hashStr = str.hashCode();
|
||||
// Хеш-значение строки "Hello 算法" равно -727081396
|
||||
|
||||
Object[] arr = { 12836, "小哈" };
|
||||
int hashTup = Arrays.hashCode(arr);
|
||||
// Хеш-значение массива [12836, 小哈] равно 1151158
|
||||
|
||||
ListNode obj = new ListNode(0);
|
||||
int hashObj = obj.hashCode();
|
||||
// Хеш-значение объекта узла utils.ListNode@7dc5e7b4 равно 2110121908
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title="built_in_hash.cs"
|
||||
int num = 3;
|
||||
int hashNum = num.GetHashCode();
|
||||
// Хеш-значение целого числа 3 равно 3;
|
||||
|
||||
bool bol = true;
|
||||
int hashBol = bol.GetHashCode();
|
||||
// Хеш-значение булевой величины true равно 1;
|
||||
|
||||
double dec = 3.14159;
|
||||
int hashDec = dec.GetHashCode();
|
||||
// Хеш-значение дробного числа 3.14159 равно -1340954729;
|
||||
|
||||
string str = "Hello 算法";
|
||||
int hashStr = str.GetHashCode();
|
||||
// Хеш-значение строки "Hello 算法" равно -586107568;
|
||||
|
||||
object[] arr = [12836, "小哈"];
|
||||
int hashTup = arr.GetHashCode();
|
||||
// Хеш-значение массива [12836, 小哈] равно 42931033;
|
||||
|
||||
ListNode obj = new(0);
|
||||
int hashObj = obj.GetHashCode();
|
||||
// Хеш-значение объекта узла 0 равно 39053774;
|
||||
```
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title="built_in_hash.go"
|
||||
// Go не предоставляет встроенную функцию hash code
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title="built_in_hash.swift"
|
||||
let num = 3
|
||||
let hashNum = num.hashValue
|
||||
// Хеш-значение целого числа 3 равно 9047044699613009734
|
||||
|
||||
let bol = true
|
||||
let hashBol = bol.hashValue
|
||||
// Хеш-значение булевой величины true равно -4431640247352757451
|
||||
|
||||
let dec = 3.14159
|
||||
let hashDec = dec.hashValue
|
||||
// Хеш-значение дробного числа 3.14159 равно -2465384235396674631
|
||||
|
||||
let str = "Hello 算法"
|
||||
let hashStr = str.hashValue
|
||||
// Хеш-значение строки "Hello 算法" равно -7850626797806988787
|
||||
|
||||
let arr = [AnyHashable(12836), AnyHashable("小哈")]
|
||||
let hashTup = arr.hashValue
|
||||
// Хеш-значение массива [AnyHashable(12836), AnyHashable("小哈")] равно -2308633508154532996
|
||||
|
||||
let obj = ListNode(x: 0)
|
||||
let hashObj = obj.hashValue
|
||||
// Хеш-значение объекта узла utils.ListNode равно -2434780518035996159
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
|
||||
```javascript title="built_in_hash.js"
|
||||
// JavaScript не предоставляет встроенную функцию hash code
|
||||
```
|
||||
|
||||
=== "TS"
|
||||
|
||||
```typescript title="built_in_hash.ts"
|
||||
// TypeScript не предоставляет встроенную функцию hash code
|
||||
```
|
||||
|
||||
=== "Dart"
|
||||
|
||||
```dart title="built_in_hash.dart"
|
||||
int num = 3;
|
||||
int hashNum = num.hashCode;
|
||||
// Хеш-значение целого числа 3 равно 34803
|
||||
|
||||
bool bol = true;
|
||||
int hashBol = bol.hashCode;
|
||||
// Хеш-значение булевой величины true равно 1231
|
||||
|
||||
double dec = 3.14159;
|
||||
int hashDec = dec.hashCode;
|
||||
// Хеш-значение дробного числа 3.14159 равно 2570631074981783
|
||||
|
||||
String str = "Hello 算法";
|
||||
int hashStr = str.hashCode;
|
||||
// Хеш-значение строки "Hello 算法" равно 468167534
|
||||
|
||||
List arr = [12836, "小哈"];
|
||||
int hashArr = arr.hashCode;
|
||||
// Хеш-значение массива [12836, 小哈] равно 976512528
|
||||
|
||||
ListNode obj = new ListNode(0);
|
||||
int hashObj = obj.hashCode;
|
||||
// Хеш-значение объекта узла Instance of 'ListNode' равно 1033450432
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="built_in_hash.rs"
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
let num = 3;
|
||||
let mut num_hasher = DefaultHasher::new();
|
||||
num.hash(&mut num_hasher);
|
||||
let hash_num = num_hasher.finish();
|
||||
// Хеш-значение целого числа 3 равно 568126464209439262
|
||||
|
||||
let bol = true;
|
||||
let mut bol_hasher = DefaultHasher::new();
|
||||
bol.hash(&mut bol_hasher);
|
||||
let hash_bol = bol_hasher.finish();
|
||||
// Хеш-значение булевой величины true равно 4952851536318644461
|
||||
|
||||
let dec: f32 = 3.14159;
|
||||
let mut dec_hasher = DefaultHasher::new();
|
||||
dec.to_bits().hash(&mut dec_hasher);
|
||||
let hash_dec = dec_hasher.finish();
|
||||
// Хеш-значение дробного числа 3.14159 равно 2566941990314602357
|
||||
|
||||
let str = "Hello 算法";
|
||||
let mut str_hasher = DefaultHasher::new();
|
||||
str.hash(&mut str_hasher);
|
||||
let hash_str = str_hasher.finish();
|
||||
// Хеш-значение строки "Hello 算法" равно 16092673739211250988
|
||||
|
||||
let arr = (&12836, &"小哈");
|
||||
let mut tup_hasher = DefaultHasher::new();
|
||||
arr.hash(&mut tup_hasher);
|
||||
let hash_tup = tup_hasher.finish();
|
||||
// Хеш-значение кортежа (12836, "小哈") равно 1885128010422702749
|
||||
|
||||
let node = ListNode::new(42);
|
||||
let mut hasher = DefaultHasher::new();
|
||||
node.borrow().val.hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
// Хеш-значение объекта узла RefCell { value: ListNode { val: 42, next: None } } равно 15387811073369036852
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title="built_in_hash.c"
|
||||
// C не предоставляет встроенную функцию hash code
|
||||
```
|
||||
|
||||
=== "Kotlin"
|
||||
|
||||
```kotlin title="built_in_hash.kt"
|
||||
val num = 3
|
||||
val hashNum = num.hashCode()
|
||||
// Хеш-значение целого числа 3 равно 3
|
||||
|
||||
val bol = true
|
||||
val hashBol = bol.hashCode()
|
||||
// Хеш-значение булевой величины true равно 1231
|
||||
|
||||
val dec = 3.14159
|
||||
val hashDec = dec.hashCode()
|
||||
// Хеш-значение дробного числа 3.14159 равно -1340954729
|
||||
|
||||
val str = "Hello 算法"
|
||||
val hashStr = str.hashCode()
|
||||
// Хеш-значение строки "Hello 算法" равно -727081396
|
||||
|
||||
val arr = arrayOf<Any>(12836, "小哈")
|
||||
val hashTup = arr.hashCode()
|
||||
// Хеш-значение массива [12836, 小哈] равно 189568618
|
||||
|
||||
val obj = ListNode(0)
|
||||
val hashObj = obj.hashCode()
|
||||
// Хеш-значение объекта узла utils.ListNode@1d81eb93 равно 495053715
|
||||
```
|
||||
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title="built_in_hash.rb"
|
||||
num = 3
|
||||
hash_num = num.hash
|
||||
# Хеш-значение целого числа 3 равно -4385856518450339636
|
||||
|
||||
bol = true
|
||||
hash_bol = bol.hash
|
||||
# Хеш-значение булевой величины true равно -1617938112149317027
|
||||
|
||||
dec = 3.14159
|
||||
hash_dec = dec.hash
|
||||
# Хеш-значение дробного числа 3.14159 равно -1479186995943067893
|
||||
|
||||
str = "Hello 算法"
|
||||
hash_str = str.hash
|
||||
# Хеш-значение строки "Hello 算法" равно -4075943250025831763
|
||||
|
||||
tup = [12836, '小哈']
|
||||
hash_tup = tup.hash
|
||||
# Хеш-значение кортежа (12836, '小哈') равно 1999544809202288822
|
||||
|
||||
obj = ListNode.new(0)
|
||||
hash_obj = obj.hash
|
||||
# Хеш-значение объекта узла #<ListNode:0x000078133140ab70> равно 4302940560806366381
|
||||
```
|
||||
|
||||
??? pythontutor "可视化运行"
|
||||
|
||||
https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%E6%95%B4%E6%95%B0%203%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%E5%B8%83%E5%B0%94%E9%87%8F%20True%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%E5%B0%8F%E6%95%B0%203.14159%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E2%80%9CHello%20%E7%AE%97%E6%B3%95%E2%80%9D%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%E5%85%83%E7%BB%84%20%2812836,%20'%E5%B0%8F%E5%93%88'%29%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%AF%B9%E8%B1%A1%20%3CListNode%20object%20at%200x1058fd810%3E%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20274267521&cumulative=false&curInstr=19&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
|
||||
|
||||
Во многих языках программирования **только неизменяемые объекты могут использоваться в качестве ключей в хеш-таблице**. Если список (динамический массив) используется в качестве ключа, то при изменении его содержимого хеш-значение также изменится, и мы не сможем найти исходное значение.
|
||||
|
||||
Хотя переменные-члены пользовательских объектов (например, узлов связного списка) могут быть изменяемыми, сами объекты можно хешировать. **Это связано с тем**, **что хеш-значение объекта обычно генерируется на основе его адреса в памяти**, и даже если содержимое объекта изменяется, адрес остается неизменным, а значит, и хеш-значение также остается прежним.
|
||||
|
||||
Возможно, вы заметили, что при запуске программы в разных окнах выводимые хеш-значения отличаются. **Это связано с тем, что интерпретатор Python при каждом запуске добавляет случайное значение «соли» к функции хеширования строк**. Такой подход эффективно предотвращает атаки типа HashDoS и повышает безопасность хеш-алгоритма.
|
||||
|
||||
Reference in New Issue
Block a user