
table
Lists and tables: add, remove, sort, join, copy and search.
A table holds values under keys. It is used in two ways, often mixed in the same table:
- a list: values at the positions 1, 2, 3...
{"iron", "gold", "copper"}. Its length is#t. - a record: values under names.
{name = "Iron Ingot", count = 64}, read witht.nameort["name"].
The table library mostly works on lists: table.insert, table.remove, table.sort and table.concat only look at the positions 1 to #t. The Tables guide explains tables from the start.
local orders = {"iron", "gold"}
table.insert(orders, "copper")
table.insert(orders, 1, "coal")
print(table.concat(orders, ", "))
local first = table.remove(orders, 1)
print(first, #orders)
table.sort(orders)
print(table.concat(orders, " "))coal, iron, gold, copper coal 3 copper gold iron
A table is shared, not copied. local b = a gives a second name to the same table: a change made through b is seen through a. Use table.copy for a separate table. In the same way, == tells whether two names point to the same table, not whether two tables hold the same things.
**The length #t counts the list part: the positions 1, 2, 3... filled one after the other. A table with only named keys has a length of 0. Setting the last** value to nil shortens the list; setting one in the middle to nil leaves a hole that # still counts, but where ipairs stops. A value far after the end (t[10] in a list of
- is not part of the list until the gap is filled.
local t = {"a", "b", "c"}
t[2] = nil
print(#t)
t[3] = nil
print(#t)
t[10] = "j"
print(#t)3 1 1
Keep your lists without holes: take values out with table.remove, which closes the gap, rather than by setting them to nil.
Every entry of a table takes 2 cells of memory, plus 4 for the table itself (see Limits). Brass has no table.unpack, table.pack or table.move, and no metatables.
table.insert(t, [pos,] value) | Adds a value at the end of a list, or at a given position. |
table.remove(t [, pos]) | Removes a value from a list and returns it: the last one, or the one at pos. |
table.sort(t [, less]) | Sorts a list in place: smallest first, or in the order given by a function. |
table.concat(t [, sep [, i [, j]]]) | Joins the values of a list into one text, with sep between them. |
table.contains(t, value) | True if the value is somewhere in the table. |
table.keys(t) | A list of the keys of a table. |
table.copy(t) | A shallow copy of a table: a new table with the same keys and values. |
Adding and removing
Adds a value at the end of a list, or at a given position.
ttable- the list
posnumber optional- the position the value goes to, from 1 to
#t + 1(the end when left out) valueany- the value to add
With two arguments, the value goes at the end, at position #t + 1. With three, it goes at pos, and the values from pos on move up one place. A queue of train orders, with an urgent one that jumps the queue:
local queue = {}
table.insert(queue, "Iron to Depot A")
table.insert(queue, "Coal to Smeltery")
table.insert(queue, 1, "URGENT: Food to Base")
for i, order in ipairs(queue) do
print(i, order)
end1 URGENT: Food to Base 2 Iron to Depot A 3 Coal to Smeltery
posmust be a whole number from 1 to#t + 1. Otherwise the program stops withbad argument #2 to 'insert' (position out of bounds).- A
nilvalue adds nothing: a list cannot holdnil, its length does not change. - With only the table, the program stops with
wrong number of arguments to 'insert'. t[#t + 1] = valuedoes the same astable.insert(t, value), without the cost of a call.
Adding at the end is cheap. Inserting at a position moves the values after it, which costs one instruction per 8 values of the list: on a list of thousands of values, prefer adding at the end.
See also table.remove()
Removes a value from a list and returns it: the last one, or the one at pos.
ttable- the list
posnumber optional- the position to remove, from 1 to
#t(the last one when left out)
- any
- the value removed, nil when the list was empty
The values after pos move down one place, so the list keeps no hole. Removing the first value of a queue gives "first come, first served"; removing the last one gives a stack (last in, first out).
local queue = {"Iron to Depot A", "Coal to Smeltery", "Food to Base"}
while #queue > 0 do
local order = table.remove(queue, 1)
print("Dispatching: " .. order)
end
print(table.remove(queue))Dispatching: Iron to Depot A Dispatching: Coal to Smeltery Dispatching: Food to Base nil
Without a position, table.remove of an empty list returns nil. With a position, it is an error: table.remove(queue, 1) on an empty queue stops with bad argument #2 to 'remove' (position out of bounds). Check #queue > 0 first, as above.
Removing while you walk a list. Walking forward and removing skips values: when the value at i goes, the next one slides into position i, and the loop goes on to i + 1. Here one "done" survives:
local list = {"done", "done", "todo"}
for i, state in ipairs(list) do
if state == "done" then
table.remove(list, i)
end
end
print(table.concat(list, " "))done todo
Walk backwards instead: removing a value only moves the ones you have already seen.
local jobs = {
{name = "press gears", done = true},
{name = "mix brass", done = false},
{name = "cut planks", done = true},
{name = "fill tanks", done = false},
}
for i = #jobs, 1, -1 do
if jobs[i].done then
table.remove(jobs, i)
end
end
for _, job in ipairs(jobs) do
print(job.name)
endmix brass fill tanks
See also table.insert()
Sorting
Sorts a list in place: smallest first, or in the order given by a function.
ttable- the list to sort, changed in place
lessfunction optionalless(a, b)returns true whenamust come beforeb
The list itself is changed, and nothing is returned. Without a function, numbers go from the smallest to the largest, and texts in the order of their character codes: capitals before small letters, and digits one by one ("10" before "9").
local speeds = {64, 16, 256, 32}
table.sort(speeds)
print(table.concat(speeds, " "))
local names = {"zinc", "Iron", "copper", "Andesite"}
table.sort(names)
print(table.concat(names, " "))16 32 64 256 Andesite Iron copper zinc
With a function, you decide the order. less(a, b) receives two values of the list and returns true when a must come before b, and false otherwise, including when they are equal. Values the function sees as equal keep the order they had (the sort is stable).
local speeds = {64, 16, 256, 32}
table.sort(speeds, function(a, b) return a > b end)
print(table.concat(speeds, " "))
local names = {"zinc", "Iron", "copper", "Andesite"}
table.sort(names, function(a, b) return a:lower() < b:lower() end)
print(table.concat(names, " "))256 64 32 16 Andesite copper Iron zinc
Write < or > in a sort function, never <= or >=. Brass does not stop on it, but values that are equal then come out in the reverse order, and a sort you run again keeps swapping them.
The top 5 items of a vault: sort the records by their count field, largest first, and show the first five. Iron and Brass have the same count: they stay in their original order.
local vault = {
{name = "Cobblestone", count = 2304},
{name = "Iron Ingot", count = 412},
{name = "Gold Nugget", count = 37},
{name = "Andesite Alloy", count = 980},
{name = "Copper Ingot", count = 1290},
{name = "Zinc Ingot", count = 96},
{name = "Brass Ingot", count = 412},
}
table.sort(vault, function(a, b)
return a.count > b.count
end)
for i = 1, math.min(5, #vault) do
print(string.format("%d. %-15s %5d", i, vault[i].name, vault[i].count))
end1. Cobblestone 2304 2. Copper Ingot 1290 3. Andesite Alloy 980 4. Iron Ingot 412 5. Brass Ingot 412
To break the ties, compare a second field when the first one is equal. With this function, Brass Ingot comes before Iron Ingot:
table.sort(vault, function(a, b)
if a.count ~= b.count then
return a.count > b.count
end
return a.name < b.name
end)Errors. Without a function, the values must be all numbers or all texts: a mix stops with attempt to compare number with string (or string with number), and tables with attempt to compare two table values: sort tables of records with a function. A hole in the list stops with attempt to compare nil with number.
Cost. Without a function, the sort is charged about n × log2(n) instructions at once for n values: 10 000 for 1000 numbers, well under a second on a Personal Computer (1200 instructions per tick), much longer on a Tube Computer (20 per tick). With a function, each comparison is a call to it, a few instructions each, about n × log2(n) calls. Either way the sort is spread over as many ticks as needed, like any code, and never freezes the server.
See also table.keys()
Joining and searching
Joins the values of a list into one text, with sep between them.
ttable- a list of texts and numbers
sepstring optional- the text put between the values (nothing when left out)
inumber optional- the first position (1 when left out)
jnumber optional- the last position (
#twhen left out)
- string
- the values joined into one text
local route = {"Mine", "Smeltery", "Depot"}
print(table.concat(route, " -> "))
print(table.concat({64, 32, 16}, "+") .. " = 112")
print(table.concat(route, ", ", 2, 3))
print("[" .. table.concat({}) .. "]")Mine -> Smeltery -> Depot 64+32+16 = 112 Smeltery, Depot []
The values must be texts or numbers (numbers are written like tostring does). Anything else, true, a table, or a hole in the list, stops with invalid value (at index 2) in table for 'concat': convert the values with tostring first. When i is after j, the result is "".
To build a long text piece by piece, put the pieces in a list and join them once at the end: it is much cheaper than s = s .. piece in a loop, which makes a new text at every step. The result is limited to 65536 characters (string too long).
See also string.split()
True if the value is somewhere in the table.
ttable- the table to search
valueany- the value to look for
- boolean
- true if one of the values of
tequalsvalue
It looks at the values of the table, list and named keys alike, never at the keys. Values are compared like == does: numbers and texts by their content, tables by identity (another table with the same content is not found). nil is never found.
local fuels = {"minecraft:coal", "minecraft:charcoal", "minecraft:blaze_rod"}
print(table.contains(fuels, "minecraft:coal"))
print(table.contains(fuels, "minecraft:stick"))
print(table.contains({speed = 64}, 64), table.contains({speed = 64}, "speed"))true false true false
table.contains walks through the whole table at every call. To test many values against a long list, build a set once, with the values as keys, and look them up directly:
local fuels = {"minecraft:coal", "minecraft:charcoal", "minecraft:blaze_rod"}
local is_fuel = {}
for _, id in ipairs(fuels) do
is_fuel[id] = true
end
print(is_fuel["minecraft:coal"], is_fuel["minecraft:stick"])true nil
See also table.keys()
A list of the keys of a table.
ttable- any table
- table
- a new list of its keys
The order is the one of pairs: the positions 1, 2, 3... first, then the other keys in the order they were added. The result is a new list, so #table.keys(t) counts the entries of any table, where #t only counts its list part. Sort the keys for an alphabetical display:
local stock = {iron = 1200, copper = 640, zinc = 96}
local names = table.keys(stock)
print(#stock, #names)
table.sort(names)
for _, name in ipairs(names) do
print(name, stock[name])
end0 3 copper 640 iron 1200 zinc 96
Keys of different kinds (numbers and texts) cannot be sorted together without a function.
See also pairs() table.sort()
Copying
A shallow copy of a table: a new table with the same keys and values.
ttable- the table to copy
- table
- a new table with the same keys and values
Use it to keep a backup before changing a table, or to hand a table to some code without letting it change yours. The copy has the list part and the named keys, in the same order.
local recipe = {"iron", "iron", "stick"}
local same = recipe
local backup = table.copy(recipe)
recipe[3] = "gold"
print(same[3], backup[3])gold stick
"Shallow" means one level only: a table stored inside is not copied, both tables share it. For a full copy, copy the inner tables too:
local function deep_copy(t)
local out = {}
for k, v in pairs(t) do
if type(v) == "table" then
out[k] = deep_copy(v)
else
out[k] = v
end
end
return out
end
local station = {name = "North", trains = {"T1", "T2"}}
local shallow = table.copy(station)
local deep = deep_copy(station)
table.insert(station.trains, "T3")
print(#shallow.trains, #deep.trains)3 2
See also table.keys()
Common patterns
Totals per item. An inventory lists its slots one by one (inventory.list()), and the same item can fill several slots. Add them up in a table keyed by the item:
local slots = {
{name = "minecraft:iron_ingot", count = 64},
{name = "minecraft:coal", count = 12},
{name = "minecraft:iron_ingot", count = 30},
}
local totals = {}
for _, stack in ipairs(slots) do
totals[stack.name] = (totals[stack.name] or 0) + stack.count
end
for name, count in pairs(totals) do
print(name, count)
endminecraft:iron_ingot 94 minecraft:coal 12
The last readings. Keep the 5 latest values of a sensor: add at the end, remove the first one when there are too many. The Data logger and graph uses the same idea.
local history = {}
local function remember(value)
table.insert(history, value)
if #history > 5 then
table.remove(history, 1)
end
end
for speed = 10, 80, 10 do
remember(speed)
end
print(table.concat(history, " "))40 50 60 70 80
A grid. A table of rows, each row a table of cells: grid[y][x]. Create every row before using it:
local grid = {}
for y = 1, 3 do
grid[y] = {}
for x = 1, 4 do
grid[y][x] = "."
end
end
grid[2][3] = "#"
for y = 1, 3 do
print(table.concat(grid[y]))
end.... ..#. ....