Multiple Indices

lua-users home
wiki

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)