Vector Glue |
|
Vectors
. This is not necessarily the most inspired code, nor the most bug-free, but it might prove useful to someone. Aside from the Vector stuff, it shows a few examples of using functionals. This came up from a mailing list discussion on getn
and the use of the key n
to hold the length of the list (well, I would say Vector
).
[!] VersionNotice: The below code pertains to an older Lua version, Lua 4. Certain features used like tag methods (settagmethod
) are no longer present in Lua 5 but have been replaced with metamethods.
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 optimizations 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