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

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 ситуации:

  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

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