Как вы копируете таблицу Lua по значению? - PullRequest
54 голосов
/ 13 марта 2009

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

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

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

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

Ответы [ 15 ]

42 голосов
/ 13 марта 2009

Копия таблицы имеет много потенциальных определений. Это зависит от того, хотите ли вы простое или глубокое копирование, хотите ли вы копировать, делиться или игнорировать метатаблицы и т. Д. Не существует единой реализации, которая могла бы удовлетворить всех.

Один из подходов состоит в том, чтобы просто создать новую таблицу и продублировать все пары ключ / значение:

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 выполняет итерацию только по подмножеству ключей таблицы (т. Е. Последовательные положительные целочисленные ключи начинаются с единицы в порядке возрастания).

30 голосов
/ 20 марта 2009

Просто чтобы проиллюстрировать это, мой личный table.copy также обращает внимание на метатаблицы:

function table.copy(t)
  local u = { }
  for k, v in pairs(t) do u[k] = v end
  return setmetatable(u, getmetatable(t))
end

Не существует достаточно широко согласованной функции копирования, которую можно назвать "стандартной".

19 голосов
/ 14 октября 2014

Чтобы поиграть в небольшой читаемый код-гольф, вот короткая версия, которая обрабатывает стандартные сложные случаи:

  • таблицы в качестве ключей,
  • сохранение метатаблиц и
  • рекурсивные таблицы.

Мы можем сделать это в 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.

12 голосов
/ 18 апреля 2013

Полная версия глубокого копирования, обрабатывающая все 3 ситуации:

  1. Таблица круговой ссылки
  2. Ключи, которые также являются таблицами
  3. Метастабильный

Общая версия:

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 и Alan Yates '.

10 голосов
/ 19 апреля 2011

Опционально глубокая, общая граф, рекурсивная версия:

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 голосов
/ 13 марта 2009

Вот что я на самом деле сделал:

for j,x in ipairs(a) do copy[j] = x end

Как 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

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

Но это слишком излишне для очень простого случая, когда нужно скопировать массив в другую переменную.

4 голосов
/ 26 июня 2013

Не забывайте, что функции также являются ссылками, поэтому, если вы хотите полностью «скопировать» все значения, вам также понадобятся отдельные функции; однако единственный способ, которым я знаю, чтобы скопировать функцию, это использовать loadstring(string.dump(func)), который, согласно справочному руководству Lua, не работает для функций со значениями вверх.

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
4 голосов
/ 14 марта 2009

(к сожалению, слегка документированный) проект stdlib имеет ряд ценных расширений для нескольких библиотек, поставляемых со стандартным дистрибутивом Lua. Среди них несколько вариаций на тему копирования и объединения таблиц.

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

При реализации таких вещей вручную нужно убедиться в правильной обработке метатаблиц. Для простых приложений таблицы как структуры у вас, вероятно, нет метатаблиц, и простой цикл, использующий pairs(), является приемлемым ответом. Но если таблица используется как дерево, или содержит циклические ссылки, или имеет метатаблицы, то все становится более сложным.

2 голосов
/ 24 сентября 2013

Предупреждение: помечено решение НЕПРАВИЛЬНО !

Если таблица содержит таблицы, ссылки на эти таблицы будут по-прежнему использоваться. Я два часа искал ошибку, которую совершил, хотя это было из-за использования приведенного выше кода.

Так что вам нужно проверить, является ли значение таблицей или нет. Если это так, вы должны вызывать 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

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

1 голос
/ 31 января 2016

Используйте библиотеку penlight здесь: https://stevedonovan.github.io/Penlight/api/libraries/pl.tablex.html#deepcopy

local pl = require 'pl.import_into'()
local newTable = pl.tablex.deepcopy(oldTable)
...