Reputation: 35
new to lua and I need some help figuring out the deepcopy process from the lua users wiki. Basically, I have an in ipairs table with multiple attributes per index chunk, and I want to copy the entire index chunk where the attributes meet certain if conditions, run in a for loop. Finally, I want to print the new table contents.
The table is very long so I'll just include a short excerpt.
local tblStudents = {
{
['Name'] = 'Jeff',
['Year'] = 'Twelve-A',
['Class'] = 'Ms Edwards',
['Attended'] = true
},
{
['Name'] = 'Tom',
['Year'] = 'Twelve-B',
['Class'] = 'Ms Edwards',
['Attended'] = true
},
{
['Name'] = 'Billy',
['Year'] = 'Twelve-A',
['Class'] = 'Ms Edwards',
['Attended'] = false
},
{
['Name'] = 'Jack',
['Year'] = 'Twelve-B',
['Class'] = 'Ms Edwards',
['Attended'] = false
},
{
['Name'] = 'Sam',
['Year'] = 'Twelve-A',
['Class'] = 'Mr Green',
['Attended'] = true
},
{
['Name'] = 'Diego',
['Year'] = 'Twelve-A',
['Class'] = 'Mr Green',
['Attended'] = false
},
{
['Name'] = 'Peta',
['Year'] = 'Twelve-A',
['Class'] = 'Mr Green',
['Attended'] = false
},
{
['Name'] = 'Will',
['Year'] = 'Twelve-A',
['Class'] = 'Ms Edwards',
['Attended'] = true
},
{
['Name'] = 'Sara',
['Year'] = 'Twelve-B',
['Class'] = 'Ms Edwards',
['Attended'] = true
},
{
['Name'] = 'Lisa',
['Year'] = 'Twelve-A',
['Class'] = 'Ms Edwards',
['Attended'] = true
}
}
What I want to do is deepcopy all of the students from Twelve-A who didn't attend to a new table, tblTruant I imagine the statement to do so would use the conditional:
for i,v in ipairs (tblStudents) do
if v.Year == 'Twelve-A' and v.Attended == false then
--deepcopy to new table.
The demo code I've been given for deepcopy is this:
local function deepCopy(original)
local copy = {}
for k, v in pairs(original) do
if type(v) == "table" then
v = deepCopy(v)
end
copy[k] = v
end
return copy
end
I'm not sure how to apply that using the conditional outlined above.
For printing the results of the new table, I've been told as I can't print a table as a string, to use this recursive function:
function dump(o)
if type(o) == 'table' then
local s = '{ '
for k,v in pairs(o) do
if type(k) ~= 'number' then k = '"'..k..'"' end
s = s .. '['..k..'] = ' .. dump(v) .. ','
end
return s .. '} '
else
return tostring(o)
end
end
My presumption would be that this would allow me to use print(dump(tblTruant) to get the desired result.
Any help greatly appreciated!
Upvotes: 1
Views: 474
Reputation: 4602
Your deepcopy
function needs some improvement:
original
is not a table,key
can be a table (though not in your example), it needs to be deeply copied,deepCopy
and dump
. Although it is irrelevant with the table in the examples, generic tables can be recursive, that is, contain references to themselves. This has to be dealt with. A possible way is to keep a "set" of tables that are already being processed by the functions. This set can be stored as a table local to the same scope as the function it serves, yet to make it "static", i.e., available from the function, both can be wrappen in a second-order function.An improved version:
local concat = table.concat
local function handleRecursiveTables (func, stub)
local calls = {}
return function (arg)
if type (arg) == 'table' and calls [arg] then
return stub (arg)
end
calls [arg] = true
return func (arg)
end
end
local function deepCopy (original)
local copy
if type (original) ~= 'table' then
copy = original
else
copy = {}
for key, value in pairs (original) do
copy [deepCopy (key)] = deepCopy (value)
end
end
if type (original) == 'table' or type (original) == 'userdata' then
setmetatable (copy, getmetatable (original))
end
return copy
end
deepCopy = handleRecursiveTables (deepCopy, function (original)
return original
end)
Call it like this:
local truants = {}
for i,v in ipairs (tblStudents) do
if v.Year == 'Twelve-A' and v.Attended == false then
--deepcopy to new table.
truants [#truants + 1] = deepCopy (v)
end
end
Similar improvements for dump
making it more generic:
local function dump (o)
if type (o) == 'string' then
return '"' .. o .. '"'
elseif type (o) == 'table' then
local items = {}
for key, value in pairs (o) do
items [#items + 1] = '[' .. dump (key) .. '] = ' .. dump (value)
end
return '[' .. concat (items, ', ') .. ']'
else
return tostring (o)
end
end
dump = handleRecursiveTables (dump, function (o)
return '(recursive table reference)'
end)
UPD A full working example:
-- Utility function to handle recursive tables. Not really necesary, bit demonstrates some principles.
-- A similar technique is used for caching expensive function calls.
local concat = table.concat -- localise table value to speed it up.
-- func is a function that takes one argument of the type table and needs to be enabled to handle recursive tables.
-- stub is the function that handles the table if the table has already been processed to stop infinite recursion.
local function handleRecursiveTables (func, stub)
local calls = {} -- a set of already processed tables.
return function (arg)
if type (arg) == 'table' then
if calls [arg] then
return stub (arg) -- stop recursion.
end
calls [arg] = true -- remember that arg has been processed.
end
return func (arg) -- actually call the function.
end
end
-- Naïve implementation of deep copy.
local function deepCopy (original)
local copy
if type (original) ~= 'table' then
copy = original -- not a table, so simply return the scalar value.
else
copy = {}
for key, value in pairs (original) do
-- We need to deep copy both the key and the value, as any can be a table.
copy [deepCopy (key)] = deepCopy (value)
end
end
-- Copy the reference to the metatable, if any.
if type (original) == 'table' or type (original) == 'userdata' then
setmetatable (copy, getmetatable (original))
end
return copy
end
-- Make the function aware of recursive tables.
-- Not that recursive calls of deepCopy will also be affected.
deepCopy = handleRecursiveTables (deepCopy, function (original)
return original
end)
-- Naïve implementation of dump.
local function dump (o)
if type (o) == 'string' then
return '"' .. o .. '"' -- quote the string, wherever it appears.
elseif type (o) == 'table' then
local items = {} -- '[key] = value' chunks.
for key, value in pairs (o) do
-- Use dumped key and value.
items [#items + 1] = '[' .. dump (key) .. '] = ' .. dump (value)
end
-- table.concat once is faster than applying .. again and again.
-- Also, there will be no trailing comma.
return '[' .. concat (items, ', ') .. ']'
else
return tostring (o) -- numbers, functions, userdata.
end
end
-- Make the function aware of recursive tables.
-- Not that recursive calls of dump will also be affected.
dump = handleRecursiveTables (dump, function (o)
return '(recursive table reference)'
end)
-- This function filters out truants from the given year and returns a table of their deep copies.
local function getTruants (students, year)
local truants = {}
for _, student in ipairs (students) do -- we don't need the key, so use _ as the variable name.
if student.Year == year and student.Attended == false then
-- you can use 'not student.Attended' instead of 'student.Attended == false'
-- but in this case, if Attended has not been explicitly set, it will be falsy.
--deepcopy to new table.
truants [#truants + 1] = deepCopy (student)
end
end
return truants
end
-- Test dataset:
local tblStudents = {
{
-- Note that Name = 'Jeff' is the same as ['Name'] = 'Jeff'.
-- 'Name' has to consist of underscores, letters (at least one of either) and numbers.
Name = 'Jeff',
Year = 'Twelve-A',
Class = 'Ms Edwards',
Attended = true
},
{
Name = 'Tom',
Year = 'Twelve-B',
Class = 'Ms Edwards',
Attended = true
},
{
Name = 'Billy',
Year = 'Twelve-A',
Class = 'Ms Edwards',
Attended = false
},
{
Name = 'Jack',
Year = 'Twelve-B',
Class = 'Ms Edwards',
Attended = false
},
{
Name = 'Sam',
Year = 'Twelve-A',
Class = 'Mr Green',
Attended = true
},
{
Name = 'Diego',
Year = 'Twelve-A',
Class = 'Mr Green',
Attended = false
},
{
Name = 'Peta',
Year = 'Twelve-A',
Class = 'Mr Green',
Attended = false
},
{
Name = 'Will',
Year = 'Twelve-A',
Class = 'Ms Edwards',
Attended = true
},
{
Name = 'Sara',
Year = 'Twelve-B',
Class = 'Ms Edwards',
Attended = true
},
{
Name = 'Lisa',
Year = 'Twelve-A',
Class = 'Ms Edwards',
Attended = true
}
}
-- The following line is an example of a self-reference in a table.
-- Without handleRecursiveTables it would cause infinite recursion.
tblStudents [1].allStudents = tblStudents
-- Print a list of truants.
print (dump (getTruants (tblStudents, 'Twelve-A')))
Upvotes: 1
Reputation: 4385
You do not have to do much more than you already have. Just create the new table and insert the interesting items into it:
tblTruant = {} -- create a new empty table
for i,v in ipairs (tblStudents) do
if v.Year == 'Twelve-A' and v.Attended == false then
table.insert(tblTruant, deepCopy(v)) -- deepcopy to new table
print(dump(tblTruant)) -- print the new table contents
Any possible improvements to dump
or deepCopy
are another matter entirely.
Upvotes: 1