Как скопировать таблицу 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
метаметод.
копия таблицы имеет много потенциальных определений. Это зависит от того, хотите ли вы простое или глубокое копирование, хотите ли вы копировать, делиться или игнорировать метатабли и т. д. Нет ни одной реализации, которая могла бы удовлетворить всех.
один из подходов заключается в том, чтобы просто создать новую таблицу и дублировать все пары ключ / значение:
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
как дуб упоминает, если ваши ключи таблицы не строго монотонно увеличиваются, это должно быть
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
он обрабатывает таблицы и метатабли, вызывая себя рекурсивно (что является его собственной наградой). Один из умных битов заключается в том, что вы можете передать ему любое значение (будь то таблица или нет) и он будет скопирован правильно. Однако стоимость заключается в том, что он потенциально может переполнить стек. Так и еще более надежный (нерекурсивный) функции может понадобиться.
но это перебор для очень простого случая, когда требуется скопировать массив в другую переменную.
The (к сожалению, слегка задокументировано) stdlib проект имеет ряд ценных расширений для нескольких библиотек, поставляемых со стандартным дистрибутивом Lua. Среди них несколько вариаций на тему копирования и слияния таблиц.
эта библиотека также включена в Lua для Windows дистрибутив, и, вероятно, должен быть частью любого серьезного набора инструментов пользователя Lua.
одна вещь, чтобы убедиться, что при реализации вещи как это вручную является правильной обработкой метатабли. Для простой таблицы как структуру приложения, вы, вероятно, не имеют каких-либо metatables, и простой цикл с помощью
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 очень ограничена без центральной библиотеки функций, на которые вы можете ссылаться. Каждая функция должна быть объявлена в коде, чтобы сохранить код работоспособным, поэтому такие решения для одной строки благоприятный.