多重索引

lua-users home
wiki

我最近需要一些允许使用多个索引的功能。通过使用数组作为“键”来访问它非常有用。它是为我的需求而专门设计的——包括语义和性能特征——但也许其他人可以从中获得一些用途或对其进行改进。我不确定是否可以使用真正的表,因为它依赖于一致的迭代顺序。我不确定表是否提供这种功能。

-- Creates a special table that accepts keys that are arrays..
-- You must specified the expected length of this array when
-- creating a MultiTable.
--
-- Given mt = MultiTable(3), accessing mt[{1,2,3}] semantically
-- acts like mt[1][2][3].
--
-- You can also access using a function call syntax.  The following
-- are equivalent:
--    mt[{1,2,3}], mt(1,2,3), mt{1,2,3}
--
function MultiDimTable( dimension )
    if not dimension then
        error( "MultiDimTable expected dimension, got nil" )
    end

    local mt = {}    -- all the nested access is hidden here

    local function mt_index(t, k)
        if type(k) ~= 'table' then
            return t[{k}]
        end
        if dimension ~= #k then
            error("MultiTable key dimension was " .. #k .. ",
                  expected " .. dimension)
        end
        local v = mt
        for i = 1, #k do
            v = v[ k[i] ]
            if not v then return nil end
        end
        return v
    end

    local function mt_newindex(t, k, v)
        if type(k) ~= 'table' then
            t[{k}] = v
            return
        end
        if dimension ~= #k then
            error("MultiTable key dimension was " .. #k .. ",
                  expected " .. dimension)
        end
        local n = mt
        for i = 1, #k - 1 do
            local n_old, k_i = n, k[i]
            n = n[k_i]
            if not n then n = {} ; n_old[k_i] = n end
        end
        n[k[#k]] = v
    end

    local function mt_call(t, k, ...)
        if type(k) == 'table' then
            return t[k]
        end
        return t[{k, ...}]
    end

    return setmetatable( {}, {
        __index = mt_index, __newindex = mt_newindex, __call = mt_call } )
end


-- tests
do
    local res, err
    res, err = pcall( function() MultiDimTable() end )
    local mt1 = MultiDimTable(1)
    mt1[4] = 5
    assert( mt1[{4}] == 5 )
    assert( mt1[4]   == 5 )
    assert( mt1{4}   == 5 )
    assert( mt1(4)   == 5 )

    mt1['abc'] = '123'
    assert( mt1[{'abc'}] == '123' )
    assert( mt1['abc'] == '123' )
    assert( mt1({'abc'}) == '123' )
    assert( mt1('abc') == '123' )

    res, err = pcall( function() return mt1[{1,2}] end )
    assert( not res and string.match(err,
        'MultiTable key dimension was 2, expected 1') )

    res, err = pcall( function() mt1[{1,2}] = 4 end )
    assert( not res and string.match(err,
        'MultiTable key dimension was 2, expected 1') )

    for i = 1, 100 do
        local n = {}
        for j = 1, i do table.insert(n, math.random()) end
        local val = math.random()
        local mtn = MultiDimTable(i)
        mtn[n] = val
        assert( mtn[n]           == val )
        assert( mtn(n)           == val )
        assert( mtn{unpack(n)}   == val )
        assert( mtn[{unpack(n)}] == val )
    end
end


原始页面内容

do             
    local meta = {}

    local xlate = {[0] = "x", "y", "z"}

    function meta:__index(key)
        if xlate[key] then return rawget(self, xlate[key]) end
    end

    function meta:__newindex(key, val)

      if xlate[key] then
        rawset(self, xlate[key], val)
      else
        rawset(self, key, val)
      end
    end
  
    function Vector(x, y, z)
      return setmetatable({x=x, y=y, z=z}, meta)
    end
end

a = Vector(1, 2, 3)

=a.x
1
 =a[0]
1
 a[2] = 7
 =a.z
7

它是如何工作的?

首先,数据只存储在通过 Vector(1, 2, 3) 创建的表中的 x、y、z 值中。我们使用一个虚拟表和一个查找表来实现我们的目标。

待续...


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2008 年 3 月 12 日凌晨 12:58 GMT (差异)