Module:TableTools

Revision as of 02:43, 16 December 2013 by >Mr. Stradivarius (use a hack to handle NaN values)

Documentation for this module may be created at Module:TableTools/doc

--[[
------------------------------------------------------------------------------------
--                               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.                                           --
------------------------------------------------------------------------------------
--]]

local p = {}

-- Define often-used variables and functions.
local floor = math.floor
local infinity = math.huge

-- Define a unique value to represent NaN. This is because NaN cannot be used as a table key.
local nan = {}

--[[
------------------------------------------------------------------------------------
-- isPositiveInteger
--
-- This function returns true if the given number 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(num)
	if type(num) == 'number' and num >= 1 and floor(num) == num and num < infinity then
		return true
	else
		return false
	end
end

--[[
------------------------------------------------------------------------------------
-- union
--
-- This returns the union of the values of n tables, as an array. For example, for
-- the tables {1, 3, 4, 5, foo = 7} and {2, bar = 3, 5, 6}, union will return
-- {1, 2, 3, 4, 5, 6, 7}.
------------------------------------------------------------------------------------
--]]
function p.union(...)
	local tables = {...}
	local vals, ret = {}, {}
	for _, t in ipairs(tables) do
		for k, v in pairs(t) do
			if type(v) == 'number' and tostring(v) == '-nan' then
				v = nan -- NaN cannot be a table key, so use a proxy variable.
			end
			vals[v] = true
		end
	end
	for val in pairs(vals) do
		if val == nan then
			-- This ensures that we output a NaN when we had one as input, although
			-- they may have been generated in a completely different way.
			val = 0/0 
		end
		ret[#ret + 1] = val
	end
	return ret
end	

--[[
------------------------------------------------------------------------------------
-- intersection
--
-- This returns the intersection of the values of n tables, as an array. For
-- example, for the tables {1, 3, 4, 5, foo = 7} and {2, bar = 3, 5, 6}, 
-- intersection will return {3, 5}.
------------------------------------------------------------------------------------
--]]
function p.intersection(...)
	local tables = {...}
	local vals, ret = {}, {}
	local lim = #tables
	for _, t in ipairs(tables) do
		for k, v in pairs(t) do
			if type(v) == 'number' and tostring(v) == '-nan' then
				v = nan -- NaN cannot be a table key, so use a proxy variable.
			end
			local valCount = vals[v] or 0
			vals[v] = valCount + 1
		end
	end
	for val, count in pairs(vals) do
		if count == lim then
			if val == nan then
				-- This ensures that we output a NaN when we had one as input, although
				-- they may have been generated in a completely different way.
				val = 0/0 
			end
			ret[#ret + 1] = val
		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)
	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)
	prefix = prefix or ''
	suffix = suffix or ''
	local nums = {}
	for k, v in pairs(t) do
		if type(k) == 'string' then			
			local num = mw.ustring.match(k, '^' .. prefix .. '([1-9]%d*)' .. suffix .. '$')
			if num then
				nums[#nums + 1] = tonumber(num)
			end
		end
	end
	table.sort(nums)
	return nums
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)
	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)
	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]
		end
	end
end

return p