Multiple Indices

lua-users home
wiki

Difference (from prior major revision) (minor diff, author diff)

Changed: 1c1,110
{{{
I recently needed some functionality that allowed multiple indices. Accessing it via with an array as "key" was pretty useful. It was purpose-built for my needs -- both its semantics and performance profile -- but maybe somebody else can get some use out of it or refine it. I wasn't sure if a true table could be used because it relies on consistent iteration order. I wasn't sure if tables offer that.


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




-----
== Original Page Contents ==
{{{!Lua

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

I recently needed some functionality that allowed multiple indices. Accessing it via with an array as "key" was pretty useful. It was purpose-built for my needs -- both its semantics and performance profile -- but maybe somebody else can get some use out of it or refine it. I wasn't sure if a true table could be used because it relies on consistent iteration order. I wasn't sure if tables offer that.

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


Original Page Contents

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

How does this work?

Firstly data is ONLY stored in x,y,z values in out table created via Vector(1, 2, 3). We use a dummy table and a lookup table to achieve our goal.

TBC....


RecentChanges · preferences
edit · history
Last edited March 11, 2008 11:58 pm GMT (diff)