Перейти к содержанию
Материалы раздела

M*LIB

Модуль mlib предоставляет контейнеры на основе M*LIB — библиотеки контейнеров для C, состоящей только из заголовочных файлов.

Модуль включает следующие контейнеры:

  • array — динамический массив;
  • dict — хеш-таблица со строковыми ключами;
  • deque — двусторонняя очередь;
  • rbtree — сортированный словарь на основе красно-чёрного дерева;
  • prioqueue — очередь с числовыми приоритетами;
  • bitset — динамический битовый набор;
  • set — хеш-множество целых чисел.

Дополнительные функции:

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, filterreduce
array, deque, setfunction(value)function(acc, value)
dict, rbtreefunction(key, value)function(acc, key, value)
bitsetfunction(index, is_set) только для for_eachfunction(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()