Refpeuk
Refpeuk

Reputation: 21

Return index of a table in Lua

I've asked several people and searched Google but haven't found an answer for this; is there a way to return the index of a variable in a table in Lua? I have a callback function that returns a variable that will always be part of a table. In the callback it is just called something like "shape_one", but the shape itself will be one of a table of shapes and will look like this: Objects.shapes[4].

If the "shape_one" has the value of Objects.shapes[4] then is there a way to return the 4?

I hope I was clear enough.

Upvotes: 2

Views: 11118

Answers (3)

U319344
U319344

Reputation: 111

I know it was used for something else, but it's applicable to this circumstance too.

shapes = {} -- Create your table, can be called anything
shapes.r_index = {} -- Holds the number value, i.e. t[1] = 'Foo'
shapes.r_table = {} -- Holds the string value, i.e. t['Foo'] = 1

mt = {} -- Create the metatable
mt.__newindex = function (self, key, value) -- For creating the new indexes
    if value == nil then -- If you're trying to delete an entry then
        if tonumber(key) then -- Check if you are giving a numerical index
            local i_value = self.r_index[key] -- get the corrosponding string index
            self.r_index[key] = nil -- Delete
            self.r_table[i_value] = nil
        else -- Otherwise do the same as above, but for a given string index
            local t_value = self.r_table[key]
            self.r_index[t_value] = nil
            self.r_table[key] = nil
        end
    else
        table.insert(self.r_index, tonumber(key), value) -- For t[1] = 'Foo'
        self.r_table[value] = key -- For t['Foo'] = 1
    end
end
mt.__index = function (self, key) -- Gives you the values back when you index them
    if tonumber(key) then
        return (self.r_index[key]) -- For For t[1] = 'Foo'
    else
        return (self.r_table[key]) -- For t['Foo'] = 1
    end
end

setmetatable(shapes, mt) -- Creates the metatable

shapes[1] = "Circle" -- Set the values
shapes[2] = "Square"

print(shapes[1], shapes[2]) -- And *should* proove that it works
print(shapes['Circle'], shapes['Square'])

shapes[1] = nil
print(shapes[1], shapes[2]) -- And *should* proove that it works
print(shapes['Circle'], shapes['Square'])

With this you should be able to access and modify the values. It uses numerical indexes so you may have to change that bit if that's not what you want. This will allow you to make new keys and get the values using one variable; it may not be the most efficient of implementations however.

Upvotes: 0

Deco
Deco

Reputation: 5159

The most efficient way to do it would to use shape_one as the key:

local shapes = { }

local function callback(shape)
    -- if you wanted to remove shape_one from the table:
    shapes[shape] = nil
    -- or if you want verify it's in there:
    print(shapes[shape] ~= nil)
end

local the_shape = { is_circle = false }

shapes[the_shape] = true

callback(the_shape)

for shape, _ in pairs(shapes) do
    callback(shape)
end

Lua uses hashing to ensure each table acts a unique key, and the algorithm it uses is very quick (doesn't iterate over the table like you would in Alexander Gladysh's solution).

Upvotes: 1

Alexander Gladysh
Alexander Gladysh

Reputation: 41493

Is this your case?

local shapes = { }

local function callback(shape_one)
  -- ???
end

local the_shape = { is_circle = false }

shapes[4] = the_shape

assert(callback(the_shape) == 4)

Both shapes[4] and the_shape contain a reference to value, but in Lua there is no other connection between these two variables. So, you can not say "index of a variable in a table", you should rather say "index of a value in a table, that matches the value in a variable". What exactly "matches" is depends on your case. In this case you most likely looking for reference equality.

Note that in Lua all table values are unique, so the_shape ~= { is_circle = false } (that is, new table with identical content), but the_shape == shapes[4] (both refer to the same value). You can compare tables by value if needed, but that's a separate topic.

So, if you really want to find an index of the value in table, you have to search it there manually. Either do a linear search:

local function callback(shape_one)
  for k, v in pairs(shapes) do
    if v == shape_one then
      return k
    end
  end
  return nil, "shape not found" -- or maybe call error() here
end

...Or cache all shapes:

local function tflip(t)
 local r = { }
 for k, v in pairs(t) do
   r[v] = k -- overrides duplicate values if any
 end
 return r
end

local shape_index = tflip(shapes)

local function callback(shape_one)
 return shape_index[shape_one] -- will return nil if not found
end

Note that shape_index would, of course, prevent garbage collection of its contents. Assuming its lifetime is the same as lifetime of shapes table and it is kept in sync with it, this is not a problem. If this is not your case, you may configure the shapes table to be weak-keyed. (Tell me if you want me to expand this point.)

BTW, you may keep shape_index up to date automatically with some metatable magic. Tell me if you want this explained as well, I will update the answer.

Upvotes: 3

Related Questions