Pergunta

Recentemente eu escrevi um pouco de Lua código algo como:

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

Obviamente, isso não era o que eu queria fazer desde variáveis ??mantiverem referências a uma tabela anônima não os valores dos próprios mesa na Lua. Este é claramente definidos na em Lua , mas eu tinha esquecido sobre isso.

Portanto, a questão é o que eu deveria escrever em vez de copy = a para obter uma cópia dos valores em a?

Foi útil?

Solução

Para jogar um pouco legíveis-code-golfe, aqui está uma versão curta que lida com casos complicados padrão:

  • tabelas como chaves,
  • metatables preservando e
  • tabelas recursiva.

Podemos fazer isso em 7 linhas:

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

Há um curto write-up da Lua operações deep-cópia em esta essência .

Outra referência útil é esta Lua usuários wiki página , que inclui um exemplo sobre como evitar o metamétodo __pairs.

Outras dicas

copy

Table tem muitas definições possíveis. Depende se você quer cópia simples ou profunda, se você deseja copiar, compartilhar ou ignorar metatables, etc. Não existe uma única aplicação que pode satisfazer a todos.

Uma abordagem é simplesmente criar uma nova tabela e duplicar todos os pares de chave / valor:

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)

Note que você deve usar pairs vez de ipairs, desde ipairs única iteração sobre um subconjunto das chaves de tabela (ie. Chaves inteiros positivos consecutivos a partir de um em ordem crescente).

Apenas para ilustrar o ponto, meu table.copy pessoal também presta atenção aos metatables:

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

Não há nenhuma função de cópia suficientemente amplamente acordada a ser chamado de "padrão".

A versão completa do cópia profunda, manipulação de todos os 3 situações:

  1. Tabela referência circular
  2. Chaves, que também são tabelas
  3. MetaTable

A versão geral:

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

Ou a versão tabela:

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

Com base na lua-users.org/wiki/CopyTable 's e Alan Yates ' funções.

Um opcionalmente profundo, gráfico-geral, a versão recursiva:

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

Talvez cópia metatable deve ser opcional também?

Aqui está o que eu realmente fiz:

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

Como Dup menciona , se suas chaves de tabela não são estritamente monótona crescente, deve ser pairs não ipairs.

Eu também encontrei uma função deepcopy que é mais robusto:

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

Ele lida com mesas e metatables chamando-se de forma recursiva ( que é a sua própria recompensa ). Um dos bits inteligentes é que você pode passá-lo qualquer valor (se uma tabela ou não) e ele será copiado corretamente. No entanto, o custo é que ele poderia estourar a pilha. Assim, e ainda mais robusto (não-recursiva) função pode ser necessário.

Mas isso é um exagero para o caso muito simples de querer copiar um array em outra variável.

A (infelizmente levemente documentado) stdlib projeto tem uma série de extensões valiosas para várias das bibliotecas fornecido com a distribuição padrão Lua. Entre eles estão diversas variações sobre o tema da cópia mesa e fusão.

Esta biblioteca também está incluído no Lua para a distribuição do Windows , e provavelmente deve ser uma parte de qualquer sério Lua caixa de ferramentas do usuário.

Uma coisa para ter certeza de quando implementar coisas como esta com a mão é o tratamento adequado dos metatables. Para aplicações simples mesa-como-estrutura que você provavelmente não tem nenhum metatables, e um loop simples usando pairs() é uma resposta aceitável. Mas, se a tabela é usada como uma árvore, ou contém referências circulares, ou tem metatables, então as coisas ficam mais complexas.

Não se esqueça que funções são também referências, por isso, se você queria completamente 'copiar' todos os valores que você precisa para obter funções separadas, também; no entanto, a única forma que conheço para copiar uma função é usar loadstring(string.dump(func)), que de acordo com o manual de referência Lua, não funciona para funções com 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

Atenção: a solução marcado é INCORRETO

Quando a tabela contém tabelas, referências a essas tabelas ainda será usado. Fui à procura de duas horas para um erro que eu estava fazendo, ao mesmo tempo que era por causa de usar o código acima.

Então, você precisa verificar se o valor for uma tabela ou não. Se for, você deve chamar table.copy recursivamente!

Esta é a função table.copy correto:

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

Nota: Isso também pode ser incompleta quando a tabela contém funções ou outros tipos especiais, mas que é possível algo que a maioria de nós não precisa. O código acima é facilmente adaptável para aqueles que dela necessitam.

Isso é tão bom quanto você vai ter para tabelas básicas. Use algo como deepcopy se você precisa copiar tabelas com metatables.

Eu acho que a razão pela qual Lua não tem 'table.copy ()' em suas bibliotecas padrão é porque a tarefa não é preciso definir. Como mostrado já está aqui, pode-se quer fazer uma cópia "um profundo nível" (o que você fez), um deepcopy com ou sem cuidar de possíveis referências duplicadas. E depois há metatables.

Pessoalmente, eu ainda gosto deles para oferecer uma função built-in. Só se as pessoas não ficaria satisfeito com sua semântica, que seria necessário para ir fazer por si próprios. Não muito frequente, porém, tem-se, na verdade, a necessidade de cópia por valor.

Na maioria dos casos, quando eu precisava para copiar uma tabela, eu queria ter uma cópia que não compartilha nada com o original, de modo que qualquer modificação da tabela original não tem impacto sobre a cópia (e vice-versa ).

Todos os trechos que foram mostrados até agora falhar a criação de uma cópia de uma tabela que pode ter chaves ou chaves compartilhado com mesas como aqueles vão ser apontando para a esquerda para a tabela original. É fácil de ver se você tentar copiar uma tabela criada como: a = {}; a[a] = a. deepcopy função referenciada por Jon cuida de que, por isso, se você precisa criar uma verdadeira / cópia completa , deve ser usado deepcopy.

Use lanterna biblioteca aqui: https://stevedonovan.github.io/Penlight/api/libraries /pl.tablex.html#deepcopy

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

Este pode ser o método mais simples:

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"

Na minha situação, quando a informação na tabela é apenas dados e outras tabelas (excluindo funções, ...), é o seguinte linha de código a solução vencedora:

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

Eu estou escrevendo código Lua para alguns de automação residencial em um Fibaro Home Center 2. A aplicação da Lua é muito limitado sem biblioteca central de funções que você pode consultar. A cada função precisa ser declarado no código de modo a manter a ser reparadas código, para um soluções de linha como este são favoráveis.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top