Skip to content

Dictionaries

Lesson 6.2

A table can use any value as a key, not only numbers. With string keys, a table becomes a dictionary or a record: named fields that belong together.

Keys and values

Write key = value inside the braces:

local player = {
  name = "Alex",
  health = 20,
  level = 12,
}

print(player.name)       --> Alex
print(player["health"])  --> 20

player.name is short for player["name"]. Use the dot when the key is a fixed, simple name. Use brackets when the key is in a variable or has spaces or symbols in it:

local stats = { ["max health"] = 20 }
print(stats["max health"])  --> 20

local field = "level"
local player = { level = 12 }
print(player[field])        --> 12
print(player.field)         --> nil

The last line is a very common mistake: player.field means the key named "field", not the value of the variable field.

A trailing comma after the last entry is allowed, and it makes adding lines easier.

Adding, changing, removing

local player = { name = "Alex" }

player.health = 20       -- add a field
player.health = 14       -- change it
player.name = nil        -- remove a field

print(player.name, player.health)  --> nil	14

Setting a key to nil removes it. That is the only way to delete a key.

Any key type

Keys can be numbers, strings, booleans, tables, even functions. Only nil (and the float NaN) can’t be keys:

local ores = {
  [1] = "first",
  [10] = "tenth",
  ["deepslate_iron"] = 12,
  [true] = "yes",
}
print(ores[10], ores[true])  --> tenth	yes

Integer-valued floats are the same key as integers: t[1] and t[1.0] are the same slot. But the string "1" and the number 1 are different keys.

Looping with pairs

pairs goes over every key and value in a table:

local prices = { apple = 2, bread = 3, cake = 10 }

for item, price in pairs(prices) do
  print(item, price)
end

This prints the three items, but in no particular order. The order can even change between runs. If you need an order, collect the keys in a list and sort it:

local prices = { apple = 2, bread = 3, cake = 10 }

local names = {}
for item in pairs(prices) do
  table.insert(names, item)
end
table.sort(names)

for _, item in ipairs(names) do
  print(item, prices[item])
end
apple	2
bread	3
cake	10

Don’t add new keys to a table while you loop over it with pairs. Changing or removing existing keys is fine.

Counting entries

# only counts the list part of a table. For a dictionary, count with a loop:

local prices = { apple = 2, bread = 3, cake = 10 }
print(#prices)  --> 0

local count = 0
for _ in pairs(prices) do
  count = count + 1
end
print(count)    --> 3

To check whether a table is empty, use next(t) == nil. next returns the first key it finds, or nil if there are none.

Lists and dictionaries in one table

One table can have both a list part and named fields:

local chest = {
  "Iron", "Gold", "Redstone",
  owner = "Alex",
  locked = true,
}
print(#chest, chest[2], chest.owner)  --> 3	Gold	Alex

ipairs only visits the list part. pairs visits everything.

Nested tables

Values can be tables, so you can build structured data:

local player = {
  name = "Alex",
  pos = { x = 100, y = 64, z = -30 },
  inventory = {
    { id = "minecraft:torch", count = 32 },
    { id = "minecraft:bread", count = 5 },
  },
}

print(player.pos.y)                --> 64
print(player.inventory[2].id)      --> minecraft:bread
print(#player.inventory)           --> 2

Reading through a missing level is an error:

local player = { name = "Alex" }
print(player.pet)        --> nil
-- print(player.pet.name)  would be an error: attempt to index a nil value (field 'pet')

Check each level first: if player.pet and player.pet.name then.

Sets

A table with true values is a quick set: a group where you only ask “is this in it?”:

local hostile = { Zombie = true, Creeper = true, Skeleton = true }

print(hostile["Creeper"])  --> true
print(hostile["Cow"])      --> nil

This lookup is instant, however big the set is. Looping through a list to find a value gets slower as the list grows.

Try it

Make a table for a block with the fields id = "minecraft:chest", x, y, z, and contents (a dictionary from item name to count with three items). Add 5 to one of the counts, remove another item, and print every remaining item with its count in alphabetical order.

Show a solution
local block = {
  id = "minecraft:chest",
  x = 10, y = 70, z = -4,
  contents = { emerald = 3, bone = 12, string = 7 },
}

block.contents.emerald = block.contents.emerald + 5
block.contents.bone = nil

local names = {}
for name in pairs(block.contents) do
  names[#names + 1] = name
end
table.sort(names)

for _, name in ipairs(names) do
  print(name, block.contents[name])
end
emerald	8
string	7

Recap: t.key and t["key"] read fields, setting nil removes them, pairs visits everything in no fixed order, and nested tables hold structured data.