¿Cómo se puede copiar una tabla de Lua por valor?

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 tienen referencias a una tabla anónima, no a los valores de la tabla en Lua. Esto está claramente establecido en la Progtwigción en Lua , pero lo había olvidado.

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

    Para jugar un poco legible-code-golf, aquí hay una versión corta que maneja los casos complicados estándar:

    • tablas como llaves,
    • preservar metatablas, 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 un breve resumen de las operaciones de copia profunda de Lua en esta esencia .

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

    La copia de tabla tiene muchas definiciones posibles. 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 pairs lugar de ipairs , ya que las ipairs solo ipairs sobre un subconjunto de las teclas de la tabla (es decir, las claves enteras positivas consecutivas comienzan en una en orden ipairs ).

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

     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 aceptada como “estándar”.

    La versión completa de Deep Copy, manejando las 3 situaciones:

    1. Referencia circular de tabla
    2. Llaves 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 mesa:

     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 las funciones de lua-users.org/wiki/CopyTable y Alan Yates .

    Una versión recursiva opcionalmente profunda, gráfica general,

     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 teclas de su tabla no son estrictamente monótonamente crecientes, deberían ser pairs no ipairs .

    También encontré una función de 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 metatables 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 stack. Por lo tanto, podría ser necesaria una función aún más robusta (no recursiva).

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

    El proyecto stdlib (desafortunadamente poco documentado) tiene una cantidad de valiosas extensiones para varias de las bibliotecas enviadas con la distribución estándar de Lua. Entre ellos hay varias variaciones sobre el tema de la copia y fusión de tablas.

    Esta biblioteca también se incluye en la distribución de Lua para Windows , y probablemente sea parte de cualquier caja de herramientas de Lua.

    Una cosa para asegurarse de cuando se implementan cosas como esta a mano es el manejo adecuado de metatables. Para aplicaciones simples de tabla como estructura, probablemente no tenga metatablas, y un bucle simple usando pairs() 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 tanto, si desea “copiar” por completo todos los valores que necesita para obtener funciones separadas; sin embargo, la única forma que conozco de copiar una función es usar una loadstring(string.dump(func)) , que de acuerdo con el manual de referencia de Lua, no funciona para funciones con valores ascendentes.

     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 

    Eso es lo mejor que obtendrás para las tablas básicas. Use algo como deepcopy si necesita copiar tablas con metatables.

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

    Personalmente, me gustaría que ofrezcan una función incorporada. Solo si las personas no estuvieran satisfechas con su semántica, tendrían que hacerlo ellas mismas. No muy a menudo, sin embargo, uno realmente tiene la necesidad de copiar por valor.

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

    Todos los fragmentos que se han mostrado hasta ahora fallan en la creación de una copia para una tabla que puede tener claves o claves compartidas 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 a = {}; a[a] = a . La función de copiado profunda a la que hace referencia Jon se encarga de eso, de modo que si necesita crear una copia real / completa, se debe usar la deepcopy copia deepcopy .

    Advertencia: ¡la solución marcada es INCORRECTA !

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

    Entonces debe verificar si el valor es una tabla o no. Si es así, ¡debe llamar a table.copy recursivamente!

    Esta es la función correcta 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 

    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.

    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 algunas domótica en un Fibaro Home Center 2. La implementación de Lua es muy limitada, sin una biblioteca central de funciones a la que pueda hacer referencia. Cada función debe declararse en el código para mantener el código en servicio, por lo que las soluciones de una línea como esta son favorables.