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

StackOverflow https://stackoverflow.com/questions/640642

  •  22-07-2019
  •  | 
  •  

Вопрос

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

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

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

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

Это было полезно?

Решение

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

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

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

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

Чтобы проиллюстрировать это, мой личный 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

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

Полная версия глубокого копирования, охватывающая все 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 и Алан Йейтс 'функции.

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

    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
    

    Как Сомнение упоминает , если ключи таблицы не являются строго монотонно растущими, это должны быть пары 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
    

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

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

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

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

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

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

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

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

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

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

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

    Я думаю, что причина, по которой Lua не имеет table.copy () в своих стандартных библиотеках, заключается в том, что задача не является точной для определения. Как уже показано здесь, можно сделать копию «на один уровень глубже» (что вы сделали), глубокая копия с или без учета возможных дубликатов ссылок. А потом есть метатаблицы.

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

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

    Все фрагменты, которые были показаны до сих пор, не могут создать копию для таблицы, которая может иметь общие ключи или ключи с таблицами, поскольку те, которые будут оставлены, указывают на исходную таблицу. Это легко увидеть, если вы попытаетесь скопировать таблицу, созданную как: a = {}; a [a] = a . deepcopy , на которую ссылается Джон, позаботится об этом, поэтому если вам нужно создать реальную / полную копию , deepcopy должен быть использован.

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

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top