Cloning Tables

Sep 25 2019, 1:58 PM PST 5 min

Sometimes it’s useful to be able to copy — or “clone” — a table. Lua provides no built in function to copy tables, so we must write our own.

Shallow Copies

Doing a shallow copy involves looping over all the keys in one table using pairs, then assigning them to another table. We can create a simple function to do this.

local function shallowCopy(original)
local copy = {}
for key, value in pairs(original) do
copy[key] = value
end
return copy
end

We’ll want a small function to print out tables to see what’s going on:

local function stringifyTable(t)
local entries = {}
for k, v in pairs(t) do
-- if we find a nested table, convert that recursively
if type(v) == **table** then
v = stringifyTable(v)
else
v = tostring(v)
end
k = tostring(k)

-- add another entry to our stringified table
entries[#entries + 1] = ("s = s"):format(k, v)
end

-- the memory location of the table
local id = tostring(t):sub(8)

return ("{s}@s"):format(table.concat(entries, **, **), id)
end

This function doesn’t cover all situations well, and it’s not too important to understand how it works, but it shows us what we need to know:

local original = { key = "Value", pieIsTasty = false }
local reassigned = original
local copied = shallowCopy(original)

t.pieIsTasty = true

print("original", stringifyTable(original))
print("reassigned", stringifyTable(reassigned))
print("copied", stringifyTable(copied))
Output:
original    {key = Value, pieIsTasty = true}@0025B560
reassigned  {key = Value, pieIsTasty = true}@0025B560
copied      {key = Value, pieIsTasty = false}@0025B420

You can see from this output that original and reassigned have the same ID, so they refer to the same table. As a result, when one is changed, the other changes too. By copying original into copied, this change does not transfer.

Arrays

Lua does most of the work for us when shallow-copying an array:

local original = {1, 3, 5, 7, 9}
local copy = {unpack(original)}

Deep Copies

Sometimes a shallow copy isn’t enough. Consider:

local original = { Shedletsky = { score=2 }, Builderman = { score = 1 } }
local snapshot = shallowCopy(original)  -- A snapshot of the scores as they were

original[**Shedletsky**].score = 5

print("original", stringifyTable(original))
print("snapshot", stringifyTable(snapshot))
Output:
original  {Shedletsky: {score: 5}@0019B6F0, Builderman: {score: 1}@0019B5B0}@0019B470
snapshot  {Shedletsky: {score: 5}@0019B6F0, Builderman: {score: 1}@0019B5B0}@0019B600

Oops! Looks like we accidentally updated the score in our snapshot — notice that while the outer tables have different IDs, the inner ones have the same IDs. We need to make copies there too. To do that, we should use recursion.

local function deepCopy(original)
local copy = {}
for k, v in pairs(original) do
-- as before, but if we find a table, make sure we copy that too
if type(v) == **table** then
v = deepCopy(v)
end
copy[k] = v
end
return copy
end

Giving what we intended:

local original = { Shedletsky = { score = 2 }, Builderman = { score = 1 } }
local snapshot = deepCopy(original)  -- A snapshot of the scores as they were

original[**Shedletsky**].score = 5

print("original", stringifyTable(original))
print("snapshot", stringifyTable(snapshot))
Output:
original  {Shedletsky: {score: 5}@0046B7E0, Builderman: {score: 1}@0046B1F0}@0046B3D0
snapshot  {Shedletsky: {score: 2}@0046B240, Builderman: {score: 1}@0046B560}@0046B510

In some cases, it may be desirable to make deepCopy() copy keys which are tables as well as values. However, this should be done with caution, since table keys are looked up by ID, not value:

local range = {1, 2, 3, 4, 5}
local isUseful = { [range] = true }

local copiedRange = shallowCopy(range)

print(isUseful[range])
print(isUseful[copiedRange])
true
nil
Tags:
• cloning
• tables