Pregunta

Recientemente escribí un poco de código Lua algo así como:

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

Obviamente, eso no era lo que quería hacer, ya que las variables contienen referencias a una tabla anónima, no los valores de la tabla en Lua. Esto está claramente establecido en Programación en Lua , pero lo había olvidado.

Entonces, la pregunta es ¿qué debo escribir en lugar de copy = a para obtener una copia de los valores en a ?

¿Fue útil?

Solución

Para jugar un poco de golf de código legible, aquí hay una versión corta que maneja los casos difíciles estándar:

  • tablas como claves,
  • preservando metatables, y
  • tablas recursivas.

Podemos hacer esto en 7 líneas:

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

Hay una breve descripción de las operaciones de copia profunda de Lua en esta esencia .

Otra referencia útil es esta página wiki de usuarios de Lua , que incluye un ejemplo sobre cómo evitar el __pairs metamethod.

Otros consejos

La copia de la tabla tiene muchas definiciones potenciales. Depende de si desea una copia simple o profunda, si desea copiar, compartir o ignorar metatablas, etc. No existe una implementación única que pueda satisfacer a todos.

Un enfoque es simplemente crear una nueva tabla y duplicar todos los pares clave / 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)

Tenga en cuenta que debe usar pares en lugar de ipairs , ya que ipairs solo itera sobre un subconjunto de las teclas de la tabla (es decir, positivo consecutivo teclas enteras que comienzan en una en orden creciente).

Solo para ilustrar el punto, mi table.copy personal también presta atención a las metatablas:

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

No existe una función de copia suficientemente acordada para ser llamada "estándar".

La versión completa de copia profunda, manejando las 3 situaciones:

  1. Referencia circular de tabla
  2. Teclas que también son tablas
  3. Metatable

La versión general:

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

O la versión de la tabla:

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

Basado en los lua-users.org/wiki/CopyTable 's y Funciones de Alan Yates .

Una versión opcional, profunda, gráfica general, 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

¿Quizás la copia metatable también debería ser opcional?

Esto es lo que realmente hice:

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

Como Doub menciona , si las claves de su tabla no están aumentando de manera estrictamente monotónica, deberían ser pares no ipairs .

También encontré una función deepcopy que es más robusta :

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

Maneja tablas y metatablas llamándose recursivamente ( que es su propia recompensa ). Uno de los bits inteligentes es que puede pasarle cualquier valor (ya sea una tabla o no) y se copiará correctamente. Sin embargo, el costo es que potencialmente podría desbordar la pila. Por lo tanto, podría ser necesaria una función más robusta (no recursiva).

Pero eso es excesivo para el caso muy simple de querer copiar una matriz en otra variable.

El (lamentablemente ligeramente documentado) stdlib tiene una serie de extensiones valiosas para varias de las bibliotecas enviado con la distribución estándar de Lua. Entre ellos hay varias variaciones sobre el tema de copiar y fusionar tablas.

Esta biblioteca también está incluida en la distribución Lua para Windows , y probablemente debería ser parte de cualquier caja de herramientas seria del usuario de Lua.

Una cosa de la que debe asegurarse al implementar cosas como esta a mano es el manejo adecuado de las metatablas. Para aplicaciones simples de tabla como estructura, probablemente no tenga metatablas, y un bucle simple usando pares () es una respuesta aceptable. Pero si la tabla se usa como un árbol, o contiene referencias circulares, o tiene metatablas, entonces las cosas se vuelven más complejas.

No olvide que las funciones también son referencias, por lo que si desea 'copiar' completamente todos los valores que necesitaría para obtener funciones separadas también; sin embargo, la única forma en que sé copiar una función es usar LoadString (string.dump (func)) , que de acuerdo con el manual de referencia de Lua, no funciona para funciones con valores superiores.

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

Advertencia: la solución marcada es INCORRECTA

Cuando la tabla contiene tablas, las referencias a esas tablas se seguirán utilizando en su lugar. Estuve buscando dos horas por un error que estaba cometiendo, mientras fue por usar el código anterior.

Por lo tanto, debe verificar si el valor es una tabla o no. Si es así, ¡debe llamar a table.copy de forma recursiva!

Esta es la función table.copy correcta:

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: Esto también puede estar incompleto cuando la tabla contiene funciones u otros tipos especiales, pero eso es posible, algo que la mayoría de nosotros no necesitamos. El código anterior es fácilmente adaptable para quienes lo necesitan.

Eso es tan bueno como lo que obtendrás para las tablas básicas. Use algo como copia profunda si necesita copiar tablas con metatablas.

Creo que la razón por la cual Lua no tiene 'table.copy ()' en sus bibliotecas estándar es porque la tarea no es precisa de definir. Como ya se muestra aquí, uno puede hacer una copia '' de un nivel de profundidad '' (lo que hizo), una copia profunda con o sin cuidado de posibles referencias duplicadas. Y luego están las metatablas.

Personalmente, todavía me gustaría que ofrecieran una función integrada. Solo si las personas no estuvieran satisfechas con su semántica, tendrían que hacerlo ellos mismos. Sin embargo, no muy a menudo uno tiene la necesidad de copiar por valor.

En la mayoría de los casos en que necesitaba copiar una tabla, quería tener una copia que no comparta nada con el original, de modo que cualquier modificación de la tabla original no tenga impacto en la copia (y viceversa ).

Todos los fragmentos que se han mostrado hasta ahora no pueden crear una copia para una tabla que puede tener claves compartidas o claves con tablas, ya que se dejarán apuntando a la tabla original. Es fácil ver si intenta copiar una tabla creada como: a = {}; a [a] = a . deepcopy la función a la que hace referencia Jon se encarga de eso, así que si necesita crear una copia real / completa , se debe usar deepcopy .

Utilice la biblioteca de penlight aquí: https://stevedonovan.github.io/Penlight/api/libraries /pl.tablex.html#deepcopy

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

Este podría ser el método más simple:

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"

En mi situación, cuando la información en la tabla es solo datos y otras tablas (excluyendo funciones, ...), la siguiente línea de código es la solución ganadora:

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

Estoy escribiendo el código Lua para algunos domóticos en un Fibaro Home Center 2. La implementación de Lua es muy limitada y no tiene una biblioteca central de funciones a las que pueda referirse. Todas las funciones deben declararse en el código para mantener el código útil, de modo que las soluciones de una línea como esta sean favorables.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top