https://linguifex.com/w/index.php?title=Module:table&feed=atom&action=historyModule:table - Revision history2024-03-28T09:19:24ZRevision history for this page on the wikiMediaWiki 1.39.1https://linguifex.com/w/index.php?title=Module:table&diff=302590&oldid=prevSware at 14:50, 3 May 20232023-05-03T14:50:34Z<p></p>
<a href="https://linguifex.com/w/index.php?title=Module:table&diff=302590&oldid=214143">Show changes</a>Swarehttps://linguifex.com/w/index.php?title=Module:table&diff=214143&oldid=prevSware at 21:30, 23 December 20202020-12-23T21:30:46Z<p></p>
<a href="https://linguifex.com/w/index.php?title=Module:table&diff=214143&oldid=171721">Show changes</a>Swarehttps://linguifex.com/w/index.php?title=Module:table&diff=171721&oldid=prevSware: Created page with "--[[ ------------------------------------------------------------------------------------ -- table (formerly TableTools) --..."2019-11-09T15:21:47Z<p>Created page with "--[[ ------------------------------------------------------------------------------------ -- table (formerly TableTools) --..."</p>
<p><b>New page</b></p><div>--[[<br />
------------------------------------------------------------------------------------<br />
-- table (formerly TableTools) --<br />
-- --<br />
-- This module inclcudes a number of functions for dealing with Lua tables. --<br />
-- It is a meta-module, meant to be called from other Lua modules, and should --<br />
-- not be called directly from #invoke. --<br />
------------------------------------------------------------------------------------<br />
--]]<br />
<br />
--[[<br />
Inserting new values into a table using a local "index" variable, which is<br />
incremented each time, is faster than using "table.insert(t, x)" or<br />
"t[#t + 1] = x". See the talk page.<br />
]]<br />
<br />
local libraryUtil = require('libraryUtil')<br />
<br />
local export = {}<br />
<br />
-- Define often-used variables and functions.<br />
local floor = math.floor<br />
local infinity = math.huge<br />
local checkType = libraryUtil.checkType<br />
local checkTypeMulti = libraryUtil.checkTypeMulti<br />
<br />
local function _check(funcName, expectType)<br />
if type(expectType) == "string" then<br />
return function(argIndex, arg, nilOk)<br />
checkType(funcName, argIndex, arg, expectType, nilOk)<br />
end<br />
else<br />
return function(argIndex, arg, expectType, nilOk)<br />
if type(expectType) == "table" then<br />
checkTypeMulti(funcName, argIndex, arg, expectType, nilOk)<br />
else<br />
checkType(funcName, argIndex, arg, expectType, nilOk)<br />
end<br />
end<br />
end<br />
end<br />
<br />
--[[<br />
------------------------------------------------------------------------------------<br />
-- isPositiveInteger<br />
--<br />
-- This function returns true if the given value is a positive integer, and false<br />
-- if not. Although it doesn't operate on tables, it is included here as it is<br />
-- useful for determining whether a given table key is in the array part or the<br />
-- hash part of a table.<br />
------------------------------------------------------------------------------------<br />
--]]<br />
function export.isPositiveInteger(v)<br />
return type(v) == 'number' and v >= 1 and floor(v) == v and v < infinity<br />
end<br />
<br />
--[[<br />
------------------------------------------------------------------------------------<br />
-- isNan<br />
--<br />
-- This function returns true if the given number is a NaN value, and false<br />
-- if not. Although it doesn't operate on tables, it is included here as it is<br />
-- useful for determining whether a value can be a valid table key. Lua will<br />
-- generate an error if a NaN is used as a table key.<br />
------------------------------------------------------------------------------------<br />
--]]<br />
function export.isNan(v)<br />
if type(v) == 'number' and tostring(v) == '-nan' then<br />
return true<br />
else<br />
return false<br />
end<br />
end<br />
<br />
--[[<br />
------------------------------------------------------------------------------------<br />
-- shallowClone<br />
--<br />
-- This returns a clone of a table. The value returned is a new table, but all<br />
-- subtables and functions are shared. Metamethods are respected, but the returned<br />
-- table will have no metatable of its own.<br />
------------------------------------------------------------------------------------<br />
--]]<br />
function export.shallowClone(t)<br />
local ret = {}<br />
for k, v in pairs(t) do<br />
ret[k] = v<br />
end<br />
return ret<br />
end<br />
<br />
--[[<br />
Shallow copy<br />
]]<br />
function export.shallowcopy(orig)<br />
local orig_type = type(orig)<br />
local copy<br />
if orig_type == 'table' then<br />
copy = {}<br />
for orig_key, orig_value in pairs(orig) do<br />
copy[orig_key] = orig_value<br />
end<br />
else -- number, string, boolean, etc<br />
copy = orig<br />
end<br />
return copy<br />
end<br />
<br />
--[[<br />
Recursive deep copy function<br />
Equivalent to mw.clone?<br />
]]<br />
local function deepcopy(orig, includeMetatable, already_seen)<br />
-- Stores copies of tables indexed by the original table.<br />
already_seen = already_seen or {}<br />
<br />
local copy = already_seen[orig]<br />
if copy ~= nil then<br />
return copy<br />
end<br />
<br />
if type(orig) == 'table' then<br />
copy = {}<br />
for orig_key, orig_value in pairs(orig) do<br />
copy[deepcopy(orig_key, includeMetatable, already_seen)] = deepcopy(orig_value, includeMetatable, already_seen)<br />
end<br />
already_seen[orig] = copy<br />
<br />
if includeMetatable then<br />
local mt = getmetatable(orig)<br />
if mt ~= nil then<br />
local mt_copy = deepcopy(mt, includeMetatable, already_seen)<br />
setmetatable(copy, mt_copy)<br />
end<br />
end<br />
else -- number, string, boolean, etc<br />
copy = orig<br />
end<br />
return copy<br />
end<br />
<br />
function export.deepcopy(orig, noMetatable, already_seen)<br />
checkType("deepcopy", 3, already_seen, "table", true)<br />
<br />
return deepcopy(orig, not noMetatable, already_seen)<br />
end<br />
<br />
--[[<br />
------------------------------------------------------------------------------------<br />
-- removeDuplicates<br />
--<br />
-- This removes duplicate values from an array. Non-positive-integer keys are<br />
-- ignored. The earliest value is kept, and all subsequent duplicate values are<br />
-- removed, but otherwise the array order is unchanged.<br />
------------------------------------------------------------------------------------<br />
--]]<br />
function export.removeDuplicates(t)<br />
checkType('removeDuplicates', 1, t, 'table')<br />
local isNan = export.isNan<br />
local ret, exists = {}, {}<br />
local index = 1<br />
for _, v in ipairs(t) do<br />
if isNan(v) then<br />
-- NaNs can't be table keys, and they are also unique, so we don't need to check existence.<br />
ret[index] = v<br />
index = index + 1<br />
else<br />
if not exists[v] then<br />
ret[index] = v<br />
index = index + 1<br />
exists[v] = true<br />
end<br />
end<br />
end<br />
return ret<br />
end<br />
<br />
--[[<br />
------------------------------------------------------------------------------------<br />
-- numKeys<br />
--<br />
-- This takes a table and returns an array containing the numbers of any numerical<br />
-- keys that have non-nil values, sorted in numerical order.<br />
------------------------------------------------------------------------------------<br />
--]]<br />
function export.numKeys(t, checked)<br />
if not checked then<br />
checkType('numKeys', 1, t, 'table')<br />
end<br />
local isPositiveInteger = export.isPositiveInteger<br />
local nums = {}<br />
local index = 1<br />
for k, _ in pairs(t) do<br />
if isPositiveInteger(k) then<br />
nums[index] = k<br />
index = index + 1<br />
end<br />
end<br />
table.sort(nums)<br />
return nums<br />
end<br />
<br />
function export.maxIndex(t)<br />
checkType('maxIndex', 1, t, 'table')<br />
local positiveIntegerKeys = export.numKeys(t)<br />
if positiveIntegerKeys[1] then<br />
return math.max(unpack(positiveIntegerKeys))<br />
else<br />
return 0 -- ???<br />
end<br />
end<br />
<br />
--[[<br />
------------------------------------------------------------------------------------<br />
-- affixNums<br />
--<br />
-- This takes a table and returns an array containing the numbers of keys with the<br />
-- specified prefix and suffix.<br />
-- affixNums({a1 = 'foo', a3 = 'bar', a6 = 'baz'}, "a")<br />
-- ↓<br />
-- {1, 3, 6}.<br />
------------------------------------------------------------------------------------<br />
--]]<br />
function export.affixNums(t, prefix, suffix)<br />
local check = _check('affixNums')<br />
check(1, t, 'table')<br />
check(2, prefix, 'string', true)<br />
check(3, suffix, 'string', true)<br />
<br />
local function cleanPattern(s)<br />
-- Cleans a pattern so that the magic characters ()%.[]*+-?^$ are interpreted literally.<br />
s = s:gsub('([%(%)%%%.%[%]%*%+%-%?%^%$])', '%%%1')<br />
return s<br />
end<br />
<br />
prefix = prefix or ''<br />
suffix = suffix or ''<br />
prefix = cleanPattern(prefix)<br />
suffix = cleanPattern(suffix)<br />
local pattern = '^' .. prefix .. '([1-9]%d*)' .. suffix .. '$'<br />
<br />
local nums = {}<br />
local index = 1<br />
for k, _ in pairs(t) do<br />
if type(k) == 'string' then<br />
local num = mw.ustring.match(k, pattern)<br />
if num then<br />
nums[index] = tonumber(num)<br />
index = index + 1<br />
end<br />
end<br />
end<br />
table.sort(nums)<br />
return nums<br />
end<br />
<br />
--[[<br />
------------------------------------------------------------------------------------<br />
-- numData<br />
--<br />
-- Given a table with keys like ("foo1", "bar1", "foo2", "baz2"), returns a table<br />
-- of subtables in the format<br />
-- { [1] = {foo = 'text', bar = 'text'}, [2] = {foo = 'text', baz = 'text'} }<br />
-- Keys that don't end with an integer are stored in a subtable named "other".<br />
-- The compress option compresses the table so that it can be iterated over with<br />
-- ipairs.<br />
------------------------------------------------------------------------------------<br />
--]]<br />
function export.numData(t, compress)<br />
local check = _check('numData')<br />
check(1, t, 'table')<br />
check(2, compress, 'boolean', true)<br />
<br />
local ret = {}<br />
for k, v in pairs(t) do<br />
local prefix, num = tostring(k):match('^([^0-9]*)([1-9][0-9]*)$')<br />
if num then<br />
num = tonumber(num)<br />
local subtable = ret[num] or {}<br />
if prefix == '' then<br />
-- Positional parameters match the blank string; put them at the start of the subtable instead.<br />
prefix = 1<br />
end<br />
subtable[prefix] = v<br />
ret[num] = subtable<br />
else<br />
local subtable = ret.other or {}<br />
subtable[k] = v<br />
ret.other = subtable<br />
end<br />
end<br />
if compress then<br />
local other = ret.other<br />
ret = export.compressSparseArray(ret)<br />
ret.other = other<br />
end<br />
return ret<br />
end<br />
<br />
--[[<br />
------------------------------------------------------------------------------------<br />
-- compressSparseArray<br />
--<br />
-- This takes an array with one or more nil values, and removes the nil values<br />
-- while preserving the order, so that the array can be safely traversed with<br />
-- ipairs.<br />
------------------------------------------------------------------------------------<br />
--]]<br />
function export.compressSparseArray(t)<br />
checkType('compressSparseArray', 1, t, 'table')<br />
local ret = {}<br />
local index = 1<br />
local nums = export.numKeys(t)<br />
for _, num in ipairs(nums) do<br />
ret[index] = t[num]<br />
index = index + 1<br />
end<br />
return ret<br />
end<br />
<br />
--[[<br />
------------------------------------------------------------------------------------<br />
-- sparseIpairs<br />
--<br />
-- This is an iterator for sparse arrays. It can be used like ipairs, but can<br />
-- handle nil values.<br />
------------------------------------------------------------------------------------<br />
--]]<br />
function export.sparseIpairs(t)<br />
checkType('sparseIpairs', 1, t, 'table')<br />
local nums = export.numKeys(t)<br />
local i = 0<br />
return function()<br />
i = i + 1<br />
local key = nums[i]<br />
if key then<br />
return key, t[key]<br />
else<br />
return nil, nil<br />
end<br />
end<br />
end<br />
<br />
--[[<br />
------------------------------------------------------------------------------------<br />
-- size<br />
--<br />
-- This returns the size of a key/value pair table. It will also work on arrays,<br />
-- but for arrays it is more efficient to use the # operator.<br />
------------------------------------------------------------------------------------<br />
--]]<br />
function export.size(t)<br />
checkType('size', 1, t, 'table')<br />
local i = 0<br />
for _ in pairs(t) do<br />
i = i + 1<br />
end<br />
return i<br />
end<br />
<br />
--[[<br />
-- This returns the length of a table, or the first integer key n counting from<br />
-- 1 such that t[n + 1] is nil. It is similar to the operator #, but may return<br />
-- a different value when there are gaps in the array portion of the table.<br />
-- Intended to be used on data loaded with mw.loadData. For other tables, use #.<br />
--]]<br />
function export.length(t)<br />
local i = 0<br />
repeat<br />
i = i + 1<br />
until t[i] == nil<br />
return i - 1<br />
end<br />
<br />
--[[<br />
Takes table and a value to be found.<br />
If the value is in the array portion of the table, return true.<br />
If the value is in the hashmap or not in the table, return false.<br />
]]<br />
function export.contains(list, x)<br />
for _, v in ipairs(list) do<br />
if v == x then return true end<br />
end<br />
return false<br />
end<br />
<br />
--[[<br />
Recursively compare two values that may be tables, including tables with<br />
nested tables as values. Return true if both values are structurally equal.<br />
Note that this handles arbitary levels of nesting. If all tables are known<br />
to be lists (with only integral keys), use export.deepEqualsList, which will<br />
be more efficient.<br />
<br />
NOTE: This is *NOT* smart enough to properly handle cycles; in such a case, it<br />
will get into an infinite loop.<br />
]]<br />
function export.deepEquals(x, y)<br />
if type(x) == "table" and type(y) == "table" then<br />
-- Two tables are the same if they have the same number of elements<br />
-- and all keys that are present in one of the tables compare equal<br />
-- to the corresponding keys in the other table, using structural<br />
-- comparison.<br />
local sizex = 0<br />
for key, value in pairs(x) do<br />
if not export.deepEquals(value, y[key]) then<br />
return false<br />
end<br />
sizex = sizex + 1<br />
end<br />
local sizey = export.size(y)<br />
if sizex ~= sizey then<br />
return false<br />
end <br />
return true<br />
end<br />
return x == y<br />
end<br />
<br />
--[[<br />
Recursively compare two values that may be lists (i.e. tables with integral<br />
keys), including lists with nested lists as values. Return true if both values<br />
are structurally equal. Note that this handles arbitary levels of nesting.<br />
Results are undefined if tables with non-integral keys are present anywhere in<br />
either structure; if that may be the case, use export.deepEquals, which will<br />
handle such tables correctly but be less efficient on lists than<br />
export.deepEqualsList.<br />
<br />
NOTE: This is *NOT* smart enough to properly handle cycles; in such a case, it<br />
will get into an infinite loop.<br />
]]<br />
function export.deepEqualsList(x, y)<br />
if type(x) == "table" and type(y) == "table" then<br />
if #x ~= #y then<br />
return false<br />
end <br />
for key, value in ipairs(x) do<br />
if not export.deepEqualsList(value, y[key]) then<br />
return false<br />
end<br />
end<br />
return true<br />
end<br />
return x == y<br />
end<br />
<br />
--[[<br />
Finds key for specified value in a given table.<br />
Roughly equivalent to reversing the key-value pairs in the table –<br />
reversed_table = { [value1] = key1, [value2] = key2, ... }<br />
– and then returning reversed_table[valueToFind].<br />
<br />
The value can only be a string or a number<br />
(not nil, a boolean, a table, or a function).<br />
<br />
Only reliable if there is just one key with the specified value.<br />
Otherwise, the function returns the first key found,<br />
and the output is unpredictable.<br />
]]<br />
function export.keyFor(t, valueToFind)<br />
local check = _check('keyFor')<br />
check(1, t, 'table')<br />
check(2, valueToFind, { 'string', 'number' })<br />
<br />
for key, value in pairs(t) do<br />
if value == valueToFind then<br />
return key<br />
end<br />
end<br />
<br />
return nil<br />
end<br />
<br />
--[[<br />
The default sorting function used in export.keysToList if no keySort<br />
is defined.<br />
]]<br />
local function defaultKeySort(key1, key2)<br />
-- "number" < "string", so numbers will be sorted before strings.<br />
local type1, type2 = type(key1), type(key2)<br />
if type1 ~= type2 then<br />
return type1 < type2<br />
else<br />
return key1 < key2<br />
end<br />
end<br />
<br />
--[[<br />
Returns a list of the keys in a table, sorted using either the default<br />
table.sort function or a custom keySort function.<br />
If there are only numerical keys, numKeys is probably more efficient.<br />
]]<br />
function export.keysToList(t, keySort, checked)<br />
if not checked then<br />
local check = _check('keysToList')<br />
check(1, t, 'table')<br />
check(2, keySort, 'function', true)<br />
end<br />
<br />
local list = {}<br />
local index = 1<br />
for key, _ in pairs(t) do<br />
list[index] = key<br />
index = index + 1<br />
end<br />
<br />
-- Place numbers before strings, otherwise sort using <.<br />
if not keySort then<br />
keySort = defaultKeySort<br />
end<br />
<br />
table.sort(list, keySort)<br />
<br />
return list<br />
end<br />
<br />
--[[<br />
Iterates through a table, with the keys sorted using the keysToList function.<br />
If there are only numerical keys, sparseIpairs is probably more efficient.<br />
]]<br />
function export.sortedPairs(t, keySort)<br />
local check = _check('keysToList')<br />
check(1, t, 'table')<br />
check(2, keySort, 'function', true)<br />
<br />
local list = export.keysToList(t, keySort, true)<br />
<br />
local i = 0<br />
return function()<br />
i = i + 1<br />
local key = list[i]<br />
if key ~= nil then<br />
return key, t[key]<br />
else<br />
return nil, nil<br />
end<br />
end<br />
end<br />
<br />
function export.reverseIpairs(list)<br />
checkType('reverse_ipairs', 1, list, 'table')<br />
<br />
local i = #list + 1<br />
return function()<br />
i = i - 1<br />
if list[i] ~= nil then<br />
return i, list[i]<br />
else<br />
return nil, nil<br />
end<br />
end<br />
end<br />
<br />
--[=[<br />
Joins an array with serial comma and serial conjunction, normally "and".<br />
An improvement on mw.text.listToText, which doesn't properly handle serial<br />
commas.<br />
<br />
Options:<br />
- conj<br />
Conjunction to use; defaults to "and".<br />
- italicizeConj<br />
Italicize conjunction: for [[Module:Template:also]]<br />
- dontTag<br />
Don't tag the serial comma and serial "and". For error messages, in<br />
which HTML cannot be used.<br />
]=]<br />
function export.serialCommaJoin(seq, options)<br />
local check = _check("serialCommaJoin", "table")<br />
check(1, seq)<br />
check(2, options, true)<br />
<br />
local length = #seq<br />
<br />
if not options then<br />
options = {}<br />
end<br />
<br />
local conj<br />
if length > 1 then<br />
conj = options.conj or "and"<br />
if options.italicizeConj then<br />
conj = "''" .. conj .. "''"<br />
end<br />
end<br />
<br />
if length == 0 then<br />
return ""<br />
elseif length == 1 then<br />
return seq[1] -- nothing to join<br />
elseif length == 2 then<br />
return seq[1] .. " " .. conj .. " " .. seq[2]<br />
else<br />
local comma = options.dontTag and "," or '<span class="serial-comma">,</span>'<br />
conj = options.dontTag and ' ' .. conj .. " " or '<span class="serial-and"> ' .. conj .. '</span> '<br />
return table.concat(seq, ", ", 1, length - 1) ..<br />
comma .. conj .. seq[length]<br />
end<br />
end<br />
<br />
--[[<br />
Concatenates all values in the table that are indexed by a number, in order.<br />
sparseConcat{ a, nil, c, d } => "acd"<br />
sparseConcat{ nil, b, c, d } => "bcd"<br />
]]<br />
function export.sparseConcat(t, sep, i, j)<br />
local list = {}<br />
<br />
local list_i = 0<br />
for _, v in export.sparseIpairs(t) do<br />
list_i = list_i + 1<br />
list[list_i] = v<br />
end<br />
<br />
return table.concat(list, sep, i, j)<br />
end<br />
<br />
--[[<br />
Values of numberic keys in array portion of table are reversed:<br />
{ "a", "b", "c" } -> { "c", "b", "a" }<br />
--]]<br />
function export.reverse(t)<br />
checkType("reverse", 1, t, "table")<br />
<br />
local new_t = {}<br />
local new_t_i = 1<br />
for i = #t, 1, -1 do<br />
new_t[new_t_i] = t[i]<br />
new_t_i = new_t_i + 1<br />
end<br />
return new_t<br />
end<br />
<br />
function export.reverseConcat(t, sep, i, j)<br />
return table.concat(export.reverse(t), sep, i, j)<br />
end<br />
<br />
-- { "a", "b", "c" } -> { a = 1, b = 2, c = 3 }<br />
function export.invert(array)<br />
checkType("invert", 1, array, "table")<br />
<br />
local map = {}<br />
for i, v in ipairs(array) do<br />
map[v] = i<br />
end<br />
<br />
return map<br />
end<br />
<br />
--[[<br />
{ "a", "b", "c" } -> { ["a"] = true, ["b"] = true, ["c"] = true }<br />
--]]<br />
function export.listToSet(t)<br />
checkType("listToSet", 1, t, "table")<br />
<br />
local set = {}<br />
for _, item in ipairs(t) do<br />
set[item] = true<br />
end<br />
return set<br />
end<br />
<br />
--[[<br />
Returns true if all keys in the table are consecutive integers starting at 1.<br />
--]]<br />
function export.isArray(t)<br />
checkType("isArray", 1, t, "table")<br />
<br />
local i = 0<br />
for _ in pairs(t) do<br />
i = i + 1<br />
if t[i] == nil then<br />
return false<br />
end<br />
end<br />
return true<br />
end<br />
<br />
return export</div>Sware