Здесь показаны различия между двумя версиями данной страницы.
| Следующая версия | Предыдущая версия | ||
|
basics_of_algorithms:hash [2023/10/04 22:21] werwolf создано |
basics_of_algorithms:hash [2023/10/19 12:55] (текущий) werwolf [Разреженные массивы] |
||
|---|---|---|---|
| Строка 21: | Строка 21: | ||
| В этом уроке мы разберемся, как устроены хэш-таблицы и как их реализовывать. | В этом уроке мы разберемся, как устроены хэш-таблицы и как их реализовывать. | ||
| - | ===== Разреженные массивы ===== | + | ===== Выводы ===== |
| - | Простейший способ определить столицу по году основания — использовать массив: | + | В этом уроке мы изучили хэш-таблицы и научились с ними работать. Повторим основные тезисы: |
| - | <code> | + | * **Хэш-таблица** обеспечивает временную сложность . Она похожа на массив, потому что в ней тоже есть операция индексации. В качестве индекса можно использовать практически любые структуры данных — строки, дату и время, массивы. |
| - | let capitals = []; | + | * Массивы, в которых большая часть элементов не определена, называются **разреженными**. Чтобы сэкономить память, программисты стараются уплотнить массив, но тогда довольно трудно предсказать размер массива. |
| + | * Остатки от деления любого числа на всегда находятся в диапазоне от до . Иногда вместо «остаток от деления на » говорят «модуль по основанию . В JavaScript и многих других языках программирования для вычисления модуля используют оператор ''%'' | ||
| - | 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> | ||
| - | |||
| - | Python | ||
| - | |||
| - | <code> | ||
| - | capitals = [None for _ in range(1873)] | ||
| - | |||
| - | capitals[1804] = "Вена" | ||
| - | 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> | ||
| - | |||
| - | Java | ||
| - | |||
| - | <code> | ||
| - | String[] capitals = new String[1873]; | ||
| - | |||
| - | 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> | ||
| - | |||
| - | PHP | ||
| - | |||
| - | <code> | ||
| - | <?php | ||
| - | |||
| - | $capitals = new SplFixedArray(1873); | ||
| - | |||
| - | $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> | ||
| - | |||
| - | Такой массив занимает много памяти. В нем хранится всего 15 городов, но при этом размер массива — это целых 1873 элемента: | ||
| - | |||
| - | <code> | ||
| - | console.log(capitals.length); // => 1873 | ||
| - | </code> | ||
| - | |||
| - | Python | ||
| - | |||
| - | <code> | ||
| - | print(len(capitals)) # => 1873 | ||
| - | </code> | ||
| - | |||
| - | Java | ||
| - | |||
| - | <code> | ||
| - | System.out.println(capitals.length); // => 1873 | ||
| - | </code> | ||
| - | |||
| - | PHP | ||
| - | |||
| - | <code> | ||
| - | <?php | ||
| - | |||
| - | print_r(count($capitals)); // => 1873 | ||
| - | </code> | ||
| - | |||
| - | Так получилось потому, что индексами в массиве могут быть только последовательные целые числа, начиная с 0. Если мы добавляем элемент с индексом 752, в массив добавляются и неопределенные элементы с индексами от 0 до 751. Длина массива получилась равной 1873, потому что: | ||
| - | |||
| - | * Наибольший год основания — 1872 | ||
| - | * Первым в массиве всегда стоит элемент с индексом 0 | ||
| - | |||
| - | Если мы применим массив, доступ к элементам выполнится очень быстро — за константное время . Но при этом мы впустую расходуем память — в нашем примере массив заполнен всего на 0,8%. | ||
| - | |||
| - | Массивы, в которых большая часть элементов не определена, называются **разреженными**. Чтобы сэкономить память, программисты стараются уплотнить массив. Скажем, в нашем примере годы находятся достаточно далеко друг от друга — на каждые двадцать пустых элементов приходится только один элемент с данными. Мы можем уменьшить массив в двадцать раз с помощью простого трюка. | ||
| - | |||
| - | Возьмем числа 0, 1, 2 и 3, поделим их на 20 и получим такой результат: | ||
| - | |||
| - | * * * * Целая часть этих чисел будет равна 0, пока мы не доберемся до такой операции: | ||
| - | |||
| - | Начиная с 20, целая часть после деления будет равна 1: | ||
| - | |||
| - | * * Начиная с 40, целая часть будет равна 2. | ||
| - | |||
| - | Получается, что после преобразования числа схлопываются: | ||
| - | |||
| - | * От 0 до 19 → 0 | ||
| - | * От 20 до 39 → 1 | ||
| - | * От 40 до 59 → 2 и так далее | ||
| - | |||
| - | Чтобы получать целую часть, воспользуемся стандартной функцией ''Math.floor'': | ||
| - | |||
| - | <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> | ||
| - | |||
| - | Python | ||
| - | |||
| - | <code> | ||
| - | # В Python для целых чисел используется целочисленное деление | ||
| - | capitals = [None for _ in range(100)]; | ||
| - | |||
| - | capitals[1804 // 20] = "Вена" | ||
| - | capitals[1237 // 20] = "Берлин" | ||
| - | capitals[1323 // 20] = "Вильнюс" | ||
| - | capitals[1614 // 20] = "Тирана" | ||
| - | capitals[1167 // 20] = "Копенгаген" | ||
| - | capitals[963 // 20] = "Люксембург" | ||
| - | capitals[1067 // 20] = "Минск" | ||
| - | capitals[1191 // 20] = "Дублин" | ||
| - | capitals[1275 // 20] = "Амстердам" | ||
| - | capitals[752 // 20] = "Ватикан" | ||
| - | capitals[1786 // 20] = "Рейкьявик" | ||
| - | capitals[1200 // 20] = "Варшава" | ||
| - | capitals[1872 // 20] = "Будапешт" | ||
| - | capitals[856 // 20] = "Мадрид" | ||
| - | capitals[1147 // 20] = "Москва" | ||
| - | </code> | ||
| - | |||
| - | Java | ||
| - | |||
| - | <code> | ||
| - | // В java для целых чисел используется целочисленное деление | ||
| - | String[] capitals = new String[100]; | ||
| - | |||
| - | capitals[881 / 20] = "Вена"; | ||
| - | capitals[1237 / 20] = "Берлин"; | ||
| - | capitals[1323 / 20] = "Вильнюс"; | ||
| - | capitals[1614 / 20] = "Тирана"; | ||
| - | capitals[1167 / 20] = "Копенгаген"; | ||
| - | capitals[963 / 20] = "Люксембург"; | ||
| - | capitals[1067 / 20] = "Минск"; | ||
| - | capitals[1191 / 20] = "Дублин"; | ||
| - | capitals[1275 / 20] = "Амстердам"; | ||
| - | capitals[752 / 20] = "Ватикан"; | ||
| - | capitals[1786 / 20] = "Рейкьявик"; | ||
| - | capitals[1200 / 20] = "Варшава"; | ||
| - | capitals[1872 / 20] = "Будапешт"; | ||
| - | capitals[856 / 20] = "Мадрид"; | ||
| - | capitals[1147 / 20] = "Москва"; | ||
| - | </code> | ||
| - | |||
| - | PHP | ||
| - | |||
| - | <code> | ||
| - | <?php | ||
| - | |||
| - | $capitals = new SplFixedArray(100); | ||
| - | |||
| - | $capitals[round(881 / 20)] = 'Вена'; | ||
| - | $capitals[round(1237 / 20)] = 'Берлин'; | ||
| - | $capitals[round(1323 / 20)] = 'Вильнюс'; | ||
| - | $capitals[round(1614 / 20)] = 'Тирана'; | ||
| - | $capitals[round(1167 / 20)] = 'Копенгаген'; | ||
| - | $capitals[round(963 / 20)] = 'Люксембург'; | ||
| - | $capitals[round(1067 / 20)] = 'Минск'; | ||
| - | $capitals[round(1191 / 20)] = 'Дублин'; | ||
| - | $capitals[round(1275 / 20)] = 'Амстердам'; | ||
| - | $capitals[round(752 / 20)] = 'Ватикан'; | ||
| - | $capitals[round(1786 / 20)] = 'Рейкьявик'; | ||
| - | $capitals[round(1200 / 20)] = 'Варшава'; | ||
| - | $capitals[round(1872 / 20)] = 'Будапешт'; | ||
| - | $capitals[round(856 / 20)] = 'Мадрид'; | ||
| - | $capitals[round(1147 / 20)] = 'Москва'; | ||
| - | </code> | ||
| - | |||
| - | Чтобы узнать, какой город основан в 1191 году, мы проверяем элемент с индексом 1191/20: | ||
| - | |||
| - | <code> | ||
| - | const getCity = (year) => capitals[Math.floor(year / 20)]; | ||
| - | |||
| - | let city = getCity(1191); // Дублин | ||
| - | </code> | ||
| - | |||
| - | Python | ||
| - | |||
| - | <code> | ||
| - | def get_city(year): | ||
| - | return capitals[year // 20] | ||
| - | |||
| - | city = get_city(1191) # Дублин | ||
| - | </code> | ||
| - | |||
| - | Java | ||
| - | |||
| - | <code> | ||
| - | class App { | ||
| - | public static String getCity(String[] capitals, int year) { | ||
| - | return capitals[year / 20]; | ||
| - | } | ||
| - | } | ||
| - | </code> | ||
| - | |||
| - | PHP | ||
| - | |||
| - | <code> | ||
| - | <?php | ||
| - | |||
| - | function getCity($year, $capitals) | ||
| - | { | ||
| - | return $capitals[round($year / 20)]; | ||
| - | } | ||
| - | </code> | ||
| - | |||
| - | У такого подхода есть недостаток — довольно трудно предсказать размер массива. Когда речь идет о годах основания, мы можем проанализировать данные и прикинуть, что каждые двадцать лет можно схлопнуть в один элемент. | ||
| - | |||
| - | Для чисел 0 до 1999 нам достаточно массива из ста элементов. Последний элемент такого уплотненного массива имеет индекс 99, а ''Math.floor(1999/20)'' как раз равно 99. | ||
| - | |||
| - | Но в другой задаче это может не сработать — например, если требуется хранить в массиве индексы, равные миллиону или миллиарду. Коэффициент 20 слишком мал для таких чисел, потому что даже уплотненный массив все равно будет слишком большим и слишком пустым. | ||
| - | |||
| - | Но мы не можем анализировать каждую возникающую задачу. Нам бы хотелось иметь универсальный способ, подходящий для разных случаев и позволяющий контролировать размер массива. Поэтому мы рассмотрим еще один способ. | ||
| ===== Модульная арифметика ===== | ===== Модульная арифметика ===== | ||
| Строка 304: | Строка 34: | ||
| Предположим, мы фиксируем размер массива. Пусть у нас будет массив из ста элементов: | Предположим, мы фиксируем размер массива. Пусть у нас будет массив из ста элементов: | ||
| - | <code> | + | <code javascript> |
| let capitals = new Array(100); | let capitals = new Array(100); | ||
| </code> | </code> | ||
| - | Python | + | <details> |
| + | <summary>Python</summary> | ||
| - | <code> | + | <code python> |
| capitals = [None for _ in range(100)] | capitals = [None for _ in range(100)] | ||
| </code> | </code> | ||
| + | </details> | ||
| - | Java | + | <details> |
| + | <summary>Java</summary> | ||
| - | <code> | + | <code java> |
| String[] capitals = new String[100]; | String[] capitals = new String[100]; | ||
| </code> | </code> | ||
| + | </details> | ||
| - | PHP | + | <details> |
| + | <summary>PHP</summary> | ||
| - | <code> | + | <code php> |
| <?php | <?php | ||
| $capitals = array_fill(0, 100, null); | $capitals = array_fill(0, 100, null); | ||
| </code> | </code> | ||
| + | </details> | ||
| Мы хотим сохранить в него несколько элементов, но их индексы могут быть произвольными целыми числами. Чтобы преобразовать индекс в число от 0 до 99, мы должны вычислить остаток от деления индекса на 100. | Мы хотим сохранить в него несколько элементов, но их индексы могут быть произвольными целыми числами. Чтобы преобразовать индекс в число от 0 до 99, мы должны вычислить остаток от деления индекса на 100. | ||
| Строка 338: | Строка 74: | ||
| Иногда вместо «остаток от деления на » говорят «модуль по основанию ». В JavaScript и многих других языках программирования для вычисления модуля используют оператор ''%'': | Иногда вместо «остаток от деления на » говорят «модуль по основанию ». В JavaScript и многих других языках программирования для вычисления модуля используют оператор ''%'': | ||
| - | <code> | + | <code javascript> |
| console.log(1999 % 20); // => 19 | console.log(1999 % 20); // => 19 | ||
| </code> | </code> | ||
| - | Python | + | <details> |
| + | <summary>Python</summary> | ||
| - | <code> | + | <code python> |
| print(1999 % 20) # => 19 | print(1999 % 20) # => 19 | ||
| </code> | </code> | ||
| + | </details> | ||
| - | Java | + | <details> |
| + | <summary>Java</summary> | ||
| - | <code> | + | <code java> |
| System.out.println(1999 % 20); // => 19 | System.out.println(1999 % 20); // => 19 | ||
| </code> | </code> | ||
| + | </details> | ||
| - | PHP | + | <details> |
| + | <summary>PHP</summary> | ||
| - | <code> | + | <code php> |
| <?php | <?php | ||
| print_r(1999 % 20); // => 19 | print_r(1999 % 20); // => 19 | ||
| </code> | </code> | ||
| + | </details> | ||
| Решим нашу задачу со столицами с помощью модуля: | Решим нашу задачу со столицами с помощью модуля: | ||
| - | <code> | + | <code javascript> |
| let capitals = new Array(100); | let capitals = new Array(100); | ||
| Строка 388: | Строка 129: | ||
| </code> | </code> | ||
| - | Python | ||
| - | <code> | + | <details> |
| + | <summary>Python</summary> | ||
| + | |||
| + | <code python> | ||
| capitals = [None for _ in range(100)] | capitals = [None for _ in range(100)] | ||
| Строка 414: | Строка 157: | ||
| city = get_city(1167) # Копенгаген | city = get_city(1167) # Копенгаген | ||
| </code> | </code> | ||
| + | </details> | ||
| - | Java | + | <details> |
| + | <summary>Java</summary> | ||
| - | <code> | + | <code java> |
| String[] capitals = new String[100]; | String[] capitals = new String[100]; | ||
| Строка 442: | Строка 187: | ||
| } | } | ||
| </code> | </code> | ||
| + | </details> | ||
| - | PHP | + | <details> |
| + | <summary>PHP</summary> | ||
| - | <code> | + | <code php> |
| <?php | <?php | ||
| Строка 475: | Строка 222: | ||
| $city = getCity(1167); // 'Копенгаген' | $city = getCity(1167); // 'Копенгаген' | ||
| </code> | </code> | ||
| + | </details> | ||
| Теперь мы точно знаем, что наш массив не вырастет до больших размеров, как это может случиться с операцией деления. Но мы можем обнаружить другую проблему. | Теперь мы точно знаем, что наш массив не вырастет до больших размеров, как это может случиться с операцией деления. Но мы можем обнаружить другую проблему. | ||
| Строка 480: | Строка 228: | ||
| Попробуем узнать, какой город основан в 1167 году. Если верить нашей таблице, это Копенгаген. Но программа говорит совсем другое: | Попробуем узнать, какой город основан в 1167 году. Если верить нашей таблице, это Копенгаген. Но программа говорит совсем другое: | ||
| - | <code> | + | <code javascript> |
| console.log(getCity(1167)); // => Минск | console.log(getCity(1167)); // => Минск | ||
| </code> | </code> | ||
| - | Python | + | <details> |
| + | <summary>Python</summary> | ||
| - | <code> | + | <code python> |
| print(get_city(1167)) # => Минск | print(get_city(1167)) # => Минск | ||
| </code> | </code> | ||
| + | </details> | ||
| - | Java | + | <details> |
| + | <summary>Java</summary> | ||
| - | <code> | + | <code java> |
| System.out.println(App.getCity(1167)); // => Минск | System.out.println(App.getCity(1167)); // => Минск | ||
| </code> | </code> | ||
| + | </details> | ||
| - | PHP | + | <details> |
| + | <summary>PHP</summary> | ||
| - | <code> | + | <code php> |
| <?php | <?php | ||
| print_r(getCity(1167)); // => Минск | print_r(getCity(1167)); // => Минск | ||
| </code> | </code> | ||
| + | </details> | ||
| Минск основан в 1067 году, а Копенгаген — в 1167. Годы отличаются, но у них один и тот же остаток от деления на 100, а именно 67. | Минск основан в 1067 году, а Копенгаген — в 1167. Годы отличаются, но у них один и тот же остаток от деления на 100, а именно 67. | ||
| Строка 517: | Строка 271: | ||
| Попробуем реализовать в коде: | Попробуем реализовать в коде: | ||
| - | <code> | + | <code javascript> |
| import LinkedList from './LinkedList.js'; | import LinkedList from './LinkedList.js'; | ||
| Строка 532: | Строка 286: | ||
| </code> | </code> | ||
| - | Python | + | <details> |
| + | <summary>Python</summary> | ||
| - | <code> | + | <code python> |
| from LinkedList import LinkedList | from LinkedList import LinkedList | ||
| Строка 547: | Строка 302: | ||
| capitals[index].add({'key': year, 'value': city}) | capitals[index].add({'key': year, 'value': city}) | ||
| </code> | </code> | ||
| + | </details> | ||
| - | Java | + | <details> |
| + | <summary>Java</summary> | ||
| - | <code> | + | <code java> |
| class App { | class App { | ||
| private LinkedList[] capitals = new LinkedList[100]; | private LinkedList[] capitals = new LinkedList[100]; | ||
| Строка 564: | Строка 321: | ||
| } | } | ||
| </code> | </code> | ||
| + | </details> | ||
| - | PHP | + | <details> |
| + | <summary>PHP</summary> | ||
| - | <code> | + | <code php> |
| <?php | <?php | ||
| Строка 584: | Строка 343: | ||
| } | } | ||
| </code> | </code> | ||
| + | </details> | ||
| Мы уже разработали структуру данных ''LinkedList'', поэтому мы можем просто импортировать ее. | Мы уже разработали структуру данных ''LinkedList'', поэтому мы можем просто импортировать ее. | ||
| Строка 593: | Строка 353: | ||
| При поиске точно также вычисляем индекс: | При поиске точно также вычисляем индекс: | ||
| - | <code> | + | <code javascript> |
| const getCapital = (year) => { | const getCapital = (year) => { | ||
| let index = year % capitals.length; | let index = year % capitals.length; | ||
| Строка 610: | Строка 370: | ||
| </code> | </code> | ||
| - | Python | + | <details> |
| + | <summary>Python</summary> | ||
| - | <code> | + | <code python> |
| def get_capital(year): | def get_capital(year): | ||
| index = year % len(capitals) | index = year % len(capitals) | ||
| Строка 625: | Строка 386: | ||
| return None | return None | ||
| </code> | </code> | ||
| + | </details> | ||
| - | Java | + | <details> |
| + | <summary>Java</summary> | ||
| - | <code> | + | <code java> |
| lass App { | lass App { | ||
| private LinkedList[] capitals = new LinkedList[100]; | private LinkedList[] capitals = new LinkedList[100]; | ||
| Строка 654: | Строка 417: | ||
| } | } | ||
| </code> | </code> | ||
| + | </details> | ||
| - | PHP | + | <details> |
| + | <summary>PHP</summary> | ||
| - | <code> | + | <code php> |
| <?php | <?php | ||
| Строка 676: | Строка 441: | ||
| } | } | ||
| </code> | </code> | ||
| + | </details> | ||
| Если в ячейке ничего нет, значит, мы ничего и не записывали. | Если в ячейке ничего нет, значит, мы ничего и не записывали. | ||
| Строка 697: | Строка 463: | ||
| При этом компьютеры умеют работать только с числами, и любые объекты в них хранятся как последовательности чисел. Каждая буква хранится как одно число, которое называют **кодом символа**: | При этом компьютеры умеют работать только с числами, и любые объекты в них хранятся как последовательности чисел. Каждая буква хранится как одно число, которое называют **кодом символа**: | ||
| - | <code> | + | <code javascript> |
| const capital = 'Мадрид'; | const capital = 'Мадрид'; | ||
| for (let i = 0; i < capital.length; i++) { | for (let i = 0; i < capital.length; i++) { | ||
| Строка 710: | Строка 476: | ||
| </code> | </code> | ||
| - | Python | + | <details> |
| + | <summary>Python</summary> | ||
| - | <code> | + | <code python> |
| capital = 'Мадрид' | capital = 'Мадрид' | ||
| Строка 724: | Строка 491: | ||
| # => 1076 | # => 1076 | ||
| </code> | </code> | ||
| + | </details> | ||
| - | Java | + | <details> |
| + | <summary>Java</summary> | ||
| - | <code> | + | <code java> |
| var capital = "Мадрид"; | var capital = "Мадрид"; | ||
| for (var i = 0; i < capital.length; i++) { | for (var i = 0; i < capital.length; i++) { | ||
| Строка 739: | Строка 508: | ||
| // => 1076 | // => 1076 | ||
| </code> | </code> | ||
| + | </details> | ||
| - | PHP | + | <details> |
| + | <summary>PHP</summary> | ||
| - | <code> | + | <code php> |
| <?php | <?php | ||
| Строка 756: | Строка 527: | ||
| // => 1076 | // => 1076 | ||
| </code> | </code> | ||
| + | </details> | ||
| Слово «Мадрид» хранится как последовательность чисел 1052, 1072, 1076, 1088, 1080 и 1076. В простейшем случае мы могли бы использовать для вычисления индекса код первой буквы — в нашем примере 1052. Но это значит все города на М (Мадрид, Москва и Минск) попадут в один и тот же список. В таком случае скорость поиска будет не очень высокой. | Слово «Мадрид» хранится как последовательность чисел 1052, 1072, 1076, 1088, 1080 и 1076. В простейшем случае мы могли бы использовать для вычисления индекса код первой буквы — в нашем примере 1052. Но это значит все города на М (Мадрид, Москва и Минск) попадут в один и тот же список. В таком случае скорость поиска будет не очень высокой. | ||
| Строка 765: | Строка 537: | ||
| Хороший результат в таком случае даст так называемая **полиномиальная функция**. | Хороший результат в таком случае даст так называемая **полиномиальная функция**. | ||
| - | Попробуем применить ее к нашей задаче с городами. Пусть коды символов строки длины будут равны . Возьмем число , которое будет больше кода любого символа — скажем, . | + | Попробуем применить ее к нашей задаче с городами. Пусть коды символов строки длины //n// будут равны c0, c1..., cn-1. Возьмем число k, которое будет больше кода любого символа — скажем, k=65537. |
| У нас получилось такое выражение: | У нас получилось такое выражение: | ||
| - | Число достаточно большое. На каждом шаге нам приходится возводить его в возрастающую степень, поэтому выражение может оказаться просто огромным. Для слова Мадрид мы получим . | + | {{ :basics_of_algorithms:form1.png |}} |
| + | |||
| + | Число достаточно большое. На каждом шаге нам приходится возводить его в возрастающую степень, поэтому выражение может оказаться просто огромным. Для слова Мадрид мы получим 13009213352423037265600321836. | ||
| + | |||
| + | На практике для ускорения вычислений и для удобства используют не все выражение, а только остаток от его деления на другое число — //m//: | ||
| - | На практике для ускорения вычислений и для удобства используют не все выражение, а только остаток от его деления на другое число — : | + | {{ :basics_of_algorithms:form2.png |}} |
| - | Обычно модуль делают равным большому простому числу или большому круглому числу — например, миллиону или миллиарду. Часто в качестве модуля выбирают степень числа — к примеру, или . | + | Обычно модуль делают равным большому простому числу или большому круглому числу — например, миллиону или миллиарду. Часто в качестве модуля выбирают степень числа 2 — к примеру, 2<sup>.0</sup> или 2<sup>64</sup>. |
| - | Чтобы промежуточные числа не становились слишком большими, все сложения и умножения выполняют по модулю . Другими словами, остаток от деления вычисляется сразу после сложения или умножения: | + | Чтобы промежуточные числа не становились слишком большими, все сложения и умножения выполняют по модулю m. Другими словами, остаток от деления вычисляется сразу после сложения или умножения: |
| - | <code> | + | <code javascript> |
| const hash = (s) => { | const hash = (s) => { | ||
| const k = 65537; | const k = 65537; | ||
| Строка 793: | Строка 569: | ||
| </code> | </code> | ||
| - | Python | + | <details> |
| + | <summary>Python</summary> | ||
| - | <code> | + | <code python> |
| def hash(s): | def hash(s): | ||
| k = 65537 | k = 65537 | ||
| Строка 809: | Строка 586: | ||
| return result | return result | ||
| </code> | </code> | ||
| + | </details> | ||
| - | Java | + | <details> |
| + | <summary>Java</summary> | ||
| - | <code> | + | <code java> |
| class App { | class App { | ||
| private int hash(String s) { | private int hash(String s) { | ||
| Строка 830: | Строка 609: | ||
| } | } | ||
| </code> | </code> | ||
| + | </details> | ||
| - | PHP | + | <details> |
| + | <summary>PHP</summary> | ||
| - | <code> | + | <code php> |
| <?php | <?php | ||
| Строка 851: | Строка 632: | ||
| } | } | ||
| </code> | </code> | ||
| + | </details> | ||
| - | Здесь в качестве модуля мы выбрали ''2%%**%%20'', то есть . Это число равно и часто используется программистами, потому что очень близко к миллиону. Слово мегабайт традиционно означает именно байт, а не миллион, как можно было бы ожидать. | + | Здесь в качестве модуля мы выбрали ''2%%**%%20'', то есть 2<sup>20</sup>. Это число равно 1048576 и часто используется программистами, потому что очень близко к миллиону. Слово мегабайт традиционно означает именно 1048576 байт, а не миллион, как можно было бы ожидать. |
| Сейчас комитет по стандартизации мер и весов рекомендует термины «мебибайт» для 1048576 байтов и «мегабайт» для 1000000 байтов, но большинство программистов все еще не используют эту новую рекомендацию. | Сейчас комитет по стандартизации мер и весов рекомендует термины «мебибайт» для 1048576 байтов и «мегабайт» для 1000000 байтов, но большинство программистов все еще не используют эту новую рекомендацию. | ||
| - | Переменная на каждом шаге цикла умножается на . Она принимает значения , , и так далее. В переменной ''result'' накапливается результат вычислений. | + | Переменная k<sup>i</sup> на каждом шаге цикла умножается на k. Она принимает значения 1, k, k2 и так далее. В переменной ''result'' накапливается результат вычислений. |
| Функция называется ''hash'', и это неслучайно. В переводе с английского слово hash означает «мелко крошить, перемешивать». | Функция называется ''hash'', и это неслучайно. В переводе с английского слово hash означает «мелко крошить, перемешивать». | ||
| Строка 862: | Строка 644: | ||
| Функция перемешивает исходные данные так, чтобы получилось число, похожее на случайное: | Функция перемешивает исходные данные так, чтобы получилось число, похожее на случайное: | ||
| - | <code> | + | <code javascript> |
| hash('Мадрид') // 792876 | hash('Мадрид') // 792876 | ||
| hash('Москва') // 399671 | hash('Москва') // 399671 | ||
| Строка 868: | Строка 650: | ||
| </code> | </code> | ||
| - | Python | + | <details> |
| + | <summary>Python</summary> | ||
| - | <code> | + | <code python> |
| hash('Мадрид') # 792876 | hash('Мадрид') # 792876 | ||
| hash('Москва') # 399671 | hash('Москва') # 399671 | ||
| hash('Минск') # 857356 | hash('Минск') # 857356 | ||
| </code> | </code> | ||
| + | </details> | ||
| - | Java | + | <details> |
| + | <summary>Java</summary> | ||
| - | <code> | + | <code java> |
| App.hash("Мадрид") // 792876 | App.hash("Мадрид") // 792876 | ||
| App.hash("Москва") // 399671 | App.hash("Москва") // 399671 | ||
| App.hash("Минск") // 857356 | App.hash("Минск") // 857356 | ||
| </code> | </code> | ||
| + | </details> | ||
| - | PHP | + | <details> |
| + | <summary>PHP</summary> | ||
| - | <code> | + | <code php> |
| <?php | <?php | ||
| Строка 893: | Строка 680: | ||
| hash('Минск') // 857356 | hash('Минск') // 857356 | ||
| </code> | </code> | ||
| + | </details> | ||
| Эту функцию так и называют — хеш-функция. В подавляющем большинстве случаев нам не приходится писать хеш-функции, потому что они уже разработаны авторами фреймворков и стандартных библиотек. Тем не менее полезно понимать принцип работы хешей. | Эту функцию так и называют — хеш-функция. В подавляющем большинстве случаев нам не приходится писать хеш-функции, потому что они уже разработаны авторами фреймворков и стандартных библиотек. Тем не менее полезно понимать принцип работы хешей. | ||
| Строка 918: | Строка 706: | ||
| Перепишем функцию вставки, опираясь на все новые знания. Вместе с этим реализуем хеш-таблицу в виде новой структуры данных, спрятав внутри класса массив с данными: | Перепишем функцию вставки, опираясь на все новые знания. Вместе с этим реализуем хеш-таблицу в виде новой структуры данных, спрятав внутри класса массив с данными: | ||
| - | <code> | + | <code javascript> |
| class Hash { | class Hash { | ||
| let table = []; | let table = []; | ||
| Строка 992: | Строка 780: | ||
| </code> | </code> | ||
| - | Python | + | <details> |
| + | <summary>Python</summary> | ||
| - | <code> | + | <code python> |
| class Hash: | class Hash: | ||
| table = [] | table = [] | ||
| Строка 1061: | Строка 850: | ||
| return None | return None | ||
| </code> | </code> | ||
| + | </details> | ||
| - | Java | + | <details> |
| + | <summary>Java</summary> | ||
| - | <code> | + | <code java> |
| class Hash { | class Hash { | ||
| Строка 1138: | Строка 929: | ||
| } | } | ||
| </code> | </code> | ||
| + | </details> | ||
| - | PHP | + | <details> |
| + | <summary>PHP</summary> | ||
| - | <code> | + | <code php> |
| <?php | <?php | ||
| Строка 1213: | Строка 1006: | ||
| } | } | ||
| </code> | </code> | ||
| + | </details> | ||
| Весь код из этого примера мы уже обсуждали, кроме удвоения массива. Создавая новых массив, в два раза больше текущего, мы должны скопировать все элементы, пересчитав их индексы. | Весь код из этого примера мы уже обсуждали, кроме удвоения массива. Создавая новых массив, в два раза больше текущего, мы должны скопировать все элементы, пересчитав их индексы. | ||
| Строка 1218: | Строка 1012: | ||
| Именно за это отвечает вложенный цикл: | Именно за это отвечает вложенный цикл: | ||
| - | <code> | + | <code javascript> |
| let newTable = new Array(this.table.length * 2); | let newTable = new Array(this.table.length * 2); | ||
| for (let list in this.table) { | for (let list in this.table) { | ||
| Строка 1232: | Строка 1026: | ||
| </code> | </code> | ||
| - | Python | + | <details> |
| + | <summary>Python</summary> | ||
| - | <code> | + | <code python> |
| new_table = [None for _ in range(len(self.table) * 2)] | new_table = [None for _ in range(len(self.table) * 2)] | ||
| Строка 1245: | Строка 1040: | ||
| new_table[new_index].add(pair) | new_table[new_index].add(pair) | ||
| </code> | </code> | ||
| + | </details> | ||
| - | Java | + | <details> |
| + | <summary>Java</summary> | ||
| - | <code> | + | <code java> |
| LinkedList[] newTable = new LinkedList[table.length * 2]; | LinkedList[] newTable = new LinkedList[table.length * 2]; | ||
| Строка 1263: | Строка 1060: | ||
| } | } | ||
| </code> | </code> | ||
| + | </details> | ||
| - | PHP | + | <details> |
| + | <summary>PHP</summary> | ||
| - | <code> | + | <code php> |
| <?php | <?php | ||
| Строка 1281: | Строка 1080: | ||
| } | } | ||
| </code> | </code> | ||
| + | </details> | ||
| В самом начале внутренний массив имеет размер 0, чтобы не тратить память, если в хеш-таблице нет ни одного элемента. При вставке первого элемента мы увеличиваем внутренний массив сразу до 128 элементов. Остальной код мы уже рассмотрели выше в этом уроке. | В самом начале внутренний массив имеет размер 0, чтобы не тратить память, если в хеш-таблице нет ни одного элемента. При вставке первого элемента мы увеличиваем внутренний массив сразу до 128 элементов. Остальной код мы уже рассмотрели выше в этом уроке. | ||