Comment copier une table Lua par valeur?
Question
Récemment, j'ai écrit un peu de code Lua, quelque chose comme:
local a = {}
for i = 1, n do
local copy = a
-- alter the values in the copy
end
Évidemment, ce n’était pas ce que je voulais faire, car les variables contiennent des références à une table anonyme et non les valeurs de la table elles-mêmes dans Lua. Ceci est clairement défini dans la Programmation en Lua , mais je l'avais oublié.
La question est donc de savoir ce que je devrais écrire au lieu de copy = a
pour obtenir une copie des valeurs dans a
?
La solution
Pour jouer à un code-golf lisible, voici une version courte qui gère les cas difficiles standard:
- tables en tant que clés,
- en préservant les méta-tables, et
- tables récursives.
Nous pouvons le faire en 7 lignes:
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
Il existe une brève description des opérations de copie profonde Lua dans cet essentiel .
Une autre référence utile est cette page wiki de Lua-users , qui comprend un exemple sur la façon d'éviter le . __pairs
metamethod.
Autres conseils
La copie de table comporte de nombreuses définitions potentielles. Cela dépend si vous voulez une copie simple ou profonde, si vous voulez copier, partager ou ignorer les méta-tables, etc. Aucune mise en œuvre unique ne peut satisfaire tout le monde.
Une approche consiste simplement à créer une nouvelle table et à dupliquer toutes les paires clé / valeur:
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)
Notez que vous devez utiliser paires
au lieu de ipairs
, car ipairs
n'itère que sur un sous-ensemble des clés de la table (c'est-à-dire des valeurs positives consécutives). clés entières commençant à un dans l'ordre croissant).
Juste pour illustrer ce point, mon table.copy
personnel prête également attention aux méta-tables:
function table.copy(t)
local u = { }
for k, v in pairs(t) do u[k] = v end
return setmetatable(u, getmetatable(t))
end
Il n’existe pas de fonction de copie suffisamment largement reconnue pour être appelée "standard".
La version complète de la copie en profondeur, qui gère les 3 situations:
- Référence circulaire du tableau
- Les clés qui sont aussi des tables
- Metatable
La version générale:
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 la version de la table:
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
Basé sur les lua-users.org/wiki/CopyTable et Fonctions d'Alan Yates .
Une version récursive, graphique général général, éventuellement profonde:
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
Peut-être que la copie métatable devrait également être facultative?
Voici ce que j'ai réellement fait:
for j,x in ipairs(a) do copy[j] = x end
En tant que mentionne Doub , si vos clés de table n'augmentent pas de façon strictement monotone, elles doivent être paires
pas ipairs
.
J'ai également trouvé une fonction deepcopy
plus robuste. :
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
Il gère les tables et les méta-tables en s’appelant récursivement ( qui constitue sa propre récompense ). L'un des astuces réside dans le fait que vous pouvez lui transmettre n'importe quelle valeur (table ou non) et qu'elle sera copiée correctement. Cependant, le coût est que cela pourrait potentiellement déborder de la pile. Donc, une fonction plus robuste et encore plus robuste (non récursive) pourrait être nécessaire.
Mais c'est exagéré dans le cas très simple de vouloir copier un tableau dans une autre variable.
Le projet stdlib (malheureusement légèrement documenté) comporte de nombreuses extensions intéressantes pour plusieurs bibliothèques. livré avec la distribution standard Lua. Parmi celles-ci figurent plusieurs variantes sur le thème de la copie et de la fusion de tables.
Cette bibliothèque est également incluse dans la distribution Lua pour Windows et devrait probablement faire partie de tout Boîte à outils des utilisateurs sérieux de Lua.
Lors de la mise en œuvre manuelle de telles opérations, vous devez vous assurer que les méta-tables sont traitées correctement. Pour les applications simples de type table-structure, vous n'avez probablement pas de méta-tables, et une simple boucle utilisant pairs ()
est une réponse acceptable. Mais si la table est utilisée sous forme d’arbre, contient des références circulaires ou contient des méta-tables, la situation devient plus complexe.
N'oubliez pas que les fonctions sont aussi des références, donc si vous souhaitez entièrement copier toutes les valeurs, vous devez également obtenir des fonctions distinctes. Cependant, la seule façon de copier une fonction que je connaisse consiste à utiliser loadstring (string.dump (func))
, qui, selon le manuel de référence de Lua, ne fonctionne pas pour les fonctions à valeurs supérieures.
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
Avertissement: la solution indiquée est INCORRECT !
.Lorsque la table contient des tables, les références à ces tables seront toujours utilisées. Je cherche depuis deux heures une erreur que j'ai commise, alors que c'était à cause de l'utilisation du code ci-dessus.
Vous devez donc vérifier si la valeur est une table ou non. Si tel est le cas, vous devez appeler table.copy récursivement!
C’est la fonction table.copy appropriée:
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
Remarque: ceci peut également être incomplet lorsque la table contient des fonctions ou d'autres types spéciaux, mais il est possible que la plupart d'entre nous n'aient pas besoin de cela. Le code ci-dessus est facilement adaptable à ceux qui en ont besoin.
C’est aussi bon que vous obtiendrez pour les tables de base. Utilisez quelque chose comme deepcopy si vous devez copier des tables avec des méta-tables.
Je pense que la raison pour laquelle Lua n'a pas 'table.copy ()' dans ses bibliothèques standard est parce que la tâche n'est pas précise à définir. Comme indiqué ci-dessus, on peut soit faire une copie "un niveau de profondeur" (ce que vous avez fait), une copie profonde avec ou sans se soucier d’éventuelles références en double. Et puis il y a des méta-tables.
Personnellement, je voudrais toujours qu’ils offrent une fonction intégrée. Seulement si les gens ne sont pas satisfaits de sa sémantique, ils devront aller le faire eux-mêmes. Pas très souvent, cependant, on a en fait le besoin de copie par valeur.
Dans la plupart des cas où je devais copier un tableau, je souhaitais avoir une copie qui ne partage rien avec l'original, de sorte que toute modification du tableau d'origine n'a aucun impact sur la copie (et inversement ).
Tous les extraits qui ont été affichés jusqu'à présent ne parviennent pas à créer une copie pour une table qui peut avoir des clés partagées ou des clés avec des tables car celles-ci seront dirigées vers la table d'origine. Il est facile de voir si vous essayez de copier une table créée en tant que: a = {}; a [a] = a
. La fonction deepcopy référencée par Jon s'en charge, donc si vous devez créer une copie réelle / complète , deepcopy
doit être utilisé.
Utilisez la bibliothèque de penlight ici: https://stevedonovan.github.io/Penlight/api/libraries /pl.tablex.html#deepcopy
local pl = require 'pl.import_into'()
local newTable = pl.tablex.deepcopy(oldTable)
Cela pourrait être la méthode la plus 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"
Dans mon cas, lorsque la table contient uniquement des données et d’autres tables (à l’exclusion des fonctions, ...), la ligne de code suivante constitue la solution gagnante:
local copyOfTable = json.decode( json.encode( sourceTable ) )
J'écris du code Lua pour une domotique sur un Fibaro Home Center 2. L'implémentation de Lua est très limitée, sans bibliothèque centrale de fonctions à laquelle vous pouvez vous référer. Chaque fonction doit être déclarée dans le code pour que le code reste utilisable. Des solutions à une ligne comme celle-ci sont donc favorables.