# Recursive table.freeze

In response to:

Finally, we have a table.freeze

Here’s a quick function so the beginners don’t have to do this themselves (I personally hate writing recursive functions)

``````local function deepFreeze(tbl)
table.freeze(tbl)
for _, v in pairs(tbl) do
if type(v) == "table" then
deepFreeze(v)
end
end
end
``````

This obviously isn’t tested as `table.freeze` isn’t out yet. I do hope that freezing a table still lets you freeze nested tables within.

12 Likes

oh so this is what they meant by deep freeze, makes more sense now

1 Like

Just a reminder that this will error and cause a stack overflow for tables with a cyclical reference.

an easy way to prevent this is by having a cache and checking if the table has already been processed

here's an example
``````local function filter(table: table, cache: table?): table?
cache = cache or {}
local new = {}

cache[table] = true

for k, v in pairs(table) do
if typeof(v) == "table" then
if not cache[v] then
new[k] = filter(v, cache)
else
new[k] = "Cyclic Table: " .. tostring(v)
end
elseif Filters[typeof(k)] and Filters[typeof(v)] then
new[k] = v
end
end

cache[table] = nil

return new
end
``````
4 Likes

Here’s a better and a reliable solution which accounts for cyclic tables:

``````local function DeepFreezeTable(tabl)
table.freeze(tabl)

for k, v in pairs(tabl) do
if typeof(v) == "table" and not table.isFrozen(v) then
DeepFreezeTable(v)
end
end
end
``````
4 Likes

The only problem with that solution is that it does not account for a chain of cyclic references of tables.
Consider this scenario:

``````local function DeepFreezeTable(tabl)
table.freeze(tabl)

for _, v in pairs(tabl) do
if typeof(v) == "table" and v ~= tabl then
DeepFreezeTable(v)
end
end
end

local AnArray = {}
table.insert(AnArray, {
Parent = AnArray
})

DeepFreezeTable(AnArray) --This will go on forever because of lack of caching.
``````

my method is still superior because it is a O(1) Constant time while table.find is O(n) Linear time, the bigger the array is the slower it becomes.

@BenMactavsin thanks

I don’t recommend doing this variable name
`table` is already a global

1 Like

Remind you that the difference is n egligibleand the code was edited after @BenMactavsin pointed out an edge case which I forgot to account for due to an oversight.

Due to hash collisions, lookup times will not always necessarily be O(1) but O(n) in the worst case which can potentially happen quite often.

I think it’s worth noting that, in cases where all of a table’s descendants are known not to be frozen prior to the call; `table.isfrozen` could be used in place of caching.

table.isfrozen Implementation
``````local DeepFreeze = (function()
local FreezeNode = function(NodeArray, Node, IsHostNode)
for Key, Value in pairs(Node) do
if type(Value) == "table" then
table.insert(NodeArray, Value)
end
end
if IsHostNode and table.isfrozen(Node) then -- No redundancy; isfrozen is only called when IsHostNode is true.
return nil
end
table.freeze(Node)
end
local StepNextNodes = function(CurrentNodes)
local NextNodes = {}
for Index, Node in pairs(CurrentNodes) do
if not table.isfrozen(Node) then
FreezeNode(NextNodes, Node, false)
end
end
return NextNodes
end
return function(HostNode)
local CurrentNodes = {}
FreezeNode(CurrentNodes, HostNode, true)
while #CurrentNodes > 0 do
CurrentNodes = StepNextNodes(CurrentNodes)
end
end
end)()
``````

But of course, that wouldn’t be the ideal functionality if some subtables are already frozen prior to the function call. In that case; you’d either have to precompute the tree, or use a HashTable in order to keep track of cyclic references.

My HashTable Implementation
``````local DeepFreeze = (function()
local FreezeNode = function(NodeArray, FrozenNodes, Node)
for Key, Value in pairs(Node) do
if type(Value) == "table" then
table.insert(NodeArray, Value)
end
end
if not table.isfrozen(Node) then
table.freeze(Node)
end
FrozenNodes[Node] = true
end
local StepNextNodes = function(CurrentNodes, FrozenNodes)
local NextNodes = {}
for Index, Node in pairs(CurrentNodes) do
if FrozenNodes[Node] == nil then
FreezeNode(NextNodes, FrozenNodes, Node)
end
end
return NextNodes
end
return function(HostNode)
local FrozenNodes = {}
local CurrentNodes = {}
FreezeNode(CurrentNodes, FrozenNodes, HostNode)
while #CurrentNodes > 0 do
CurrentNodes = StepNextNodes(CurrentNodes, FrozenNodes)
end
end
end)()
``````

I used an inline function in order to define both examples, but it should be preferred to have a ModuleScript make the return instead. I’m also operating on the assumption that `table.freeze` won’t throw an error when called on tables that have potentially already been frozen.

Edit: So we’ve gotten a bit more information, and it unfortunately seems that `table.freeze` will indeed error when called on a frozen table. Both implementations have been edited accordingly.

2 Likes

Or they can just create it on their own not knowing that this post exist.

1 Like