Element by Element comparison in Lua - 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

Related

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)

The most elegant solution using Lua tables to this snippet of Python

I'm learning Lua and coming from Python Lua tables seem rather convoluted, the simple example below is so elegant but translating this into Lua is difficult for me, as Lua has no concept of tuples.
So I'm looking for the best Lua solution for this snippet
a = [(1, 1), (2, 2), (3, 3), (4, 4)]
if (3, 3) in a:
print("Yay!")
else:
print("Nay!")
A pair is just like length-2 list, so you could express a simply as
a = {{1, 1}, {2, 2}, {3, 3}, {4, 4}}
The tricky part here is rather that Python's in compares objects by their value rather than their identity. That is, in Python, (1,2) in [(1,2)] but (1,2) is not (1,2).
Lua has no notion of "value" equality (except for strings and numbers, which don't have identities).
You could override the behavior of == by setting the __eq metametod. Unfortunately, Lua doesn't have a function for searching a table for a value equal to some query, so it might be overkill.
Directly, you could write a "contains pair" function that works on the a as defined above like this:
function containsPair(list, pair)
-- Find a key of list with a value equal to `pair` (as a pair)
for k, v in ipairs(list) do
if v[1] == pair[1] and v[2] == pair[2] then
return k
end
end
end
if containsPair(a, {3, 3}) then
......
end
You could make it more general by passing a function do the comparison (or equivalently, just use == but implement the __eq metamethod):
function containsLike(list, lhs, eq)
-- Find a key of list with a value equal to lhs
for k, lhs in ipairs(list) do
if eq(lhs, rhs) then
return k
end
end
end
function pairEq(a, b)
return a[1] == b[1] and a[2] == b[2]
end
if containsLike(list, {3, 3}, pairEq) then
......
end
If what you're really after is a set of pairs, you could instead use a "two-dimensional map" (a map of maps):
a = {}
a[1] = {}
a[1][1] = true
a[2] = {}
a[2][2] = true
a[3] = {}
a[3][3] = true
if a[3] and a[3][3] then
......
end
Checking that rows have already been created could be cumbersome. You can use metatables to imitate Python's defaultdict and clean this up:
function default(f)
return setmetatable({}, {
__index = function(self, k)
-- self[k] is nil, but was asked for.
-- Let's assign it to the default value:
self[k] = f()
-- and return the new assignment:
return self[k]
end,
})
end
local a = default(function() return {} end)
a[1][1] = true
a[2][2] = true
a[3][3] = true
if a[3][3] then
......
end

How to make LPeg.match return nil

I'm currently getting familiar with the LPeg parser module. For this I want to match a version string (e.g. 11.4) against a list.
Such a list is a string with a tight syntax that can also contain ranges. Here is an EBNF-like, but in any case quite simple grammar (I write it down because LPeg code below can be a bit difficult to read):
S = R, { ',', R }
R = N, [ '-', N ]
N = digit+, [ '.', digit+ ]
An example string would be 1-9,10.1-11,12. Here is my enormous code:
local L = require "lpeg"
local LV, LP, LC, LR, floor = L.V, L.P, L.C, L.R, math.floor
local version = "7.25"
local function check(a, op, b)
if op and a+0 <= version and version <= b+0 then
return a..op..b -- range
elseif not op and floor(version) == floor(a+0) then
return a -- single item
end
end
local grammar = LP({ "S",
S = LV"R" * (LP"," * LV"R")^0,
R = LV"V" * (LC(LP"-") * LV"V")^-1 / check,
V = LC(LV"D" * (LP"." * LV"D")^-1),
D = (LR("09")^1),
})
function checkversion(str)
return grammar:match(str)
end
So you would call it like checkversion("1-7,8.1,8.3,9") and if the current version is not matched by the list you should get nil.
Now, the trouble is, if all calls to check return nothing (meaning, if the versions do not match), grammar:match(...) will actually have no captures and so return the current position of the string. But this is exactly what I do not want, I want checkversion to return nil or false if there is no match and something that evaluates to true otherwise, actually just like string:match would do.
If I on the other hand return false or nil from check in case of a non-match, I end up with return values from match like nil, "1", nil, nil which is basically impossible to handle.
Any ideas?
I think you can or + it with a constant capture of nil:
grammar = grammar + lpeg.Cc(nil)
This is the pattern I eventually used:
nil_capturing_pattern * lpeg.Cc(nil)
I incorporated it into the grammar in the S rule (Note that this also includes changed grammar to "correctly" determine version order, since in version numbering "4.7" < "4.11" is true, but not in calculus)
local Minor_mag = log10(Minor);
local function check(a, am, op, b, bm)
if op then
local mag = floor(max(log10(am), log10(bm), Minor_mag, 1))+1;
local a, b, v = a*10^mag+am, b*10^mag+bm, Major*10^mag+Minor;
if a <= v and v <= b then
return a..op..b;
end
elseif a == Major and (am == "0" or am == Minor) then
return a.."."..am;
end
end
local R, V, C, Cc = lpeg.R, lpeg.V, lpeg.C, lpeg.Cc
local g = lpeg.P({ "S",
S = V("R") * ("," * V("R"))^0 * Cc(nil),
R = (V("Vm") + V("VM")) * (C("-") * (V("Vm") + V("VM")))^-1 / check,
VM = V("D") * Cc("0"),
Vm = V("D") * "." * V("D"),
D = C(R("09")^1),
});
Multiple returns from match are not impossible to handle, if you catch them in a way that makes handling them easier. I added a function matched that does that, and added the fallback return of false to your check.
do
local L = require "lpeg"
local LV, LP, LC, LR, floor = L.V, L.P, L.C, L.R, math.floor
local version = 6.25
local function check(a, op, b)
if op and a+0 <= version and version <= b+0 then
return a..op..b -- range
elseif not op and floor(version) == floor(a+0) then
return a -- single item
end
return false
end
local grammar = LP({ "S",
S = LV"R" * (LP"," * LV"R")^0,
R = LV"V" * (LC(LP"-") * LV"V")^-1 / check,
V = LC(LV"D" * (LP"." * LV"D")^-1),
D = (LR("09")^1),
})
local function matched(...)
local n = select('#',...)
if n == 0 then return false end
for i=1,n do
if select(i,...) then return true end
end
return false
end
function checkversion(ver,str)
version = ver
return matched(grammar:match(str))
end
end
I enclosed the whole thing in do ... end so that the local version which is used here as an upvalue to check would have constrained scope, and added a parameter to checversion() to make it clearer to run through few test cases. For example:
cases = { 1, 6.25, 7.25, 8, 8.5, 10 }
for _,v in ipairs(cases) do
print(v, checkversion(v, "1-7,8.1,8.3,9"))
end
When run, I get:
C:\Users\Ross\Documents\tmp\SOQuestions>q18793493.lua
1 true
6.25 true
7.25 false
8 true
8.5 true
10 false
C:\Users\Ross\Documents\tmp\SOQuestions>
Note that either nil or false would work equally well in this case. It just feels saner to have collected a list that can be handled as a normal Lua array-like table without concern for the holes.

Lua table.concat

Is there a way to use the arg 2 value of table.concat to represent the current table index?
eg:
t = {}
t[1] = "a"
t[2] = "b"
t[3] = "c"
X = table.concat(t,"\n")
desired output of table concat (X):
"1 a\n2 b\n3 c\n"
Simple answer : no.
table.concat is something really basic, and really fast.
So you should do it in a loop anyhow.
If you want to avoid excessive string concatenation you can do:
function concatIndexed(tab,template)
template = template or '%d %s\n'
local tt = {}
for k,v in ipairs(tab) do
tt[#tt+1]=template:format(k,v)
end
return table.concat(tt)
end
X = concatIndexed(t) -- and optionally specify a certain per item format
Y = concatIndexed(t,'custom format %3d %s\n')
I don't think so: how would you tell it that the separator between keys and values is supposed to be a space, for example?
You can write a general mapping function to do what you'd like:
function map2(t, func)
local out = {}
for k, v in pairs(t) do
out[k] = func(k, v)
end
return out
end
function joinbyspace(k, v)
return k .. ' ' .. v
end
X = table.concat(map2(t, joinbyspace), "\n")
No. But there is a work around:
local n = 0
local function next_line_no()
n = n + 1
return n..' '
end
X = table.concat(t,'\0'):gsub('%f[%Z]',next_line_no):gsub('%z','\n')
function Util_Concat(tab, seperator)
if seperator == nil then return table.concat(tab) end
local buffer = {}
for i, v in ipairs(tab) do
buffer[#buffer + 1] = v
if i < #tab then
buffer[#buffer + 1] = seperator
end
end
return table.concat(buffer)
end
usage tab is where the table input is and seperator be both nil or string (if it nil it act like ordinary table.concat)
print(Util_Concat({"Hello", "World"}, "_"))
--Prints
--Hello_world

Lua Operator Overloading

I've found some places on the web saying that operators in Lua are overloadable but I can't seem to find any example.
Can someone provide an example of, say, overloading the + operator to work like the .. operator works for string concatenation?
EDIT 1: to Alexander Gladysh and RBerteig:
If operator overloading only works when both operands are the same type and changing this behavior wouldn't be easy, then how come the following code works? (I don't mean any offense, I just started learning this language):
printf = function(fmt, ...)
io.write(string.format(fmt, ...))
end
Set = {}
Set.mt = {} -- metatable for sets
function Set.new (t)
local set = {}
setmetatable(set, Set.mt)
for _, l in ipairs(t) do set[l] = true end
return set
end
function Set.union (a,b)
-- THIS IS THE PART THAT MANAGES OPERATOR OVERLOADING WITH OPERANDS OF DIFFERENT TYPES
-- if user built new set using: new_set = some_set + some_number
if type(a) == "table" and type(b) == "number" then
print("building set...")
local mixedset = Set.new{}
for k in pairs(a) do mixedset[k] = true end
mixedset[b] = true
return mixedset
-- elseif user built new set using: new_set = some_number + some_set
elseif type(b) == "table" and type(a) == "number" then
print("building set...")
local mixedset = Set.new{}
for k in pairs(b) do mixedset[k] = true end
mixedset[a] = true
return mixedset
end
if getmetatable(a) ~= Set.mt or
getmetatable(b) ~= Set.mt then
error("attempt to 'add' a set with a non-set value that is also not a number", 2)
end
local res = Set.new{}
for k in pairs(a) do res[k] = true end
for k in pairs(b) do res[k] = true end
return res
end
function Set.tostring (set)
local s = "{"
local sep = ""
for e in pairs(set) do
s = s .. sep .. e
sep = ", "
end
return s .. "}"
end
function Set.print (s)
print(Set.tostring(s))
end
s1 = Set.new{10, 20, 30, 50}
s2 = Set.new{30, 1}
Set.mt.__add = Set.union
-- now try to make a new set by unioning a set plus a number:
s3 = s1 + 8
Set.print(s3) --> {1, 10, 20, 30, 50}
The metatable function only works on tables, but you can use debug.metatable to set the strings metatable...
> mt = {}
> debug.setmetatable("",mt)
> mt.__add = function (op1, op2) return op1 .. op2 end
> ="foo"+"bar"
foobar
>
Another approach is to use debug.getmetatable to augment the built-in string metatable (answering the question in the comment below):
~ e$ lua
Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio
> debug.getmetatable("").__add = function (op1, op2) return op1 .. op2 end
> ="foo"+"bar"
foobar
>
See the Metatables section of Lua Programming Manual and Metatables and Metamethods chapter of the Programming in Lua 2nd edition.
Note that for comparison operators operator overloading works only when both operand types are the same.

Resources