You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
239 lines
7.5 KiB
239 lines
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`.
|
|
|
|
```lua
|
|
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 with `pairs` — it visits
|
|
**every** key, preserves keys in the result, and the order is unspecified.
|
|
- **`i`-prefixed name** (`imap`-style: `ifilter`, `ireduce`, …) iterates with
|
|
`ipairs` over the sequence part `1..#t` **in 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.
|
|
|
|
```lua
|
|
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.
|
|
|
|
```lua
|
|
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.
|
|
|
|
```lua
|
|
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).
|
|
|
|
```lua
|
|
-- 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.
|
|
|
|
```lua
|
|
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).
|
|
|
|
```lua
|
|
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).
|
|
|
|
```lua
|
|
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 |
|
|
|
|
```lua
|
|
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.
|
|
|
|
```lua
|
|
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 `==`).
|
|
|
|
```lua
|
|
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".
|
|
|
|
```lua
|
|
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).
|
|
|
|
```lua
|
|
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`.
|
|
|
|
```lua
|
|
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.
|
|
|
|
```lua
|
|
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.
|
|
|
|
```lua
|
|
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.
|
|
|
|
```lua
|
|
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.
|
|
|
|
```lua
|
|
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`, `unpack` are all still present.
|
|
- **Map vs. sequence is the recurring choice.** Plain functions preserve keys via
|
|
`pairs` (unspecified order); `i`-prefixed ones produce gapless sequences via
|
|
`ipairs` (in order). Pick by whether you have an array or a map.
|
|
- **Most helpers return new tables.** `map`, `filter*`, `merge*`, `deepCopy`,
|
|
`reverse`, `keys`, `values` never mutate their input.
|
|
- **Type-checked.** Passing a non-table where a table is expected raises a clear
|
|
error naming the offending function.
|
|
|