向量胶水

lua-users home
wiki

这是一个围绕 Lua 表的简单包装器,它提供了 Vectors 的受限语义。 这不一定是最好的代码,也不一定是没有错误的,但它可能对某些人有用。 除了向量之外,它还展示了一些使用函数式编程的例子。 这是从邮件列表中关于 getn 和使用键 n 来保存列表长度(好吧,我会说 Vector)的讨论中产生的。

[!] 版本通知: 以下代码适用于旧版本的 Lua,Lua 4。 某些使用的特性,如标签方法 (settagmethod),在 Lua 5 中不再存在,但已被元方法取代。

一些示例输出出现在最后。 阅读最后一个函数 (tmap) 可能显示了重新定义的 / 和 ^ 运算符的价值。

代码

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

示例用法

这里有一些示例输出。 函数 p 使查看结果更容易。

> 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 是可能的,但使用 n 键一样容易。

> print(a.n)
4

我们可以正常使用 tinserttremove,并通过整数索引向量。

> 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

但向量不喜欢被用作表

> 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

我们也可以设置 n 来“截断”向量,然后将其更改回以恢复被截断的值

> a.n = 2
> p(a)
this is
> a.n = 3
> p(a)
this is a
> a.n = 4
> p(a)
this is a test
但是,将 n 设置到超出末尾不会创建任何值,因此我们可能会遇到麻烦
> 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
正如您可能从代码中看到的那样,reduce (/) 和 map (^) 运算符可以非常有表现力...

> p(strupper^ a)
THIS IS A TEST
> b = vector(4, 8, -3.4, 7)
> print (max/ b)
8

不幸的是,我们必须自己定义原语

> function sum(a, b) return a + b end
> print (sum/ b)
15.6

因此,拥有一些函数式操作符非常方便

> 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 被定义为允许对多个参数进行映射

> function ratio(a, b) return a / b end
> p(tmap(ratio, b, add1^ b))
0.8 0.8888888888888888 1.416666666666667 0.875

tjoin 的定义显示了 reduce 的另一种用法。(注意 reduce 运算符是可交换的,如果不是对称的)

> p(a)
this is a test
> print(tjoin(a, ", "))
this, is, a, test
> print(tjoin(a, "\n"))
this
is
a
test

但这个小函数也可能有用...

> 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

接着这个主题,我留给你以下可能揭示性的代码

> 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


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2007 年 1 月 6 日下午 7:43 GMT (差异)