Найти в Дзене
Я, Golang-инженер

#53. MAP'ы в Golang

Оглавление

Это статья об основах программирования на Go. На канале я рассказываю об опыте перехода в IT с нуля, структурирую информацию и делюсь мнением.

Хой, джедаи и амазонки!

Прошёл темы по MAP'ам (они же карты, они же отображения) и хочу поделиться примерами задач, которые себе придумал для закрепления материала. А также будет теория, чем полезны карты.

Напоминаю, что только так можно стать программистом - кодить, кодить по обучающим материалам, придумывать себе новые задачи, и вновь кодить, кодить и кодить.

1. Теория по MAP'ам

1.1. Что такое карты в Go?

Карты (maps) в языке Go - это удобная структура данных, которая позволяет хранить коллекции пар ключ-значение. Пример создания карты и обращения к ней:

Пример создания карты в Go
Пример создания карты в Go

Мы создали карту с именем database и прошлись по её значениям через ключевое слово range. Ключи не трогали, хотя могли - обычное ключевое слово range - работает точно так же, как с массивами или срезами.

Как видим, значения в карте напечатались не в порядке их указания в карте, а в случайном порядке. Такова особенность хранения в хеш-таблице (она под капотом карты). Если нам нужен упорядоченный порядок - нужно создавать массив из карты.

Как сказал выше, карты являются реализацией хеш-таблиц и обладают следующими принципами работы:

  1. Синтаксис: для создания карты используется ключевое слово map с указанием типов ключа и значения, например map[string]int для карты с ключом типа string и значением типа int.
  2. Уникальность ключей: ключи в карте должны быть уникальными. При попытке добавить элемент с уже существующим ключом, значение старого ключа будет заменено на новое. Это бывает полезно, когда нужно посчитать количество только уникальных элементов в массиве.
  3. Добавление элементов: новые элементы могут быть добавлены в карту с помощью оператора присваивания (=) и указания ключа и значения.
  4. Получение значения по ключу: для получения значения из карты используется синтаксис mapName[key], где mapName - имя карты, а key - ключ, по которому нужно получить значение.
  5. Удаление элементов: для удаления элемента из карты используется встроенная функция delete(mapName, key), где mapName - имя карты, а key - ключ элемента, который нужно удалить.
  6. Проверка наличия ключа: мы можем проверить наличие ключа в карте, используя синтаксис value, ok := mapName[key]. Если значение ok равно true, значит ключ присутствует в карте, а значение по ключу из карты будет сохранено в переменной value.
  7. Итерация по картам: мы можем итерироваться по всем элементам карты с помощью цикла for range. В каждой итерации мы получаем ключ и значение текущего элемента карты.

Карты в Go предоставляют эффективный способ хранения и доступа к данным по ключу. Они широко используются для разных целей, включая индексирование данных, подсчет, группировку и отображение данных. Подробнее о целях см. ниже.

1.2. Для чего полезны MAP'ы в Go?

Основное назначение карт следующее:

  1. Индексирование данных: карты позволяют эффективно хранить и получать значения по уникальному ключу. Это особенно полезно для операций поиска и доступа к данным.
  2. Подсчет и агрегация: карты позволяют собирать и агрегировать данные, помогая в подсчете количества вхождений элементов, группировке данных и создании статистических отчетов.
  3. Удаление и добавление элементов: карты предоставляют возможность динамического добавления и удаления элементов, что делает их подходящими для сценариев, где требуется изменяемая коллекция данных.
  4. Кэширование данных: карты могут использоваться для кэширования данных, что позволяет улучшить производительность путем сохранения уже вычисленных значений для будущего использования.
  5. Отображение данных: карты предоставляют удобный способ представления сложных структур данных, позволяя ассоциировать информацию с уникальными ключами.

Разумеется, список приведенных целей использует лишь некоторые из возможностей карт в Go, и детали использования карт могут варьироваться в зависимости от конкретных задач и контекста.

По пункту 1. Когда я говорю, что карты в Go позволяют "эффективно" хранить и получать значения по уникальному ключу, я имею в виду, что это действие происходит быстро и с минимальными затратами времени и ресурсов компьютера и/или сети.

Карты в Go реализованы на основе хеш-таблиц, что позволяет достичь высокой эффективности при доступе к данным. Когда происходит поиск значения по ключу в карте, Go использует хеш-функцию для вычисления индекса элемента во внутренней хеш-таблице. Затем происходит проверка этого индекса и доступ к значению элемента по указанному ключу. Благодаря этому процессу, поиск происходит намного быстрее, чем при простом переборе элементов в коллекции.

Эффективность карт зависит от различных факторов, таких как количество элементов в карте, выбранная хеш-функция, а также доступность оперативной памяти. Однако в среднем, сложность получения значения по ключу в карте составляет O(1), что означает, что время доступа не зависит от размера карты и остается постоянным.

Так, использование карт позволяет эффективно хранить и получать значения по ключу без необходимости проходить через весь набор данных. Это делает их облегченными и удобными для различных задач, где требуется быстрый доступ к значениям по ключу.

Далее переходим к задачам.

2. Задачи с картами

2.1. Проверка наличия элемента в карте

Возьму знакомый пример выше, и добавлю в него условие проверки наличия ключа:

Код
Код

В строке 16 я воспользовался синтаксисом, описанном в п.6 раздела 1.1 этой публикации. Запрошенного цвета не оказалось в карте.

Закомментируем фрагмент кода, где осуществляем проверку. Вот что получим:

Код
Код

Программа напечатала пустоту, если можно так выразиться.

Отсюда полезное наблюдение: если обратиться к карте в Go по несуществующему ключу, то будет возвращено значение по умолчанию для типа значений, определенного в карте.

Код
Код

Важно помнить, что при обращении к несуществующему ключу в карте, не возникает ошибки компиляции или выполнения программы.

2.2. Собрать уникальные элементы из массива с помощью карты

Задача: есть карта или массив (тут нам не важно), где хранятся какие-либо повторяющиеся элементы: названия животных / имена / наименования товаров или даже просто буквы. Требуется вывести на печать не повторяющиеся города.

Если по пути создания массива и проверки, есть ли в нём уже элемент - это может создать большую нагрузку на "железо", если элементов очень много.

Можно воспользоваться свойством Map, описанном в п.2 раздела 1.1 этой публикации. Давайте попробуем.

По легенде, ученику нужно посчитать, сколько нужно ветеринаров в заповедник. Чтобы хорошо обеспечивать помощь зверям, на каждый вид нужен отдельный специалист, специализирующийся на конкретном виде зверя.
Когда ученик заполнял массив, он допустил ошибку - несколько раз повторил один и тот же вид. Помогите ученику переписать код, чтобы посчитать требуемое количество ветеринаров.

Вот так выглядит код ученика:

Исходный код
Исходный код

Попробуем переписать код, чтобы узнать точное количество ветеринаров, используя знания о хеш-таблицах: если в карту добавить существующий ключ, старый перезапишется.

Новый код
Новый код

Мы создали карту и добавляли в неё все по порядку элементы массива. Если были дубли - они заменяли предыдущие элементы созданной карты.

Не зная этой особенности карты, пришлось бы создавать срез с проверкой на уникальность, либо подгружать сторонние пакеты, например "github.com/deckarep/golang-set".

2.3. Добавить и удалить элементы из карты

Ещё несколько задач для наработки навыка общения с картами.

По легенде выяснилось, что крокодилов в заповеднике нет, его туда добавили по ошибке. Но есть два других вида: сокол и паук. Нужно поправить код.

Вот что имеется - карта со зверями в ключах:

Код со зверями начальный
Код со зверями начальный

Вот что получается после правок:

-8

Что здесь происходит новенького:

  1. В строке 16 мы удалили элемент из карты по ключу "Крокодил", применив ключевое слово delete.
  2. Добавляем элементы в карту в строках 17-18 за счёт индексации ([ ]) ключей "Сокол" и "Паук".
  3. Печатаем ключи. Обратите внимание, когда при применении ключевого слова range, требуется обращение только к ключам (в картах) или к индексам (в массивах), то не требуется писать , _ после, т.е. вместо:
for key, _ := range ...

можно написать:

for key := range ...

В общем, всё - готово. Удалили лишний ключ, добавили новые. Хорошо было бы их теперь напечатать в алфавитном порядке, а не как попало.

2.4. Напечатать ключи карты в алфавитном порядке

Задача: нужно напечатать всех животных из карты в алфавитном порядке.

Для этого создадим отдельный срез, в который будем помещать ключи карты, а затем сортировать элементы среза в алфавитном порядке.

Пусть у среза будет нулевая длина, а ёмкость хотя можем также сделать нулевой - но для улучшения производительности, зададим ёё. Мы знаем ёмкость создаваемого среза - она равна длине карты.

Новый код
Новый код

Что здесь происходит:

  1. В строках 9-19 создана карта с ключами - известными нам зверями в заповеднике.
  2. Строка 22: создаём срез с ёмкостью, равной длине карты.
  3. Строка 23: заполняем срез ключами карты в хаотичном порядке.
  4. Строка 26: сортируем в алфавитном порядке элементы среза с помощью функции Strings пакета sort.
  5. Строка 27: печатаем элементы среза.

По поводу пакета sort. Вот что о функции Strings говорит официальная документация:

Фрагмент официальной документации
Фрагмент официальной документации

Напоминаю, правило хорошего тона - читаем официальную документацию о новых инструментах, с которыми знакомимся в ходе работы: увидели в чужом коде, подсказала нейросетка или что-то ещё.

2.5. Поиск ключевых слов

По легенде в администрацию заповедника приходит много писем. Все интересуются, как дела у животных, чем помочь и вообще, что новенького. Чтобы автоматизировать работу, требуется написать программу, которая по ключевым словам из тела письма будет рассылать письма на отдельные службы.

На вход подаётся строка - письмо email. На выходе мы должны получить - кому направить письмо.

Здесь я всё упрощаю - важна суть.

Пусть у нас будет пять служб, кому может прийти письмо:

  1. Экскурсии.
  2. Сотрудничество.
  3. Новости.
  4. Благотворительность.
  5. Неизвестный запрос - требуется участие администратора.

Мы будем разбивать строку на массив по пробелу. Смотрим, что получается:

Готовый код
Готовый код

Что здесь происходит:

  1. В строках 9-12 я прописываю текст полученного email. Странную структуру через "плюс" делаю для удобства отображения текста в IDE.
  2. В строке 13 создаю срез из элементов строки, разделённых пробелом.
  3. В строке 19 создаю переменную и вызываю функцию для создания карты.
  4. В строке 36 создаю пустую карту.
  5. В строках 37-39 заполняю ключи карты элементами среза.
  6. Возвращаемся в строку 19, здесь в созданную переменную записываем результат выполнения функции createMapWords - карту.
  7. Выполняем ряд проверок на ключевые слова. Ключевые слова я выбрал так, как посчитал нужным. Они могут быть и другими или их может быть несколько. В данном случае найдена строка "экскурсию" в ключах карты.
  8. В строке 15 печатаем, в какой отдел по мнению программы нужно чтобы ушло письмо.

Пример упрощённый, но вполне реальная задача на мой взгляд. Из самого крутого здесь - этот фрагмент кода:

if _, ok := words[key]; ok {
//какие-то действия
}

Это альтернатива записи синтаксиса из раздела 2.1. Если использовать полный синтаксис, выглядело бы это примерно так:

_, ok := words[key]
if ok{
//какие-то действия
}

Не особо-то сэкономили строки кода, но полезно знать что есть и такой синтаксис.

3. Выводы

Лучше закрепил информацию о картах в Go, а также попрактиковал комбинирование работы с массивами/срезами и картами. Далее систематизирую информацию:

В Go, срезы и карты являются двумя различными типами данных, предназначенными для разных задач.

Срезы:

  • Срезы представляют собой упорядоченную последовательность элементов.
  • Они удобны для доступа к элементам по индексу и выполнения операций, связанных с изменением размера или подмножествами данных.
  • Время выполнения операций в срезах обычно зависит от длины среза и почти независимо от объема данных, который они содержат.

Карты:

  • Карты являются неупорядоченной коллекцией пар ключ-значение.
  • Они отлично подходят для эффективного поиска значений по ключу.
  • Время выполнения операций в картах обычно зависит от количества элементов (пар ключ-значение) в карте. При большом количестве элементов время выполнения операций может увеличиться.

Карты - это ассоциативные массивы, представленные в виде ключ-значение. Они обеспечивают быстрый доступ к значениям по ключу, что делает их идеальным инструментом для поиска уникальных элементов в массиве. Карты реализованы с использованием хеш-таблиц, которые позволяют быстро и эффективно обрабатывать большие объемы данных.

Хеш-таблицы - это структура данных, которая использует хэш-функцию для преобразования ключа в индекс таблицы, где хранится соответствующее значение. Хэш-функция уникально определяет позицию элемента в таблице, что обеспечивает эффективный доступ к данным. Они обеспечивают константное время выполнения для операций вставки, поиска и удаления элементов под предположением хорошо рассеянной хэш-функции.

На сегодня всё. Спасибо, что дочитал публикацию до конца.

Успехов, бро!

--//--//--

Напоминаю, если захочешь купить курс от SkillBox, воспользуйся моей реферальной ссылкой. Ты получишь огромную скидку на курс и плюс в карму за помощь каналу.

Nick Seagrave seagrave https://unsplash.com/photos/1tpLdmxki-c
Nick Seagrave seagrave https://unsplash.com/photos/1tpLdmxki-c

Бро, ты уже здесь? 👉 Подпишись на канал для новичков «Войти в IT» в Telegram, будем изучать IT вместе 👨‍💻👩‍💻👨‍💻