Как скопировать таблицу Lua по значению?
недавно я написал немного кода Lua что-то вроде:
local a = {}
for i = 1, n do
local copy = a
-- alter the values in the copy
end
очевидно, это было не то, что я хотел сделать, так как переменные содержат ссылки на анонимную таблицу, а не значения самой таблицы в Lua. Это четко изложено в программирование в Lua, но я забыл об этом.
Итак, вопрос в том, что я должен написать вместо copy = a
чтобы получить копию значений в a
?
15 ответов
чтобы играть немного читаемый-код-гольф, вот короткая версия, которая обрабатывает стандартные сложные случаи:
- таблицы как ключи,
- сохранение metatables, и
- рекурсивной таблицы.
мы можем сделать это в 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
метаметод.
таблица copy имеет много потенциальных определений. Это зависит от того, хотите ли вы простую или глубокую копию, хотите ли вы копировать, делиться или игнорировать метатабли и т. д. Нет ни одной реализации, которая могла бы удовлетворить всех.
один подход состоит в том, чтобы просто создать новую таблицу и скопировать все пары ключ/значение:
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)
обратите внимание, что вы должны использовать pairs
вместо ipairs
С ipairs
только перебирать подмножество ключей таблицы (т. е. подряд положительные целочисленные ключи, начинающиеся с единицы в порядке возрастания).
просто чтобы проиллюстрировать этот момент, мой личный table.copy
также обращает внимание на metatables:
function table.copy(t)
local u = { }
for k, v in pairs(t) do u[k] = v end
return setmetatable(u, getmetatable(t))
end
нет функции копирования, достаточно широко согласованной, чтобы называться "стандартной".
полная версия deep copy, обрабатывающая все 3 ситуации:
- таблица циклическая ссылка
- ключи, которые также являются таблицы
- Metatable
общие версии:
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
As Doub упоминает, если ваши клавиши таблицы не строго монотонно увеличиваются, это должно быть pairs
не 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.
одна вещь, чтобы убедиться в реализации вещей как это вручную является правильной обработкой metatables. Для простых приложений table-as-structure у вас, вероятно, нет никаких метатаблей и простого цикла с использованием pairs()
является приемлемым ответом. Но если таблица используется как дерево, или содержит циклические ссылки, или имеет метатабли, то все становится более сложным.
Не забывайте, что функции также являются ссылками, поэтому, если вы хотите полностью "скопировать" все значения, вам также нужно будет получить отдельные функции; однако единственный способ скопировать функцию-Использовать loadstring(string.dump(func))
, который согласно справочному руководству Lua, не работает для функций с upvalues.
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
Это так хорошо, как вы получите для базовых таблиц. Используйте что-то вроде deepcopy, если вам нужно скопировать таблицы с метатаблями.
Я думаю, причина, по которой у Lua нет таблицы.copy ()' в своих стандартных библиотеках потому, что задача не является точной для определения. Как показано уже здесь, можно сделать копию "на один уровень глубже" (что вы и сделали), deepcopy С или без заботы о возможных дублирующих ссылках. А еще есть метатабли.
лично я все равно хотел бы, чтобы они предложили встроенную функцию. Только если люди не будут довольны его семантикой, им придется сделать это самим. Не очень часто, однако, у человека действительно есть потребность в копировании по значению.
в большинстве случаев, когда мне нужно было скопировать таблицу, я хотел иметь копию, которая ничего не разделяет с оригиналом, так что любая модификация исходной таблицы не влияет на копию (и наоборот).
все фрагменты, которые были показаны до сих пор не удается создать копию для таблицы, которая может иметь общие ключи или ключи с таблицами, как те, которые будут слева, указывая на исходную таблицу. Это легко увидеть, если вы пытаетесь скопировать таблицу, созданную как:a = {}; a[a] = a
. deepcopy функция, на которую ссылается Джон, заботится об этом, поэтому, если вам нужно создать реальную/полную копию,deepcopy
должен быть использован.
предупреждение: отмеченное решение неправильно!
если таблица содержит таблицы, ссылки на эти таблицы будут использоваться. Я искал два часа для ошибки, которую я делал, в то время как это было из-за использования вышеуказанного кода.
поэтому вам нужно проверить, является ли значение таблицей или нет. Если это так, вы должны позвонить таблице.копировать рекурсивно!
это правильная таблица.функция копирования:
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
Примечание.: Это также может быть неполным, когда таблица содержит функции или другие специальные типы, но это возможно, что большинству из нас не нужно. Приведенный выше код легко адаптируется для тех, кто в ней нуждается.
используйте библиотеку 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 очень ограничена без центральной библиотеки функций, на которые вы можете ссылаться. Каждая функция должна быть объявлена в коде, чтобы сохранить работоспособность кода, поэтому такие решения для одной строки благоприятный.