Comment Copiez-vous une table Lua par valeur?
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 puisque 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 exposé dans Programmation dans Lua , mais je l'avais oublié.
Donc, la question Est Que dois-je écrire au lieu de copy = a
pour obtenir une copie des valeurs dans a
?
15 réponses
Pour jouer un peu lisible-code-golf, voici une version courte qui gère les cas difficiles standard:
- tables comme clés,
- préserver les métatables, 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 y a une courte écriture des opérations de copie profonde Lua dans CET essentiel .
Une autre référence utile est cette page Wiki Lua-users, qui comprend un exemple sur la façon d'éviter la métaméthode __pairs
.
La copie de Table a 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étatables, etc. Il n'y a pas de mise en œuvre unique qui puisse 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 pairs
au lieu de ipairs
, puisque ipairs
itérer uniquement sur un sous-ensemble des clés de la table (ie. clés entières positives consécutives commençant à un dans ordre croissant).
Juste pour illustrer le point, mon personnel table.copy
fait également attention aux métatables:
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'y a pas de fonction de copie suffisamment acceptée pour être appelée "standard".
La version complète de deep copy, traitant toutes les 3 situations:
- Tableau de référence circulaire
- Clés qui sont aussi des tableaux
- Métatable
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 le lua-users.org/wiki/CopyTable les fonctions de etAlan Yates .
Une version récursive optionnellement profonde, générale et graphique:
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 être facultative aussi?
Voici ce que j'ai fait:
for j,x in ipairs(a) do copy[j] = x end
Comme Doub mentionne , si vos clés de table ne sont pas strictement monotoniques, elles devraient être pairs
pas ipairs
.
J'ai aussi trouvé un deepcopy
fonction qui est 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étatables en s'appelant récursivement ( qui est sa propre récompense ). L'un des bits intelligents est que vous pouvez lui transmettre n'importe quelle valeur (qu'il s'agisse d'une table ou non) et il sera copié correctement. Cependant, le coût est - ce qu'il pourrait potentiellement déborder de la pile. Donc, et encore plus robuste (non récursif) function pourrait être nécessaire.
Mais c'est exagéré pour le cas très simple de vouloir copier un tableau dans une autre variable.
Le projet (malheureusement peu documenté) stdlib possède un certain nombre d'extensions précieuses pour plusieurs bibliothèques livrées avec la distribution Lua standard. Parmi eux, plusieurs variations sur le thème de la copie et de la fusion de tables.
Cette bibliothèque est également incluse dans la distributionLua Pour Windows , et devrait probablement faire partie de la boîte à outils de tout utilisateur sérieux de Lua.
Une chose à assurer lors de la mise en œuvre de choses comme celle-ci à la main est la suivante: manipulation correcte des métatables. Pour les applications simples table-as-structure, vous n'avez probablement pas de métatables, et une simple boucle utilisant pairs()
est une réponse acceptable. Mais si la table est utilisée comme un arbre, ou contient des références circulaires, ou a metatables, alors les choses deviennent plus complexes.
N'oubliez pas que les fonctions sont aussi des références, donc si vous voulez "copier" complètement toutes les valeurs dont vous avez besoin pour obtenir des fonctions séparées, cependant, la seule façon de copier une fonction est d'utiliser loadstring(string.dump(func))
, qui, selon le manuel de référence Lua, ne fonctionne pas pour les fonctions avec des valeurs élevées.
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
C'est aussi bon que vous obtiendrez pour les tables de base. Utilisez quelque chose comme deepcopy si vous avez besoin de copier des tables avec des métatables.
Je pense que la raison pour laquelle Lua n'a pas de table.copier()' dans ses bibliothèques standard est parce que la tâche n'est pas précis à définir. Comme déjà montré ici, on peut soit faire une copie "one level deep" (ce que vous avez fait), une deepcopy avec ou sans se soucier des références en double possibles. Et puis il y a des métatables.
Personnellement, je voudrais toujours qu'ils offrent une fonction intégrée. Seulement si les gens ne seraient pas satisfaits de sa sémantique, ils auraient besoin d'aller le faire eux-mêmes. Pas très souvent, cependant, on a réellement le besoin de copie par valeur.
Dans la plupart des cas où j'avais besoin de copier une table, je voulais avoir une copie qui ne partage rien avec l'original, de sorte que toute modification de la table d'origine n'ait aucun impact sur la copie (et vice versa).
Tous les extraits qui ont été montrés jusqu'à présent échouent à 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 vont être laissées pointant vers la table d'origine. Il est facile de voir si vous essayez de copier une table créée comme: a = {}; a[a] = a
. deepcopy la fonction référencée par Jon s'en occupe, donc si vous avez besoin de créer une copie réelle/complète, deepcopy
devrait être utilisé.
Attention: la solution marquée estincorrecte !
Lorsque la table contient des tables, les références à ces tables seront toujours utilisées à la place. J'ai cherché deux heures pour une erreur que je faisais, 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 c'est le cas, vous devriez appeler table.copier récursivement!
C'est la table correcte.fonction de copie:
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: cela peut également être incomplet lorsque le table contient des fonctions ou d'autres types spéciaux, mais c'est possible quelque chose dont la plupart d'entre nous n'ont pas besoin. Le code ci-dessus est facilement adaptable pour ceux qui en ont besoin.
Utilisez la bibliothèque 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 ma situation, lorsque les informations dans la table sont uniquement des données et d'autres tables (à l'exclusion des fonctions, ...), la ligne de code suivante est-elle 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 auxquelles vous pouvez vous référer. Chaque fonction doit être déclarée dans le code afin de garder le code réparable, donc des solutions d'une ligne comme celle-ci sont favorables.