dev

This module was made as a sandbox for the TableTools module. This documentation is kept to prevent redlinks.


-- <nowiki>
--------------------------------------------------------------------------------
-- This module includes a number of functions for dealing with Lua tables.
--
-- @see [[Wikipedia:Module:TableTools]] for a similar module.
--------------------------------------------------------------------------------
local p = {}
 
-- Define often-used variables and functions.
local floor = math.floor
local infinity = math.huge

local function makeMsg(name, index, msg)
    return string.format('bad argument %s to %q (%s)',
            type(index) == 'string' and '\"'..index..'\"' or '#'..index,
            name,
            msg
        )
end

local function assertTrue(cond, name, index, msg, ...)
    if cond then
        -- Second mode
        if type(index) == 'string' then
            error(name..': '..string.format(index, msg, ...), 3)
        else 
            error(makeMsg(name, index, string.format(msg, ...)), 3)
        end
    end
end

local function checkType(name, argIdx, arg, expectTypes, nilOk)
    local function makeArgNumber(val)
        return table.concat{ type(val) == 'number' and '#' or '\'', val, type(val) ~= "number" and '\'' or '' }
    end

	local argType = type(arg)
    
    local t = {}
    t.checkType = error
    
    if type(expectTypes) == "string" then 
        expectTypes = { expectTypes } 
    end
    
    if nilOk and arg == nil then 
        return
    end
    
    for _, v in ipairs(expectTypes) do
        if argType == v:lower() then
            return
        end
    end
    
    local n = #expectTypes
    local typeList
    
    if n > 1 then
        typeList = table.concat(expectTypes, '/')
    else
        typeList = expectTypes[1]
    end
    
    local msg = string.format("bad argument %s to '%s' (%s expected, got %s)",
        makeArgNumber(argIdx),
        name,
        typeList,
        type(arg)
   )
 
   t.checkType(msg, 3)
end
--------------------------------------------------------------------------------
-- Returns a new table with all parameters stored into keys 1, 2, etc. and with
-- a field `n` with the total number of parameters. Note that the resulting
-- table may not be a sequence.
--
-- @see <http://www.lua.org/manual/5.2/manual.html#pdf-table.pack>
--------------------------------------------------------------------------------
function p.pack(...)
    return {n = select('#', ...), ...}
end

--------------------------------------------------------------------------------
-- Returns the first `n` arguments in `...`. If `n` is negative, arguments are
-- counted from the end of the table.
--
-- @see [[Lua reference manual/Standard libraries#select]]
-- @see <http://lua-users.org/wiki/VarargTheSecondClassCitizen>
--------------------------------------------------------------------------------
function p.selectFirst(n, ...)
    checkType('Dev:TableTools.selectFirst', 1, n, 'number')

    local function err()
        error(makeMsg('Dev:TableTools.selectFirst', 1, 'index out of range'), 3)
    end

    local function recurse(index, next, ...)
        if index == 0 then
            return
        end

        return next, recurse(index - 1, ...)
    end

    n = math.modf(n)

    local count = select("#", ...)

    if -count > n then
        err()
    elseif -1 > n and n >= -count then
        return recurse(count + 1 + n, ...)
    elseif n == -1 then
        return ...
    elseif n == 0 then
        err()
    elseif n == 1 then
        return (...)
    elseif 1 < n and n <= count then
        return recurse(n, ...)
    elseif count < n then
        return ...
    end
end

--------------------------------------------------------------------------------
-- Returns `true` if a given table is a sequence.
--
-- @see <http://stackoverflow.com/a/6080274>
--------------------------------------------------------------------------------
function p.isSequence(t)
    checkType('Dev:TableTools.isSequence', 1, t, 'table')

    local i = 1

    for _ in pairs(t) do
        if t[i] == nil then
            return false
        end

        i = i + 1
    end

    return true
end

--------------------------------------------------------------------------------
-- Returns the number of elements in a table, even if it is not a sequence.
--
-- @see <http://stackoverflow.com/a/2705804>
--------------------------------------------------------------------------------
function p.size(t, countNamed)
    checkType('Dev:TableTools.size', 1, t, 'table')
    checkType('Dev:TableTools.size', 2, countNamed, 'boolean', true)

	local i = 0
	for k in pairs(t) do
		if not countedNamed and countedNamed ~= nil then
			if type(k) == "number" then
				i = i + 1
			end
	    else
	        i = i + 1
		end
	end
	
	return i
end

---------------------------------------------------------------------------------
-- function: setPrototype(t: table, proto: table, indexFunc: function)
--
-- Sets up a metatable prototype with a table.
---------------------------------------------------------------------------------
function p.setPrototype(t, proto, indexFunc, parentProto)
    local fName = 'Dev:TableTools.setPrototype'
	checkType(fName, 1, t, 'table')
	checkType(fName, 2, proto, 'table')
	checkType(fName, 3, indexFunc, 'function', true)
	checkType(fName, 4, parentProto, 'table', true)
	
	local mt = getmetatable(t) or {}
	
	assertTrue(mt.__index, fName, 1, 'cannot overwrite existing `__index` metafield on a table with an existing metatable')
	
	mt.__proto = proto or {}
	parentProto = parentProto or {}
	
	if p.size(parentProto) ~= 0 then
		mt.__proto.prototype = parentProto
	end
	
	indexFunc = indexFunc or function(t, k, proto, parentProto)
		if k == 'prototype' then
			return mt.__proto
		else
			return mt.__proto[k] or (p.size(parentProto or {}) ~= 0 and mt.__proto.prototype[k] or nil)
		end
	end
	
	mt.__index = function(t, k)
		return indexFunc(t, k, mt.__proto, mt.__proto.prototype)
	end
	
	return setmetatable(t, mt)
end

---------------------------------------------------------------------------------
-- function: sequenceToSet(t: table)
-- 
-- Creates a shallow copy of `t`. This means any subtables and functions will be shared.
-- Use `deepCopy()` for a deep copy function.
---------------------------------------------------------------------------------
function p.sequenceToSet(t)
	checkType('Dev:TableTools.sequenceToSet', 1, t, 'table')
	customArgError(not p.isSequence(t), 'Dev:TableTools.sequenceToSet', 1, 'table is not a sequence')
	
	local ret = {}
	p.each(t, function(v)
		ret[v] = true
	end)
	return ret
end

--------------------------------------------------------------------------------
-- Returns `true` if a given table contains a certain element.
--
-- @see <http://stackoverflow.com/q/2282444>
--------------------------------------------------------------------------------
function p.includes(t, v)
	checkType('Dev:TableTools.includes', 1, t, 'table')
	assertTrue(not p.isSequence(t), 'Dev:TableTools.includes', 1, 'provided table is not a sequence')
	
	return p.some(t, 
		function(i, v) 
			return t[i] == v 
		end
   )
end

--------------------------------------------------------------------------------
-- Merges the content of the second table with the content in the first one.
--
-- @see <http://wiki.garrysmod.com/page/table/Merge>
--------------------------------------------------------------------------------
function p.merge(dest, source)
    checkType('Dev:TableTools.merge', 1, dest, 'table')
    checkType('Dev:TableTools.merge', 2, source, 'table')

    for k, v in pairs(source) do
        if type(v) == 'table' and type(dest[k]) == 'table' then
            -- Don't overwrite one table with another; instead merge them
            -- recurisvely.
            p.merge(dest[k], v)
        else
            dest[k] = v
        end
    end

    return dest
end

---------------------------------------------------------------------------------
-- function: unshift(t: table, ...items: any)
-- 
-- Prepends each of `...items` to the front of `t`.
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table>
---------------------------------------------------------------------------------
function p.unshift(t, ...)
	local items = p.pack(...)
	local fName = 'Dev:TableTools.unshift'
	
	checkType(fName, 1, t, 'table')
	assertTrue(not p.isSequence(t), fName, 1, 'provided table is not a sequence')
	
	for _, v in ipairs(items) do
		table.insert(t, 1, v)
	end
	return t
end

---------------------------------------------------------------------------------
-- function: push(t: table, ...items: any)
--
-- Appends each of `...items` to the end of `t`.
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table>
---------------------------------------------------------------------------------
function p.push(t, ...)
	local items = p.pack(...)
	local fName = 'Dev:TableTools.push'
	
	checkType('push', 1, t, 'table')
    assertTrue(not p.isSequence(t), 'push', 1, 'provided table is not a sequence')
	
	for _, v in ipairs(items) do
		table.insert(t, v)
	end
	
	return t
end

---------------------------------------------------------------------------------
-- function: map(t: table, callbackfn: function(v: any, i?: number, t?: table))
--
-- Creates a new table and populates with results from the callback function.
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table>
--
---------------[ CALLBACK PARAMETERS ]-------------
-- The callback to `map` has three values passed to it described above.
-- `v`, the value of the table index, `i`, the table index, and `t`, the actual table.
-- `map` will popluate the new table with the return values of this function.
---------------------------------------------------------------------------------
function p.map(t, callbackfn)
    local fName = 'Dev:TableTools.map'
	checkType(fName, 1, t, 'table')
	assertTrue(not p.isSequence(t), fName, 1, 'provided table is not a sequence')
	checkType(fName, 2, callbackfn, 'function')
	
	local ret = {}
	
	for i, v in ipairs(t) do
	    p.push(ret, callbackfn(i, v, t))
	end
	
	return ret
end

---------------------------------------------------------------------------------
-- function: filter(t: table, callbackfn: function(v: any, i?: number, t?: table))
--
-- Creates a new table with all elements that pass the test implemented by the provided function.
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table>
--
---------------[ CALLBACK PARAMETERS ]-------------
-- callbackfn(v: any, i?: number, t?: table)
--
-- The callback to `filter` has three values passed to it described above.
-- `v`, the value of the table index, `i`, the table index, and `t`, the actual table.
-- `filter` will popluate the new table with the value of the old table if the returned value
-- from the callback is true.
---------------------------------------------------------------------------------
function p.filter(t, callbackfn)
    local fName = 'Dev:TableTools.filter'
	checkType(fName, 1, t, 'table')
	assertTrue(not p.isSequence(t), fName, 1, 'provided table is not a sequence')
	checkType(fName, 2, callbackfn, 'function')
	
	local ret = {}
	
	for i, v in ipairs(t) do
		if callbackfn(v, i, t) then
			p.push(ret, v)
		end
	end
	
	return ret
end

---------------------------------------------------------------------------------
-- function: removeLast(t: table)
--
-- Removes the last element of the table and returns it.
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table>
---------------------------------------------------------------------------------
function p.removeLast(t)
    local fName = 'Dev:TableTools.removeLast'
	checkType(fName, 1, t, 'table')
	assertTrue(not p.isSequence(t), fName, 1, 'provided table is not a sequence')

	return table.remove(t, p.size(t))
end

---------------------------------------------------------------------------------
-- function: removeFirst(t: table)
--
-- Removes the First element of the table and returns it.
--
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table>
---------------------------------------------------------------------------------
function p.removeFirst(t)
    local fName = 'Dev:TableTools.removeFirst'
	checkType(fName, 1, t, 'table')
	assertTrue(not p.isSequence(t), fName, 1, 'provided table is not a sequence')

	return table.remove(t, 1)
end

---------------------------------------------------------------------------------
-- function: fill(t?: table, v: any, startIndex: number, endIndex?: number)
--
-- Sets the table index starting at `startIndex` with the value of `v` ending at `endIndex`.
-- If end is not specified, it defualts to the length of the table.
--
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table>
---------------------------------------------------------------------------------
function p.fill(t, v, startIndex, endIndex)
    local fName = 'Dev:TableTools.fill'
    local t = t or {}
    
	checkType(fName, 1, t, 'table', true)
	assertTrue(not p.isSequence(t), fName, 1, 'provided table is not a sequence')
	checkType(fName, 3, startIndex, 'number')
	checkType(fName, 4, endIndex, 'number', true)

	for i = startIndex, endIndex or #t, 1 do
		t[i] = v
	end
	
	return t
end

---------------------------------------------------------------------------------
-- function: indexOf(t: table, v: any)
--
-- Searches for `v` the first index in `t`. If nothing is found, it returns `-1`.
--
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table>
---------------------------------------------------------------------------------
function p.indexOf(t, value)
	checkType('Dev:TableTools.indexOf', 1, t, 'table')
	
	local index
	
	p.some(t, function(k, v) 
		if v == value then
			index = k
			return true 
		else
			return false
		end
	end)
	
	return index or -1
end

---------------------------------------------------------------------------------
-- function: lastIndexOf(t: table, v: any)
--
-- Searches for `v` the first index in `t`. If nothing is found, it returns `-1`.
--
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table>
---------------------------------------------------------------------------------
function p.lastIndexOf(t, v)
	checkType('Dev:TableTools.lastIndexOf', 1, t, 'table')
	assertTrue(not p.isSequence(t), 'Dev:TableTools.lastIndexOf', 1, 'provided table is not a sequence')
	
	for i, value in p.reverseIpairs(t) do
		if v == value then return i end 
	end
	return -1
end

---------------------------------------------------------------------------------
-- function: every(t: table, callbackfn: function(k: any, v?: any, t?: table, i?: number) => check)
-- 
-- Tests every element from the return value from `callbackfn`. If any elements fail
-- the test, it returns false.
--
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table>
-- 
---------------[ CALLBACK PARAMETERS ]-------------
-- callbackfn(k: any, v?: any, t?: table, i?: number)
--
-- If the callback returns false, `every()` will consider the test failed and
-- return false.
--   *`k` is the table key `every()` is currently over.
--   *`v` is the value of the table key `every()` is currently over.
--   *`t` is the table `every()` was called on.
--   *`i` is the number of iterations `every()` has iterated over.
---------------------------------------------------------------------------------
function p.every(t, callbackfn)
	checkType('Dev:TableTools.every', 1, t, 'table')
	checkType('Dev:TableTools.every', 2, callbackfn, 'function')
	
	local i = 0
	for k, v in pairs(t) do
		i = i + 1
		if not callbackfn(k, v, t, i) then
			return false
		end
	end
	
	return true
end


---------------------------------------------------------------------------------
-- function: some(t: table, callbackfn: function(v: any, i: number, t: table) => check)
--
-- Tests whether at least one element in the table passes the test implemented 
-- by the provided function. It returns a Boolean value.
--
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table>
----------------------------------------------------------------------------------
function p.some(t, callbackfn)
	checkType('Dev:TableTools.some', 1, t, 'table')
	checkType('Dev:TableTools.some', 2, callbackfn, 'function')
	
	local i = 0
	for k, v in pairs(t) do
		i = i + 1
		if callbackfn(k, v, t, i) then
			return true
		end
	end
	return false
end

---------------------------------------------------------------------------------
-- function: reduce(
--	 t: table, 
--	 callbackfn: function(accumlator: any, curVal: any, i?: number, t?: table) => accumlator, 
--	 initVal: any
--)
-- 
-- Executes a reducer callback function on each element of the table, resulting in single output value.
--
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table>
--
---------------[ CALLBACK PARAMETERS ]-------------
-- callbackfn(accumlator: any, curVal: any, i?: number, t?: table)
--
-- The callback to `every` has four values passed to it described above.
-- `accumlator` is the accumulated value previously returned in the last invocation 
-- of the callback value of to accumalate.
-- `curVal` is the current element being processed in the table.
-- `i` is the current index of the processes element.
-- `t` is the table `reduce()` was called on.
---------------------------------------------------------------------------------
function p.reduce(t, callbackfn, initVal)
    local fName = 'Dev:TableTools.reduce'
    
	checkType(fName, 1, t, 'table')
	assertTrue(not p.isSequence(t), fName, 1, 'provided table is not a sequence')
	checkType(fName, 2, callbackfn, 'function')
	assertTrue(callbackfn('', '', '', '') == nil, fName, 1, 'no return value for callback')
	
	local accumulator
	for i, v in pairs(t) do
		if i == 1 then
			accumulator = initVal and callbackfn(initVal, v, i, t) or v
		elseif i ~= 1 then
			accumulator = callbackfn(accumulator, v, i, t)
		end
	end
	
	return accumulator
end


---------------------------------------------------------------------------------
-- function: reduceRight(
--	 t: table, 
--	 callbackfn: function(accumlator: any, curVal: any, i?: number, t?: table), 
--	 initVal: any
--)
-- 
-- Executes a reducer callback function on each element of the table from left to right,
-- resulting in single output value. Very similar to `reduce()`.
--
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table>
--
---------------[ CALLBACK PARAMETERS ]-------------
-- callbackfn(accumlator: any, curVal: any, i?: number, t?: table)
--
-- The callback to `every` has four values passed to it described above.
-- `accumlator` is the accumulated value previously returned in the last invocation 
-- of the callback value of to accumalate.
-- `curVal` is the current element being processed in the table.
-- `i` is the current index of the processes element.
-- `t` is the table `reduceRight()` was called on.
---------------------------------------------------------------------------------
function p.reduceRight(t, callbackfn, initVal)
    local fName = 'Dev:TableTools.reduceRight'
    
	checkType(fName, 1, t, 'table')
	assertTrue(not p.isSequence(t), fName, 1, 'provided table is not a sequence')
	checkType(fName, 2, callbackfn, 'function')
	assertTrue(callbackfn('', '', '', '') == nil, fName, 2, 'no return value for callback')
	
	local accumulator
	for i, v, start in p.reverseIpairs(t) do
		if i == start then
			accumulator = initVal and callbackfn(initVal, v, i, t) or v
		elseif i ~= start then
			accumulator = callbackfn(accumulator, v, i, t)
		end
	end
	return accumulator
end

---------------------------------------------------------------------------------
-- function: reverseIpairs(t: table)
-- 
-- Returns a iterator function to iterate backwards over a sequence table.
-- This works like `ipairs()` except it works backwards, and it provides an additional
-- value in the iteration, `start`. The `start` value is the index the function
-- started iterating at.
--
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table>
--
-----------------[ EXAMPLE ]----------------
-- for i, v, start in TableTools.reverseIpairs(t) do
--	 -- code block
-- end
---------------------------------------------------------------------------------
function p.reverseIpairs(t)
	checkType('Dev:TableTools.reverseIpairs', 1, t, 'table')
	assertTrue(not p.isSequence(t), 'Dev:TableTools.reverseIpairs', 1, 'provided table is not a sequence')
	
	return function(a, i)
		checkType('?', 2, i, 'number')
		checkType('?', 1, a, 'table')
		
		i = i - 1
		local v = a[i]
		if v then
			return i, v, p.size(t)
		end
	end, t, p.size(t)+1
end

---------------------------------------------------------------------------------
-- function: reverse(t: table)
-- 
-- Reverses the table in place. The first array element becomes the last, and 
-- the last array element becomes the first.
--
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table
---------------------------------------------------------------------------------
function p.reverse(t)
	checkType('Dev:TableTools.reverse', 1, t, 'table')
	assertTrue(not p.isSequence(t), 'Dev:TableTools.reverse', 1, 'provided table is not a sequence')
	
	local tmp = {}
	
	for _, v in p.reverseIpairs(t) do
		p.push(tmp, v)
	end
	p.empty(t)
	
	for i, v in ipairs(tmp) do
		t[i] = v
	end
	
	return t
end

---------------------------------------------------------------------------------
-- function: empty(t: table)
-- 
-- Empties the table of all keys.
--
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table
---------------------------------------------------------------------------------
function p.empty(t)
	checkType('Dev:TableTools.empty', 1, t, 'table')

	for k, v in pairs(t) do
		t[k] = nil
	end
		
	return t
end

---------------------------------------------------------------------------------
-- function: tail(t: table)
-- 
-- Moves the first key from the table to the end of it.
--
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table
---------------------------------------------------------------------------------
function p.tail(t)
	checkType('Dev:TableTools.tail', 1, t, 'table')

	p.push(t, p.removeFirst(t))
		
	return t
end

---------------------------------------------------------------------------------
-- function: untail(t: table)
-- 
-- Moves the last key from the table to the front of it.
--
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table
---------------------------------------------------------------------------------
function p.untail(t)
	checkType('Dev:TableTools.untail', 1, t, 'table')

	p.unshift(t, p.removeLast(t))
		
	return t
end


------------------------------------------------------------------------------------
-- 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 p.isPositiveInteger(v)
	if type(v) == 'number' and v >= 1 and floor(v) == v and v < infinity then
		return true
	else
		return false
	end
end


------------------------------------------------------------------------------------
-- isNan
--
-- This function returns true if the given number is a NaN value, and false
-- if not. Although it doesn't operate on tables, it is included here as it is
-- useful for determining whether a value can be a valid table key. Lua will
-- generate an error if a NaN is used as a table key.
------------------------------------------------------------------------------------
function p.isNan(v)
	if type(v) == 'number' and tostring(v) == '-nan' then
		return true
	else
		return false
	end
end


------------------------------------------------------------------------------------
-- shallowClone
--
-- This returns a clone of a table. The value returned is a new table, but all
-- subtables and functions are shared. Metamethods are respected, but the returned
-- table will have no metatable of its own.
------------------------------------------------------------------------------------
function p.shallowClone(t)
    checkType('Dev:TableTools.shallowClone', 1, t, 'table')
	local ret = {}
	for k, v in pairs(t) do
		ret[k] = v
	end
	return ret
end


------------------------------------------------------------------------------------
-- 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 p.removeDuplicates(t)
	checkType('removeDuplicates', 1, t, 'table')
	local isNan = p.isNan
	local ret, exists = {}, {}
	for i, 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[#ret + 1] = v
		else
			if not exists[v] then
				ret[#ret + 1] = v
				exists[v] = true
			end
		end	
	end
	return ret
end			


------------------------------------------------------------------------------------
-- numKeys
--
-- This takes a table and returns an array containing the numbers of any numerical
-- keys that have non-nil values, sorted in numerical order.
------------------------------------------------------------------------------------
function p.numKeys(t)
	checkType('numKeys', 1, t, 'table')
	local isPositiveInteger = p.isPositiveInteger
	local nums = {}
	for k, v in pairs(t) do
		if isPositiveInteger(k) then
			nums[#nums + 1] = k
		end
	end
	table.sort(nums)
	return nums
end


------------------------------------------------------------------------------------
-- affixNums
--
-- This takes a table and returns an array containing the numbers of keys with the
-- specified prefix and suffix. For example, for the table
-- {a1 = 'foo', a3 = 'bar', a6 = 'baz'} and the prefix "a", affixNums will
-- return {1, 3, 6}.
------------------------------------------------------------------------------------
function p.affixNums(t, prefix, suffix)
	checkType('affixNums', 1, t, 'table')
	checkType('affixNums', 2, prefix, 'string', true)
	checkType('affixNums', 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 = {}
	for k, v in pairs(t) do
		if type(k) == 'string' then			
			local num = mw.ustring.match(k, pattern)
			if num then
				nums[#nums + 1] = tonumber(num)
			end
		end
	end
	table.sort(nums)
	return nums
end


------------------------------------------------------------------------------------
-- numData
--
-- Given a table with keys like ("foo1", "bar1", "foo2", "baz2"), returns a table
-- of subtables in the format 
-- { [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 p.numData(t, compress)
	checkType('numData', 1, t, 'table')
	checkType('numData', 2, compress, 'boolean', true)
	local ret = {}
	for k, v in pairs(t) do
		local prefix, num = mw.ustring.match(tostring(k), '^([^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 = p.compressSparseArray(ret)
		ret.other = other
	end
	return ret
end


------------------------------------------------------------------------------------
-- compressSparseArray
--
-- This takes an array with one or more nil values, and removes the nil values
-- while preserving the order, so that the array can be safely traversed with
-- ipairs.
------------------------------------------------------------------------------------
function p.compressSparseArray(t)
	checkType('compressSparseArray', 1, t, 'table')
	local ret = {}
	local nums = p.numKeys(t)
	for _, num in ipairs(nums) do
		ret[#ret + 1] = t[num]
	end
	return ret
end


------------------------------------------------------------------------------------
-- sparseIpairs
--
-- This is an iterator for sparse arrays. It can be used like ipairs, but can
-- handle nil values.
------------------------------------------------------------------------------------
function p.sparseIpairs(t)
	checkType('sparseIpairs', 1, t, 'table')
	local nums = p.numKeys(t)
	local i = 0
	local lim = #nums
	return function ()
		i = i + 1
		if i <= lim then
			local key = nums[i]
			return key, t[key]
		else
			return nil, nil
		end
	end
end

---------------------------------------------------------------------------------
-- function: each(t: table, callbackfn: function(k: any, v?: any, t?: table, i?: number)
-- 
-- Executes a provided function once for each array element.
---------------------------------------------------------------------------------
function p.each(t, callbackfn)
	checkType('Dev:TableTools.each', 1, t, 'table')
	checkType('Dev:TableTools.each', 2, callbackfn, "function")
	
	local i = 0
	
	for k, v in p.sortedPairs(t) do
		callbackfn(v, k, t, i)
		i = i + 1
	end
	return t
end

---------------------------------------------------------------------------------
-- function: format(t: table)
-- 
-- Takes the table and takes each value as an argument and puts it through `string.format()`.
---------------------------------------------------------------------------------
function p.format(t)
	checkType('Dev:TableTools.format', 1, t, { 'table', 'string' }, true)
	if type(t) == 'string' or t == '' or t == nil then return t end
	
	return string.format(unpack(t))
end

---------------------------------------------------------------------------------
-- function: tableUtil(t: table, mt?: table)
-- 
-- Takes all functions from above and any native functions relating to tables and 
-- makes them methods on the returned table
--
-- @see <https://hypixel-skyblock.fandom.com/wiki/Module:Table
---------------------------------------------------------------------------------
function p.tableUtil(t, mt)
    checkType('Dev:TableTools.tableUtil', 1, t, 'table', true)
    checkType('Dev:TableTools.tableUtil', 2, mt, 'table', true)
    
    t, mt = t or {}, mt or {}
    
    origMt = getmetatable(t)
    
    p.merge(mt, origMt or {})
    indexField = mt.__index
    mt.__index = nil
    setmetatable(t, mt)

    tmp = p.merge(p, table)
    local others = {
        rawget = rawget,
        rawset = rawset,
        ipairs = ipairs,
        pairs = pairs,
        next = next,
        tonumber = tonumber,
        tostring = tostring,
        type = type,
        print = mw.log,
        log = mw.log,
        getmetatable = getmetatable,
    }
    p.merge(tmp, others)
    
    return p.setPrototype(t, tmp, indexField)
end

return p

-- </nowiki>
-- (Add categories here.)