Как скопировать таблицу 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 ситуации:

  1. таблица циклическая ссылка
  2. ключи, которые также являются таблицы
  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 очень ограничена без центральной библиотеки функций, на которые вы можете ссылаться. Каждая функция должна быть объявлена в коде, чтобы сохранить работоспособность кода, поэтому такие решения для одной строки благоприятный.