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
end
I’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 end
Obviously, 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 end
I 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) do
Here 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 end
Another 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 end
Same 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
end
I’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:
end
Wrap 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.