Skip to content
Lesson 10 of 18

Step 1 of 5 · Reading · ~4 min

Learn

Tables — The One Data Structure

Tables as Dictionaries

Same table, different keys. Give a table string keys and it is a dictionary; give it integer keys and it is an array; give it both and it is both at once. Internally Lua keeps a dense array part and a hash part inside one object and hides the seam from you completely.

Three spellings of the same table

local user = {name = "Ada", age = 36}

local user2 = {}
user2["name"] = "Ada"
user2["age"] = 36

local user3 = {}
user3.name = "Ada"
user3.age = 36

t.name is pure sugar for t["name"]. Use the dot when the key is a fixed, identifier-shaped name; use brackets for everything else.

KeyDot formBracket form
namet.namet["name"]
total count (has a space)impossiblet["total count"]
the number 1impossiblet[1]
whatever the variable k holdswrongt[k]

That last row is the trap, and it is silent:

local t = {x = 10}
local k = "x"
print(t[k])
print(t.k)

--> 10
--> nil

t.k looks up the literal string "k". It does not look at the variable k, and it does not warn you.

Missing keys are nil, not errors

local stock = {apples = 4, pears = 0}
print(stock.apples)
print(stock.pears)
print(stock.plums)
print(stock.plums or 0)

--> 4
--> 0
--> nil
--> 0

t[k] or default is Lua's entire story on default values, and it powers the counting idiom you are about to use:

local counts = {}
counts["the"] = (counts["the"] or 0) + 1
counts["the"] = (counts["the"] or 0) + 1
print(counts["the"])

--> 2

✗ one caveat — or cannot tell nil apart from false. If a key can legitimately hold false, t[k] or default hands back the default and quietly loses the stored value. Test with if t[k] == nil then when that is possible.

Deleting a key is assigning nil

local stock = {apples = 4, pears = 2}
stock.apples = nil
print(stock.apples)

--> nil

There is no delete keyword. Setting a key to nil removes it from the table, and iteration stops visiting it.

The length operator ignores dictionary keys

local m = {a = 1, b = 2, c = 3}
print(#m)

--> 0

# measures the array part only, so it is 0 for any pure dictionary. To ask "is this table empty?", use next(t) == nil. To count string keys, walk them and add up — there is no shortcut.

pairs sees everything, in no fixed order

ipairs visits 1, 2, 3 until the first gap. pairs visits every key, whatever its type, and the order is unspecified: it can differ between runs, between Lua versions, and after any insertion.

✗ printing straight out of pairs

for k, v in pairs(counts) do
    print(k .. ": " .. v)
end

Every line it produces is correct. The order is a coin flip, which is exactly what a byte-exact test cannot tolerate.

✓ collect the keys, sort them, then print

local counts = {pear = 1, apple = 2}
local keys = {}
for k in pairs(counts) do
    table.insert(keys, k)
end
table.sort(keys)
for _, k in ipairs(keys) do
    print(k .. ": " .. counts[k])
end

--> apple: 2
--> pear: 1

table.sort sorts a list in place, which is why the keys must be copied into one first. A dictionary has no order to sort.

Your exercise

Word Frequency reads one line of space-separated words and prints each distinct word with its count, one per line, as <word>: <count>, in alphabetical order.

The starter has already done the hard half: counts maps each word to how often it appeared, and keys holds those words already sorted. You write the loop that prints them.

The mistake the grader will catch is looping over pairs(counts) instead of ipairs(keys). Every count is right either way, but the line order is unspecified, so identical code can pass one run and fail the next — and the visible test hi hi hi has exactly one key, which hides the problem entirely. The second is the separator: the tests want the: 3, a colon followed by one space, so print(k, counts[k]) fails on the tab.

Up nextIterators and the Generic forTables — The One Data Structure

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…