Search code examples
recursionlualua-table

How to redefine values in inner tables?


Basically, scan the entire table for values of type say booleans for example and change them into a string, must work for inner tables and dictionaries...

local Table = {
  String = "abc",
  Number = 123,
  Boolean = true,
  InnerTable = {
     Boolean2 = false,
     InnerInnerTable = {
        Boolean3 = true,
        InnerInnerInnerTable = {
           -- And so on...
        }
     }
  }
}

In this example I want to change every boolean in the table to a string like "true" but without knowing what the table looks like, what I need is a function for any table parsed to be edited (dictionary or not). I couldn't accomplish this with a for loop or custom recursive functions so I need help.


Solution

  • What you need is a simple traversal of the table structure which maps booleans to strings. This can be implemented recursively as follows:

    local function deep_bool_to_string(tab)
        for k, v in pairs(tab) do
            if type(v) == "boolean" then
                tab[k] = tostring(v)
            elseif type(v) == "table" then
                deep_bool_to_string(v)
            end
        end
    end
    

    Usage in your example: deep_bool_to_string(Table). Mutates Table.

    Note that this only recursively dives into values, not keys of tables, as the latter isn't well defined: Should {["true"] = 1, [true] = 2} become {["true"] = 1} or {["true"] = 2}?

    In its current form, this function has two limitations:

    1. A circular table structure will cause it to overflow the stack.
    2. A too deeply nested table structure may do the same.

    (1) can be fixed by keeping track of already converted tables:

    local deep_bool_to_string = function(tab)
        local seen = {} -- "Set" of seen tables
        local function convert(t)
            seen[t] = true
            for k, v in pairs(t) do
                if type(v) == "boolean" then
                    t[k] = tostring(v)
                elseif type(v) == "table" and not seen[v] then
                    convert(v)
                end
            end
        end
        convert(tab)
    end
    

    (2) can be fixed by implementing the traversal using a table-based "stack":

    local deep_bool_to_string = function(tab)
        local seen = {[tab] = true} -- "Set" of seen tables
        local to_convert = {tab} -- "Stack" of tables to convert
        repeat
            local t = table.remove(to_convert) -- "pop" from stack
            for k, v in pairs(t) do
                if type(v) == "boolean" then
                    t[k] = tostring(v)
                elseif type(v) == "table" and not seen[v] then -- new table found?
                    seen[v] = true
                    table.insert(to_convert, v) -- "push" on stack
                end
            end
        until #to_convert == 0
    end
    
    

    All these are implementations of depth-first traversals, since they are usually more convenient to write (and more efficient) than breath-first traversals since they use a stack rather than a queue.