Aside from iterating through the keys manually, it is simple to automatically keep track of it via metamethods. Considering you probably don't want to keep track of every table you make, you can just write a function that will allow you to convert any table into a key-countable object. The following isn't perfect, but I think it would illustrate the point:
function CountedTable(x)
assert(type(x) == 'table', 'bad parameter #1: must be table')
local mt = {}
-- `keys` will represent the number of non integral indexes
-- `indxs` will represent the number of integral indexes
-- `all` will represent the number of both
local keys, indxs, all = 0, 0, 0
-- Do an initial count of current assets in table.
for k, v in pairs(x) do
if (type(k) == 'number') and (k == math.floor(k)) then indxs = indxs + 1
else keys = keys + 1 end
all = all + 1
end
-- By using `__nexindex`, any time a new key is added, it will automatically be
-- tracked.
mt.__newindex = function(t, k, v)
if (type(k) == 'number') and (k == math.floor(k)) then indxs = indxs + 1
else keys = keys + 1 end
all = all + 1
t[k] = v
end
-- This allows us to have fields to access these datacounts, but won't count as
-- actual keys or indexes.
mt.__index = function(t, k)
if k == 'keyCount' then return keys
elseif k == 'indexCount' then return indxs
elseif k == 'totalCount' then return all end
end
return setmetatable(x, mt)
end
Examples of using this would include:
-- Note `36.35433` would NOT be counted as an integral index.
local foo = CountedTable { 1, 2, 3, 4, [36.35433] = 36.35433, [54] = 54 }
local bar = CountedTable { x = 23, y = 43, z = 334, [true] = true }
local foobar = CountedTable { 1, 2, 3, x = 'x', [true] = true, [64] = 64 }
print(foo.indexCount) --> 5
print(bar.keyCount) --> 4
print(foobar.totalCount) --> 6
Live Working Example
Hope this helped! :)