Как скопировать таблицу Lua по значению?
Недавно я написал немного кода Lua примерно так:
local a = {}
for i = 1, n do
local copy = a
-- alter the values in the copy
end
Очевидно, что это не то, что я хотел сделать, поскольку переменные содержат ссылки на анонимную таблицу, а не значения самой таблицы в Lua. Это ясно изложено в Программирование в Lua, но я забыл об этом.
Итак, вопрос в том, что я должен писать вместо copy = a
, чтобы получить копию значений в a
?
Ответы
Ответ 1
Чтобы играть в маленький удобочитаемый код-гольф, здесь короткая версия, которая обрабатывает стандартные сложные случаи:
- таблицы как ключи,
- сохранение метаданных и
- рекурсивные таблицы.
Мы можем сделать это в 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 с глубокой копией.
Еще одна полезная ссылка эта страница wiki-пользователей Lua, которая включает пример того, как избежать метатета __pairs
.
Ответ 2
В копии таблицы есть много потенциальных определений. Это зависит от того, хотите ли вы простую или глубокую копию, хотите ли вы копировать, делиться или игнорировать метаданные и т.д. Нет единой реализации, которая могла бы удовлетворить всех.
Один из подходов состоит в простом создании новой таблицы и дублировании всех пар ключ/значение:
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
следует использовать pairs
, так как ipairs
выполняет только итерацию по подмножеству клавиш таблицы (т.е. последовательные положительные целые ключи, начинающиеся с одного в порядке возрастания).
Ответ 3
Чтобы проиллюстрировать это, мой личный 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
Нет функции копирования, достаточно широко согласованной, чтобы ее называли "стандартной".
Ответ 4
Полная версия глубокой копии, обрабатывающая все три ситуации:
- Таблица круговой ссылки
- Ключи, которые также являются таблицами
- Метастабильная
Общая версия:
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 и Алан Йейтс.
Ответ 5
Необязательно глубокая, общая по графу, рекурсивная версия:
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
Возможно, метатетическая копия также должна быть необязательной?
Ответ 6
Вот что я на самом деле сделал:
for j,x in ipairs(a) do copy[j] = x end
Как Doub упоминает, если ваши таблицы не строго монотонно увеличиваются, это должно быть pairs
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
Он обрабатывает таблицы и метатеги, вызывая себя рекурсивно (который является его собственной наградой). Один из умных битов состоит в том, что вы можете передать ему любое значение (независимо от таблицы или нет), и оно будет скопировано правильно. Однако стоимость заключается в том, что он может потенциально переполнить стек. Поэтому может потребоваться еще более надежная (нерекурсивная) функция .
Но это переполнение для очень простого случая, когда вы хотите скопировать массив в другую переменную.
Ответ 7
В документе (stdlib (к сожалению, легко документирован) имеется ряд ценных расширений для нескольких библиотек, поставляемых со стандартным дистрибутивом Lua, Среди них несколько вариантов темы копирования и слияния таблиц.
Эта библиотека также включена в Lua для Windows и, вероятно, должна быть частью любого серьезного инструментария Lua.
Одна вещь, чтобы убедиться, что при реализации таких вещей вручную это правильная обработка метаданных. Для простых приложений table-as-structure у вас, вероятно, нет никаких метаданных, и простой цикл с использованием pairs()
является приемлемым ответом. Но если таблица используется как дерево или содержит циклические ссылки или имеет метатеги, тогда все становится более сложным.
Ответ 8
Не забывайте, что функции также являются ссылками, поэтому, если вы хотите полностью "скопировать" все значения, необходимые для получения отдельных функций; однако, единственный способ, которым я знаю, чтобы скопировать функцию, - это использовать 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
Ответ 9
Это так хорошо, как вы получите для базовых таблиц. Используйте что-то вроде deepcopy, если вам нужно скопировать таблицы с метатегами.
Ответ 10
Я думаю, причина, по которой у Lua нет "table.copy()" в своих стандартных библиотеках, потому что задача неточна для определения. Как уже показано здесь, можно либо сделать копию "на один уровень глубины" (что вы сделали), глубокая копия с или без ухода за возможными дублирующими ссылками. И тогда есть metatables.
Лично я хотел бы, чтобы они предложили встроенную функцию. Только если люди не будут довольны своей семантикой, им нужно будет сделать это сами. Не очень часто, однако, на самом деле есть необходимость копирования по значению.
Ответ 11
В большинстве случаев, когда мне нужно было скопировать таблицу, я хотел иметь копию, которая не имеет ничего общего с оригиналом, так что любая модификация исходной таблицы не влияет на копию (и наоборот).
Все фрагменты, которые были показаны до сих пор, не удается создать копию таблицы, которая может иметь общие ключи или ключи с таблицами, поскольку они будут оставлены, указывая на исходную таблицу. Легко видеть, пытаетесь ли вы скопировать таблицу, созданную как: a = {}; a[a] = a
. deepcopy, на которую ссылается Джон, позаботится об этом, поэтому, если вам нужно создать реальную/полную копию, следует использовать deepcopy
.
Ответ 12
Предупреждение: отмеченное решение НЕПРАВИЛЬНО!
Когда таблица содержит таблицы, ссылки на эти таблицы будут по-прежнему использоваться. Я искал два часа за ошибку, которую я делал, в то время как это было из-за использования вышеуказанного кода.
Итак, вам нужно проверить, является ли значение таблицей или нет. Если это так, вы должны вызывать 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
Примечание. Это также может быть неполным, если таблица содержит функции или другие специальные типы, но это возможно, что большинство из нас не нуждается. Вышеупомянутый код легко адаптируется для тех, кто в нем нуждается.
Ответ 13
Используйте библиотеку penlight здесь:
https://stevedonovan.github.io/Penlight/api/libraries/pl.tablex.html#deepcopy
local pl = require 'pl.import_into'()
local newTable = pl.tablex.deepcopy(oldTable)
Ответ 14
Это может быть самый простой способ:
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"
Ответ 15
В моей ситуации, когда информацией в таблице являются только данные и другие таблицы (исключая функции,...), следующая строка кода является выигрышным решением:
local copyOfTable = json.decode( json.encode( sourceTable ) )
Я пишу код Lua для некоторой домашней автоматизации в домашнем центре Fibaro 2. Реализация Lua очень ограничена без центральной библиотеки функций, на которую вы можете ссылаться. Каждая функция должна быть объявлена в коде, чтобы сохранить работоспособность кода, поэтому однострочные решения, подобные этому, являются благоприятными.