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?

52
demandé sur greatwolf 2009-03-13 00:52:05

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.

19
répondu Tyler 2014-10-14 17:49:10

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).

42
répondu Doub 2013-11-14 22:34:04

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".

30
répondu Norman Ramsey 2009-03-20 00:56:28

La version complète de deep copy, traitant toutes les 3 situations:

  1. Tableau de référence circulaire
  2. Clés qui sont aussi des tableaux
  3. 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 .

12
répondu islet8 2017-05-23 12:17:56

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?

10
répondu Alan Yates 2011-04-19 01:17:52

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.

6
répondu Jon Ericson 2017-05-23 11:54:38

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.

4
répondu RBerteig 2009-03-13 21:19:37

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
4
répondu rsethc 2013-06-26 15:25:05

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.

1
répondu Aaron Saarela 2009-03-13 00:48:21

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.

1
répondu akauppi 2009-03-15 23:05:28

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é.

1
répondu Paul Kulchenko 2012-06-10 22:20:40

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.

1
répondu scippie 2013-09-25 12:09:01

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)
1
répondu wakeupbuddy 2016-01-31 18:29:53

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"
0
répondu Black 2015-06-22 14:24:09

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.

0
répondu sir KitKat 2016-10-02 23:31:10