I’m currently working on a tagging system for this website using pandoc, and I’ve decided to take a little break to write about a function I wrote for the system. My tags aren’t ready yet (I don’t have a deduper yet, for example, nor do I have any setup to actually make the tag pages), by the way. I’m just proud of myself for getting this function together:
local function is_empty(tbl)
if type(tbl) ~= "table" then return false end
if next(tbl) == nil then return true end
for k, v in pairs(tbl) do
if type(v) ~= "table" then return false end
if type(k) ~= "number" then return false end
if k == #tbl then
return is_empty(v)
else
if not is_empty(v) then return false end
end
end
endI’m not sure if is_empty is really a good name for it,
because it doesn’t exactly tell you that (or not only that),
but whether a table contains only empty tables. I decided to implement
it because of the way pandoc implements metadata, and I use that
metadata (namely the tags field) to build my tag file that
will, hopefully, build my tag index. I could’ve just checked for the
emptiness of, say, tags[1][1].c, but I think that’s fragile
(what if pandoc changes the structure?) and ugly. Thus, this function.
Let’s talk about it.
if type(tbl) ~= "table" then return false endObviously, if the thing passed to is_empty isn’t a
table, it’s not an empty table. Easy so far.
if next(tbl) == nil then return true endI needed to check the length of tbl next: if it’s
{}, we can go ahead and mark it as empty too. It wasn’t as
easy as it looked at first though: I was using #tbl == 0,
which only checks for the numerically-indexed keys in tbl.
Pandoc eventually has keys like "c" and "t",
which aren’t numbers, so my function was returning true on
tables that weren’t empty. next() gets the next index of
the table (I think; I saw the answer
on Stack Overflow and the documentation is sparse), and works with
both numerical and other - indexed keys.
for k, v in pairs(tbl) doHere we go. Into the depths of the table. pairs()
recurses through the table, returning keys and values (bound here to
k and v).
if type(v) ~= "table" then return false endAnother easy one: if v is not a table, it’s something
important like 1, "hello", or whatever. So the
table isn’t empty.
if type(k) ~= "number" then return false endSame idea as the v typecheck above: if k
isn’t a number (the default key-type in Lua), then it’s holding
information of some kind. For example, if you have a table that’s
something like { junk = {} } I’m assuming you want
junk to be an empty table, or you’d set it to
nil and delete it. That being said, I might revisit this
later if necessary.
if k == #tbl then
return is_empty(v)
else
if not is_empty(v) then return false end
endI’ve put all the recursion stuff together at the end. I’m not sure if it’s tail recursion, but I don’t really care for this use-case. It works, and that’s good enough for me.
First, we check if we’re looking at the last item in the table. If
so, we just check whether it is empty, using the same function
we’re building. Otherwise, we look inside the table and if it’s
not empty, we return false, or else we keep going
with the for loop.
Now just to end everything:
endWrap it all up in the function is_empty, and you’ve got
a good way to look inside a table to see if it’s turtles all the way
down.