48,355
edits
(Created page with "--[[ ------------------------------------------------------------------------------------ -- table (formerly TableTools) --...") |
m (1 revision imported) |
||
| (7 intermediate revisions by 2 users not shown) | |||
| Line 1: | Line 1: | ||
local export = {} | |||
--[[ | --[==[ intro: | ||
This module provides functions for dealing with Lua tables. All of them, except for two helper functions, take a table | |||
as their first argument. | |||
Some functions are available as methods in the arrays created by [[Module:array]]. | |||
Functions by what they do: | |||
* Create a new table: | |||
** `shallowCopy`, `deepCopy`, `removeDuplicates`, `numKeys`, `compressSparseArray`, `keysToList`, `reverse`, `invert`, `listToSet` | |||
* Create an array: | |||
** `removeDuplicates`, `numKeys`, `compressSparseArray`, `keysToList`, `reverse` | |||
* Return information about the table: | |||
** `size`, `length`, `contains`, `isArray`, `deepEquals` | |||
* Treat the table as an array (that is, operate on the values in the array portion of the table: values indexed by | |||
consecutive integers starting at {1}): | |||
** `removeDuplicates`, `length`, `contains`, `serialCommaJoin`, `reverseIpairs`, `reverse`, `invert`, `listToSet`, `isArray` | |||
* Treat a table as a sparse array (that is, operate on values indexed by non-consecutive integers): | |||
** `numKeys`, `maxIndex`, `compressSparseArray`, `sparseConcat`, `sparseIpairs` | |||
* Generate an iterator: | |||
** `sparseIpairs`, `sortedPairs`, `reverseIpairs` | |||
* Other functions: | |||
** `sparseConcat`, `serialCommaJoin`, `reverseConcat` | |||
The original version was a copy of {{w|Module:TableTools}} on Wikipedia via [[c:Module:TableTools|Module:TableTools]] on | |||
Commons, but in the course of time this module has been almost completely rewritten, with many new functions added. The | |||
main legacy of this is the use of camelCase for function names rather than snake_case, as is normal in the English | |||
Wiktionary. | |||
]==] | |||
local load_module = "Module:load" | |||
local | local math_module = "Module:math" | ||
local | |||
local | local table = table | ||
local concat = table.concat | |||
local dump = mw.dumpObject | |||
local ipairs = ipairs | |||
local ipairs_default_iter = ipairs{export} | |||
local next = next | |||
local pairs = pairs | |||
local require = require | |||
-- | local select = select | ||
-- | local signed_index -- defined as export.signedIndex | ||
local table_len -- defined as export.length | |||
local type = type | |||
--[[ | --[==[ | ||
-- | Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]==] | ||
local function is_integer(...) | |||
is_integer = require(math_module).is_integer | |||
return is_integer(...) | |||
function | |||
end | end | ||
local function safe_require(...) | |||
safe_require = require(load_module).safe_require | |||
return safe_require(...) | |||
return | |||
end | end | ||
--[[ | --[==[ | ||
Given an array and a signed index, returns the true table index. If the signed index is negative, the array will be counted from the end, where {-1} is the highest index in the array; otherwise, the returned index will be the same. To aid optimization, the first argument may be a number representing the array length instead of the array itself; this is useful when the array length is already known, as it avoids recalculating it each time this function is called.]==] | |||
]] | function export.signedIndex(t, k) | ||
function export. | if not is_integer(k) then | ||
error("index must be an integer") | |||
end | end | ||
return | return k < 0 and (type(t) == "table" and table_len(t) or t) + k + 1 or k | ||
end | end | ||
signed_index = export.signedIndex | |||
--[ | --[==[ | ||
An iterator which works like `pairs`, but ignores any `__pairs` metamethod.]==] | |||
function export.rawPairs(t) | |||
return next, t, nil | |||
end | end | ||
function export. | --[==[ | ||
An iterator which works like `ipairs`, but ignores any `__ipairs` metamethod.]==] | |||
function export.rawIpairs(t) | |||
return | return ipairs_default_iter, t, 0 | ||
end | end | ||
--[[ | --[==[ | ||
This returns the length of a table, or the first integer key n counting from 1 such that t[n + 1] is nil. It is a more reliable form of the operator `#`, which can become unpredictable under certain circumstances due to the implementation of tables under the hood in Lua, and therefore should not be used when dealing with arbitrary tables. `#` also does not use metamethods, so will return the wrong value in cases where it is desirable to take these into account (e.g. data loaded via `mw.loadData`). If `raw` is set, then metamethods will be ignored, giving the true table length. | |||
For arrays, this function is faster than `export.size`.]==] | |||
function export.length(t, raw) | |||
local n = 0 | |||
if raw then | |||
for i in ipairs_default_iter, t, 0 do | |||
n = i | |||
function export. | |||
local | |||
end | end | ||
return n | |||
end | end | ||
repeat | |||
return | n = n + 1 | ||
until t[n] == nil | |||
return n - 1 | |||
end | end | ||
table_len = export.length | |||
function | local function getIteratorValues(i, j , step, t_len) | ||
i, j = i and signed_index(t_len, i), j and signed_index(t_len, j) | |||
if step == nil then | |||
i, j = i or 1, j or t_len | |||
return | return i, j, j < i and -1 or 1 | ||
elseif step == 0 or not is_integer(step) then | |||
return | error("step must be a non-zero integer") | ||
elseif step < 0 then | |||
return i or t_len, j or 1, step | |||
end | end | ||
return i or 1, j or t_len, step | |||
end | end | ||
--[[ | --[==[ | ||
Given an array `list` and function `func`, iterate through the array applying {func(r, k, v)}, and returning the result, | |||
where `r` is the value calculated so far, `k` is an index, and `v` is the value at index `k`. For example, | |||
{reduce(array, function(a, _, v) return a + v end)} will return the sum of `array`. | |||
Optional arguments: | |||
* `i`: start index; negative values count from the end of the array | |||
* `j`: end index; negative values count from the end of the array | |||
* `step`: step increment | |||
These must be non-zero integers. The function will determine where to iterate from, whether to iterate forwards or | |||
backwards and by how much, based on these inputs (see examples below for default behaviours). | |||
Examples: | |||
# No values for i, j or step results in forward iteration from the start to the end in steps of 1 (the default). | |||
# step=-1 results in backward iteration from the end to the start in steps of 1. | |||
# i=7, j=3 results in backward iteration from indices 7 to 3 in steps of 1 (i.e. step=-1). | |||
# j=-3 results in forward iteration from the start to the 3rd last index. | |||
- | # j=-3, step=-1 results in backward iteration from the end to the 3rd last index.]==] | ||
- | function export.reduce(t, func, i, j, step) | ||
- | i, j, step = getIteratorValues(i, j, step, table_len(t)) | ||
--]] | local ret = t[i] | ||
function export. | for k = i + step, j, step do | ||
ret = func(ret, k, t[k]) | |||
local ret = | |||
for | |||
ret | |||
end | end | ||
return ret | return ret | ||
end | end | ||
do | |||
local function replace(t, func, i, j, step, generate) | |||
local t_len = table_len(t) | |||
-- Normalized i, j and step, based on the inputs. | |||
local norm_i, norm_j, norm_step = getIteratorValues(i, j, step, t_len) | |||
if norm_step > 0 then | |||
i, j, step = 1, t_len, 1 | |||
function | |||
i | |||
local | |||
if | |||
else | else | ||
i, j, step = t_len, 1, -1 | |||
end | end | ||
-- "Signed" variables are multiplied by -1 if `step` is negative. | |||
local t_new, signed_i, signed_j = generate and {} or t, norm_i * step, norm_j * step | |||
for k = i, j, step do | |||
-- Replace the values iff they're within the i to j range and `step` wouldn't skip the key. | |||
-- Note: i > j if `step` is positive; i < j if `step` is negative. Otherwise, the range is empty. | |||
local signed_k = k * step | |||
if signed_k >= signed_i and signed_k <= signed_j and (k - norm_i) % norm_step == 0 then | |||
t_new[k] = func(k, t[k]) | |||
-- Otherwise, add the existing value if `generate` is set. | |||
elseif generate then | |||
t_new[k] = t[k] | |||
end | |||
end | |||
return t_new | |||
end | end | ||
--[[ | --[==[ | ||
Given an array `list` and function `func`, iterate through the array applying {func(k, v)} (where `k` is an index, and | |||
`v` is the value at index `k`), replacing the relevant values with the result. For example, | |||
{apply(array, function(_, v) return 2 * v end)} will double each member of the array. | |||
end | |||
Optional arguments: | |||
* `i`: start index; negative values count from the end of the array | |||
* `j`: end index; negative values count from the end of the array | |||
* `step`: step increment | |||
- | These must be non-zero integers. The function will determine where to iterate from, whether to iterate forwards or | ||
backwards and by how much, based on these inputs (see examples below for default behaviours). | |||
Examples: | |||
# No values for i, j or step results in forward iteration from the start to the end in steps of 1 (the default). | |||
# step=-1 results in backward iteration from the end to the start in steps of 1. | |||
# i=7, j=3 results in backward iteration from indices 7 to 3 in steps of 1 (i.e. step=-1). | |||
]] | # j=-3 results in forward iteration from the start to the 3rd last index. | ||
function export. | # j=-3, step=-1 results in backward iteration from the end to the 3rd last index.]==] | ||
function export.apply(t, func, i, j, step) | |||
return replace(t, func, i, j, step, false) | |||
end | end | ||
--[[ | --[==[ | ||
Given an array `list` and function `func`, iterate through the array applying {func(k, v)} (where `k` is an index, and | |||
`v` is the value at index `k`), and return a shallow copy of the original array with the relevant values replaced. For example, | |||
{generate(array, function(_, v) return 2 * v end)} will return a new array in which each value has been doubled. | |||
Optional arguments: | |||
* `i`: start index; negative values count from the end of the array | |||
* `j`: end index; negative values count from the end of the array | |||
* `step`: step increment | |||
These must be non-zero integers. The function will determine where to iterate from, whether to iterate forwards or | |||
backwards and by how much, based on these inputs (see examples below for default behaviours). | |||
Examples: | |||
# No values for i, j or step results in forward iteration from the start to the end in steps of 1 (the default). | |||
# step=-1 results in backward iteration from the end to the start in steps of 1. | |||
# i=7, j=3 results in backward iteration from indices 7 to 3 in steps of 1 (i.e. step=-1). | |||
# j=-3 results in forward iteration from the start to the 3rd last index. | |||
# j=-3, step=-1 results in backward iteration from the end to the 3rd last index.]==] | |||
function export.generate(t, func, i, j, step) | |||
return replace(t, func, i, j, step, true) | |||
end | |||
end | end | ||
--[[ | --[==[ | ||
Given an array `list` and function `func`, iterate through the array applying {func(k, v)} (where `k` is an index, and | |||
`v` is the value at index `k`), and returning whether the function is true for all iterations. | |||
Optional arguments: | |||
* `i`: start index; negative values count from the end of the array | |||
* `j`: end index; negative values count from the end of the array | |||
function | * `step`: step increment | ||
These must be non-zero integers. The function will determine where to iterate from, whether to iterate forwards or | |||
backwards and by how much, based on these inputs (see examples below for default behaviours). | |||
Examples: | |||
# No values for i, j or step results in forward iteration from the start to the end in steps of 1 (the default). | |||
# step=-1 results in backward iteration from the end to the start in steps of 1. | |||
# i=7, j=3 results in backward iteration from indices 7 to 3 in steps of 1 (i.e. step=-1). | |||
# j=-3 results in forward iteration from the start to the 3rd last index. | |||
# j=-3, step=-1 results in backward iteration from the end to the 3rd last index.]==] | |||
function export.all(t, func, i, j, step) | |||
i, j, step = getIteratorValues(i, j, step, table_len(t)) | |||
for k = i, j, step do | |||
if not func(k, t[k]) then | |||
return false | |||
]] | |||
function export. | |||
for | |||
if | |||
return | |||
end | end | ||
end | end | ||
return true | |||
return | |||
end | end | ||
--[[ | --[==[ | ||
Given an array `list` and function `func`, iterate through the array applying {func(k, v)} (where `k` is an index, and | |||
`v` is the value at index `k`), and returning whether the function is true for at least one iteration. | |||
Optional arguments: | |||
* `i`: start index; negative values count from the end of the array | |||
* `j`: end index; negative values count from the end of the array | |||
* `step`: step increment | |||
These must be non-zero integers. The function will determine where to iterate from, whether to iterate forwards or | |||
function | backwards and by how much, based on these inputs (see examples below for default behaviours). | ||
-- | Examples: | ||
# No values for i, j or step results in forward iteration from the start to the end in steps of 1 (the default). | |||
# step=-1 results in backward iteration from the end to the start in steps of 1. | |||
]] | # i=7, j=3 results in backward iteration from indices 7 to 3 in steps of 1 (i.e. step=-1). | ||
function export. | # j=-3 results in forward iteration from the start to the 3rd last index. | ||
# j=-3, step=-1 results in backward iteration from the end to the 3rd last index.]==] | |||
function export.any(t, func, i, j, step) | |||
i, j, step = getIteratorValues(i, j, step, table_len(t)) | |||
for k = i, j, step do | |||
if not not (func(k, t[k])) then | |||
return true | |||
if | |||
return | |||
end | end | ||
end | end | ||
return false | |||
end | end | ||
--[==[ | |||
Joins an array with serial comma and serial conjunction, normally {"and"}. An improvement on {mw.text.listToText}, | |||
which doesn't properly handle serial commas. | |||
Options: | |||
* `conj`: Conjunction to use; defaults to {"and"}. | |||
* `punc`: Punctuation to use; default to {","}. | |||
* `dontTag`: Don't tag the serial comma and serial {"and"}. For error messages, in which HTML cannot be used. | |||
* `dump`: Each item will be serialized with {mw.dumpObject}. For warnings and error messages.]==] | |||
]=] | |||
function export.serialCommaJoin(seq, options) | function export.serialCommaJoin(seq, options) | ||
-- If the `dump` option is set, determine the table length as part of the | |||
-- dump loop, instead of calling `table_len` separately. | |||
local length | |||
if options and options.dump then | |||
local i, item = 1, seq[1] | |||
if item ~= nil then | |||
local dumped = {} | |||
repeat | |||
dumped[i] = dump(item) | |||
i = i + 1 | |||
item = seq[i] | |||
until item == nil | |||
seq = dumped | |||
end | end | ||
length = i - 1 | |||
else | |||
length = table_len(seq) | |||
end | end | ||
if length == 0 then | if length == 0 then | ||
return "" | return "" | ||
elseif length == 1 then | elseif length == 1 then | ||
return seq[1] | return seq[1] | ||
end | |||
local conj = options and options.conj | |||
if conj == nil then | |||
conj = "and" | |||
end | |||
if length == 2 then | |||
return seq[1] .. " " .. conj .. " " .. seq[2] | return seq[1] .. " " .. conj .. " " .. seq[2] | ||
end | end | ||
local punc, dont_tag | |||
if options then | |||
punc = options.punc | |||
if punc == nil then | |||
punc = "," | |||
end | |||
dont_tag = options.dontTag | |||
else | |||
punc = "," | |||
end | end | ||
local comma | |||
if dont_tag then | |||
comma = "" -- since by default the serial comma doesn't display, when we can't tag we shouldn't display it. | |||
conj = " " .. conj .. " " | |||
else | |||
comma = "<span class=\"serial-comma\">" .. punc .. "</span>" | |||
conj = "<span class=\"serial-and\"> " .. conj .. "</span> " | |||
end | end | ||
return concat(seq, punc .. " ", 1, length - 1) .. comma .. conj .. seq[length] | |||
return | |||
end | end | ||
-- | --[==[ | ||
function export. | A function which works like `table.concat`, but respects any `__index` metamethod. This is useful for data loaded via `mw.loadData`.]==] | ||
function export.concat(t, sep, i, j) | |||
local list, k = {}, 0 | |||
local | while true do | ||
k = k + 1 | |||
local v = t[k] | |||
if v == nil then | |||
return concat(list, sep, i, j) | |||
end | |||
list[k] = v | |||
end | end | ||
end | end | ||
--[[ | --[==[ | ||
Add a list of aliases for a given key to a table. The aliases must be given as a table.]==] | |||
function export.alias(t, k, aliases) | |||
function export. | for _, alias in pairs(aliases) do | ||
t[alias] = t[k] | |||
for _, | |||
end | end | ||
end | end | ||
local mt = {} | |||
function mt:__index(k) | |||
function | local submodule = safe_require("Module:table/" .. k) | ||
self[k] = submodule | |||
return submodule | |||
return | |||
end | end | ||
return export | return setmetatable(export, mt) | ||