7.5 KiB
table
a keeps Lua's standard table library (insert, remove, concat, sort,
pack, unpack) and adds the higher-order and collection helpers stock Lua
leaves out: map/filter/reduce, merge and deep-copy, aggregates, lookups, and a
read-only proxy. The additions live on the same global table.
local nums = {1, 2, 3, 4, 5}
local evens = table.ifilter(nums, function(n) return n % 2 == 0 end)
local doubled = table.map(nums, function(n) return n * 2 end)
print(table.sum(nums), table.max(nums)) --> 15 5
Sequences vs. general tables
Lua tables are both arrays and maps, so most helpers come in two flavours. The distinction runs through the whole module:
- Plain name (
map,filter,reduce) iterates withpairs— it visits every key, preserves keys in the result, and the order is unspecified. i-prefixed name (imap-style:ifilter,ireduce, …) iterates withipairsover the sequence part1..#tin order, and produces a fresh gapless sequence.
Reach for the i variants when you have an array and want array semantics
(order preserved, no gaps); use the plain ones for maps or when keys matter.
Transforming
table.map(tbl, fn)
Apply fn(value) to every value, preserving keys. Order unspecified.
table.map({a = 1, b = 2}, function(v) return v * 10 end) --> {a = 10, b = 20}
table.filter(tbl, predicate)
Keep the entries where predicate(value) is truthy, preserving keys. On a
sequence this may leave gaps — use ifilter to avoid them.
table.filter({a = 1, b = 2, c = 3}, function(v) return v > 1 end) --> {b = 2, c = 3}
table.ifilter(tbl, predicate)
Keep the sequence elements matching predicate, producing a new gapless
sequence in order.
table.ifilter({1, 2, 3, 4}, function(v) return v % 2 == 0 end) --> {2, 4}
table.filterMap(tbl, fn)
Map and filter in one pass: apply fn(value), and drop entries where fn
returns nil. Keys are preserved (may leave gaps on a sequence).
-- keep and square only the even numbers
table.filterMap({a = 1, b = 2, c = 4}, function(v)
if v % 2 == 0 then return v * v end
end) --> {b = 4, c = 16}
table.ifilterMap(tbl, fn)
The sequence version of filterMap: map over 1..#t in order, drop nil
results, and return a gapless sequence.
table.ifilterMap({1, 2, 3, 4}, function(v)
if v % 2 == 0 then return v * 10 end
end) --> {20, 40}
Reducing
table.reduce(tbl, fn, init)
Fold every value into a single accumulator: acc = fn(acc, value), starting from
init. Iterates with pairs, so order is unspecified — use it for
order-independent folds (sum, product, building a set).
table.reduce({1, 2, 3}, function(acc, v) return acc + v end, 0) --> 6
table.ireduce(tbl, fn, init)
Like reduce but folds the sequence part in order, for when order matters
(e.g. concatenation, left-to-right composition).
table.ireduce({"a", "b", "c"}, function(acc, v) return acc .. v end, "") --> "abc"
Aggregates
These walk all values with pairs, so they work on sequences and maps alike.
Each returns nil for an empty table (except sum, which returns 0), and a
NaN value anywhere propagates to the result.
| Function | Result |
|---|---|
table.min(tbl) |
smallest value, or nil if empty |
table.max(tbl) |
largest value, or nil if empty |
table.mean(tbl) |
arithmetic mean, or nil if empty |
table.sum(tbl) |
sum of values, 0 if empty |
local t = {4, 8, 15, 16, 23, 42}
print(table.min(t), table.max(t)) --> 4 42
print(table.sum(t), table.mean(t)) --> 108 18.0
Querying
table.keys(tbl) / table.values(tbl)
Return an array of all keys, or all values. Order is unspecified.
table.keys({a = 1, b = 2}) --> {"a", "b"} (some order)
table.values({a = 1, b = 2}) --> {1, 2} (matching order)
table.contains(tbl, value)
Return true if any value equals value (compared with ==).
table.contains({"red", "green"}, "green") --> true
table.find(tbl, predicate)
Return the first value matching predicate, together with its key:
(value, key), or (nil, nil) if none match. Because a matching value could
itself be false, test the returned key against nil to distinguish "found
false" from "not found".
local v, k = table.find({10, 20, 30}, function(x) return x > 15 end)
print(v, k) --> 20 2
table.isEmpty(tbl)
Return true if the table has no keys at all (faster and more correct than
#tbl == 0, which only inspects the sequence part).
table.isEmpty({}) --> true
table.isEmpty({x = 1}) --> false
Combining and copying
table.merge(...)
Merge any number of tables into a new table (a shallow merge). Sequence parts
are appended in argument order; other keys are copied, with later arguments
overwriting earlier ones on a collision. Not recursive — for nested merges use
deepMerge.
table.merge({1, 2}, {3, 4}) --> {1, 2, 3, 4} (sequences concatenate)
table.merge({a = 1, b = 2}, {b = 9}) --> {a = 1, b = 9} (later wins)
table.deepMerge(...)
Like merge, but where the same key holds a table on both sides, those tables
are merged recursively. The result shares no tables with the inputs —
everything is deep-copied — so mutating the result never touches the originals.
table.deepMerge(
{ db = { host = "localhost", port = 5432 } },
{ db = { port = 5433 } }
) --> { db = { host = "localhost", port = 5433 } }
table.deepCopy(value)
Return a deep copy of a table, recursively copying nested tables. Metatables are not copied. Non-table values pass through unchanged.
local original = { list = {1, 2, 3} }
local copy = table.deepCopy(original)
copy.list[1] = 99
print(original.list[1]) --> 1 (unaffected)
deepMerge and deepCopy cap recursion at 100 levels and error beyond that, so
a cyclic table raises rather than looping forever.
table.reverse(tbl)
Return a new array with the sequence elements in reverse order.
table.reverse({1, 2, 3}) --> {3, 2, 1}
Protecting
table.readonly(tbl [, name])
Return a read-only proxy of tbl: reads pass through to the underlying table,
writes raise an error. The optional name is included in that error message.
Useful for exposing a constant namespace that callers must not mutate.
local config = table.readonly({ retries = 3 }, "config")
print(config.retries) --> 3
config.retries = 5 --> error: Attempt to update a read-only table config
Caveat: the proxy is an empty shell that forwards reads through a metatable,
so pairs, next, and # see no contents — it is meant for guarding API
tables you read by known key, not for iterable data.
Notes
- Additions, not replacements. Stock
table.insert,remove,concat,sort,pack,unpackare all still present. - Map vs. sequence is the recurring choice. Plain functions preserve keys via
pairs(unspecified order);i-prefixed ones produce gapless sequences viaipairs(in order). Pick by whether you have an array or a map. - Most helpers return new tables.
map,filter*,merge*,deepCopy,reverse,keys,valuesnever mutate their input. - Type-checked. Passing a non-table where a table is expected raises a clear error naming the offending function.