В программировании нам часто приходится брать набор данных и проходить его, чтобы найти заданное значение или выполнить действие. Нам, новичкам, первое, что обычно приходит на ум, — это использование цикла; мы просматриваем каждый элемент наших данных, пока не получим решение, соответствующее нашим условиям. Это отлично работает для большинства мелкомасштабных поисков и базовых вычислений, но когда набор данных становится слишком большим или нам нужно вернуться к значениям, которые мы уже передали в нашем цикле, мы можем увидеть некоторые недостатки традиционного цикла.
Если вы изучали программирование, вы, скорее всего, когда-то видели или слышали о хеш-картах, они повсюду. Когда я впервые начал изучать, я был сбит с толку тем, что такое хэш-карта, как будто это был какой-то мистический тип данных, который я еще не открыл, или что-то, что существует только в определенных языках. Я был смущен этим и, казалось, не нуждался в этом, поэтому я просто продолжал работать. Я написал еще один пост здесь о построении структуры данных двоичного дерева и о том, как мы можем использовать ее для управления большими наборами данных; оказывается, бинарное дерево — это реализация хеш-карты!
Так что же такое хэш-карта? Короче говоря, это структура данных, которая использует пары ключ-значение для отслеживания значений, которые были переданы в нашей итерации. Вот простой пример, чтобы показать концепцию.
const arrayToMap = [3,6,4,12,1,23,8,10,26] //I am manually entering the elements here for the hash map //the key will be the valueand the value is the index const hashMap = {3:0, 6:1, 4:2, 12:3, 1:4, 23:5, 8:6, 10:7, 26:8}
Выше у нас есть неупорядоченный массив и хеш-карта значений массива. Мы можем видеть значение, за которым следует соответствующий индекс из исходного массива. Теперь мы можем упорядочить хеш-карту по значениям и при этом сохранить позицию в исходном массиве. Это избавляет нас от необходимости управлять тем, как добавлять новые значения в нужное место. Мы просто помещаем их в массив и можем управлять положением в хэше. Ниже приведен тот же хеш, но упорядоченный по значению, а не по индексу.
const hashMap = {1:4, 3:0, 4:2, 6:1, 8:6, 12:3, 10:7, 23:5, 26:8}
Это может быть действительно мощно! Теперь у нас есть исходные данные в массиве, полностью нетронутые, и вся информация, необходимая нам для выполнения наших действий, И, поскольку мы сохранили исходные индексы в качестве ключей в нашем хэше, мы МОЖЕМ очень легко манипулировать исходным массивом. Нам просто нужно найти значение в нашем хеше и использовать его ключ для доступа к этому значению в исходном массиве.
arrayToMap[0] //this returns 3 since our value at index 0 is 3 arrayToMap[0] = 20 //changes the value from 3 to 20
Довольно просто, верно!?
Теперь мы можем использовать эту структуру, чтобы попытаться решить проблему. Давайте рассмотрим проблему TwoSums. В этой задаче нам предлагается взять массив чисел и найти ДВА значения, которые в сумме дают заданное целевое число. Когда я посмотрел на эту проблему, я сразу же начал использовать циклы и настроил решение методом грубой силы.
const twoSum = (array, target) => {
let indexes = []
for(let i = 0; i < array.length; i++){
for(let j = i + 1; j < array.length; j++){
if (array[i] + array[j] === target) {
indexes.push(i);
indexes.push(j);
}
}
}
return indexes;
}
Это берет первое значение массива и добавляет его ко всем остальным значениям в массиве, а затем проверяет, равно ли оно целевому числу. Это прекрасное решение, и оно хорошо работает, если набор данных не слишком велик, но есть способ получше. Ниже мы реализуем хеш-карту для отслеживания уже переданных значений, и я объясню, почему это важно, после того, как мы рассмотрим функцию.
const twoSum = (array, target) => { const hashMap = {} for (let i = 0; i < array.length; i++) { hashMap[array[i]] = i; } for (let i = 0; i < array.length; i++) { let diff = target - array[i] if (hashMap[diff] !== undefined && hashMap[diff] !== i) { return [hashMap[diff], i] } } }
В этой функции мы сначала создаем нашу хеш-карту, перебирая массив и назначая пары ключ-значение переменной hashMap. Поскольку у нас есть ВСЕ наши значения внутри этой хеш-карты, мы можем легко просто проверить число, которое нам нужно. Мы определяем необходимое число, вычитая текущее значение из целевого числа, затем проверяем, есть ли это значение в нашей хеш-карте. Мы можем искать значение в хеш-карте, используя тот же синтаксис, что и для доступа к индексу массива, и нам не нужно перебирать каждый элемент, чтобы найти его.
hashMap[x] //x = the number you are searching for
Мы можем сделать это, так как мы установили все ключи в фактические значения, а значение в индекс!
Внедряя метод хеширования, мы значительно сокращаем наши накладные расходы. Теперь вместо того, чтобы сравнивать каждый элемент массива с одним числом за раз и проверять, соответствует ли оно нашему условию, вместо этого мы просто выясняем, какое число необходимо для выполнения условия, и проверяем, есть ли это число в нашем наборе данных с помощью хэш-карты. . Это означает гораздо меньше итераций, но все же очень читаемый код. Понимание различных структур данных может дать нам больше возможностей для эффективного управления нашими данными, и многие структуры основаны на этой концепции хэш-карты, теперь нам просто нужно попрактиковаться в ее использовании.