Table: словари в Nim
Разбираемся, как в Nim устроены словари — тип Table из модуля std/tables — и чем работа с парами ключ-значение похожа на dict из Python.
Table — тип данных для хранения пар «ключ — значение» в Nim, доступный через стандартный модуль
std/tables, аналог словаряdictв Python.
Зачем нужны пары ключ-значение
Мы уже умеем хранить список однотипных значений через seq — но что, если данные естественно связаны парами? Например, нужно быстро находить цену товара по его названию, или возраст ученика по имени. Можно было бы завести два параллельных seq — один с именами, другой с возрастами — и искать совпадающий индекс, но это неудобно и медленно при большом количестве данных: пришлось бы перебирать весь список, чтобы найти нужную пару.
Для таких задач существует Table — структура, которая хранит пары «ключ — значение» и умеет находить значение по ключу почти мгновенно, независимо от того, сколько всего пар она содержит. Если вы знакомы с Python, это прямой аналог dict: vozrast = {"Petya": 14, "Masha": 15} в Python и похожий Table в Nim решают одну и ту же задачу.
Импорт модуля tables
В отличие от типов вроде seq, которые доступны сразу, Table нужно явно подключить через import — в Nim стандартная библиотека разбита на модули, и не все из них загружаются в программу автоматически, чтобы не раздувать код лишним.
import std/tables
var vozrast = initTable[string, int]()
vozrast["Petya"] = 14
vozrast["Masha"] = 15
echo vozrast["Petya"]Вывод:
14Разбираем построчно
Строка import std/tables подключает модуль словарей из стандартной библиотеки — std/ здесь означает «стандартная библиотека», а tables — конкретный модуль внутри неё. Без этой строки имя Table и функция initTable были бы просто неизвестны компилятору.
Дальше initTable[string, int]() создаёт пустой словарь и явно указывает два типа в квадратных скобках: тип ключей (string — имена учеников) и тип значений (int — возраст). Это похоже на то, как мы указывали один тип элемента для seq[int], только здесь типов два — для ключа и для значения отдельно. Круглые скобки в конце () — обычный вызов функции, создающей пустую структуру.
Строки vozrast["Petya"] = 14 добавляют пару в словарь — синтаксис с квадратными скобками для записи по ключу выглядит так же, как в Python. А echo vozrast["Petya"] читает значение по ключу тем же способом — если ключ существует, вернётся связанное с ним значение.
Проверка наличия ключа
Что если обратиться по ключу, которого в словаре нет? Как и в Python, это вызовет ошибку — поэтому перед обращением к потенциально отсутствующему ключу принято сначала проверять его наличие через hasKey.
import std/tables
var ocenki = initTable[string, float]()
ocenki["Petya"] = 4.5
if ocenki.hasKey("Masha"):
echo "Балл Маши: ", ocenki["Masha"]
else:
echo "Записи о Маше пока нет"Вывод:
Записи о Маше пока нетЗдесь ocenki.hasKey("Masha") возвращает bool — true, если ключ есть в таблице, и false в противном случае. Это тот же паттерн, что и в Python-коде вида if "Masha" in ocenki:, только оформленный как вызов метода, а не оператор in.
Перебор пар: pairs
Чтобы пройтись по всем парам ключ-значение сразу, используется функция pairs в цикле for.
import std/tables
var tsveta = initTable[string, string]()
tsveta["yabloko"] = "krasnyy"
tsveta["limon"] = "zheltyy"
tsveta["list"] = "zelenyy"
for klyuch, znachenie in pairs(tsveta):
echo klyuch, " — ", znachenieВывод:
yabloko — krasnyy
limon — zheltyy
list — zelenyyКонструкция for klyuch, znachenie in pairs(tsveta) одновременно достаёт и ключ, и значение на каждой итерации — в переменные с именами klyuch и znachenie (их можно назвать как угодно, это не зарезервированные слова). Важно: порядок перебора пар в Table не гарантированно совпадает с порядком добавления — если порядок важен для вашей задачи, его нужно обеспечивать отдельно, например через сортировку ключей.
Мини-пример: подсчёт слов
Соберём импорт, запись, проверку и перебор в одной практической задаче — посчитаем, сколько раз встречается каждое слово в списке.
import std/tables
let slova = @["kot", "pes", "kot", "popugay", "kot", "pes"]
var schetchik = initTable[string, int]()
for slovo in slova:
if schetchik.hasKey(slovo):
schetchik[slovo] = schetchik[slovo] + 1
else:
schetchik[slovo] = 1
for slovo, kolichestvo in pairs(schetchik):
echo slovo, ": ", kolichestvoВывод:
kot: 3
pes: 2
popugay: 1Здесь для каждого слова из seq мы проверяем, встречалось ли оно раньше: если да — увеличиваем счётчик на 1, если нет — заводим новую запись со значением 1. Это классическая задача «подсчёт частот», которая на словарях решается в разы проще и быстрее, чем на паре параллельных seq с поиском по индексу.
Частые ошибки новичков
Первая и самая частая ошибка — забыть import std/tables в начале файла и получить ошибку компиляции «undeclared identifier» на слове Table или initTable, хотя всё остальное написано верно.
Вторая ошибка — обратиться к несуществующему ключу без предварительной проверки через hasKey, из-за чего программа завершается с ошибкой во время выполнения. В реальном коде почти всегда стоит либо проверять hasKey заранее, либо использовать более безопасные способы чтения, если задача это позволяет.
Третья ошибка — ожидать от Table сохранения порядка добавления пар, как в современных версиях dict в Python (начиная с Python 3.7 порядок там гарантирован). В Nim обычная Table такой гарантии не даёт — для задач, где порядок важен, нужно либо сортировать ключи перед выводом, либо использовать специальный упорядоченный вариант таблицы.
Итоги
Tableхранит пары «ключ — значение» и требует явногоimport std/tablesперед использованием — в отличие отseq, который доступен сразу.initTable[K, V]()создаёт пустой словарь с явно указанными типами ключа и значения; запись и чтение — через квадратные скобки, как в Python.hasKeyпроверяет наличие ключа перед обращением к нему — обращение к несуществующему ключу без проверки завершает программу ошибкой.pairs()в циклеforперебирает сразу и ключи, и значения, но без гарантии порядка добавления.