Liam Hill
Liam Hill

Reputation: 35

lua deepcopy select index / values to new table and then print new table

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

Answers (2)

Alexander Mashin
Alexander Mashin

Reputation: 4602

Your deepcopy function needs some improvement:

  • it doesn't handle the case when original is not a table,
  • since key can be a table (though not in your example), it needs to be deeply copied,
  • deep copy assumes copying reference to the metatable, albeit you are not using one,
  • there is another issue with 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

Michał Politowski
Michał Politowski

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

Related Questions