Как скопировать таблицу Lua по значению?
Вопрос
Недавно я написал немного кода Lua, что-то вроде:
local a = {}
for i = 1, n do
local copy = a
-- alter the values in the copy
end
Очевидно, это было не то, что я хотел сделать, поскольку в Lua переменные содержат ссылки на анонимную таблицу, а не сами значения таблицы.Это четко изложено в Программирование на Lua, но я забыл об этом.
Итак, вопрос в том, что мне написать вместо copy = a
чтобы получить копию значений в a
?
Решение
Чтобы поиграть в гольф с читаемым кодом, вот короткая версия, которая обрабатывает стандартные сложные случаи:
- таблицы как ключи,
- сохранение метатаблиц и
- рекурсивные таблицы.
Мы можем сделать это в 7 строк:
function copy(obj, seen)
if type(obj) ~= 'table' then return obj end
if seen and seen[obj] then return seen[obj] end
local s = seen or {}
local res = setmetatable({}, getmetatable(obj))
s[obj] = res
for k, v in pairs(obj) do res[copy(k, s)] = copy(v, s) end
return res
end
Краткое описание операций глубокого копирования Lua есть в эта суть.
Еще одна полезная ссылка: эта вики-страница пользователей Lua, который включает пример того, как избежать __pairs
метаметод.
Другие советы
Копия таблицы имеет много потенциальных определений. Это зависит от того, хотите ли вы простое или глубокое копирование, хотите ли вы копировать, делиться или игнорировать метатаблицы и т. Д. Не существует единой реализации, которая могла бы удовлетворить всех.
Один из подходов состоит в том, чтобы просто создать новую таблицу и продублировать все пары ключ / значение:
function table.shallow_copy(t)
local t2 = {}
for k,v in pairs(t) do
t2[k] = v
end
return t2
end
copy = table.shallow_copy(a)
Обратите внимание, что вы должны использовать пары
вместо ipairs
, поскольку ipairs
выполняет итерацию только по подмножеству ключей таблицы (т.е. последовательный положительный результат целочисленные ключи, начинающиеся с единицы в порядке возрастания).
Чтобы проиллюстрировать это, мой личный table.copy
также обращает внимание на метатаблицы:
function table.copy(t)
local u = { }
for k, v in pairs(t) do u[k] = v end
return setmetatable(u, getmetatable(t))
end
Не существует функции копирования, достаточно широко согласованной для того, чтобы называть ее "стандартной".
Полная версия глубокого копирования, охватывающая все 3 ситуации:
<Ол>Общая версия:
local function deepcopy(o, seen)
seen = seen or {}
if o == nil then return nil end
if seen[o] then return seen[o] end
local no
if type(o) == 'table' then
no = {}
seen[o] = no
for k, v in next, o, nil do
no[deepcopy(k, seen)] = deepcopy(v, seen)
end
setmetatable(no, deepcopy(getmetatable(o), seen))
else -- number, string, boolean, etc
no = o
end
return no
end
Или версия таблицы:
function table.deepcopy(o, seen)
seen = seen or {}
if o == nil then return nil end
if seen[o] then return seen[o] end
local no = {}
seen[o] = no
setmetatable(no, deepcopy(getmetatable(o), seen))
for k, v in next, o, nil do
k = (type(k) == 'table') and k:deepcopy(seen) or k
v = (type(v) == 'table') and v:deepcopy(seen) or v
no[k] = v
end
return no
end
На основании lua-users.org/wiki/CopyTable и Алан Йейтс 'функции.
Опционально глубокая рекурсивная версия общего графов:
function table.copy(t, deep, seen)
seen = seen or {}
if t == nil then return nil end
if seen[t] then return seen[t] end
local nt = {}
for k, v in pairs(t) do
if deep and type(v) == 'table' then
nt[k] = table.copy(v, deep, seen)
else
nt[k] = v
end
end
setmetatable(nt, table.copy(getmetatable(t), deep, seen))
seen[t] = nt
return nt
end
Возможно, метатабельная копия также должна быть необязательной?
Вот что я на самом деле сделал:
for j,x in ipairs(a) do copy[j] = x end
Как Сомнение упоминает , если ключи таблицы не являются строго монотонно растущими, это должны быть пары
not ipairs
.
Я также нашел deepcopy
, которая является более надежной
function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
Он обрабатывает таблицы и метатаблицы, вызывая себя рекурсивно (, что является его собственной наградой ). Один из умных битов заключается в том, что вы можете передать ему любое значение (будь то таблица или нет), и оно будет скопировано правильно. Однако стоимость заключается в том, что он потенциально может переполнить стек. Поэтому может потребоваться еще более надежная (нерекурсивная) функция .
Но это слишком излишне для очень простого случая, когда нужно скопировать массив в другую переменную.
(к сожалению, слегка документированный) stdlib имеет ряд ценных расширений для нескольких библиотек поставляется со стандартным дистрибутивом Lua. Среди них несколько вариаций на тему копирования и объединения таблиц. Р>
Эта библиотека также включена в Lua для Windows и, вероятно, должна быть частью любого серьезный набор инструментов пользователя Lua.
При реализации таких вещей вручную необходимо убедиться в правильной обработке метатаблиц. Для простых приложений таблицы как структуры у вас, вероятно, нет метатаблиц, и простой цикл, использующий pair ()
, является приемлемым ответом. Но если таблица используется как дерево, или содержит циклические ссылки, или имеет метатаблицы, то все становится более сложным.
Не забывайте, что функции также являются ссылками, поэтому, если вы хотите полностью «скопировать» все значения, вам также понадобятся отдельные функции; однако единственный способ, которым я знаю, чтобы скопировать функцию, это использовать loadstring (string.dump (func))
, который, согласно справочному руководству Lua, не работает для функций с повышенными значениями. р>
do
local function table_copy (tbl)
local new_tbl = {}
for key,value in pairs(tbl) do
local value_type = type(value)
local new_value
if value_type == "function" then
new_value = loadstring(string.dump(value))
-- Problems may occur if the function has upvalues.
elseif value_type == "table" then
new_value = table_copy(value)
else
new_value = value
end
new_tbl[key] = new_value
end
return new_tbl
end
table.copy = table_copy
end
Предупреждение: помечено решение НЕПРАВИЛЬНО !
Если таблица содержит таблицы, ссылки на эти таблицы все равно будут использоваться. Я два часа искал ошибку, которую совершил, хотя это было из-за использования приведенного выше кода.
Итак, вам нужно проверить, является ли значение таблицей или нет. Если это так, вы должны вызывать table.copy рекурсивно!
Это правильная функция table.copy:
function table.copy(t)
local t2 = {};
for k,v in pairs(t) do
if type(v) == "table" then
t2[k] = table.copy(v);
else
t2[k] = v;
end
end
return t2;
end
Примечание. Это также может быть неполным, если таблица содержит функции или другие специальные типы, но это возможно, что большинству из нас не нужно. Приведенный выше код легко адаптируется для тех, кто в нем нуждается.
Это так же хорошо, как вы получите для базовых таблиц. Используйте что-то вроде deepcopy, если вам нужно скопировать таблицы с метатаблицами.
Я думаю, что причина, по которой Lua не имеет table.copy () в своих стандартных библиотеках, заключается в том, что задача не является точной для определения. Как уже показано здесь, можно сделать копию «на один уровень глубже» (что вы сделали), глубокая копия с или без учета возможных дубликатов ссылок. А потом есть метатаблицы.
Лично я все же хотел бы, чтобы они предложили встроенную функцию. Только если люди не будут довольны его семантикой, им придется пойти и сделать это самостоятельно. Однако не очень часто у человека есть необходимость в копировании по значению.
В большинстве случаев, когда мне нужно было копировать таблицу, я хотел иметь копию, которая ничего не делит с оригиналом, чтобы любая модификация исходной таблицы не влияла на копию (и наоборот ). р>
Все фрагменты, которые были показаны до сих пор, не могут создать копию для таблицы, которая может иметь общие ключи или ключи с таблицами, поскольку те, которые будут оставлены, указывают на исходную таблицу. Это легко увидеть, если вы попытаетесь скопировать таблицу, созданную как: a = {}; a [a] = a
. deepcopy , на которую ссылается Джон, позаботится об этом, поэтому если вам нужно создать реальную / полную копию , deepcopy
должен быть использован.
Используйте библиотеку penlight здесь: https://stevedonovan.github.io/Penlight/api/libraries /pl.tablex.html#deepcopy р>
local pl = require 'pl.import_into'()
local newTable = pl.tablex.deepcopy(oldTable)
Это может быть самый простой метод:
local data = {DIN1 = "Input(z)", DIN2 = "Input(y)", AINA1 = "Input(x)"}
function table.copy(mytable) --mytable = the table you need to copy
newtable = {}
for k,v in pairs(mytable) do
newtable[k] = v
end
return newtable
end
new_table = table.copy(data) --copys the table "data"
В моей ситуации, когда информация в таблице представляет собой только данные и другие таблицы (за исключением функций,...), выигрышным решением является следующая строка кода:
local copyOfTable = json.decode( json.encode( sourceTable ) )
Я пишу код Lua для домашней автоматизации на Fibaro Home Center 2.Реализация Lua очень ограничена, нет центральной библиотеки функций, к которой вы могли бы обратиться.Каждая функция должна быть объявлена в коде, чтобы поддерживать работоспособность кода, поэтому однострочные решения, подобные этому, являются предпочтительными.