Table
A table is one structure doing two jobs: an array keyed by consecutive
integers from 1, and a map keyed by strings, integers or booleans. Most
of what is here works on the array side; keys and values see both, and
map access itself is just t[k] and t[k] = v.
Table is the mutating half of the sequence API: it changes the table it
is given, or answers a question about one. For deriving a new sequence —
map, filter, reduce — see Iter. No name means two different
things across the two halves: Table.reverse reverses in place,
Iter.reverse returns a new table.
local scores: table<integer> = {10, 20, 30}local byName: table<string, integer> = {ada: 1, grace: 2}
println(#scores) -- 3, the array lengthprintln(scores[2] ?? 0) -- 20, 1-basedprintln(byName["ada"] ?? 0) -- 1Changing a table
Section titled “Changing a table”Table.insert<V>(t: table<V>, value: V, pos: integer?) -> nil
Appends value to the end of the array part, or inserts it at pos,
shifting everything from pos onward one place to the right.
local xs: table<integer> = {1, 2, 3}
Table.insert(xs, 4)println(Table.concat(xs, ",")) -- 1,2,3,4
Table.insert(xs, 0, 1)println(Table.concat(xs, ",")) -- 0,1,2,3,4
-- Building a list in a loop.local squares: table<integer> = {}for n = 1, 4 do Table.insert(squares, n * n)endprintln(Table.concat(squares, " ")) -- 1 4 9 16Table.remove<V>(t: table<V>, pos: integer?) -> V?
Removes and returns the last element, or the one at pos, shifting the rest
left. nil when there is nothing to remove, which is what makes a table
usable as a stack without a separate emptiness check.
local stack: table<integer> = {1, 2, 3}
println(Table.remove(stack) ?? 0) -- 3, from the endprintln(Table.concat(stack, ",")) -- 1,2println(Table.remove(stack, 1) ?? 0) -- 1, from the frontprintln(Table.concat(stack, ",")) -- 2
-- Draining a stack until it is empty.local queue: table<string> = {"a", "b"}while #queue > 0 do println(Table.remove(queue, 1) ?? "") -- a, then bendprintln(Table.remove(queue) == nil) -- true, nothing leftTable.sort<V>(t: table<V>, cmp: fn(V, V) -> boolean) -> nil
Sorts the array part in place. cmp(a, b) answers true when a must come
before b.
For ordering by an extracted key rather than a comparator, see
Iter.sortBy, which returns a new table.
local xs: table<integer> = {3, 1, 4, 1, 5}
Table.sort(xs, (a, b) => a < b)println(Table.concat(xs, ",")) -- 1,1,3,4,5
Table.sort(xs, (a, b) => a > b)println(Table.concat(xs, ",")) -- 5,4,3,1,1
-- Sorting by something other than the value itself. The comparator's-- parameters take their types from the table, so they need no annotation.local words: table<string> = {"pear", "fig", "banana"}Table.sort(words, (a, b) => String.len(a) < String.len(b))println(Table.concat(words, ",")) -- fig,pear,bananaTable.reverse<V>(t: table<V>) -> nil
Reverses the array part in place. Iter.reverse
returns a new table instead.
local xs: table<integer> = {1, 2, 3}Table.reverse(xs)println(Table.concat(xs, ",")) -- 3,2,1
-- In place: the original binding changes.local letters: table<string> = {"a", "b", "c"}Table.reverse(letters)println(letters[1] ?? "") -- cTable.clear<V>(t: table<V>) -> nil
Removes every entry, array part and map part alike. The table itself stays the same table, so anything else holding a reference to it sees it empty.
local named: table<string, integer> = {a: 1, b: 2}Table.clear(named)println(named["a"] == nil) -- true
local xs: table<integer> = {1, 2, 3}Table.clear(xs)println(#xs) -- 0
-- Reusing a buffer rather than allocating a new one.local batch: table<integer> = {}for round = 1, 2 do Table.clear(batch) Table.insert(batch, round) println(#batch) -- 1, both timesendReading a table
Section titled “Reading a table”Table.len<V>(t: table<V>) -> integer
The array length — the same number #t gives, as a value you can pass
around.
println(Table.len({1, 2, 3})) -- 3println(Table.len({})) -- 0
-- `#` is the operator form; this is the one you can hand to something else.local sizes: table<integer> = Iter.map({{1}, {1, 2}}, t => Table.len(t))println(Table.concat(sizes, ",")) -- 1,2Table.contains<V>(t: table<V>, value: V) -> boolean
Whether value appears in the array part. Compares with ==, so a class
implementing OpEq is matched by its own rule rather
than by identity.
println(Table.contains({1, 2, 3}, 2)) -- trueprintln(Table.contains({1, 2, 3}, 9)) -- falseprintln(Table.contains({"a", "b"}, "b")) -- true
-- Filtering out what is already known.local seen: table<string> = {"ada"}for name in {"ada", "grace"} do if not Table.contains(seen, name) then println("new: " .. name) -- new: grace endendTable.indexOf<V>(t: table<V>, value: V) -> integer?
The 1-based position of the first == match, or nil. For a predicate
search, use Iter.findIndex.
println(Table.indexOf({"a", "b", "c"}, "b") ?? 0) -- 2println(Table.indexOf({1, 2}, 9) == nil) -- true
-- Removing a known value.local xs: table<string> = {"a", "b", "c"}local at: integer? = Table.indexOf(xs, "b")if at != nil then Table.remove(xs, at!)endprintln(Table.concat(xs, ",")) -- a,cTable.keys<K, V>(t: table<K, V>) -> table<K>
Every key: the array indices 1..#t in order, then the map’s keys in no
particular order.
local t: table<string, integer> = {}t["ada"] = 1t["grace"] = 2
local names: table<string> = Table.keys(t)println(#names) -- 2
-- Array indices come out in order.println(Table.concat(Table.keys({"x", "y", "z"}), ",")) -- 1,2,3Table.values<K, V>(t: table<K, V>) -> table<V>
Every value, in the same order as Table.keys.
println(Table.concat(Table.values({"x", "y"}), ",")) -- x,y
local ages: table<string, integer> = {}ages["ada"] = 36println(Table.values(ages)[1] ?? 0) -- 36
-- Summing a map's values, whatever its keys are.local counts: table<string, integer> = {}counts["a"] = 2counts["b"] = 3println(Iter.reduce(Table.values(counts), 0, (acc, n) => acc + n)) -- 5Table.slice<V>(t: table<V>, from: integer, to: integer?) -> table<V>
A new table over the 1-based range, to defaulting to the end. Negative
indices count from the end, and an out-of-range slice is empty rather than an
error.
local xs: table<integer> = {1, 2, 3, 4, 5}
println(Table.concat(Table.slice(xs, 2, 3), ",")) -- 2,3println(Table.concat(Table.slice(xs, 3), ",")) -- 3,4,5println(Table.concat(Table.slice(xs, -2), ",")) -- 4,5println(#Table.slice(xs, 99, 120)) -- 0
-- The original is untouched.println(Table.concat(xs, ",")) -- 1,2,3,4,5Table.copy<V>(t: table<V>) -> table<V>
A shallow copy of both halves. Elements are shared, so copying a table of instances gives a new table pointing at the same instances.
local xs: table<integer> = {1, 2}local ys: table<integer> = Table.copy(xs)
Table.insert(ys, 3)println(Table.concat(xs, ",")) -- 1,2, unchangedprintln(Table.concat(ys, ",")) -- 1,2,3
-- Shallow: a nested table is the same table in both copies.local nested: table<table<integer>> = {{1}}local clone: table<table<integer>> = Table.copy(nested)Table.insert(clone[1] ?? {}, 2)println(#(nested[1] ?? {})) -- 2, the inner table is sharedTable.concat<V>(t: table<V>, sep: string?, from: integer?, to: integer?) -> string
Joins the elements with sep between them, rendering each as tostring
would. sep defaults to the empty string, and from / to restrict the
range.
String.join is the same operation with
the arguments the other way round.
println(Table.concat({1, 2, 3}, ", ")) -- 1, 2, 3println(Table.concat({"a", "b"})) -- ab, no separatorprintln(Table.concat({1, 2, 3, 4}, "-", 2, 3)) -- 2-3
-- Rendering a row.local row: table<string> = {"ada", "36"}println("| " .. Table.concat(row, " | ") .. " |") -- | ada | 36 |There is no Table.unpack
Section titled “There is no Table.unpack”A function returning “however many values the table holds” has no type Saule can write — the arity is not known until the call runs — so the table is passed as a table.