Таблицы в Lua как основа для всех сложных типов данных
Опубликовано:
Разработчиков на других языках при знакомстве с Lua может удивить, что здесь есть только один структурированный тип table, от которого создаются таблицы. Привычных списков или массивов, словарей или отображений, множеств и другого в Lua нет. На первый взгляд это кажется ограничением, но на практике таблицы в Lua обладают такой гибкостью, которая позволяет эмулировать все остальное под разные задачи.
В Lua таблицы являются единственным составным типом данных. Однако их гибкость позволяет рассматривать таблицы скорее как основу для всех остальных сложных типов.
В конце статьи про переменные в Lua мы создавали и заполняли таблицу примерно так:
local t = {} -- создается пустая таблица t.a = 10 -- первое поле t.b = 'hello' -- второе поле print(t.a, t.b) -- 10 hello
Может показаться, что это поля (идентификаторы), которые находятся внутри объекта-таблицы. На самом деле выражение типа t.a — это синтаксический сахар языка Lua. В таблице не будет переменной a, там окажется ключ-строка "a". Убедиться в этом можно так:
print(t['a'], t['b']) -- 10 hello t['c'] = 2.3 print(t.c) -- 2.3
Использовать синтаксис через точку можно только в том случае, если имя поля соответствует правилам написания идентификаторов в Lua (начинается с буквы или подчеркивания, не содержит пробелов и спецсимволов).
Такие таблицы в Lua подобны словарям в Python. Из этого следует, что ключи в них не обязаны быть строками. В Lua ключом может быть любой тип данных, кроме nil (этот тип имеет только одно одноименное значение):
local t = {['qty']=100, b=5, [1]='hello', [0.1]='дробь'} print(t.qty) -- 100 print(t.b) -- 5 print(t[1]) -- hello print(t[0.1]) -- дробь
Синтаксис полей-идентификаторов допустим и при заполнении таблицы в момент ее создания. Поэтому таблицы в Lua могут выглядеть и использоваться как записи:
local book1 = {title="Hello World!", pages=10, author=nil}
Учитывая, что полям можно присваивать функции в качестве значений, таблицы в Lua также являются основой для объектно-ориентированного программирования (класс хранит методы, а объекты — это записи), что будет рассмотрено в отдельной статье.
Если таблица определяется так, что ключи не указываются, а перечисляются только значения, то создается последовательность (аналог списка и массива). При этом характерной особенностью Lua является начало индексации с единицы.
local n = {'one', 'two', 'three', 'four'} print(n[1]) -- one print(#n) -- 4 (длина последовательности)
Хранение в памяти таблицы–последовательности отличается. Lua выделяет для неё непрерывный блок памяти (массив), что существенно увеличивает скорость доступа к элементам (даже если элементы разного типа). Когда же таблица заполняется именованными или разнородными ключами, в памяти задействуется хеш-таблица.
Чтобы при удалении элементов последовательности гарантированно сохранить ее хранение в памяти в виде массива, следует использовать функцию remove из модуля table. При обычном присваивании элементу значения nil его ячейка очищается, но в массиве образуется "дыра". Это нарушает целостность последовательности и в дальнейшем, при изменении размеров таблицы, может привести к тому, что оставшиеся элементы переместятся в хеш-таблицу.
print(n[2]) -- two table.remove(n, 2) print(n[2]) -- three print(#n) -- 3
С другой стороны, когда таблица заполнена с помощью задаваемых ключей и является хеш-таблицей, удалять элементы из нее надо путем присваивания nil.
local t = {['qty']=100, b=5, [1]='hello', [0.1]='дробь'} t.b = nil t[1] = nil -- подсчет количества элементов, -- встроенной функции для хеш-таблиц нет local count = 0 for _ in pairs(t) do count = count + 1 end print(count) -- 2
Lua позволяет создавать смешанные таблицы, в которых часть данных хранится в массиве, а другая в хеш-таблице:
local n = {'one', 'two', a=100, 'four', b=20} print(n[1]) -- one print(n[2]) -- two print(n[3]) -- four print(n.a) -- 100 print(n.b) -- 20
Поскольку ключи обязаны быть уникальными, это свойство структур используется для эмуляции множеств в Lua. Ключам назначается роль значений, а в качестве значений им всем присваивается true.
local fruits = { apple = true, banana = true, orange = true } print(fruits.apple) -- true print(fruits.pineapple) -- nil fruits.pear = true -- добавление fruits.banana = true -- ничего не изменится fruits.apple = nil -- удаление элемента из множества
В конце отметим, что таблицы в Lua широко используются не только для создания списков, словарей и объектов, также в качестве модулей (описано в конце урока про переменные) и для хранения глобальной области видимости _G (глобальные переменные не приветствуются, но когда они появляются, то добавляются сюда). Таблица _G создаётся автоматически в момент запуска интерпретатора Lua. По умолчанию она уже имеет содержимое: хранит в себе ссылки на все встроенные функции, стандартные библиотеки (модули), системные переменные.
Кроме этого, в Lua на базе таблиц создают многомерные структуры (матрицы, графы), разреженные массивы и эффективные очереди, реализуют механизмы кэширования данных (мемоизации), а также настраивают прокси-объекты для контроля доступа к памяти и реактивного программирования