Adding _concat to numbers to create number ranges - am I mad? - lua

Just as a random experiment I'm considering adding a __concat() metamethod to the number metatable (usually a new metatable as numbers don't seem to have metatables by default?).
The idea is that I could do something like 3..5 and get back 3, 4, 5.
I could then have a function foo(tbl, ...) that does something with multiple indexes on a table and call it like foo(tbl, 3..5).
Am I barking mad or does this seem like a viable thing to do?
Rough draft of code (not tested yet):
-- get existing metatable if there is one
local m = getmetatable( 0 ) or {};
-- define our concat method
m.__concat = function( left, right )
-- note: lua may pre-coerce numbers to strings?
-- http://lua-users.org/lists/lua-l/2006-12/msg00482.html
local l, r = tonumber(left), tonumber(right);
if not l or not r then -- string concat
return tostring(left)..tostring(right);
else -- create number range
if l > r then l, r = r, l end; -- smallest num first?
local t = {};
for i = l, r do t[#t+1] = i end;
return (table.unpack or unpack)(t);
end
end
-- set the metatable
setmetatable( 0, m );
Additional question: Is there any way for me to populate a ... value by value (to remove the need for a table & unpack in the example above)?

Your idea can be implemented using __call metamethod:
local mt = debug.getmetatable(0) or {}
mt.__call = function(a,b) -- a, b - positive integer numbers
return (('0'):rep(a-1)..('1'):rep(b-a+1)):match(('()1'):rep(b-a+1))
end
debug.setmetatable(0, mt)
-- Example:
print((3)(5)) --> 3 4 5

Related

(Lua) How do I break a string into entries on a table?

So I want to take an input like R U R' U' and turn it into a table that contains
R
U
R'
U'
I haven't found an example of code that worked. I have tried this solution from codegrepper, and it didn't work. I have not come up with anything else in my head but my general program, which is supposed to take an input like R and find its place in a table. If R is 1, then it will take the value 1 from another table, which will have the r^ as value 1. Then it will do this with the rest and print it when it is done. So if there is an optimization with this that could make it all quicker than I would like to see it. Thanks and goodbye.
function split(str, pat)
local t = {}
local fpat = "(.-)" .. pat
local last_end = 1
local s, e, cap = str:find(fpat, 1)
while s do
if s ~= 1 or cap ~= "" then table.insert(t, cap) end
last_end = e + 1
s, e, cap = str:find(fpat, last_end)
end
if last_end <= #str then
cap = str:sub(last_end)
table.insert(t, cap)
end
return t
end
then split it with split(var," ")
local myString = "R U R' U'"
local myTable = {}
for e in string.gmatch(myString, "%S+") do
table.insert(myTable, e)
end
Lua Users Wiki
s:gmatch(pattern)
This returns a pattern finding iterator. The iterator will search
through the string passed looking for instances of the pattern you
passed.
First you need to match all the space-separated parts. You can do this using gmatch. Then you can insert these parts as keys in a hash table, the value being the one-indexed index of the occurrence:
local str = "R U R' U'"
local index = 1
local last_occurrence = {}
for match in str:gmatch"%S+" do
last_occurrence[match] = index
index = index + 1
end
Now you can use your "other table" to obtain the value in constant time:
local other_table = {"r^"}
local value = other_table[last_occurrence.R] -- "r^"

lua: piggybacking multiple variables across 1 if statement

I have around 7+ variables: a=1, b=10, c=12...etc
I need to write an if statement for each that does this:
if var>0 then var-=1 end
If I need each of the variables to record their values after each iteration, is there a way for me to avoid writing out one if statement per variable?
I tried defining them all in a table like:
a=1;b=2;c=3
local t = {a,b,c}
for _,v in pairs(t) do
if v>0 then v-=1 end
end
a,b,c=t[1],t[2],t[3]
This code failed though, not sure why. Ultimately I'm looking for more efficient way than simply writing the ifs. You can define efficient in terms of cost or tokens or both. The values used would be random, no pattern. The variable names can potentially be changed, i.e. a_1,a_2, a_3, its not ideal though.
There are a couple of solutions. To shorten your code, you could write a function that processes the value and run it on each variable:
local function toward0(var)
if var > 0 then
return var - 1
end
return var
end
a = toward0(a)
b = toward0(b)
c = toward0(c)
You could also store the data in a table instead of in variables. Then you can process them in a loop:
local valuesThatNeedToBeDecremented = {a = 1, b = 10, c = 12}
for k, v in pairs(valuesThatNeedToBeDecremented) do
if v > 0 then
valuesThatNeedToBeDecremented[k] = v - 1
end
end
You forgot to reasign the new values to the table!
local a, b, c = 1, 2, 3
local t = {a, b, c}
for k, v in ipairs(t) do
if v > 0 then v -= 1 end
t[k] = v
end
a, b, c = t[1], t[2], t[3]
print(a, b, c)

My program filters some uneven numbers but also some even numbers

So I need to write a program which gets a table as an input and gives the same table as an output without the values with even keys. So basically I need to filter out the even keys and their values and leave the uneven keys with their values.
local function selecteer_oneven(tabel)
for q, n in ipairs(tabel) do
if (q % 2) == 0 then
table.remove(tabel, q)
end
end
return tabel
end
local function printtabel(tabel)
for k,v in pairs(tabel) do
print(k,v)
end
end
io.write("Geef een lua-tabel: ")
local tabelstring = "t = "..io.read()
local string2tab = loadstring(tabelstring)
string2tab()
local resultaat = selecteer_oneven(t)
printtabel(resultaat)
my input is
{ "aap", "kat", "hond", "paard", "kip", "salamander", "programmeren is leuk" }
and my output is
1 aap
2 hond
3 paard
4 salamander
5 programmeren is leuk
(sorry it is in Dutch)
"Aap", "Hond", "Programmeren is leuk" are the only uneven ones. "paard", and "salamander" are even.
Dont do table.remove on the table you are checking at same time.
Better do a second local table and insert q.
And finaly return the second table...
local function selecteer_oneven(tabel)
local tabel2={}
for q, n in ipairs(tabel) do
if (q % 2) ~= 0 then
table.insert(tabel2, q)
end
end
return tabel2
end
...dont tested - yet ;-)
EDIT: Tested with lua -i
-- <ready>
>function selecteer_oneven(tabel)
local tabel2={}
for q, n in ipairs(tabel) do
if (q % 2) ~= 0 then
table.insert(tabel2, q)
end
end
return tabel2
end
-- <ready>
>dump(selecteer_oneven({1,2,3,4,5,6,7,8,9,10}))
1=1
2=3
3=5
4=7
5=9
-- <ready>
>-- whats dump?
-- <ready>
>code.dump
-- dump()
return function(dump)
for key,value in pairs(dump) do
io.write(string.format("%s=%s\n",key,value))
end
end
-- <ready>

Lua converting my identifier to a string when I put it into a table?

a = {}
b = "s"
a.b = "white"
a["s"] = 2
local keyset={}
local n=0
for k,v in pairs(a) do
n=n+1
keyset[n]=k
print(type(k))
-- output is String(x2)
end
Does Lua recognize b as a string and not as an identifier if I do a.b?
From the Lua Documentation:
A common mistake for beginners is to confuse a.x with a[x]. The first form represents a["x"], that is, a table indexed by the string "x". The second form is a table indexed by the value of the variable x.
If anyone would like to add on to why Lua does this, feel free to do it! The question is however answered at this point.
UPD Before the for loop, a = {['b'] = 'white', ['s'] = 2}.
Forget about the keyset table; that's not converting anything.
#! /usr/bin/env lua
a = {}
b = "s" -- completely different variable, not used in a
a.b = "white" -- syntactic sugar for a["b"] = "white"
a["s"] = 2 -- variable b just happens to have same value as this key, so it fits
-- at this point there are two entries in a
-- a["b"] = "white" and a["s"] = 2
for k, v in pairs( a ) do
print( 'key:', k, type(k), ' val:', v, type(v) )
end
print( b, a[b] ) -- retrieve value from a["s"] coincidental key fit
key: s string val: 2 number
key: b string val: white string
s 2

Element by Element comparison in Lua

I'm trying to find a way to do element-by-element comparison in Lua using the standard < operator. For example, here's what I'd like to do:
a = {5, 7, 10}
b = {6, 4, 15}
c = a < b -- should return {true, false, true}
I already have code working for addition (and subtraction, multiplication, etc). My issue is that Lua forces the result of a comparison to a boolean. I don't want a boolean, I want a table as the result of the comparison.
Here is my code so far, with addition working, but less-than comparison not working:
m = {}
m['__add'] = function (a, b)
-- Add two tables together
-- Works fine
c = {}
for i = 1, #a do
c[i] = a[i] + b[i]
end
return c
end
m['__lt'] = function (a, b)
-- Should do a less-than operator on each element
-- Doesn't work, Lua forces result to boolean
c = {}
for i = 1, #a do
c[i] = a[i] < b[i]
end
return c
end
a = {5, 7, 10}
b = {6, 4, 15}
setmetatable(a, m)
c = a + b -- Expecting {11, 11, 25}
print(c[1], c[2], c[3]) -- Works great!
c = a < b -- Expecting {true, false, true}
print(c[1], c[2], c[3]) -- Error, lua makes c into boolean
The Lua programming manual says that the result of the __lt metamethod call is always converted to a boolean. My question is, how can I work around that? I heard that Lua is good for DSL, and I really need the syntax to work here. I think it should be possible using MetaLua, but I'm not really sure where to start.
A coworker suggested that I just use << instead with the __shl metamethod. I tried it and it works, but I really want to use < for less than, rather than a hack using the wrong symbol.
Thanks!
You only have two choices to make this work with your syntax:
Option 1: Patch the Lua core.
This is probably going to be very difficult, and it'll be a maintenance nightmare in the future. The biggest issue is that Lua assumes on a very low level that the comparison operators <, >, ==, ~= return a bool value.
The byte-code that Lua generates actually does a jump on any comparison. For example, something like c = 4 < 5 gets compiled to byte-code that looks much more like if (4 < 5) then c = true else c = false end.
You can see what the byte-code looks like with luac -l file.lua. If you compare the byte-code of c=4<5 with c=4+5 you'll see what I mean. The addition code is shorter and simpler. Lua assumes you'll do branching with comparisons, not assignment.
Option 2: Parse your code, change it, and run that
This is what I think you should do. It would be very hard, expect most of the work is already done for you (using something like LuaMinify).
First of all, write a function you can use for comparisons of anything. The idea here is to do your special comparison if it's a table, but fall back on using < for everything else.
my_less = function(a, b)
if (type(a) == 'table') then
c = {}
for i = 1, #a do
c[i] = a[i] < b[i]
end
return c
else
return a < b
end
end
Now all we need to do is replace every less than operator a<b with my_less(a,b).
Let's use the parser from LuaMinify. We'll call it with the following code:
local parse = require('ParseLua').ParseLua
local ident = require('FormatIdentity')
local code = "c=a*b<c+d"
local ret, ast = parse(code)
local _, f = ident(ast)
print(f)
All this will do is parse the code into a syntax tree, and then spit it back out again. We'll change FormatIdentity.lua to make it do the substitution. Replace the section near line 138 with the following code:
elseif expr.AstType == 'BinopExpr' then --line 138
if (expr.Op == '<') then
tok_it = tok_it + 1
out:appendStr('my_less(')
formatExpr(expr.Lhs)
out:appendStr(',')
formatExpr(expr.Rhs)
out:appendStr(')')
else
formatExpr(expr.Lhs)
appendStr( expr.Op )
formatExpr(expr.Rhs)
end
That's all there is to it. It will replace something like c=a*b<c+d with my_less(a*b,c+d). Just shove all your code through at runtime.
Comparisons in Lua return a boolean value.
There is nothing you can do about it short of changing the core of Lua.
Can you put up with a bit verbose v()-notation:
v(a < b) instead of a < b ?
local vec_mt = {}
local operations = {
copy = function (a, b) return a end,
lt = function (a, b) return a < b end,
add = function (a, b) return a + b end,
tostring = tostring,
}
local function create_vector_instance(operand1, operation, operand2)
local func, vec = operations[operation], {}
for k, elem1 in ipairs(operand1) do
local elem2 = operand2 and operand2[k]
vec[k] = func(elem1, elem2)
end
return setmetatable(vec, vec_mt)
end
local saved_result
function v(...) -- constructor for class "vector"
local result = ...
local tp = type(result)
if tp == 'boolean' and saved_result then
result, saved_result = saved_result
elseif tp ~= 'table' then
result = create_vector_instance({...}, 'copy')
end
return result
end
function vec_mt.__add(v1, v2)
return create_vector_instance(v1, 'add', v2)
end
function vec_mt.__lt(v1, v2)
saved_result = create_vector_instance(v1, 'lt', v2)
end
function vec_mt.__tostring(vec)
return
'Vector ('
..table.concat(create_vector_instance(vec, 'tostring'), ', ')
..')'
end
Usage:
a = v(5, 7, 10); print(a)
b = v(6, 4, 15); print(b)
c = a + b ; print(c) -- result is v(11, 11, 25)
c = v(a + b); print(c) -- result is v(11, 11, 25)
c = v(a < b); print(c) -- result is v(true, false, true)
As others have already mentioned, there is no straight-forward solution to this. However, with the use of a generic Python-like zip() function, such as the one shown below, you can simplify the problem, like so:
--------------------------------------------------------------------------------
-- Python-like zip() iterator
--------------------------------------------------------------------------------
function zip(...)
local arrays, ans = {...}, {}
local index = 0
return
function()
index = index + 1
for i,t in ipairs(arrays) do
if type(t) == 'function' then ans[i] = t() else ans[i] = t[index] end
if ans[i] == nil then return end
end
return table.unpack(ans)
end
end
--------------------------------------------------------------------------------
a = {5, 7, 10}
b = {6, 4, 15}
c = {}
for a,b in zip(a,b) do
c[#c+1] = a < b -- should return {true, false, true}
end
-- display answer
for _,v in ipairs(c) do print(v) end

Resources