# Multiple Indices

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)