M*LIB
Модуль mlib предоставляет контейнеры на основе M*LIB — библиотеки контейнеров для C, состоящей только из заголовочных файлов.
Модуль включает следующие контейнеры:
- array — динамический массив;
- dict — хеш-таблица со строковыми ключами;
- deque — двусторонняя очередь;
- rbtree — сортированный словарь на основе красно-чёрного дерева;
- prioqueue — очередь с числовыми приоритетами;
- bitset — динамический битовый набор;
- set — хеш-множество целых чисел.
Дополнительные функции:
- сериализация —
save/loadиdumps/loadsдля всех контейнеров, кромеprioqueue; - функциональные операции —
for_each,map,reduceиfilterс ограничениями по типам контейнеров.
array, dict, deque, rbtree и значения prioqueue хранят ссылки на
Lua-значения. Ключи dict и rbtree должны быть строками без нулевого байта,
приоритет prioqueue — конечным числом, элементы set — целыми числами.
bitset хранит только биты.
Сериализация
Сериализация поддерживает array, dict, deque, rbtree, set и bitset. Очередь prioqueue сериализовать нельзя.
В значениях поддерживаются только nil, boolean, integer, number и string. Таблицы, функции, потоки и userdata не поддерживаются. Максимальный размер сериализованного объекта — 524 288 байт; один контейнер может содержать до 4096 элементов, одна строка — до 65 535 байт, а bitset — до 262 144 бит.
Ограничение max_size у deque в формат не записывается: загруженная очередь
не ограничена.
Формат внутренний и не содержит версии. Не используйте его как переносимый протокол между устройствами с разной архитектурой или разными версиями прошивки.
mlib.save(obj, filename)
Сохраняет контейнер в файл и возвращает true. Ошибка открытия файла, неподдерживаемое значение или превышение лимита вызывает исключение.
local arr = mlib.array()
arr:push(42)
arr:push("hello")
arr:push(3.14)
assert(mlib.save(arr, "/data.bin"))
obj, err = mlib.load(filename)
Загружает контейнер из файла. Тип определяется автоматически. При ошибке возвращает nil, err.
local loaded, err = mlib.load("/data.bin")
assert(loaded, err)
print(loaded:get(1)) -- 42
str = mlib.dumps(obj)
Сериализует контейнер в бинарную строку. Неподдерживаемое значение или превышение лимита вызывает исключение.
local d = mlib.dict()
d:set("name", "test")
d:set("value", 123)
local data = mlib.dumps(d)
obj, err = mlib.loads(str)
Десериализует контейнер из бинарной строки. При ошибке возвращает nil, err.
local received, err = mlib.loads(data)
assert(received, err)
print(received:get("name")) -- "test"
Функциональные операции
Сигнатура функции обратного вызова зависит от контейнера:
| Контейнер | for_each, map, filter | reduce |
|---|---|---|
array, deque, set | function(value) | function(acc, value) |
dict, rbtree | function(key, value) | function(acc, key, value) |
bitset | function(index, is_set) только для for_each | function(acc, index, is_set) |
prioqueue | не поддерживается | не поддерживается |
map и filter не поддерживают bitset. Для dict и rbtree функция
map меняет значения, сохраняя ключи. Для set функция map должна возвращать
целое число. Результат map или filter для deque не наследует ограничение
max_size. Изменять исходный контейнер внутри функции обратного вызова нельзя.
mlib.for_each(container, callback)
Вызывает callback для каждого элемента. Возвращаемое значение функции игнорируется.
local arr = mlib.array()
arr:push(1)
arr:push(2)
arr:push(3)
mlib.for_each(arr, function(value)
print(value)
end)
result = mlib.map(container, callback)
Преобразует элементы и возвращает новый контейнер того же типа.
local arr = mlib.array()
arr:push(1)
arr:push(2)
arr:push(3)
local doubled = mlib.map(arr, function(value)
return value * 2
end)
-- doubled содержит: 2, 4, 6
result = mlib.reduce(container, callback, initial)
Последовательно сворачивает контейнер в одно значение, начиная с initial.
local arr = mlib.array()
arr:push(1)
arr:push(2)
arr:push(3)
local sum = mlib.reduce(arr, function(acc, value)
return acc + value
end, 0)
-- sum = 6
result = mlib.filter(container, callback)
Возвращает новый контейнер того же типа только с теми элементами, для которых callback вернул истинное значение.
local arr = mlib.array()
arr:push(1)
arr:push(2)
arr:push(3)
arr:push(4)
local evens = mlib.filter(arr, function(value)
return value % 2 == 0
end)
-- evens содержит: 2, 4
Array (динамический массив)
Динамический массив с произвольным доступом O(1). Вставка и удаление в конце выполняются за амортизированное O(1), в середине — за O(n).
arr = mlib.array()
Создать новый пустой массив.
local mlib = require("mlib")
local arr = mlib.array()
arr:push(value)
Добавить элемент в конец массива.
arr:push(42)
arr:push("hello")
arr:push({x = 1, y = 2})
value = arr:pop()
Извлекает и удаляет последний элемент. Возвращает nil, если массив пуст.
value = arr:get(index)
Возвращает элемент по индексу с нумерацией от 1 или nil, если индекс вне
диапазона.
arr:set(index, value)
Установить значение по индексу.
size = arr:size()
Возвращает количество элементов. Также поддерживается #arr.
arr:insert(index, value)
Вставить элемент в указанную позицию.
value = arr:remove(index)
Удалить элемент по индексу и вернуть его значение.
arr:clear()
Удалить все элементы.
arr:iter(callback)
Вызывает callback(index, value) для всех элементов. Индексы начинаются с 1. Для досрочного завершения callback может вернуть false; изменять массив внутри неё нельзя.
arr:iter(function(i, v)
print(string.format("[%d] = %s", i, tostring(v)))
end)
table = arr:to_table()
Экспортировать массив в Lua-таблицу.
arr:from_table(table)
Добавить элементы из таблицы.
Dict (хеш-таблица)
Хеш-таблица со строковыми ключами. Доступ, вставка и удаление O(1) в среднем.
d = mlib.dict()
Создать новый пустой словарь.
local d = mlib.dict()
d:set(key, value)
Добавить или обновить пару ключ-значение.
d:set("name", "ESP32")
d:set("temp", 25.5)
d:set("config", {pin = 4, mode = "output"})
value = d:get(key)
Возвращает значение по ключу или nil, если ключ не найден.
value = d:del(key)
Удалить пару по ключу и вернуть значение.
exists = d:has(key)
Проверить существование ключа.
size = d:size()
Возвращает количество пар. Также поддерживается #d.
keys = d:keys()
Возвращает список всех ключей. Порядок ключей не определён.
d:clear()
Удалить все пары.
d:iter(callback)
Вызывает callback(key, value) для всех пар. Для досрочного завершения callback может вернуть false; изменять словарь внутри неё нельзя. Порядок обхода не определён.
table = d:to_table()
Экспортировать в Lua-таблицу.
d:from_table(table)
Добавляет или обновляет пары со строковыми ключами. Пары с ключами других типов игнорируются.
Deque (двусторонняя очередь)
Двусторонняя очередь с произвольным доступом O(1). Вставка/удаление с обоих концов O(1).
dq = mlib.deque([max_size])
Создаёт пустую очередь. Значение max_size ограничивает её размер; 0 по умолчанию означает отсутствие ограничения.
При заполненной ограниченной очереди push_back удаляет первый элемент, а push_front — последний. insert в заполненную очередь вызывает исключение.
dq:push_back(value)
Добавить элемент в конец.
dq:push_front(value)
Добавить элемент в начало.
value = dq:pop_back()
Извлекает и удаляет последний элемент. Для пустой очереди возвращает nil.
value = dq:pop_front()
Извлекает и удаляет первый элемент. Для пустой очереди возвращает nil.
value = dq:front()
Возвращает первый элемент без удаления или nil для пустой очереди.
value = dq:back()
Возвращает последний элемент без удаления или nil для пустой очереди.
value = dq:get(index)
Возвращает элемент по индексу с нумерацией от 1 или nil, если индекс вне диапазона.
dq:set(index, value)
Установить элемент по индексу.
dq:insert(index, value)
Вставить элемент в указанную позицию. Элементы после позиции сдвигаются.
value = dq:remove(index)
Удаляет элемент по индексу и возвращает его значение. Если индекс вне
диапазона, возвращает nil.
dq:reverse()
Развернуть очередь (первый становится последним и наоборот).
size = dq:size()
Получить количество элементов. Также поддерживается #dq.
max_size = dq:max_size([value])
Без аргумента возвращает ограничение размера. С аргументом устанавливает его и возвращает новое значение. Если новое ненулевое ограничение меньше текущего размера, лишние элементы удаляются с начала очереди.
dq:clear()
Удалить все элементы.
dq:iter(callback)
Вызывает callback(index, value) для всех элементов. Для досрочного завершения callback может вернуть false; изменять очередь внутри неё нельзя.
table = dq:to_table()
Экспорт в таблицу.
RBTree (красно-чёрное дерево)
Сортированный словарь на основе красно-чёрного дерева. Поиск, вставка и удаление выполняются за O(log n). Обход идёт в порядке сортировки ключей.
rb = mlib.rbtree()
Создать новое пустое дерево.
local rb = mlib.rbtree()
rb:set(key, value)
Добавить или обновить пару ключ-значение.
rb:set("apple", 1)
rb:set("banana", 2)
rb:set("cherry", 3)
value = rb:get(key)
Получить значение по ключу.
value = rb:del(key)
Удалить пару по ключу и вернуть значение.
exists = rb:has(key)
Проверить существование ключа.
size = rb:size()
Получить количество пар. Также поддерживается #rb.
key, value = rb:min()
Возвращает пару с минимальным ключом или nil, nil для пустого дерева.
local key, value = rb:min() -- "apple", 1
key, value = rb:max()
Возвращает пару с максимальным ключом или nil, nil для пустого дерева.
local key, value = rb:max() -- "cherry", 3
keys = rb:keys()
Получить список всех ключей (отсортированный).
rb:clear()
Удалить все пары.
rb:iter(callback)
Вызывает callback(key, value) в порядке сортировки ключей. Для досрочного завершения callback может вернуть false; изменять дерево внутри неё нельзя.
rb:iter(function(k, v)
print(k, v)
end)
-- apple 1
-- banana 2
-- cherry 3
table = rb:to_table()
Экспортировать в Lua-таблицу.
PrioQueue (очередь с приоритетами)
Очередь с приоритетами (min-heap). Вставка O(log n), извлечение минимума O(log n).
pq = mlib.prioqueue()
Создать новую очередь.
local pq = mlib.prioqueue()
pq:push(priority, value)
Добавить элемент с указанным конечным числовым приоритетом. Меньший приоритет
извлекается раньше. NaN и бесконечность вызывают исключение.
pq:push(3, "низкий приоритет")
pq:push(1, "высокий приоритет")
pq:push(2, "средний приоритет")
priority, value = pq:pop()
Извлечь и удалить элемент с наименьшим приоритетом.
local priority, value = pq:pop() -- 1, "высокий приоритет"
priority, value = pq:pop() -- 2, "средний приоритет"
priority, value = pq:pop() -- 3, "низкий приоритет"
priority, value = pq:top()
Возвращает элемент с наименьшим приоритетом без удаления. top и pop возвращают nil, если очередь пуста.
size = pq:size()
Получить количество элементов. Также поддерживается #pq.
empty = pq:empty()
Проверить, пуста ли очередь.
pq:clear()
Удалить все элементы.
Bitset (битовый набор)
Битовый набор для работы с флагами и битовыми операциями. Динамический размер.
bs = mlib.bitset()
Создаёт пустой битовый набор размером 0. Перед обращением к отдельным битам задайте размер через resize или from_number.
local bs = mlib.bitset()
bs:resize(8)
bs:set(index)
Устанавливает бит в позиции index в 1. Индексация начинается с 0; индекс должен быть меньше bs:size().
bs:set(0) -- установить бит 0
bs:set(5) -- установить бит 5
bs:reset(index)
Сбросить бит в позиции index в 0.
bs:flip(index)
Инвертировать бит в позиции index.
value = bs:get(index)
Получить значение бита (true/false).
if bs:get(5) then
print("Бит 5 установлен")
end
size = bs:size()
Получить размер битового набора. Также поддерживается #bs.
count = bs:count()
Получить количество установленных битов (popcount).
bs:clear()
Очищает набор и устанавливает его размер в 0.
bs:resize(size)
Изменяет размер битового набора. Допустимо от 0 до 262 144 бит.
bs:set_all()
Установить все биты в 1.
bs:reset_all()
Сбросить все биты в 0.
bs:flip_all()
Инвертировать все биты.
bs:band(other)
Выполняет побитовое И с другим набором и изменяет текущий набор.
bs:bor(other)
Выполняет побитовое ИЛИ и изменяет текущий набор.
bs:bxor(other)
Выполняет побитовое исключающее ИЛИ и изменяет текущий набор.
После band, bor или bxor размер текущего набора равен меньшему из размеров двух операндов.
num = bs:to_number()
Преобразует набор в Lua-число. Если установленные биты не помещаются в целочисленный тип Lua или результат нельзя точно представить типом lua_Number, функция вызывает исключение.
local bs = mlib.bitset()
bs:resize(3)
bs:set(0)
bs:set(2)
local n = bs:to_number() -- 5 (двоичное 101)
bs:from_number(num [, bits])
Загружает биты из целого числа. bits задаёт размер набора; по умолчанию используется 32. Максимальное значение bits равно разрядности целочисленного типа Lua в текущей сборке.
bs:from_number(255, 8) -- 8 бит, все установлены
Set (хеш-множество целых чисел)
Хеш-множество для целых чисел. Добавление, поиск и удаление выполняются в среднем за O(1). Порядок элементов не определён.
s = mlib.set()
Создать новое пустое множество.
local mlib = require("mlib")
local s = mlib.set()
s:add(value)
Добавить число в множество.
s:add(12345)
s:add(67890)
s:add(111)
exists = s:has(value)
Проверяет наличие числа в множестве.
if s:has(12345) then
print("Число в множестве")
end
removed = s:del(value)
Удаляет число из множества. Возвращает true, если элемент был удалён, или false, если его не было.
s:del(111) -- true
s:del(999) -- false (не было)
size = s:size()
Возвращает количество элементов. Также поддерживается #s.
s:clear()
Удалить все элементы.
s:iter(callback)
Вызывает callback(value) для всех элементов. Для досрочного завершения callback может вернуть false; изменять множество внутри неё нельзя.
s:iter(function(v)
print(v)
end)
table = s:to_table()
Экспортирует множество в Lua-таблицу. Порядок элементов не определён.
local t = s:to_table() -- порядок элементов не определён
s:from_table(table)
Добавляет целочисленные элементы последовательной части таблицы. Значения других типов игнорируются.
s:from_table({1, 2, 3, 4, 5})
Примеры использования
Планировщик задач с приоритетами
local mlib = require("mlib")
local scheduler = mlib.prioqueue()
-- Добавляем задачи с приоритетами (меньше = важнее)
scheduler:push(1, {name = "critical_task", fn = function() print("CRITICAL!") end})
scheduler:push(5, {name = "normal_task", fn = function() print("normal") end})
scheduler:push(10, {name = "background_task", fn = function() print("background") end})
-- Выполняем задачи по приоритету
while not scheduler:empty() do
local prio, task = scheduler:pop()
print("Executing:", task.name, "priority:", prio)
task.fn()
end
Битовые флаги для GPIO
local mlib = require("mlib")
local gpio_state = mlib.bitset()
gpio_state:resize(32)
-- Константы пинов
local LED_PIN = 2
local BUTTON_PIN = 4
local RELAY_PIN = 5
-- Установить состояние
gpio_state:set(LED_PIN)
gpio_state:set(RELAY_PIN)
-- Проверить состояние
if gpio_state:get(BUTTON_PIN) then
print("Кнопка нажата")
end
-- Инвертировать LED
gpio_state:flip(LED_PIN)
-- Получить маску всех активных пинов
local active_mask = gpio_state:to_number()
print(string.format("Active pins mask: 0x%X", active_mask))
Сортированный словарь настроек
local mlib = require("mlib")
local settings = mlib.rbtree()
settings:set("brightness", 80)
settings:set("contrast", 50)
settings:set("volume", 70)
settings:set("wifi_enabled", true)
-- Вывести все настройки в алфавитном порядке
settings:iter(function(key, value)
print(key .. " = " .. tostring(value))
end)
-- Найти первую и последнюю настройку
local first_key, first_value = settings:min()
local last_key, last_value = settings:max()