Подтвердить что ты не робот

Как скопировать таблицу Lua по значению?

Недавно я написал немного кода Lua примерно так:

local a = {}
for i = 1, n do
   local copy = a
   -- alter the values in the copy
end

Очевидно, что это не то, что я хотел сделать, поскольку переменные содержат ссылки на анонимную таблицу, а не значения самой таблицы в Lua. Это ясно изложено в Программирование в Lua, но я забыл об этом.

Итак, вопрос в том, что я должен писать вместо copy = a, чтобы получить копию значений в a?

4b9b3361

Ответ 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

Примечание. Это также может быть неполным, если таблица содержит функции или другие специальные типы, но это возможно, что большинство из нас не нуждается. Вышеупомянутый код легко адаптируется для тех, кто в нем нуждается.

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