|
|
| (6 intermediate revisions by 2 users not shown) |
| Line 1: |
Line 1: |
| --[[
| | local export = {} |
| ------------------------------------------------------------------------------------
| |
| -- table (formerly TableTools) --
| |
| -- --
| |
| -- This module includes a number of functions for dealing with Lua tables. --
| |
| -- It is a meta-module, meant to be called from other Lua modules, and should --
| |
| -- not be called directly from #invoke. --
| |
| ------------------------------------------------------------------------------------
| |
| --]]
| |
|
| |
|
| --[[ | | --[==[ intro: |
| Inserting new values into a table using a local "index" variable, which is
| | This module provides functions for dealing with Lua tables. All of them, except for two helper functions, take a table |
| incremented each time, is faster than using "table.insert(t, x)" or
| | as their first argument. |
| "t[#t + 1] = x". See the talk page.
| |
| ]]
| |
|
| |
|
| local libraryUtil = require('libraryUtil')
| | Some functions are available as methods in the arrays created by [[Module:array]]. |
|
| |
|
| local export = {}
| | 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` |
|
| |
|
| -- Define often-used variables and functions.
| | The original version was a copy of {{w|Module:TableTools}} on Wikipedia via [[c:Module:TableTools|Module:TableTools]] on |
| local floor = math.floor
| | Commons, but in the course of time this module has been almost completely rewritten, with many new functions added. The |
| local infinity = math.huge
| | main legacy of this is the use of camelCase for function names rather than snake_case, as is normal in the English |
| local checkType = libraryUtil.checkType
| | Wiktionary. |
| local checkTypeMulti = libraryUtil.checkTypeMulti
| | ]==] |
|
| |
|
| local function _check(funcName, expectType) | | local load_module = "Module:load" |
| if type(expectType) == "string" then
| | local math_module = "Module:math" |
| return function(argIndex, arg, nilOk)
| |
| checkType(funcName, argIndex, arg, expectType, nilOk)
| |
| end
| |
| else
| |
| return function(argIndex, arg, expectType, nilOk)
| |
| if type(expectType) == "table" then
| |
| checkTypeMulti(funcName, argIndex, arg, expectType, nilOk)
| |
| else
| |
| checkType(funcName, argIndex, arg, expectType, nilOk)
| |
| end
| |
| end
| |
| end
| |
| end
| |
|
| |
|
| --[[
| | local table = table |
| ------------------------------------------------------------------------------------
| |
| -- isPositiveInteger
| |
| --
| |
| -- This function returns true if the given value is a positive integer, and false
| |
| -- if not. Although it doesn't operate on tables, it is included here as it is
| |
| -- useful for determining whether a given table key is in the array part or the
| |
| -- hash part of a table.
| |
| ------------------------------------------------------------------------------------
| |
| --]]
| |
| function export.isPositiveInteger(v)
| |
| return type(v) == 'number' and v >= 1 and floor(v) == v and v < infinity
| |
| end
| |
|
| |
|
| --[[
| | local concat = table.concat |
| ------------------------------------------------------------------------------------
| | local dump = mw.dumpObject |
| -- isNan
| | local ipairs = ipairs |
| --
| | local ipairs_default_iter = ipairs{export} |
| -- This function returns true if the given number is a NaN value, and false
| | local next = next |
| -- if not. Although it doesn't operate on tables, it is included here as it is
| | local pairs = pairs |
| -- useful for determining whether a value can be a valid table key. Lua will
| | local require = require |
| -- generate an error if a NaN is used as a table key. | | local select = select |
| ------------------------------------------------------------------------------------ | | local signed_index -- defined as export.signedIndex |
| --]]
| | local table_len -- defined as export.length |
| function export.isNan(v)
| | local type = type |
| if type(v) == 'number' and tostring(v) == '-nan' then
| |
| return true
| |
| else
| |
| return false
| |
| end
| |
| end
| |
|
| |
|
| --[[ | | --[==[ |
| ------------------------------------------------------------------------------------
| | 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.]==] |
| -- shallowClone
| | local function is_integer(...) |
| --
| | is_integer = require(math_module).is_integer |
| -- This returns a clone of a table. The value returned is a new table, but all
| | return is_integer(...) |
| -- subtables and functions are shared. Metamethods are respected, but the returned
| |
| -- table will have no metatable of its own. | |
| ------------------------------------------------------------------------------------
| |
| --]]
| |
| function export.shallowClone(t) | |
| local ret = {} | |
| for k, v in pairs(t) do
| |
| ret[k] = v
| |
| end
| |
| return ret | |
| end | | end |
|
| |
|
| --[[
| | local function safe_require(...) |
| Shallow copy
| | safe_require = require(load_module).safe_require |
| ]]
| | return safe_require(...) |
| function export.shallowcopy(orig) | |
| local orig_type = type(orig) | |
| local copy | |
| if orig_type == 'table' then
| |
| copy = {}
| |
| for orig_key, orig_value in pairs(orig) do
| |
| copy[orig_key] = orig_value
| |
| end
| |
| else -- number, string, boolean, etc
| |
| copy = orig
| |
| end
| |
| return copy
| |
| end | | end |
|
| |
|
| --[[ | | --[==[ |
| Recursive deep copy function
| | 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.]==] |
| Equivalent to mw.clone?
| | function export.signedIndex(t, k) |
| ]]
| | if not is_integer(k) then |
| local function deepcopy(orig, includeMetatable, already_seen)
| | error("index must be an integer") |
| -- Stores copies of tables indexed by the original table.
| |
| already_seen = already_seen or {}
| |
|
| |
| local copy = already_seen[orig]
| |
| if copy ~= nil then | |
| return copy | |
| end | | end |
| | | return k < 0 and (type(t) == "table" and table_len(t) or t) + k + 1 or k |
| if type(orig) == 'table' then
| |
| copy = {}
| |
| for orig_key, orig_value in pairs(orig) do
| |
| copy[deepcopy(orig_key, includeMetatable, already_seen)] = deepcopy(orig_value, includeMetatable, already_seen)
| |
| end
| |
| already_seen[orig] = copy
| |
|
| |
| if includeMetatable then
| |
| local mt = getmetatable(orig)
| |
| if mt ~= nil then
| |
| local mt_copy = deepcopy(mt, includeMetatable, already_seen)
| |
| setmetatable(copy, mt_copy)
| |
| end
| |
| end
| |
| else -- number, string, boolean, etc
| |
| copy = orig
| |
| end
| |
| return copy
| |
| end | | end |
| | signed_index = export.signedIndex |
|
| |
|
| function export.deepcopy(orig, noMetatable, already_seen) | | --[==[ |
| checkType("deepcopy", 3, already_seen, "table", true)
| | An iterator which works like `pairs`, but ignores any `__pairs` metamethod.]==] |
|
| | function export.rawPairs(t) |
| return deepcopy(orig, not noMetatable, already_seen) | | return next, t, nil |
| end | | end |
|
| |
|
| --[[ | | --[==[ |
| ------------------------------------------------------------------------------------
| | An iterator which works like `ipairs`, but ignores any `__ipairs` metamethod.]==] |
| -- append
| | function export.rawIpairs(t) |
| --
| | return ipairs_default_iter, t, 0 |
| -- This appends two tables together and returns the result. Compare the Lisp
| |
| -- expression (append list1 list2).
| |
| ------------------------------------------------------------------------------------
| |
| --]]
| |
| function export.append(t1, t2) | |
| checkType('append', 1, t1, 'table') | |
| checkType('append', 2, t2, 'table')
| |
| local ret = {}
| |
| for _, v in ipairs(t1) do
| |
| table.insert(ret, v)
| |
| end
| |
| for _, v in ipairs(t2) do
| |
| table.insert(ret, v)
| |
| end
| |
| return ret
| |
| 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. |
| -- removeDuplicates
| |
| --
| |
| -- This removes duplicate values from an array. Non-positive-integer keys are
| |
| -- ignored. The earliest value is kept, and all subsequent duplicate values are
| |
| -- removed, but otherwise the array order is unchanged.
| |
| ------------------------------------------------------------------------------------
| |
| --]]
| |
| function export.removeDuplicates(t)
| |
| checkType('removeDuplicates', 1, t, 'table')
| |
| local isNan = export.isNan
| |
| local ret, exists = {}, {}
| |
| local index = 1
| |
| for _, v in ipairs(t) do
| |
| if isNan(v) then
| |
| -- NaNs can't be table keys, and they are also unique, so we don't need to check existence.
| |
| ret[index] = v
| |
| index = index + 1
| |
| else
| |
| if not exists[v] then
| |
| ret[index] = v
| |
| index = index + 1
| |
| exists[v] = true
| |
| end
| |
| end
| |
| end
| |
| return ret
| |
| end
| |
|
| |
|
| --[[
| | For arrays, this function is faster than `export.size`.]==] |
| ------------------------------------------------------------------------------------
| | function export.length(t, raw) |
| -- numKeys
| | local n = 0 |
| --
| | if raw then |
| -- This takes a table and returns an array containing the numbers of any numerical
| | for i in ipairs_default_iter, t, 0 do |
| -- keys that have non-nil values, sorted in numerical order.
| | n = i |
| ------------------------------------------------------------------------------------
| |
| --]]
| |
| function export.numKeys(t, checked) | |
| if not checked then
| |
| checkType('numKeys', 1, t, 'table')
| |
| end
| |
| local isPositiveInteger = export.isPositiveInteger | |
| local nums = {} | |
| local index = 1
| |
| for k, _ in pairs(t) do
| |
| if isPositiveInteger(k) then
| |
| nums[index] = k
| |
| index = index + 1 | |
| end | | end |
| | return n |
| end | | end |
| table.sort(nums) | | repeat |
| return nums | | n = n + 1 |
| | until t[n] == nil |
| | return n - 1 |
| end | | end |
| | table_len = export.length |
|
| |
|
| function export.maxIndex(t) | | local function getIteratorValues(i, j , step, t_len) |
| checkType('maxIndex', 1, t, 'table') | | i, j = i and signed_index(t_len, i), j and signed_index(t_len, j) |
| local positiveIntegerKeys = export.numKeys(t) | | if step == nil then |
| if positiveIntegerKeys[1] then
| | i, j = i or 1, j or t_len |
| return math.max(unpack(positiveIntegerKeys)) | | return i, j, j < i and -1 or 1 |
| else | | elseif step == 0 or not is_integer(step) then |
| return 0 -- ??? | | 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, |
| -- affixNums
| | 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`. |
| -- This takes a table and returns an array containing the numbers of keys with the
| |
| -- specified prefix and suffix.
| |
| -- affixNums({a1 = 'foo', a3 = 'bar', a6 = 'baz'}, "a")
| |
| -- ↓
| |
| -- {1, 3, 6}.
| |
| ------------------------------------------------------------------------------------
| |
| --]]
| |
| function export.affixNums(t, prefix, suffix)
| |
| local check = _check('affixNums')
| |
| check(1, t, 'table')
| |
| check(2, prefix, 'string', true)
| |
| check(3, suffix, 'string', true)
| |
|
| |
| local function cleanPattern(s)
| |
| -- Cleans a pattern so that the magic characters ()%.[]*+-?^$ are interpreted literally.
| |
| s = s:gsub('([%(%)%%%.%[%]%*%+%-%?%^%$])', '%%%1')
| |
| return s
| |
| end
| |
|
| |
| prefix = prefix or ''
| |
| suffix = suffix or ''
| |
| prefix = cleanPattern(prefix)
| |
| suffix = cleanPattern(suffix)
| |
| local pattern = '^' .. prefix .. '([1-9]%d*)' .. suffix .. '$'
| |
|
| |
| local nums = {}
| |
| local index = 1
| |
| for k, _ in pairs(t) do
| |
| if type(k) == 'string' then
| |
| local num = mw.ustring.match(k, pattern)
| |
| if num then
| |
| nums[index] = tonumber(num)
| |
| index = index + 1
| |
| end
| |
| end
| |
| end
| |
| table.sort(nums)
| |
| return nums
| |
| end
| |
|
| |
|
| --[[
| | Optional arguments: |
| ------------------------------------------------------------------------------------
| | * `i`: start index; negative values count from the end of the array |
| -- numData
| | * `j`: end index; negative values count from the end of the array |
| --
| | * `step`: step increment |
| -- Given a table with keys like ("foo1", "bar1", "foo2", "baz2"), returns a table
| | These must be non-zero integers. The function will determine where to iterate from, whether to iterate forwards or |
| -- of subtables in the format
| | backwards and by how much, based on these inputs (see examples below for default behaviours). |
| -- { [1] = {foo = 'text', bar = 'text'}, [2] = {foo = 'text', baz = 'text'} }
| |
| -- Keys that don't end with an integer are stored in a subtable named "other".
| |
| -- The compress option compresses the table so that it can be iterated over with
| |
| -- ipairs.
| |
| ------------------------------------------------------------------------------------
| |
| --]]
| |
| function export.numData(t, compress)
| |
| local check = _check('numData')
| |
| check(1, t, 'table')
| |
| check(2, compress, 'boolean', true)
| |
|
| |
| local ret = {}
| |
| for k, v in pairs(t) do
| |
| local prefix, num = tostring(k):match('^([^0-9]*)([1-9][0-9]*)$')
| |
| if num then
| |
| num = tonumber(num)
| |
| local subtable = ret[num] or {}
| |
| if prefix == '' then
| |
| -- Positional parameters match the blank string; put them at the start of the subtable instead.
| |
| prefix = 1
| |
| end
| |
| subtable[prefix] = v
| |
| ret[num] = subtable
| |
| else
| |
| local subtable = ret.other or {}
| |
| subtable[k] = v
| |
| ret.other = subtable
| |
| end
| |
| end
| |
| if compress then
| |
| local other = ret.other
| |
| ret = export.compressSparseArray(ret)
| |
| ret.other = other
| |
| end
| |
| return ret
| |
| end
| |
|
| |
|
| --[[
| | Examples: |
| ------------------------------------------------------------------------------------
| | # No values for i, j or step results in forward iteration from the start to the end in steps of 1 (the default). |
| -- compressSparseArray
| | # 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). |
| -- This takes an array with one or more nil values, and removes the nil values
| | # j=-3 results in forward iteration from the start to the 3rd last index. |
| -- while preserving the order, so that the array can be safely traversed with | | # j=-3, step=-1 results in backward iteration from the end to the 3rd last index.]==] |
| -- ipairs. | | function export.reduce(t, func, i, j, step) |
| ------------------------------------------------------------------------------------ | | i, j, step = getIteratorValues(i, j, step, table_len(t)) |
| --]] | | local ret = t[i] |
| function export.compressSparseArray(t) | | for k = i + step, j, step do |
| checkType('compressSparseArray', 1, t, 'table') | | ret = func(ret, k, t[k]) |
| local ret = {} | |
| local index = 1
| |
| local nums = export.numKeys(t)
| |
| for _, num in ipairs(nums) do | |
| ret[index] = t[num] | |
| index = index + 1
| |
| end | | end |
| return ret | | return ret |
| end | | end |
|
| |
|
| --[[
| | do |
| ------------------------------------------------------------------------------------
| | local function replace(t, func, i, j, step, generate) |
| -- sparseIpairs
| | local t_len = table_len(t) |
| --
| | -- Normalized i, j and step, based on the inputs. |
| -- This is an iterator for sparse arrays. It can be used like ipairs, but can
| | local norm_i, norm_j, norm_step = getIteratorValues(i, j, step, t_len) |
| -- handle nil values.
| | if norm_step > 0 then |
| ------------------------------------------------------------------------------------
| | i, j, step = 1, t_len, 1 |
| --]]
| |
| function export.sparseIpairs(t) | |
| checkType('sparseIpairs', 1, t, 'table')
| |
| local nums = export.numKeys(t)
| |
| local i = 0
| |
| return function()
| |
| i = i + 1 | |
| local key = nums[i] | |
| if key then | |
| return key, t[key] | |
| else | | else |
| return nil, nil | | i, j, step = t_len, 1, -1 |
| | 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 | | end |
| | return t_new |
| end | | end |
| end
| |
|
| |
|
| --[[ | | --[==[ |
| ------------------------------------------------------------------------------------
| | Given an array `list` and function `func`, iterate through the array applying {func(k, v)} (where `k` is an index, and |
| -- size
| | `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. |
| -- This returns the size of a key/value pair table. It will also work on arrays,
| | |
| -- but for arrays it is more efficient to use the # operator. | | 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 export.size(t) | | * `step`: step increment |
| checkType('size', 1, t, 'table')
| | These must be non-zero integers. The function will determine where to iterate from, whether to iterate forwards or |
| local i = 0
| | backwards and by how much, based on these inputs (see examples below for default behaviours). |
| for _ in pairs(t) do
| | |
| i = i + 1
| | 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.apply(t, func, i, j, step) |
| | return replace(t, func, i, j, step, false) |
| end | | end |
| return i
| |
| end
| |
|
| |
|
| --[[ | | --[==[ |
| -- This returns the length of a table, or the first integer key n counting from
| | Given an array `list` and function `func`, iterate through the array applying {func(k, v)} (where `k` is an index, and |
| -- 1 such that t[n + 1] is nil. It is similar to the operator #, but may return
| | `v` is the value at index `k`), and return a shallow copy of the original array with the relevant values replaced. For example, |
| -- a different value when there are gaps in the array portion of the table.
| | {generate(array, function(_, v) return 2 * v end)} will return a new array in which each value has been doubled. |
| -- Intended to be used on data loaded with mw.loadData. For other tables, use #.
| |
| --]]
| |
| function export.length(t) | |
| local i = 0
| |
| repeat
| |
| i = i + 1
| |
| until t[i] == nil
| |
| return i - 1
| |
| end | |
|
| |
|
| --[[
| | Optional arguments: |
| Recursively compare two values that may be tables, including tables with
| | * `i`: start index; negative values count from the end of the array |
| nested tables as values. Return true if both values are structurally equal.
| | * `j`: end index; negative values count from the end of the array |
| Note that this handles arbitary levels of nesting. If all tables are known
| | * `step`: step increment |
| to be lists (with only integral keys), use export.deepEqualsList, which will
| | These must be non-zero integers. The function will determine where to iterate from, whether to iterate forwards or |
| be more efficient.
| | backwards and by how much, based on these inputs (see examples below for default behaviours). |
|
| |
|
| NOTE: This is *NOT* smart enough to properly handle cycles; in such a case, it
| | Examples: |
| will get into an infinite loop.
| | # 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. |
| function export.deepEquals(x, y)
| | # i=7, j=3 results in backward iteration from indices 7 to 3 in steps of 1 (i.e. step=-1). |
| if type(x) == "table" and type(y) == "table" then | | # j=-3 results in forward iteration from the start to the 3rd last index. |
| -- Two tables are the same if they have the same number of elements
| | # j=-3, step=-1 results in backward iteration from the end to the 3rd last index.]==] |
| -- and all keys that are present in one of the tables compare equal
| | function export.generate(t, func, i, j, step) |
| -- to the corresponding keys in the other table, using structural
| | return replace(t, func, i, j, step, true) |
| -- comparison.
| |
| local sizex = 0
| |
| for key, value in pairs(x) do
| |
| if not export.deepEquals(value, y[key]) then
| |
| return false
| |
| end
| |
| sizex = sizex + 1
| |
| end
| |
| local sizey = export.size(y)
| |
| if sizex ~= sizey then
| |
| return false
| |
| end
| |
| return true | |
| end | | end |
| return x == y
| |
| end | | end |
|
| |
|
| --[[ | | --[==[ |
| Recursively compare two values that may be lists (i.e. tables with integral
| | Given an array `list` and function `func`, iterate through the array applying {func(k, v)} (where `k` is an index, and |
| keys), including lists with nested lists as values. Return true if both values
| | `v` is the value at index `k`), and returning whether the function is true for all iterations. |
| are structurally equal. Note that this handles arbitary levels of nesting.
| |
| Results are undefined if tables with non-integral keys are present anywhere in
| |
| either structure; if that may be the case, use export.deepEquals, which will
| |
| handle such tables correctly but be less efficient on lists than
| |
| export.deepEqualsList.
| |
|
| |
|
| NOTE: This is *NOT* smart enough to properly handle cycles; in such a case, it
| | Optional arguments: |
| will get into an infinite loop.
| | * `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 export.deepEqualsList(x, y) | | * `step`: step increment |
| if type(x) == "table" and type(y) == "table" then | | These must be non-zero integers. The function will determine where to iterate from, whether to iterate forwards or |
| if #x ~= #y then | | 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 | | return false |
| end
| |
| for key, value in ipairs(x) do
| |
| if not export.deepEqualsList(value, y[key]) then
| |
| return false
| |
| end
| |
| end | | end |
| return true
| |
| end | | end |
| return x == y | | return true |
| end | | end |
|
| |
|
| --[[ | | --[==[ |
| Given a list and a value to be found, return true if the value is in the array | | Given an array `list` and function `func`, iterate through the array applying {func(k, v)} (where `k` is an index, and |
| portion of the list. Shallow comparison is used unless `deepCompare` is given
| | `v` is the value at index `k`), and returning whether the function is true for at least one iteration. |
| (in which case comparison is done using `deepEqualsList`).
| | |
| ]]
| | Optional arguments: |
| function export.contains(list, x, deepCompare)
| | * `i`: start index; negative values count from the end of the array |
| checkType('contains', 1, list, 'table')
| | * `j`: end index; negative values count from the end of the array |
| if deepCompare then
| | * `step`: step increment |
| for _, v in ipairs(list) do
| | These must be non-zero integers. The function will determine where to iterate from, whether to iterate forwards or |
| if export.deepEqualsList(v, x) then return true end
| | backwards and by how much, based on these inputs (see examples below for default behaviours). |
| end
| |
| else
| |
| for _, v in ipairs(list) do
| |
| if v == x then return true end
| |
| end
| |
| end
| |
| return false
| |
| end
| |
|
| |
|
| --[[
| | Examples: |
| Given a general table and a value to be found, return true if the value is in
| | # No values for i, j or step results in forward iteration from the start to the end in steps of 1 (the default). |
| either the array or hashmap portion of the table. Shallow comparison is used
| | # step=-1 results in backward iteration from the end to the start in steps of 1. |
| unless `deepCompare` is given (in which case comparison is done using
| | # i=7, j=3 results in backward iteration from indices 7 to 3 in steps of 1 (i.e. step=-1). |
| `deepEquals`).
| | # 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.tableContains(tbl, x, deepCompare) | | function export.any(t, func, i, j, step) |
| checkType('tableContains', 1, tbl, 'table') | | i, j, step = getIteratorValues(i, j, step, table_len(t)) |
| if deepCompare then | | for k = i, j, step do |
| for _, v in pairs(tbl) do
| | if not not (func(k, t[k])) then |
| if export.deepEquals(v, x) then return true end
| | return true |
| end
| |
| else
| |
| for _, v in pairs(tbl) do
| |
| if v == x then return true end | |
| end | | end |
| end | | end |
| Line 492: |
Line 265: |
| end | | end |
|
| |
|
| --[[ | | --[==[ |
| Given a list and a value to be inserted, append or insert the value if not
| | Joins an array with serial comma and serial conjunction, normally {"and"}. An improvement on {mw.text.listToText}, |
| already present in the list. Shallow comparison is used unless `deepCompare`
| | which doesn't properly handle serial commas. |
| is given (in which case comparison is done using `deepEqualsList`). Appends to
| |
| the end, like the default behavior of table.insert(), unless `pos` is given,
| |
| in which case insertion happens at position `pos` (i.e. before the existing
| |
| item at position `pos`).
| |
|
| |
|
| NOTE: The order of `item` and `pos` is reversed in comparison to table.insert(),
| | Options: |
| which uses `table.insert(list, item)` to insert at the end but
| | * `conj`: Conjunction to use; defaults to {"and"}. |
| `table.insert(list, pos, item)` to insert at position POS. | | * `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. |
| -- append to list if element not already present
| | * `dump`: Each item will be serialized with {mw.dumpObject}. For warnings and error messages.]==] |
| function export.insertIfNot(list, item, pos, deepCompare) | | function export.serialCommaJoin(seq, options) |
| if not export.contains(list, item, deepCompare) then | | -- If the `dump` option is set, determine the table length as part of the |
| if pos then | | -- dump loop, instead of calling `table_len` separately. |
| table.insert(list, pos, item) | | local length |
| else
| | if options and options.dump then |
| table.insert(list, item) | | 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 |
| end
| |
|
| |
|
| --[[
| | if length == 0 then |
| Finds key for specified value in a given table. | | return "" |
| Roughly equivalent to reversing the key-value pairs in the table –
| | elseif length == 1 then |
| reversed_table = { [value1] = key1, [value2] = key2, ... }
| | return seq[1] |
| – and then returning reversed_table[valueToFind].
| |
|
| |
| The value can only be a string or a number | |
| (not nil, a boolean, a table, or a function).
| |
|
| |
| Only reliable if there is just one key with the specified value.
| |
| Otherwise, the function returns the first key found,
| |
| and the output is unpredictable.
| |
| ]]
| |
| function export.keyFor(t, valueToFind)
| |
| local check = _check('keyFor')
| |
| check(1, t, 'table')
| |
| check(2, valueToFind, { 'string', 'number' })
| |
|
| |
| for key, value in pairs(t) do
| |
| if value == valueToFind then | |
| return key
| |
| end
| |
| end | | end |
|
| |
| return nil
| |
| end
| |
|
| |
|
| --[[
| | local conj = options and options.conj |
| The default sorting function used in export.keysToList if no keySort | | if conj == nil then |
| is defined.
| | conj = "and" |
| ]]
| |
| local function defaultKeySort(key1, key2) | |
| -- "number" < "string", so numbers will be sorted before strings.
| |
| local type1, type2 = type(key1), type(key2) | |
| if type1 ~= type2 then
| |
| return type1 < type2 | |
| else
| |
| return key1 < key2
| |
| end | | end |
| end
| |
|
| |
|
| --[[
| | if length == 2 then |
| Returns a list of the keys in a table, sorted using either the default
| | return seq[1] .. " " .. conj .. " " .. seq[2] |
| table.sort function or a custom keySort function.
| |
| If there are only numerical keys, numKeys is probably more efficient.
| |
| ]]
| |
| function export.keysToList(t, keySort, checked)
| |
| if not checked then | |
| local check = _check('keysToList')
| |
| check(1, t, 'table')
| |
| check(2, keySort, 'function', true)
| |
| end
| |
|
| |
| local list = {}
| |
| local index = 1
| |
| for key, _ in pairs(t) do
| |
| list[index] = key | |
| index = index + 1
| |
| end
| |
|
| |
| -- Place numbers before strings, otherwise sort using <.
| |
| if not keySort then
| |
| keySort = defaultKeySort
| |
| end
| |
|
| |
| table.sort(list, keySort)
| |
|
| |
| return list
| |
| end
| |
| | |
| --[[
| |
| Iterates through a table, with the keys sorted using the keysToList function.
| |
| If there are only numerical keys, sparseIpairs is probably more efficient.
| |
| ]]
| |
| function export.sortedPairs(t, keySort)
| |
| local check = _check('keysToList')
| |
| check(1, t, 'table')
| |
| check(2, keySort, 'function', true)
| |
|
| |
| local list = export.keysToList(t, keySort, true)
| |
|
| |
| local i = 0
| |
| return function()
| |
| i = i + 1
| |
| local key = list[i]
| |
| if key ~= nil then
| |
| return key, t[key]
| |
| else
| |
| return nil, nil
| |
| end
| |
| end | | end |
| end
| |
|
| |
|
| function export.reverseIpairs(list)
| | local punc, dont_tag |
| checkType('reverse_ipairs', 1, list, 'table') | | if options then |
| | | punc = options.punc |
| local i = #list + 1
| | if punc == nil then |
| return function()
| | punc = "," |
| i = i - 1 | |
| if list[i] ~= nil then | |
| return i, list[i] | |
| else
| |
| return nil, nil
| |
| end | | end |
| | dont_tag = options.dontTag |
| | else |
| | punc = "," |
| end | | end |
| end
| |
|
| |
|
| --[=[
| | local comma |
| Joins an array with serial comma and serial conjunction, normally "and". | | if dont_tag then |
| An improvement on mw.text.listToText, which doesn't properly handle serial | | comma = "" -- since by default the serial comma doesn't display, when we can't tag we shouldn't display it. |
| commas.
| | conj = " " .. conj .. " " |
|
| |
| Options:
| |
| - conj | |
| Conjunction to use; defaults to "and".
| |
| - italicizeConj
| |
| Italicize conjunction: for [[Module:Template:also]]
| |
| - dontTag
| |
| Don't tag the serial comma and serial "and". For error messages, in
| |
| which HTML cannot be used.
| |
| ]=]
| |
| function export.serialCommaJoin(seq, options)
| |
| local check = _check("serialCommaJoin", "table")
| |
| check(1, seq)
| |
| check(2, options, true)
| |
|
| |
| local length = #seq
| |
|
| |
| if not options then
| |
| options = {}
| |
| end
| |
|
| |
| local conj
| |
| if length > 1 then
| |
| conj = options.conj or "and"
| |
| if options.italicizeConj then
| |
| conj = "''" .. conj .. "''"
| |
| end | |
| end
| |
|
| |
| if length == 0 then
| |
| return ""
| |
| elseif length == 1 then
| |
| return seq[1] -- nothing to join
| |
| elseif length == 2 then
| |
| return seq[1] .. " " .. conj .. " " .. seq[2]
| |
| else | | else |
| local comma = options.dontTag and "," or '<span class="serial-comma">,</span>' | | comma = "<span class=\"serial-comma\">" .. punc .. "</span>" |
| conj = options.dontTag and ' ' .. conj .. " " or '<span class="serial-and"> ' .. conj .. '</span> ' | | conj = "<span class=\"serial-and\"> " .. conj .. "</span> " |
| return table.concat(seq, ", ", 1, length - 1) ..
| |
| comma .. conj .. seq[length]
| |
| end | | end |
| end
| |
|
| |
|
| --[[
| | return concat(seq, punc .. " ", 1, length - 1) .. comma .. conj .. seq[length] |
| Concatenates all values in the table that are indexed by a number, in order. | |
| sparseConcat{ a, nil, c, d } => "acd"
| |
| sparseConcat{ nil, b, c, d } => "bcd"
| |
| ]]
| |
| function export.sparseConcat(t, sep, i, j)
| |
| local list = {}
| |
|
| |
| local list_i = 0
| |
| for _, v in export.sparseIpairs(t) do
| |
| list_i = list_i + 1
| |
| list[list_i] = v
| |
| end
| |
|
| |
| return table.concat(list, sep, i, j)
| |
| end | | end |
|
| |
|
| --[[ | | --[==[ |
| Values of numberic keys in array portion of table are reversed:
| | A function which works like `table.concat`, but respects any `__index` metamethod. This is useful for data loaded via `mw.loadData`.]==] |
| { "a", "b", "c" } -> { "c", "b", "a" }
| | function export.concat(t, sep, i, j) |
| --]]
| | local list, k = {}, 0 |
| function export.reverse(t) | | while true do |
| checkType("reverse", 1, t, "table")
| | k = k + 1 |
|
| | local v = t[k] |
| local new_t = {} | | if v == nil then |
| local new_t_i = 1 | | return concat(list, sep, i, j) |
| for i = #t, 1, -1 do
| | end |
| new_t[new_t_i] = t[i] | | list[k] = v |
| new_t_i = new_t_i + 1
| |
| end | | end |
| return new_t
| |
| end | | end |
|
| |
|
| function export.reverseConcat(t, sep, i, j)
| | --[==[ |
| return table.concat(export.reverse(t), sep, i, j)
| | Add a list of aliases for a given key to a table. The aliases must be given as a table.]==] |
| end
| | function export.alias(t, k, aliases) |
| | | for _, alias in pairs(aliases) do |
| -- { "a", "b", "c" } -> { a = 1, b = 2, c = 3 }
| | t[alias] = t[k] |
| function export.invert(array) | |
| checkType("invert", 1, array, "table")
| |
|
| |
| local map = {}
| |
| for i, v in ipairs(array) do | |
| map[v] = i | |
| end | | end |
|
| |
| return map
| |
| end | | end |
|
| |
|
| --[[
| | local mt = {} |
| { "a", "b", "c" } -> { ["a"] = true, ["b"] = true, ["c"] = true }
| |
| --]]
| |
| function export.listToSet(t)
| |
| checkType("listToSet", 1, t, "table")
| |
|
| |
| local set = {}
| |
| for _, item in ipairs(t) do
| |
| set[item] = true
| |
| end
| |
| return set
| |
| end
| |
|
| |
|
| --[[
| | function mt:__index(k) |
| Returns true if all keys in the table are consecutive integers starting at 1.
| | local submodule = safe_require("Module:table/" .. k) |
| --]]
| | self[k] = submodule |
| function export.isArray(t) | | return submodule |
| checkType("isArray", 1, t, "table") | |
| | |
| local i = 0
| |
| for _ in pairs(t) do
| |
| i = i + 1
| |
| if t[i] == nil then
| |
| return false
| |
| end
| |
| end
| |
| return true | |
| end | | end |
|
| |
|
| return export | | return setmetatable(export, mt) |