Это статья об основах программирования на Go. На канале я рассказываю об опыте перехода в IT с нуля, структурирую информацию и делюсь мнением.
Хой, джедаи и амазонки!
Прошёл темы по MAP'ам (они же карты, они же отображения) и хочу поделиться примерами задач, которые себе придумал для закрепления материала. А также будет теория, чем полезны карты.
Напоминаю, что только так можно стать программистом - кодить, кодить по обучающим материалам, придумывать себе новые задачи, и вновь кодить, кодить и кодить.
1. Теория по MAP'ам
1.1. Что такое карты в Go?
Карты (maps) в языке Go - это удобная структура данных, которая позволяет хранить коллекции пар ключ-значение. Пример создания карты и обращения к ней:
Мы создали карту с именем database и прошлись по её значениям через ключевое слово range. Ключи не трогали, хотя могли - обычное ключевое слово range - работает точно так же, как с массивами или срезами.
Как видим, значения в карте напечатались не в порядке их указания в карте, а в случайном порядке. Такова особенность хранения в хеш-таблице (она под капотом карты). Если нам нужен упорядоченный порядок - нужно создавать массив из карты.
Как сказал выше, карты являются реализацией хеш-таблиц и обладают следующими принципами работы:
- Синтаксис: для создания карты используется ключевое слово map с указанием типов ключа и значения, например map[string]int для карты с ключом типа string и значением типа int.
- Уникальность ключей: ключи в карте должны быть уникальными. При попытке добавить элемент с уже существующим ключом, значение старого ключа будет заменено на новое. Это бывает полезно, когда нужно посчитать количество только уникальных элементов в массиве.
- Добавление элементов: новые элементы могут быть добавлены в карту с помощью оператора присваивания (=) и указания ключа и значения.
- Получение значения по ключу: для получения значения из карты используется синтаксис mapName[key], где mapName - имя карты, а key - ключ, по которому нужно получить значение.
- Удаление элементов: для удаления элемента из карты используется встроенная функция delete(mapName, key), где mapName - имя карты, а key - ключ элемента, который нужно удалить.
- Проверка наличия ключа: мы можем проверить наличие ключа в карте, используя синтаксис value, ok := mapName[key]. Если значение ok равно true, значит ключ присутствует в карте, а значение по ключу из карты будет сохранено в переменной value.
- Итерация по картам: мы можем итерироваться по всем элементам карты с помощью цикла for range. В каждой итерации мы получаем ключ и значение текущего элемента карты.
Карты в Go предоставляют эффективный способ хранения и доступа к данным по ключу. Они широко используются для разных целей, включая индексирование данных, подсчет, группировку и отображение данных. Подробнее о целях см. ниже.
1.2. Для чего полезны MAP'ы в Go?
Основное назначение карт следующее:
- Индексирование данных: карты позволяют эффективно хранить и получать значения по уникальному ключу. Это особенно полезно для операций поиска и доступа к данным.
- Подсчет и агрегация: карты позволяют собирать и агрегировать данные, помогая в подсчете количества вхождений элементов, группировке данных и создании статистических отчетов.
- Удаление и добавление элементов: карты предоставляют возможность динамического добавления и удаления элементов, что делает их подходящими для сценариев, где требуется изменяемая коллекция данных.
- Кэширование данных: карты могут использоваться для кэширования данных, что позволяет улучшить производительность путем сохранения уже вычисленных значений для будущего использования.
- Отображение данных: карты предоставляют удобный способ представления сложных структур данных, позволяя ассоциировать информацию с уникальными ключами.
Разумеется, список приведенных целей использует лишь некоторые из возможностей карт в 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. Добавить и удалить элементы из карты
Ещё несколько задач для наработки навыка общения с картами.
По легенде выяснилось, что крокодилов в заповеднике нет, его туда добавили по ошибке. Но есть два других вида: сокол и паук. Нужно поправить код.
Вот что имеется - карта со зверями в ключах:
Вот что получается после правок:
Что здесь происходит новенького:
- В строке 16 мы удалили элемент из карты по ключу "Крокодил", применив ключевое слово delete.
- Добавляем элементы в карту в строках 17-18 за счёт индексации ([ ]) ключей "Сокол" и "Паук".
- Печатаем ключи. Обратите внимание, когда при применении ключевого слова range, требуется обращение только к ключам (в картах) или к индексам (в массивах), то не требуется писать , _ после, т.е. вместо:
for key, _ := range ...
можно написать:
for key := range ...
В общем, всё - готово. Удалили лишний ключ, добавили новые. Хорошо было бы их теперь напечатать в алфавитном порядке, а не как попало.
2.4. Напечатать ключи карты в алфавитном порядке
Задача: нужно напечатать всех животных из карты в алфавитном порядке.
Для этого создадим отдельный срез, в который будем помещать ключи карты, а затем сортировать элементы среза в алфавитном порядке.
Пусть у среза будет нулевая длина, а ёмкость хотя можем также сделать нулевой - но для улучшения производительности, зададим ёё. Мы знаем ёмкость создаваемого среза - она равна длине карты.
Что здесь происходит:
- В строках 9-19 создана карта с ключами - известными нам зверями в заповеднике.
- Строка 22: создаём срез с ёмкостью, равной длине карты.
- Строка 23: заполняем срез ключами карты в хаотичном порядке.
- Строка 26: сортируем в алфавитном порядке элементы среза с помощью функции Strings пакета sort.
- Строка 27: печатаем элементы среза.
По поводу пакета sort. Вот что о функции Strings говорит официальная документация:
Напоминаю, правило хорошего тона - читаем официальную документацию о новых инструментах, с которыми знакомимся в ходе работы: увидели в чужом коде, подсказала нейросетка или что-то ещё.
2.5. Поиск ключевых слов
По легенде в администрацию заповедника приходит много писем. Все интересуются, как дела у животных, чем помочь и вообще, что новенького. Чтобы автоматизировать работу, требуется написать программу, которая по ключевым словам из тела письма будет рассылать письма на отдельные службы.
На вход подаётся строка - письмо email. На выходе мы должны получить - кому направить письмо.
Здесь я всё упрощаю - важна суть.
Пусть у нас будет пять служб, кому может прийти письмо:
- Экскурсии.
- Сотрудничество.
- Новости.
- Благотворительность.
- Неизвестный запрос - требуется участие администратора.
Мы будем разбивать строку на массив по пробелу. Смотрим, что получается:
Что здесь происходит:
- В строках 9-12 я прописываю текст полученного email. Странную структуру через "плюс" делаю для удобства отображения текста в IDE.
- В строке 19 создаю переменную и вызываю функцию для создания карты.
- В строке 36 создаю пустую карту.
- В строках 37-39 заполняю ключи карты элементами среза.
- Возвращаемся в строку 19, здесь в созданную переменную записываем результат выполнения функции createMapWords - карту.
- Выполняем ряд проверок на ключевые слова. Ключевые слова я выбрал так, как посчитал нужным. Они могут быть и другими или их может быть несколько. В данном случае найдена строка "экскурсию" в ключах карты.
- В строке 15 печатаем, в какой отдел по мнению программы нужно чтобы ушло письмо.
Пример упрощённый, но вполне реальная задача на мой взгляд. Из самого крутого здесь - этот фрагмент кода:
if _, ok := words[key]; ok {
//какие-то действия
}
Это альтернатива записи синтаксиса из раздела 2.1. Если использовать полный синтаксис, выглядело бы это примерно так:
_, ok := words[key]
if ok{
//какие-то действия
}
Не особо-то сэкономили строки кода, но полезно знать что есть и такой синтаксис.
3. Выводы
Лучше закрепил информацию о картах в Go, а также попрактиковал комбинирование работы с массивами/срезами и картами. Далее систематизирую информацию:
В Go, срезы и карты являются двумя различными типами данных, предназначенными для разных задач.
Срезы:
- Срезы представляют собой упорядоченную последовательность элементов.
- Они удобны для доступа к элементам по индексу и выполнения операций, связанных с изменением размера или подмножествами данных.
- Время выполнения операций в срезах обычно зависит от длины среза и почти независимо от объема данных, который они содержат.
Карты:
- Карты являются неупорядоченной коллекцией пар ключ-значение.
- Они отлично подходят для эффективного поиска значений по ключу.
- Время выполнения операций в картах обычно зависит от количества элементов (пар ключ-значение) в карте. При большом количестве элементов время выполнения операций может увеличиться.
Карты - это ассоциативные массивы, представленные в виде ключ-значение. Они обеспечивают быстрый доступ к значениям по ключу, что делает их идеальным инструментом для поиска уникальных элементов в массиве. Карты реализованы с использованием хеш-таблиц, которые позволяют быстро и эффективно обрабатывать большие объемы данных.
Хеш-таблицы - это структура данных, которая использует хэш-функцию для преобразования ключа в индекс таблицы, где хранится соответствующее значение. Хэш-функция уникально определяет позицию элемента в таблице, что обеспечивает эффективный доступ к данным. Они обеспечивают константное время выполнения для операций вставки, поиска и удаления элементов под предположением хорошо рассеянной хэш-функции.
На сегодня всё. Спасибо, что дочитал публикацию до конца.
Успехов, бро!
--//--//--
Напоминаю, если захочешь купить курс от SkillBox, воспользуйся моей реферальной ссылкой. Ты получишь огромную скидку на курс и плюс в карму за помощь каналу.
Бро, ты уже здесь? 👉 Подпишись на канал для новичков «Войти в IT» в Telegram, будем изучать IT вместе 👨💻👩💻👨💻