2009-03-12 8 views
51

Недавно я написал немного коды Lua что-то вроде:Как скопировать таблицу Lua по значению?

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

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

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

ответ

19

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

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

Мы можем сделать это в 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 операций глубокой копии в this gist.

Другая полезная ссылка: this Lua-users wiki page, которая включает пример о том, как избежать метаметода __pairs.

+0

Почему существуют два параметра, если он копирует один объект? – claudekennilol

+0

@claudekennilol Второй параметр должен быть проигнорирован «внешним» абонентом и используется только для рекурсивных вызовов. Это позволяет избежать повторного глубокого копирования таблиц, которые встречаются более одного раза в одной таблице. Например. a = {} b = {k1 = a, k2 = a}. Если b2 является копией b, приятно сохранить b2.k1 == b2.k2. Дополнительный параметр 'seen' помогает сделать это возможным. – Tyler

6

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

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

Doub mentions Как, если ключи таблицы не строго монотонно возрастает, она должна быть 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 

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

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

+0

Обратите внимание, что эта версия deepcopy не работает для самореферентных (рекурсивных) таблиц. (Это то, что вы получаете, если придерживаетесь общепринятой практики для объектно-ориентированного Lua.) – yoyo

+0

Для проблемы переполнения стека решение этой проблемы для использования хвостовых вызовов позволило бы решить эту проблему. Однако это может быть невозможно. – Schollii

1

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

42

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

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

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

+1

Этот код является неправильным. Я использовал его, и он не справляется с одной конкретной проблемой: что делать, если у вас есть таблицы внутри таблиц. Вам нужно проверить тип v и использовать таблицу.copy рекурсивно, когда это таблица. Я отправлю код в отдельном сообщении ниже. – scippie

+10

Код не является ошибочным, это вполне допустимый способ скопировать таблицу, если вам не нужна рекурсивная копия. Если вам нужна рекурсивная копия, сделайте функцию рекурсивной. Сама причина, по которой он не включен в стандартные библиотеки, заключается в том, что нет единого определения «копия таблицы». – Doub

+2

Я могу видеть точку @scippie. Возможно, что нужно, это имя, которое лучше указывает на тип копирования, который выполняется? Я обновлю имя на 'shallow_copy'; сообщите мне, есть ли какие-либо разногласия. – greatwolf

4

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

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

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

1

Я думаю, причина, почему у Луи нет «стола».copy() 'в своих стандартных библиотеках, потому что задача неточна для определения. Как уже показано здесь, можно либо сделать копию «на один уровень глубины» (что вы сделали), глубокая копия с или без ухода за возможными дублирующими ссылками. И тогда есть metatables.

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

30

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

Обратите внимание, что считается неправильной практикой изменения или расширения стандартных пространств имен, таких как 'table'. –

+1

так делает lua-users-wiki. Http://lua-users.org/wiki/CopyTable –

+2

Это работает до тех пор, пока кто-то не установит свойство '__metatable' метатега. – Eric

10

Необязательно глубоко, граф-общем, рекурсивная версия:

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 

Возможно метатаблица копия должна быть факультативно также?

+2

Вам нужно добавить 'nt' к' seen' перед итерацией или есть ситуации, когда вы переполните стек. В принципе, удалите, как 15, и вставьте его после строки 6. – Mud

+0

Я думал только о «но что, если t является его собственным метатебельным», но, конечно, это также проблема, если это просто «local t = {}; t [1] = t' или что-то. Кроме того, после просмотра http://lua-users.org/wiki/CopyTable, я вижу, вы забыли скопировать ключи, которые могут быть самими таблицами. – Robin

1

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

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

12

Полная версия глубокой копии, обработки все 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 "функций.

+0

Добро пожаловать в [SO] и благодарим вас за предложения! Похоже, что переменная 'seen' является своего рода системой кэширования. Мне нужно попробовать. –

+0

Это хорошо работает в моем проекте и надеется на ваши отзывы, если есть подводные камни. – islet8

+2

ХА, мне хотелось бы прокрутить вниз, прежде чем писать это сам! (IMO это должен быть принятый ответ.) – yoyo

4

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

Внимание: отмеченное решение INCORRECT!

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

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

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

+3

Я думаю, что немного грубо сказать, что это «неправильно». Это зависит от того, что у вас внутри таблицы и чего вы хотите скопировать. Вы заметите, что несколько других ответов _also_ учитывают рекурсивное копирование таблиц. Другие учитывают метаданные и функции. –

+1

ИМО. ОП ничего не говорит о содержимом таблицы. Следовательно, ответ должен быть как можно более полным, что не соответствует выбранному ответу. Другие, такие как я, будут искать этот вопрос и выберут выбранный ответ, ища ошибку в своем собственном коде, пока она не в их. – scippie

+0

Ну, для чего это стоит, я OP, и я не рассматривал ситуацию рекурсивного копирования таблицы, содержащей таблицы. Несколько других ответов касались этой проблемы. –

0

Это может быть самый простой способ:

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" 
0

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

local copyOfTable = json.decode(json.encode(sourceTable)) 

Я пишу код Lua для некоторой домашней автоматизации на домашнем центре Fibaro 2. Реализация Lua очень ограничена без центральной библиотеки функций, на которую вы можете ссылаться. Каждая функция должна быть объявлена ​​в коде, чтобы сохранить работоспособность кода, поэтому однострочные решения, подобные этому, являются благоприятными.

Смежные вопросы