Difference between revisions of "Module:TableTools"
add isNan function, shallowClone function and removeDuplicates function, fix up valueIntersection function to work properly for NaNs
>Mr. Stradivarius (clone tn rather than returning an altered tn) |
>Mr. Stradivarius (add isNan function, shallowClone function and removeDuplicates function, fix up valueIntersection function to work properly for NaNs) |
||
Line 25: | Line 25: | ||
-- isPositiveInteger | -- isPositiveInteger | ||
-- | -- | ||
-- This function returns true if the given | -- 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 | -- 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 | -- useful for determining whether a given table key is in the array part or the | ||
Line 31: | Line 31: | ||
------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ||
--]] | --]] | ||
function p.isPositiveInteger( | function p.isPositiveInteger(v) | ||
if type( | if type(v) == 'number' and v >= 1 and floor(v) == v and v < infinity then | ||
return true | return true | ||
else | else | ||
return false | return false | ||
end | 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) | |||
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) | |||
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 | end | ||
Line 167: | Line 228: | ||
function p.valueIntersection(...) | function p.valueIntersection(...) | ||
local lim = select('#', ...) | local lim = select('#', ...) | ||
if lim | if lim < 2 then | ||
error(" | error(lim .. ' argument' .. (lim == 1 and '' or 's') .. " passed to 'intersection' (minimum is 2)", 2) | ||
end | end | ||
local isNan = p.isNan | |||
local vals, ret = {}, {} | local vals, ret = {}, {} | ||
local isSameTable = true -- Tracks table equality. | |||
local tableTemp -- Used to store the table from the previous loop so that we can check table equality. | |||
for i = 1, lim do | for i = 1, lim do | ||
local t = select(i, ...) | local t = select(i, ...) | ||
checkType('valueIntersection', i, t, 'table') | checkType('valueIntersection', i, t, 'table') | ||
if tableTemp and t ~= tableTemp then | |||
isSameTable = false | |||
end | |||
tableTemp = t | |||
for k, v in pairs(t) do | for k, v in pairs(t) do | ||
if | -- NaNs are never equal to any other value, so they can't be in the intersection. | ||
v = | -- Which is lucky, as they also can't be table keys. | ||
if not isNan(v) then | |||
local valCount = vals[v] or 0 | |||
vals[v] = valCount + 1 | |||
end | end | ||
end | end | ||
end | |||
if isSameTable then | |||
-- If all the tables are equal, then the intersection is that table (including NaNs). | |||
-- All we need to do is convert it to an array and remove duplicate values. | |||
for k, v in pairs(tableTemp) do | |||
ret[#ret + 1] = v | |||
end | |||
return p.removeDuplicates(ret) | |||
end | end | ||
for val, count in pairs(vals) do | for val, count in pairs(vals) do | ||
if count == lim then | if count == lim then | ||
ret[#ret + 1] = val | ret[#ret + 1] = val | ||
end | end |