Vector Glue

lua-users home
wiki

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

> 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


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