Read only iterable table in lua? - lua
I want to have a read only table in my Lua program. If ever a key is removed or a key is associated with a new value, an error must be thrown.
function readonly(table)
local meta = { } -- metatable for proxy
local proxy = { } -- this table is always empty
meta.__index = table -- refer to table for lookups
meta.__newindex = function(t, key, value)
error("You cannot make any changes to this table!")
end
setmetatable(proxy, meta)
return proxy -- user will use proxy instead
end
It works great.
t = { }
t["Apple"] = "Red"
t[true] = "True!"
t[51] = 29
for k,v in pairs(t) do
print(v)
end
t = readonly(t)
t[51] = 30
Prints
Red
True!
29
input:7: You cannot make any changes to this table!
Problem
for k, v in pairs(t) do
print(v)
end
Will print nothing under all circumstances now. That's because the proxy table will never have anything inside of it. pairs apparently never calls index and thus cannot retrieve anything from the actual table.
What can I do to make this readonly table iterable?
I'm on Lua 5.1 and have access to these metamethods:
Lua 5.1 Manual
You can modify standard Lua function pairs to work correctly with your read-only tables.
local function readonly_newindex(t, key, value)
error("You cannot make any changes to this table!")
end
function readonly(tbl)
return
setmetatable({}, {
__index = tbl,
__newindex = readonly_newindex
})
end
local original_pairs = pairs
function pairs(tbl)
if next(tbl) == nil then
local mt = getmetatable(tbl)
if mt and mt.__newindex == readonly_newindex then
tbl = mt.__index
end
end
return original_pairs(tbl)
end
Usage:
t = { }
t["Apple"] = "Red"
t[true] = "True!"
t[51] = 29
for k,v in pairs(t) do
print(k, v)
end
t = readonly(t)
for k,v in pairs(t) do
print(k, v)
end
t[51] = 30
One solution is to create a wholly custom iterator for the table.
function readonly(table)
local meta = { } -- metatable for proxy
local proxy = { } -- this table is always empty
meta.__index = table -- refer to table for lookups
meta.__newindex = function(t, key, value)
error("You cannot make any changes to this table!")
end
local function iter()
return next, table
end
setmetatable(proxy, meta)
return proxy, iter -- user will use proxy instead
end
Usage:
t = { }
t["Apple"] = "Red"
t[true] = "True!"
t[51] = 29
for k,v in pairs(t) do
print(v)
end
t, tIter = readonly(t)
t[51] = 30
for k, v in tIter do
print(v)
end
Related
Converting Python function to Lua function
I am trying to convert existing python function into lua function. But my lua function is not producing same result as python function. Any help is appreciated. Python function: import json test = '{"http://localhost:8080/":{"phone":{"-detail/phone detail.template.html":"5167n,a,7,2","s/motorola-xoom-with-wifi.json":"516a0,5,4,3"},"favicon.ico":"016ad,3,3,2","img/phones/motorola-xoom-with-wi-fi.":{"1.jpg":"*02s,2s,4v,h3|116da,o,l,6","2.jpg":"*02s,2s,4v,kp|116da,j,i,8","3.jpg":"*02s,2s,4v,ob|116da,o,m,8,7,,7,7,7","4.jpg":"*02s,2s,4v,rx|116da,o,m,9,8,,7,7,7","5.jpg":"*02s,2s,4v,vj|116da,p,m,a,8,,7,7,7"}}}' def tri(param): t = {} for key in param: if key not in param: continue if isinstance(param[key], dict) and param[key] is not None: flat = tri(param[key]) for x in flat: if x not in flat: continue t[key + x] = flat[x] else: t[key] = param[key] return t print(tri(json.loads(test))) Lua code ( which is not producing same result as python function) local json = require('cjson') local test = '{"http://localhost:8080/":{"phone":{"-detail/phone-detail.template.html":"5167n,a,7,2","s/motorola-xoom-with-wi-fi.json":"516a0,5,4,3"},"favicon.ico":"016ad,3,3,2","img/phones/motorola-xoom-with-wi-fi.":{"1.jpg":"*02s,2s,4v,h3|116da,o,l,6","2.jpg":"*02s,2s,4v,kp|116da,j,i,8","3.jpg":"*02s,2s,4v,ob|116da,o,m,8,7,,7,7,7","4.jpg":"*02s,2s,4v,rx|116da,o,m,9,8,,7,7,7","5.jpg":"*02s,2s,4v,vj|116da,p,m,a,8,,7,7,7"}}}' local function tri(param) t = {} for key in pairs(param) do if param[key] == nil then end if type(param[key]) == "table" then flat = tri(param[key]) for k in pairs(flat) do t[key .. k] = flat[k] end else t[key] = param[key] end end return t end print(json.encode(tri(json.decode(test))))
local function tri(param) t = {} -- every time we call tri t will be "reset" to an empty table for key in pairs(param) do if param[key] == nil then end if type(param[key]) == "table" then flat = tri(param[key]) -- here we call tri, but we still need t! for k in pairs(flat) do t[key .. k] = flat[k] end else t[key] = param[key] end end return t end Making at least t global should solve that problem. But there is also no reason for flat to be global so we make it local too. local function tri(param) local t = {} for key in pairs(param) do if param[key] == nil then end if type(param[key]) == "table" then local flat = tri(param[key]) for k in pairs(flat) do t[key .. k] = flat[k] end else t[key] = param[key] end end return t end
Your task could be done a bit easier using json.traverse() function from this Lua JSON module. Traversing lets you perform arbitrary operations with JSON elements on-the-fly. This code concatenates element's path (for every JSON element except JSON containers: arrays/objects) and uses it as a key for Lua table. local json = require'json' local t = {} local function callback(path, json_type, value) if value ~= nil then -- value == nil for containers (arrays/objects) t[table.concat(path)] = value end end local test = '{"http://localhost:8080/":{"phone":{"-detail/phone detail.template.html":"5167n,a,7,2","s/motorola-xoom-with-wifi.json":"516a0,5,4,3"},"favicon.ico":"016ad,3,3,2","img/phones/motorola-xoom-with-wi-fi.":{"1.jpg":"*02s,2s,4v,h3|116da,o,l,6","2.jpg":"*02s,2s,4v,kp|116da,j,i,8","3.jpg":"*02s,2s,4v,ob|116da,o,m,8,7,,7,7,7","4.jpg":"*02s,2s,4v,rx|116da,o,m,9,8,,7,7,7","5.jpg":"*02s,2s,4v,vj|116da,p,m,a,8,,7,7,7"}}}' json.traverse(test, callback) -- Now t == { -- ["http://localhost:8080/favicon.ico"] = "016ad,3,3,2", -- ["http://localhost:8080/img/phones/motorola-xoom-with-wi-fi.1.jpg"] = "*02s,2s,4v,h3|116da,o,l,6", -- ["http://localhost:8080/img/phones/motorola-xoom-with-wi-fi.2.jpg"] = "*02s,2s,4v,kp|116da,j,i,8", -- ["http://localhost:8080/img/phones/motorola-xoom-with-wi-fi.3.jpg"] = "*02s,2s,4v,ob|116da,o,m,8,7,,7,7,7", -- ["http://localhost:8080/img/phones/motorola-xoom-with-wi-fi.4.jpg"] = "*02s,2s,4v,rx|116da,o,m,9,8,,7,7,7", -- ["http://localhost:8080/img/phones/motorola-xoom-with-wi-fi.5.jpg"] = "*02s,2s,4v,vj|116da,p,m,a,8,,7,7,7", -- ["http://localhost:8080/phone-detail/phone detail.template.html"] = "5167n,a,7,2", -- ["http://localhost:8080/phones/motorola-xoom-with-wifi.json"] = "516a0,5,4,3" -- }
Lua: Merge 2 strings collections in a case-insensitive manner
I want to merge two strings collections in a case-insensitive manner: string_collection1 = {"hello","buddy","world","ciao"} string_collection2 = {"Hello","Buddy","holly","Bye", "bYe"} merged_string_collection = merge_case_insensitive(string_collection1,string_collection2) --> {"hello","buddy","world","holly","bye","ciao"} Here's an attempt, but it does not work... function merge_case_insensitive(t1,t2) t3 = {} for _,s1 in pairs(t1) do for _,s2 in pairs(t2) do if string.lower(s1) == string.lower(s2) then t3[s1] = s1 end end end t4 = {} i = 1 for s,_ in pairs(t3) do t4[i] = string.lower(s) i = i + 1 end return t4 end string_collection1 = {"hello","buddy","world","ciao"} string_collection2 = {"Hello","Buddy","holly","Bye", "bYe"} merged_string_collection = merge_case_insensitive(string_collection1,string_collection2) for k,v in pairs(merged_string_collection) do print(k,v) end
It does not work because you use == to compare both strings which is case-sensitive. You could do something like string.lower(s1) == string.lower(s2) to fix that. Edit: As you can't figure out the rest yourself, here's some code: local t1 = {"hello","buddy","world","ciao"} local t2 = {"Hello","Buddy","holly","Bye", "bYe"} local aux_table = {} local merged_table = {} for k,v in pairs(t1) do aux_table[v:lower()] = true end for k,v in pairs(t2) do aux_table[v:lower()] = true end for k,v in pairs(aux_table) do table.insert(merged_table, k) end merged_table now contains the lower case version of every word in both input tables. Now pour that into a function that takes any number of input tables and you are done. What we did here: we use the lower case version of every word in those tables and store them in a list. aux_table[string.lower("Hello")] will index the same value as aux_table[string.lower("hello")]. So we end up with one entry for each word, even if a word comes in multiple variations. Using the keys saves us the hassle of comparing strings and distiguishing between unique words and others.
To get a table with all strings from two other tables appearing once (without regard to case), you need something like this: function merge_case_insensitive(t1,t2) local ans = {} for _,v in pairs(t1) do ans[v:lower()] = true end for _,v in pairs(t2) do ans[v:lower()] = true end return ans end string_collection1 = {"hello","buddy","world","ciao"} string_collection2 = {"Hello","Buddy","holly","Bye", "bYe"} merged_string_collection = merge_case_insensitive(string_collection1,string_collection2) for k in pairs(merged_string_collection) do print(k) end Edit: And in case you want an array result (without adding another iteration) function merge_case_insensitive(t1,t2) local ans = {} local function add(t) for _,v in pairs(t) do v = v:lower() if ans[v] == nil then ans[#ans+1] = v end ans[v] = true end end add(t1) add(t2) return ans end string_collection1 = {"hello","buddy","world","ciao"} string_collection2 = {"Hello","Buddy","holly","Bye", "bYe"} merged_string_collection = merge_case_insensitive(string_collection1,string_collection2) for _,v in ipairs(merged_string_collection) do print(v) end
We can do this by simply iterations over both tables, and storing a temporary dictionary for checking what words we have already found, and if not there yet, putting them in our new array: function Merge(t1, t2) local found = {} --Temporary dictionary local new = {} --New array local low --Value to store low versions of words in later for i,v in ipairs(t1) do --Begin iterating over table one low = v:lower() if not found[low] then --If not found yet new[#new+1] = low --Put it in the new table found[low] = true --Add it to found end end for i,v in ipairs(t2) do --Repeat with table 2 low = v:lower() if not found[low] then new[#new+1] = low found[low] = true end end return new --Return the new array end This method eliminates the need for a third iteration, like in Piglet's answer, and doesn't keep redefining a function and closure and calling them like in tonypdmtr's answer.
attaching metatables within tabes
I have parser that parses a config file and produces a table. The resulting table can look something like: root = { global = { }, section1 = { subsect1 = { setting = 1 subsubsect2 = { } } } } The goal is to have a table I can read settings from and if the setting doesn't exist, it'll try to grab it from it's parent. At the top level it will grab from global. If it's not in global it'll return nil. I attach metatables to root like this: local function attach_mt(tbl, parent) for k,v in pairs(tbl) do print(k, v) if type(v) == 'table' then attach_mt(v, tbl) setmetatable(v, { __index = function(t,k) print("*****parent=", dump(parent)) if parent then return tbl[k] else if rawget(tbl, k) then return rawget(tbl, k) end end print(string.format("DEBUG: Request for key: %s: not found", k)) return nil end }) end end end attach_mt(root) However, when requesting keys it doesn't work. What appears to be the case is that is always nil. How do I read from the parent table?
local function attach_mt(tbl, parent) setmetatable(tbl, {__index = parent or root.global}) for k, v in pairs(tbl) do if type(v) == 'table' then attach_mt(v, tbl) end end end attach_mt(root) setmetatable(root.global, nil)
time complexity of metatable in Lua when accessing
local cls = {base = "base"} local ins = {} cls.__index = cls setmetatable(ins, cls) What is the time complexity of accesssing ins.base?
You can expect O(1) time complexity from the official Lua implementation. The code for __index is roughly equivalent to this Lua code, taken from the manual: function gettable_event (table, key) local h if type(table) == "table" then local v = rawget(table, key) if v ~= nil then return v end h = metatable(table).__index if h == nil then return nil end else h = metatable(table).__index if h == nil then error(ยทยทยท) end end if type(h) == "function" then return (h(table, key)) -- call the handler else return h[key] -- or repeat operation on it end end The __index lookup itself has no loops, and since Lua tables are backed by hash tables, table lookups are usually a constant-time operation.
infinite recursion in __index metamethod
As I understand lua don't call __index unless the key wasn't found in the table so I have that code and it suffers from infinite recursion in the __index part which I don't get as both values used inside the __index function already exist in the table!? This is basically a test script for trying to save the size of the table in a memory to retreive when # is called do local lenKey,originalKey = {},{} fastKey = {} fastKey.__len = function(t) return t[lenKey] end fastKey.__index = function (t,k) t[lenKey] = t[lenKey] +1 return t[oroginalKey][k] end fastKey.__newindex = function(t,k,v) t[originalKey][k] = v end fastKey.__pairs = function () return function (t, k) return next(t[oroginalKey], k) end end function fastLen(t) local proxy = {} local c = 0 for _ in pairs(t) do c=c+1 end proxy[lenKey] = c proxy[originalKey] = t setmetatable(proxy,fastKey) return proxy end end n = fastLen{1,2,3,x=5} --n:insert(1) -- here the __index is called and gets stackoverflow print(#n)
You've got two typos in there: both the __index and __pairs functions contain oroginalKey instead of originalKey.