Come si copia una tabella Lua per valore?
Domanda
Recentemente ho scritto un po 'di codice Lua qualcosa del tipo:
local a = {}
for i = 1, n do
local copy = a
-- alter the values in the copy
end
Ovviamente, non era quello che volevo fare poiché le variabili contengono riferimenti a una tabella anonima e non i valori della tabella stessa in Lua. Questo è chiaramente definito in Programmazione in Lua , ma me ne ero dimenticato.
Quindi la domanda è cosa devo scrivere invece di copy = a
per ottenere una copia dei valori in a
?
Soluzione
Per giocare un po 'di golf leggibile-codice, ecco una versione breve che gestisce i casi complicati standard:
- tabelle come chiavi,
- conservazione dei metatables e
- tabelle ricorsive.
Possiamo farlo in 7 righe:
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
Esiste una breve descrizione delle operazioni di copia profonda di Lua in questa sintesi .
Un altro riferimento utile è questa pagina wiki degli utenti Lua , che include un esempio su come evitare il __pairs
metamethod.
Altri suggerimenti
La copia della tabella ha molte potenziali definizioni. Dipende se desideri una copia semplice o approfondita, se vuoi copiare, condividere o ignorare metatables, ecc. Non esiste un'unica implementazione in grado di soddisfare tutti.
Un approccio è semplicemente quello di creare una nuova tabella e duplicare tutte le coppie chiave / valore:
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)
Nota che dovresti usare coppie
invece di ipairs
, dal momento che ipairs
scorre solo su un sottoinsieme delle chiavi della tabella (es. positivo consecutivo chiavi intere che iniziano da una in ordine crescente).
Solo per illustrare il punto, anche il mio table.copy
personale presta attenzione ai metatables:
function table.copy(t)
local u = { }
for k, v in pairs(t) do u[k] = v end
return setmetatable(u, getmetatable(t))
end
Non esiste una funzione di copia sufficientemente condivisa per essere chiamata "standard".
La versione completa di Deep Copy, gestendo tutte e 3 le situazioni:
- Riferimento circolare tabella
- Tasti che sono anche tabelle
- MetaTable
La versione generale:
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 versione della tabella:
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
Basato sulle lua-users.org/wiki/CopyTable e Alan Yates 'funzioni.
Una versione facoltativa, graficamente generale, ricorsiva:
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
Forse anche la copia metabile dovrebbe essere facoltativa?
Ecco cosa ho effettivamente fatto:
for j,x in ipairs(a) do copy[j] = x end
Dato che menzioni Doub , se le chiavi della tabella non aumentano rigorosamente monotonicamente, dovrebbero essere coppie non
ipairs
.
Ho anche trovato una funzione deepcopy
più 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
Gestisce tabelle e metatable chiamando se stesso in modo ricorsivo ( che è la sua ricompensa ). Uno dei bit intelligenti è che puoi passargli qualsiasi valore (che sia una tabella o meno) e verrà copiato correttamente. Tuttavia, il costo è che potrebbe potenzialmente traboccare lo stack. Pertanto, potrebbe essere necessaria una funzione ancora più solida (non ricorsiva).
Ma questo è eccessivo per il semplicissimo caso di voler copiare un array in un'altra variabile.
Il progetto (purtroppo leggermente documentato) stdlib ha una serie di preziose estensioni per diverse librerie spedito con la distribuzione Lua standard. Tra questi ci sono diverse varianti sul tema della copia e fusione delle tabelle.
Questa libreria è inclusa anche nella distribuzione Lua per Windows e dovrebbe probabilmente far parte di qualsiasi cassetta degli attrezzi dell'utente serio di Lua.
Una cosa da accertare quando si implementano cose come questa a mano è la corretta gestione dei metatable. Per semplici applicazioni da tavolo come struttura probabilmente non hai metatable e un semplice ciclo che utilizza coppie ()
è una risposta accettabile. Ma se la tabella viene utilizzata come albero o contiene riferimenti circolari o ha metatable, le cose diventano più complesse.
Non dimenticare che anche le funzioni sono riferimenti, quindi se volessi "copiare" completamente tutti i valori avresti bisogno di ottenere anche funzioni separate; tuttavia, l'unico modo che conosco per copiare una funzione è usare loadstring (string.dump (func))
, che secondo il manuale di riferimento Lua, non funziona per le funzioni con valori upval.
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
Attenzione: la soluzione contrassegnata è INCORRETTO !
Quando la tabella contiene tabelle, verranno comunque utilizzati i riferimenti a tali tabelle. Ho cercato per due ore un errore che stavo commettendo, mentre era a causa dell'utilizzo del codice sopra.
Quindi è necessario verificare se il valore è una tabella o meno. Se lo è, dovresti chiamare table.copy in modo ricorsivo!
Questa è la funzione table.copy corretta:
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: questo potrebbe anche essere incompleto quando la tabella contiene funzioni o altri tipi speciali, ma è possibile che qualcosa di noi non sia necessario. Il codice sopra è facilmente adattabile per chi ne ha bisogno.
È buono come otterrai per le tabelle di base. Usa qualcosa come deepcopy se devi copiare tabelle con metatables.
Penso che il motivo per cui Lua non abbia 'table.copy ()' nelle sue librerie standard è perché il compito non è preciso da definire. Come mostrato già qui, si può fare una copia "a un livello di profondità" (che hai fatto), una deepcopy con o senza cura di possibili riferimenti duplicati. E poi ci sono metatables.
Personalmente, vorrei comunque che offrissero una funzione integrata. Solo se le persone non fossero soddisfatte della sua semantica, avrebbero bisogno di farlo da sole. Non molto spesso, tuttavia, si ha effettivamente la necessità di una copia per valore.
Nella maggior parte dei casi, quando avevo bisogno di copiare una tabella, volevo avere una copia che non condividesse nulla con l'originale, in modo tale che qualsiasi modifica della tabella originale non abbia alcun impatto sulla copia (e viceversa ).
Tutti gli snippet mostrati finora non riescono a creare una copia per una tabella che può avere chiavi condivise o chiavi con tabelle poiché rimarranno puntate verso la tabella originale. È facile vedere se si tenta di copiare una tabella creata come: a = {}; a [a] = a
. La deepcopy a cui fa riferimento Jon si occupa di questo, quindi se è necessario creare una copia reale / completa , deepcopy
dovrebbe essere usato.
Utilizza la libreria penlight qui: https://stevedonovan.github.io/Penlight/api/libraries /pl.tablex.html#deepcopy
local pl = require 'pl.import_into'()
local newTable = pl.tablex.deepcopy(oldTable)
Questo potrebbe essere il metodo più semplice:
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"
Nella mia situazione, quando le informazioni nella tabella sono solo dati e altre tabelle (escluse le funzioni, ...), è la seguente riga di codice la soluzione vincente:
local copyOfTable = json.decode( json.encode( sourceTable ) )
Sto scrivendo il codice Lua per un po 'di domotica su un Fibaro Home Center 2. L'implementazione di Lua è molto limitata senza una libreria centrale di funzioni a cui puoi fare riferimento. Ogni funzione deve essere dichiarata nel codice in modo da mantenere il codice utile, quindi le soluzioni a una linea come questa sono favorevoli.