Vector Glue |
|
getn and the use of the key n to hold the length of the list (well, I would say Vector). Here is a simple implementation of a wrapper which creates Lua tables which only implement Vectors. I'm not saying this is the most inspired code, or that it is bug-free, but it might prove useful to someone. Aside from the Vector stuff, it shows a few examples of using functionals.
Some sample output appears at the end. Reading the last function (tmap) might show the value of the redefined / and ^ operators.
do
local TAG = newtag()
local bless = function(t)
t.n = getn(t)
return settag(t, %TAG)
end
function vector_seti(t, i, v)
if type(i) == "number" and i > 0 and i == floor(i)
then rawset(t, i, v)
if t.n < i then rawset(t, "n", i) end
elseif i == "n" and type(v) == "number"
and v >= 0 and v == floor(v)
then rawset(t, "n", v)
else error("Invalid index to vector")
end
end
settagmethod(TAG, "settable", vector_seti)
function vector(...)
return settag(arg, %TAG)
end
function vector_concat(t1, t2)
if tag(t1) ~= %TAG or tag(t2) ~= %TAG
then error("Can't concat a vector with a non-vector")
end
local t3 = {}
for i = 1, t1.n do t3[i] = t1[i] end
for i = 1, t2.n do t3[t1.n + i] = t2[i] end
return %bless(t3)
end
settagmethod(TAG, "concat", vector_concat)
-- this should be safe from optimisations since 1*t is defined
-- to be t. Consequently, it doesn't guarantee to create a
-- new vector
function vector_repeat(t, n)
if tag(t) ~= %TAG then n, t = t, n end
if not tonumber(n)
then error("Repeat needs a vector and a number")
end
local tt = {}
if n == 1 then
return t
elseif n > 0 then
for j = 1, t.n do
local v = t[j]
for i = 1, n do tt[(i-1)*t.n + j] = v end
end
elseif n < 0 then
for j = 0, t.n - 1 do
local v = t[j + 1]
for i = 1, -n do tt[i*t.n - j] = v end
end
end
return %bless(tt)
end
settagmethod(TAG, "mul", vector_repeat)
settagmethod(TAG, "unm",
function(t) return vector_repeat(t, -1) end)
function vector_reduce(t, fn)
if tag(t) ~= %TAG then fn, t = t, fn end
if type(fn) ~= "function" then
error ("Reduce needs a function of two arguments")
end
if t.n == 0 then return nil end
local val = t[1]
for i = 2, t.n do
val = fn(val, t[i])
end
return val
end
settagmethod(TAG, "div", vector_reduce)
function tjoin(t, str)
str = str or " "
return t / function(a, b) return a .. %str .. b end
end
function vector_map(t, fn)
if tag(t) ~= %TAG then fn, t = t, fn end
if type(fn) ~= "function" then
error ("Map needs a function of one argument")
end
local tt = {}
for i = 1, t.n do
tt[i] = fn(t[i])
end
return %bless(tt)
end
settagmethod(TAG, "pow", vector_map)
function andf(a, b) return a and b end
function vectorp(a) return tag(a) == %TAG end
function tmap(fn, ...)
local targ = %bless(arg)
if type(fn) ~= "function" then
error("tmap requires a function") end
if 0 == andf/ vectorp^targ then
error("map only works on vectors") end
local tt = {}
for i = 1, min/ getn^targ do
local sel = function(t) return t[%i] end
tt[i] = call(fn, sel^targ)
end
return %bless(tt)
end
end
Here's some sample output. The function p just
makes it a little easier to see the results.
> function p(t) print(tjoin(t)) end >
> a = vector("this", "is", "a", "vector")
> p(a)
this is a vector
> p(2 * a) this is a vector this is a vector > p(a .. a) this is a vector this is a vector
> p(-3 * a) vector a is this vector a is this vector a is this > p(-a) vector a is this
getn is possible, but it's just as easy to use the n key.
> print(a.n) 4
We can use tinsert and tremove normally, and index the vector by an integer.
> tinsert(a, ".") > print(a.n) 5 > p(a) this is a vector . > tremove(a, 3) > p(a) this is vector . > a[4] = "a" > p(a) this is vector a > a[3], a[4] = a[4], a[3] > p(a) this is a vector
But the vector doesn't like be used as a table
> a.note = 27 error: Invalid index to vector stack traceback: 1: function `error' [C] 2: function `vector_seti' at line 16 [file `table.lua'] 3: main of string "a.note = 27" at line 1
We can also set n to "truncate" the vector, and then change it back to restore the truncated values
> a.n = 2 > p(a) this is > a.n = 3 > p(a) this is a > a.n = 4 > p(a) this is a test
> a.n = 5 > p(a) error: attempt to concat local `b' (a nil value) stack traceback: 1: function `fn' at line 82 [file `table.lua'] 2: function `vector_reduce' at line 73 [file `table.lua'] 3: function `tjoin' at line 82 [file `table.lua'] 4: function `p' at line 116 [file `table.lua'] 5: main of string "p(a)" at line 1
> p(strupper^ a) THIS IS A TEST > b = vector(4, 8, -3.4, 7) > print (max/ b) 8
Unfortunately, we have to define primitives ourselves
> function sum(a, b) return a + b end > print (sum/ b) 15.6
So it's quite handy to have some functional manipulators around
> function bind2(fn, b) return function(a) return %fn(a, %b) end end > add1 = bind2(sum, 1) > p(add1^ b) 5 9 -2.4 8 > function average(a) return (sum/ a) / a.n end > print(average(b)) 3.9
tmap is defined to allow mapping over more than one argument
> function ratio(a, b) return a / b end > p(tmap(ratio, b, add1^ b)) 0.8 0.8888888888888888 1.416666666666667 0.875
The definition of tjoin shows another use of reduce. (Note that the reduce operator is commutative, if not symmetrical)
> p(a) this is a test > print(tjoin(a, ", ")) this, is, a, test > print(tjoin(a, "\n")) this is a test
But this little functional might be useful, too...
> function joiner(s) return function(a, b) return a .. %s .. b end end
> print(joiner("\n")/ tmap(joiner("\t"), a, b))
this 4
is 8
a -3.4
test 7
And following up on that theme, I leave you with the following possibly revealing bit of code:
> function tab2vec(t) \
local v = vector() \
for key, val in t do tinsert(v, vector(key, val)) end \
return v \
end
> family = {juan = 23, marie = 16, alfonso = 45, silvana = 42}
> -- sort by age
> sort(ft, function(a, b) return a[2] < b[2] end)
> print(joiner("\n")/ bind2(tjoin, "\t")^ ft)
marie 16
juan 23
silvana 42
alfonso 45
> -- sort by name
> sort(ft, function(a, b) return a[1] < b[1] end)
> print(joiner("\n")/ bind2(tjoin, "\t")^ ft)
alfonso 45
juan 23
marie 16
silvana 42