web-dev-qa-db-ja.com

Luaテーブルを値でどのようにコピーしますか?

最近、次のようなLuaコードを少し書きました。

local a = {}
for i = 1, n do
   local copy = a
   -- alter the values in the copy
end

明らかに、変数は匿名テーブルへの参照を保持しているため、Luaのテーブル自体の値ではなく、私がやりたいことではありませんでした。これは Luaのプログラミング で明確にレイアウトされていますが、私はそれを忘れていました。

それで、質問はcopy = aの代わりに何を書いてaの値のコピーを取得する必要がありますか?

55
Jon Ericson

少し読みやすいコードゴルフをプレイするために、標準的なトリッキーなケースを処理する短いバージョンを以下に示します。

  • キーとしてのテーブル、
  • メタテーブルの保存、および
  • 再帰テーブル。

7行でこれを行うことができます。

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

this Gist にはLuaのディープコピー操作の簡単な説明があります。

別の便利なリファレンスは このLua-users wikiページ です。これには、__pairsメタメソッド。

21
Tyler

テーブルコピーには多くの潜在的な定義があります。シンプルコピーとディープコピーのどちらを使用するか、メタテーブルをコピー、共有、または無視するかどうかなどに依存します。誰もが満足できる単一の実装はありません。

1つのアプローチは、新しいテーブルを作成し、すべてのキー/値ペアを複製することです。

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)

pairsはテーブルキーのサブセット(つまり、1から順に増加する連続した正の整数キー)のみを反復するため、ipairsの代わりにipairsを使用する必要があることに注意してください。 。

43
Doub

ポイントを説明するために、私の個人的なtable.copyはメタテーブルにも注意を払っています。

function table.copy(t)
  local u = { }
  for k, v in pairs(t) do u[k] = v end
  return setmetatable(u, getmetatable(t))
end

「標準」と呼ばれるほど広く合意されたコピー機能はありません。

30
Norman Ramsey

3つの状況すべてを処理するディープコピーのフルバージョン:

  1. テーブル循環参照
  2. テーブルでもあるキー
  3. メタテーブル

一般版:

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

または、テーブルバージョン:

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

lua-users.org/wiki/CopyTable 'および Alan Yates '関数に基づいています。

12
islet8

オプションで深みのあるグラフ一般の再帰バージョン:

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

おそらくメタテーブルのコピーもオプションにする必要がありますか?

10
Alan Yates

私が実際にしたことは次のとおりです。

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

Doub言及 のように、テーブルキーが厳密に単調増加していない場合、pairsではなくipairsである必要があります。

また、より堅牢な deepcopy 関数が見つかりました。

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

再帰的にそれ自身を呼び出すことで、テーブルとメタテーブルを処理します( 独自の報酬 )。巧妙なビットの1つは、(テーブルであるかどうかにかかわらず)任意の値を渡すことができ、正しくコピーされることです。ただし、コストは潜在的にスタックをオーバーフローさせる可能性があることです。そのため、さらに堅牢な(非再帰的) 関数 が必要になる場合があります。

しかし、配列を別の変数にコピーしたいという非常に単純な場合には、それはやり過ぎです。

6
Jon Ericson

(残念ながら少し文書化されています) stdlib プロジェクトには、標準のLuaディストリビューションに同梱されているいくつかのライブラリに対する貴重な拡張機能が多数あります。それらの中には、テーブルのコピーとマージのテーマに関するいくつかのバリエーションがあります。

このライブラリは Lua for Windows ディストリビューションにも含まれており、おそらく深刻なLuaユーザーのツールボックスの一部になるはずです。

このようなものを手動で実装する際に確認する必要があることの1つは、メタテーブルの適切な処理です。単純な構造としてのテーブルアプリケーションの場合、おそらくメタテーブルはないので、pairs()を使用した単純なループは受け入れられる答えです。しかし、テーブルがツリーとして使用されている場合、循環参照が含まれている場合、またはメタテーブルがある場合、事態はより複雑になります。

4
RBerteig

関数も参照であることを忘れないでください。したがって、すべての値を完全に「コピー」したい場合は、別個の関数も取得する必要があります。ただし、関数をコピーする唯一の方法は、loadstring(string.dump(func))を使用することです。これは、Luaリファレンスマニュアルによると、アップバリューを持つ関数では機能しません。

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

警告:マークされたソリューションは[〜#〜] incorrect [〜#〜]!です。

テーブルにテーブルが含まれる場合、それらのテーブルへの参照が代わりに使用されます。上記のコードを使用したためでしたが、私は2時間かけてミスを探していました。

そのため、値がテーブルかどうかを確認する必要があります。その場合、table.copyを再帰的に呼び出す必要があります!

これは正しいtable.copy関数です。

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

注:これは、テーブルに関数または他の特殊な型が含まれている場合も不完全な場合がありますが、ほとんどの人が必要としない可能性があります。上記のコードは、それを必要とする人に簡単に適応できます。

2
scippie

基本的なテーブルで得られるのと同じくらい良いです。メタテーブルを含むテーブルをコピーする必要がある場合は、ディープコピーなどを使用します。

1
Aaron Saarela

Luaの標準ライブラリに「table.copy()」がないのは、タスクを定義するのが正確ではないためだと思います。ここですでに示したように、「1レベルの深さ」のコピーを作成することができます(これはあなたが行いました)。そして、メタテーブルがあります。

個人的には、組み込み機能を提供してほしいです。人々がそのセマンティクスに満足しない場合にのみ、彼らは自分でそれを行う必要があります。ただし、それほど頻繁ではありませんが、実際には値ごとのコピーが必要です。

1
akauppi

ここでペンライトライブラリを使用します: 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

テーブルをコピーする必要があるほとんどの場合、元のテーブルの変更がコピーに影響を与えないように(およびその逆も)、オリジナルと何も共有しないコピーが必要でした。

これまでに示されたすべてのスニペットは、共有キーまたはテーブルとのキーが元のテーブルを指しているため、それらのキーのコピーを作成できません。 a = {}; a[a] = aとして作成されたテーブルをコピーしようとしても簡単にわかります。 deepcopy Jonが参照する関数がそれを処理するため、実/完全コピーを作成する必要がある場合は、deepcopyを使用する必要があります。

1
Paul Kulchenko

これは最も簡単な方法かもしれません:

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

私の状況では、テーブル内の情報がデータと他のテーブルのみ(関数を除く...)である場合、次のコード行が最も優れたソリューションです。

local copyOfTable = json.decode( json.encode( sourceTable ) )

Fibaro Home Center 2でいくつかのホームオートメーション用のLuaコードを書いています。Luaの実装は非常に限られており、参照できる関数の中央ライブラリはありません。すべての関数はコードで宣言する必要があるため、コードを保守しやすくするため、このような1行のソリューションが適しています。

0
sir KitKat