# Vector Glue

Here is a simple wrapper around Lua tables that provides the restricted semantics of `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.

### Code

```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

```

### Sample Usage

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
>
```
Create a vector

```> a = vector("this", "is", "a", "vector")
> p(a)
this is a vector
```
repeat, concatenate:
```> p(2 * a)
this is a vector this is a vector
> p(a .. a)
this is a vector this is a vector
```
works backwards, too, so we define unary minus as well
```> 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
```
However, setting n beyond the end doesn't create any values, so we can get ourselves into trouble:
```> 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
```
As you might see from the code, the reduce (/) and map (^) operators can be extremely expressive...

```> 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
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
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
```

RecentChanges · preferences
edit · history
Last edited January 6, 2007 6:43 pm GMT (diff)