https://wso.williams.edu/wiki/index.php?title=Module:TableTools&feed=atom&action=historyModule:TableTools - Revision history2024-03-28T20:52:13ZRevision history for this page on the wikiMediaWiki 1.32.1https://wso.williams.edu/wiki/index.php?title=Module:TableTools&diff=23091&oldid=prevAl15: 1 revision imported2019-05-06T00:16:06Z<p>1 revision imported</p>
<table class="diff diff-contentalign-left" data-mw="interface">
<tr class="diff-title" lang="en">
<td colspan="1" style="background-color: #fff; color: #222; text-align: center;">← Older revision</td>
<td colspan="1" style="background-color: #fff; color: #222; text-align: center;">Revision as of 00:16, May 6, 2019</td>
</tr><tr><td colspan="2" class="diff-notice" lang="en"><div class="mw-diff-empty">(No difference)</div>
</td></tr></table>Al15https://wso.williams.edu/wiki/index.php?title=Module:TableTools&diff=23090&oldid=prevWikipedia>Alex 21: Implementing talk page edit requested2019-03-12T13:06:11Z<p>Implementing talk page edit requested</p>
<p><b>New page</b></p><div>--[[<br />
------------------------------------------------------------------------------------<br />
-- TableTools --<br />
-- --<br />
-- This module includes 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 />
local libraryUtil = require('libraryUtil')<br />
<br />
local p = {}<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 />
--[[<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 p.isPositiveInteger(v)<br />
if type(v) == 'number' and v >= 1 and floor(v) == v and v < infinity then<br />
return true<br />
else<br />
return false<br />
end<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 p.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 p.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 />
------------------------------------------------------------------------------------<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 p.removeDuplicates(t)<br />
checkType('removeDuplicates', 1, t, 'table')<br />
local isNan = p.isNan<br />
local ret, exists = {}, {}<br />
for i, 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[#ret + 1] = v<br />
else<br />
if not exists[v] then<br />
ret[#ret + 1] = v<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 p.numKeys(t)<br />
checkType('numKeys', 1, t, 'table')<br />
local isPositiveInteger = p.isPositiveInteger<br />
local nums = {}<br />
for k, v in pairs(t) do<br />
if isPositiveInteger(k) then<br />
nums[#nums + 1] = k<br />
end<br />
end<br />
table.sort(nums)<br />
return nums<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. For example, for the table<br />
-- {a1 = 'foo', a3 = 'bar', a6 = 'baz'} and the prefix "a", affixNums will<br />
-- return {1, 3, 6}.<br />
------------------------------------------------------------------------------------<br />
--]]<br />
function p.affixNums(t, prefix, suffix)<br />
checkType('affixNums', 1, t, 'table')<br />
checkType('affixNums', 2, prefix, 'string', true)<br />
checkType('affixNums', 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 />
for k, v in pairs(t) do<br />
if type(k) == 'string' then <br />
local num = mw.ustring.match(k, pattern)<br />
if num then<br />
nums[#nums + 1] = tonumber(num)<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 p.numData(t, compress)<br />
checkType('numData', 1, t, 'table')<br />
checkType('numData', 2, compress, 'boolean', true)<br />
local ret = {}<br />
for k, v in pairs(t) do<br />
local prefix, num = mw.ustring.match(tostring(k), '^([^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 = p.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 p.compressSparseArray(t)<br />
checkType('compressSparseArray', 1, t, 'table')<br />
local ret = {}<br />
local nums = p.numKeys(t)<br />
for _, num in ipairs(nums) do<br />
ret[#ret + 1] = t[num]<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 p.sparseIpairs(t)<br />
checkType('sparseIpairs', 1, t, 'table')<br />
local nums = p.numKeys(t)<br />
local i = 0<br />
local lim = #nums<br />
return function ()<br />
i = i + 1<br />
if i <= lim then<br />
local key = nums[i]<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 />
<br />
function p.size(t)<br />
checkType('size', 1, t, 'table')<br />
local i = 0<br />
for k in pairs(t) do<br />
i = i + 1<br />
end<br />
return i<br />
end<br />
<br />
<br />
local function defaultKeySort(item1, item2)<br />
-- "number" < "string", so numbers will be sorted before strings.<br />
local type1, type2 = type(item1), type(item2)<br />
if type1 ~= type2 then<br />
return type1 < type2<br />
else -- This will fail with table, boolean, function.<br />
return item1 < item2<br />
end<br />
end<br />
<br />
--[[<br />
Returns a list of the keys in a table, sorted using either a default<br />
comparison function or a custom keySort function.<br />
]]<br />
function p.keysToList(t, keySort, checked)<br />
if not checked then<br />
checkType('keysToList', 1, t, 'table')<br />
checkTypeMulti('keysToList', 2, keySort, { 'function', 'boolean', 'nil' })<br />
end<br />
<br />
local list = {}<br />
local index = 1<br />
for key, value in pairs(t) do<br />
list[index] = key<br />
index = index + 1<br />
end<br />
<br />
if keySort ~= false then<br />
keySort = type(keySort) == 'function' and keySort or defaultKeySort<br />
<br />
table.sort(list, keySort)<br />
end<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 p.sortedPairs(t, keySort)<br />
checkType('sortedPairs', 1, t, 'table')<br />
checkType('sortedPairs', 2, keySort, 'function', true)<br />
<br />
local list = p.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 />
--[[<br />
Returns true if all keys in the table are consecutive integers starting at 1.<br />
--]]<br />
function p.isArray(t)<br />
checkType("isArray", 1, t, "table")<br />
<br />
local i = 0<br />
for k, v 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 />
-- { "a", "b", "c" } -> { a = 1, b = 2, c = 3 }<br />
function p.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 p.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 />
<br />
return set<br />
end<br />
<br />
--[[<br />
Recursive deep copy function.<br />
Preserves identities of subtables.<br />
<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 />
already_seen[mt] = 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 p.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 />
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 p.sparseConcat(t, sep, i, j)<br />
local list = {}<br />
<br />
local list_i = 0<br />
for _, v in p.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 />
-- 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 />
-- Note: #frame.args in frame object always be set to 0, regardless of <br />
-- the number of unnamed template parameters, so use this function for<br />
-- frame.args.<br />
--]]<br />
function p.length(t)<br />
local i = 1<br />
while t[i] ~= nil do<br />
i = i + 1<br />
end<br />
return i - 1<br />
end<br />
<br />
function p.inArray(arr, valueToFind)<br />
checkType("inArray", 1, arr, "table")<br />
<br />
-- if valueToFind is nil, error?<br />
<br />
for _, v in ipairs(arr) do<br />
if v == valueToFind then<br />
return true<br />
end<br />
end<br />
<br />
return false<br />
end<br />
<br />
return p</div>Wikipedia>Alex 21