Create: Computing AgesBrass Docs
Libraries

table

Lists and tables: add, remove, sort, join, copy and search.

All computers

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 with t.name or t["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.

Brass
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, " "))
Screen
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

  1. is not part of the list until the gap is filled.
Brass
local t = {"a", "b", "c"}
t[2] = nil
print(#t)
t[3] = nil
print(#t)
t[10] = "j"
print(#t)
Screen
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.

Note

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.

Functions
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

#

table.insert(t, [pos,] value)

⚙ cost 1 per 8 values, with a position

Adds a value at the end of a list, or at a given position.

Parameters
t table
the list
pos number optional
the position the value goes to, from 1 to #t + 1 (the end when left out)
value any
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:

Brass
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)
end
Screen
1   URGENT: Food to Base
2   Iron to Depot A
3   Coal to Smeltery
  • pos must be a whole number from 1 to #t + 1. Otherwise the program stops with bad argument #2 to 'insert' (position out of bounds).
  • A nil value adds nothing: a list cannot hold nil, its length does not change.
  • With only the table, the program stops with wrong number of arguments to 'insert'.
  • t[#t + 1] = value does the same as table.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()

#

table.remove(t [, pos])

→ any⚙ cost 1 per 8 values moved

Removes a value from a list and returns it: the last one, or the one at pos.

Parameters
t table
the list
pos number optional
the position to remove, from 1 to #t (the last one when left out)
Returns
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).

Brass
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))
Screen
Dispatching: Iron to Depot A
Dispatching: Coal to Smeltery
Dispatching: Food to Base
nil
An empty list

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:

Brass
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, " "))
Screen
done todo

Walk backwards instead: removing a value only moves the ones you have already seen.

Brass
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)
end
Screen
mix brass
fill tanks

See also table.insert()

Sorting

#

table.sort(t [, less])

⚙ cost n × log2(n) without less

Sorts a list in place: smallest first, or in the order given by a function.

Parameters
t table
the list to sort, changed in place
less function optional
less(a, b) returns true when a must come before b

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").

Brass
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, " "))
Screen
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).

Brass
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, " "))
Screen
256 64 32 16
Andesite copper Iron zinc
Watch out

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.

Brass
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))
end
Screen
1. 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:

Brass
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

#

table.concat(t [, sep [, i [, j]]])

→ string⚙ cost 1 per 16 characters

Joins the values of a list into one text, with sep between them.

Parameters
t table
a list of texts and numbers
sep string optional
the text put between the values (nothing when left out)
i number optional
the first position (1 when left out)
j number optional
the last position (#t when left out)
Returns
string
the values joined into one text
Brass
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({}) .. "]")
Screen
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()

#

table.contains(t, value)

→ boolean⚙ cost 1 per 8 values

True if the value is somewhere in the table.

Parameters
t table
the table to search
value any
the value to look for
Returns
boolean
true if one of the values of t equals value

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.

Brass
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"))
Screen
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:

Brass
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"])
Screen
true    nil

See also table.keys()

#

table.keys(t)

→ table

A list of the keys of a table.

Parameters
t table
any table
Returns
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:

Brass
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])
end
Screen
0   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

#

table.copy(t)

→ table

A shallow copy of a table: a new table with the same keys and values.

Parameters
t table
the table to copy
Returns
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.

Brass
local recipe = {"iron", "iron", "stick"}
local same = recipe
local backup = table.copy(recipe)
recipe[3] = "gold"
print(same[3], backup[3])
Screen
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:

Brass
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)
Screen
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:

Brass
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)
end
Screen
minecraft: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.

Brass
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, " "))
Screen
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:

Brass
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
Screen
....
..#.
....