web-dev-qa-db-fra.com

Comment copier 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, car les variables contiennent des références à une table anonyme et non les valeurs de la table elle-même dans Lua. Ceci est clairement exposé dans Programmer en Lua , mais je l'avais oublié.

La question est donc que devrais-je écrire au lieu de copy = a pour obtenir une copie des valeurs dans a?

54
Jon Ericson

Pour jouer à un peu de code-golf lisible, voici une version courte qui traite les cas difficiles standard:

  • tables comme clés,
  • préserver 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 de Lua dans this Gist .

Une autre référence utile est cette page wiki de Lua-users , qui inclut un exemple sur la manière d’éviter la métaméthode __pairs.

19
Tyler

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éta-tables, etc. Il n'y a pas d'implémentation 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, car ipairs n'itère que sur un sous-ensemble des clés de la table (c'est-à-dire des clés entières positives consécutives commençant à un dans l'ordre croissant).

42
Doub

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 qualifiée de "standard".

30
Norman Ramsey

La version complète de deep copy, qui gère les 3 situations:

  1. Tableau de référence circulaire
  2. Touches qui sont aussi des tables
  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 les fonctions lua-users.org/wiki/CopyTable et Alan Yates '.

12
islet8

Une version récursive, généralement générale, graphique et générale

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 également?

10
Alan Yates

Voici ce que j'ai réellement fait:

for j,x in ipairs(a) do copy[j] = x end

Comme Doub mentionne , si vos clés de table n’augmentent pas de façon strictement monotone, il devrait être pairs pas ipairs.

J'ai aussi trouvé une fonction deepcopy 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éta-tables en s’appelant récursivement ( ce qui est 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, et même plus robuste (non-récursif) fonction pourrait être nécessaire.

Mais c’est exagéré dans le cas très simple de vouloir copier un tableau dans une autre variable.

6
Jon Ericson

Le projet stdlib (malheureusement peu documenté) comporte un certain nombre d’extensions utiles pour plusieurs bibliothèques livrées 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 la boîte à outils de tout utilisateur sérieux de Lua.

Une chose à vérifier lors de la mise en œuvre de telles opérations à la main est le traitement approprié des méta-tables. Pour les applications simples table-en-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'arborescence, contient des références circulaires ou contient des méta-tables, la situation devient plus complexe.

4
RBerteig

N'oubliez pas que les fonctions sont aussi des références, donc si vous voulez complètement copier toutes les valeurs, vous devez également avoir des fonctions séparées. Cependant, le seul moyen que je connaisse pour copier une fonction est d'utiliser loadstring(string.dump(func)), qui, selon le manuel de référence de Lua, ne fonctionne pas pour les fonctions avec des 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
4
rsethc

Attention: la solution indiquée estINCORRECT!

Lorsque la table contient des tables, les références à ces tables seront toujours utilisées. Je cherchais une erreur pendant deux heures, 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.copy récursivement!

C'est la fonction table.copy correcte:

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'en ayons pas besoin Le code ci-dessus est facilement adaptable à ceux qui en ont besoin.

2
scippie

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.

1
Aaron Saarela

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 "d'un niveau" (ce que vous avez fait), une copie profonde avec ou sans se soucier des éventuelles références dupliquées. 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.

1
akauppi

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)
1
wakeupbuddy

Dans la plupart des cas, lorsque je devais copier une table, je souhaitais une copie ne partageant rien avec l'original, de sorte que toute modification de la table d'origine n'ait 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. deepcopy la fonction référencée par Jon s'en charge, donc si vous avez besoin de créer une copie réelle/complète, deepcopy devrait être utilisé.

1
Paul Kulchenko

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
Black

Dans mon cas, lorsque la table contient uniquement des données et que 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. Les solutions à une ligne comme celle-ci sont donc favorables.

0
sir KitKat