Module:UtilsTable

local p = {} local h = {}

local lang = mw.getLanguage('en') local utilsFunction = require("Module:UtilsFunction") local inspect = require("Module:UtilsTable/Inspect")

local MAX_INT = 9000 -- arbitrary high number local MIN_INT = -9000

function h.iteratee(iteratee) if type(iteratee) == "function" then return iteratee end if type(iteratee) == "string" or type(iteratee) == "number" then return h.property(iteratee) end if type(iteratee) == "table" then return p.matches(iteratee) end return iteratee end

function h.property(path) return function(tbl) if type(tbl) == "string" or type(tbl) == "number" then return path == tbl and tbl or nil end return p.property(path)(tbl) end end

function p.property(path) return function(tbl) if type(path) ~= "table" then path = { path } end local result = tbl for i, key in ipairs(path) do			result = result[key] if result == nil then return result end end return result end end

function p.print(tbl) return inspect(tbl, {		multiline = function(t)			local size = 0			for key, val in pairs(t) do				if type(val) == "table" then					return true				end				size = size + 1			end			local singleLineLength = #inspect(t, { multiline = false })			return singleLineLength > 40 or size - #tbl > 2		end	}) end

-- Return string keys in tbl function p.props(tbl) local result = {} for k in pairs(tbl) do		if type(k) == "string" then table.insert(result, k)		end end return result end

-- Shallow copy of table with only string keys. function p.record(tbl) local result = {} for k, v in pairs(tbl) do		if type(k) == "string" then result[k] = v		end end return result end

function p.isEmpty(tbl) return p.size(tbl) == 0 end

function p.isEqual(tbl) return function(other) if type(tbl) ~= "table" or type(other) ~= "table" then return tbl == other end return p.matches(tbl)(other) and p.matches(other)(tbl) end end

function p.keyBy(iteratee) iteratee = h.iteratee(iteratee) return function(tbl) local result = {} for i, v in ipairs(tbl) do			result[iteratee(v)] = v		end return result end end

function p.keyOf(tbl, val) for k, v in pairs(tbl) do		if v == val then return k		end end return nil end

function p.keys(tbl) local keyset = {} for k in pairs(tbl) do     keyset[#keyset + 1] = k   end return keyset end

function p.hash(tbl) local hash = {} for k, v in pairs(tbl) do		hash[v] = k	end return hash end

function p.isArray(tbl) if type(tbl) ~= "table" then return false end return #tbl == p.size(tbl) end

function p.mapKeys(iteratee) local mappingFunction = h.iteratee(iteratee) return function(tbl) local result = {} for k, v in pairs(tbl) do			result[k] = mappingFunction(k, v)		end return result end end

function p.matches(sourceTbl) return function(tbl) if sourceTbl == tbl then return true end if type(sourceTbl) ~= type(tbl) then return false end if p.size(sourceTbl) == 0 and p.size(tbl) > 0 then return false end for k, v in pairs(sourceTbl) do			if type(v) ~= "table" and v ~= tbl[k] then return false end if type(v) == "table" and not p.matches(v)(tbl[k]) then return false end end return true end end

function p.mapValues(iteratee) local mappingFunction = h.iteratee(iteratee) return function(tbl) local result = {} for k, v in pairs(tbl) do			result[k] = mappingFunction(v) end return result end end

function p.merge(tbl, ...) return p.mergeWith(utilsFunction.noop)(tbl, ...) end

function p.mergeWith(customizer) return function(tbl, ...) if tbl == nil then return nil end local result for _, source in ipairs({...}) do			result = customizer(tbl, source) if not result and type(source) == "table" then for k, v in pairs(source) do					if type(v) == "table" and type(tbl[k]) == "table" then tbl[k] = p.mergeWith(customizer)({}, tbl[k], v)					else tbl[k] = v					end end else tbl = result end end return tbl end end

function p.path(pathComponents) local path = "" for _, pathComponent in pairs(pathComponents or {}) do		if string.match(pathComponent, "^[A-z_][A-z_0-9]*$") then path = path .. "." .. pathComponent else path = path .. "[" .. p.print(pathComponent) .. "]"		end end return path end

-- table.remove for non-integer key function p.remove(tbl, key) local output = tbl[key] tbl[key] = nil return output end

function p.shallowClone(tbl) -- mostly to be able to use # operator on something from mw.loadData local tbl2 = {} for k, v in pairs(tbl) do		tbl2[k] = v	end return tbl2 end

function p.size(tbl) return #p.keys(tbl) end

function p.toPairs(tbl) local result = {} for k, v in pairs(tbl) do		table.insert(result, {k, v}) end return result end

-- See also ivalues function p.values(tbl) local valueset = {} for _, v in pairs(tbl) do     table.insert(valueset, v)   end return valueset end

-- See also p.values function p.ivalues(tbl) local valueset = {} for i=1, table.maxn(tbl) do  	  if tbl[i] then table.insert(valueset, tbl[i]) end end return valueset end

--	"Array" functions (using the `ipairs` iterator, mostly) -- function p.alphabetize(tbl) local hash = {} local nocase = {} for i, v in ipairs(tbl) do		nocase[i] = lang:caseFold(v) hash[nocase[i]] = v	end table.sort(nocase) for i, v in ipairs(nocase) do		tbl[i] = hash[v] end return tbl end

function p.append(tbl, val) tbl[p.lastIndex(tbl)+1] = val end

function p.concat(array, ...) local result = mw.clone(array) for i, arrayOrValue in ipairs({...}) do		local last = p.lastIndex(result) if type(arrayOrValue) ~= "table" then p.append(result, arrayOrValue) else for i, value in ipairs(arrayOrValue) do				p.append(result, value) end end end return result end

function p.crop(tbl, max) for k, _ in ipairs(tbl) do		if k > max then tbl[k] = nil end end end

function p.difference(tbl) return function(other) local result = {} for i, v in ipairs(tbl) do			if not p.find(v)(other) then table.insert(result, v)			end end return result end end

function p.filter(iteratee) return function (tbl) local predicateFn = h.iteratee(iteratee) local results = {} for _, val in ipairs(tbl) do			if predicateFn(val) then table.insert(results, val) end end return results end end

function p.find(iteratee) iteratee = h.iteratee(iteratee) return function(tbl) for i, v in ipairs(tbl) do			if iteratee(v) then return i, v			end end return nil, nil end end

function p.findLast(iteratee) iteratee = h.iteratee(iteratee) return function(tbl) local i, v = p.find(iteratee)(p.reverse(tbl)) if not i then return nil, nil end return #tbl - i + 1, v	end end

function p.flatten(tbl) local result = {} for _, v in ipairs(tbl) do		if p.isArray(v) then result = p.mergeArrays(result, v)		else table.insert(result, v)		end end return result end

function p.flattenDeep(tbl) local result = {} for _, v in ipairs(tbl) do		if p.isArray(v) then result = p.mergeArrays(result, p.flattenDeep(v)) else table.insert(result, v)		end end return result end

function p.flatMap(mappingFunction) return function(tbl) return p.flatten(p.map(mappingFunction)(tbl)) end end

function p.groupBy(key) return function (tbl) local result = {} for _, v in ipairs(tbl) do			local groupingKey = v[key] or "" local group = result[groupingKey] or {} table.insert(group, v)			result[groupingKey] = group end return result end end

function p.intersection(tbl) return function(other) local result = {} for i, v in ipairs(tbl) do			if p.find(v)(other) then table.insert(result, v)			end end return result end end

-- Return last integer key of {tbl}, or 0 if there are none -- Useful for spare arrays function p.lastIndex(tbl) local keys = p.keys(tbl) local isInteger = function(key) return type(key) == "number" and math.floor(key) == key end local indexes = p.filter(isInteger)(keys) return math.max(0, unpack(indexes)) end

function p.map(iteratee) return function (tbl) local mappingFunction = iteratee if type(iteratee) == "string" then mappingFunction = function(val) return val[iteratee] end end local tbl2 = {} for k, v in ipairs(tbl) do			tbl2[k] = mappingFunction(v) end return tbl2 end end

function p.mapMultiple(iteratee) mappingFunction = h.iteratee(iteratee) return function(tbl) local res = {} for k, v in ipairs(tbl) do			res[k] = {mappingFunction(v)} end return res end end

function p.max(tbl) tbl = p.padNils(MIN_INT)(tbl) return math.max(unpack(tbl)) end

-- @param Returns a new array grouping together the values of the arrays given. Order is preserved. -- Deprecated. Use concat function p.mergeArrays(...) local merged = {} for _, tbl in ipairs(arg) do		for _, v in ipairs(tbl) do			merged[#merged+1] = v		end end return merged end

-- prints the table as a comma-separated list with and function p.printList(tbl) if #tbl == 1 then return tbl[1] elseif #tbl == 2 then return table.concat(tbl, ' and ') else last = table.remove(tbl, #tbl) list = table.concat(tbl, ', ') return list .. ', and ' .. (last or '') end end

-- Returns the list with duplicate values removed. function p.removeDuplicates(list) local hash = {} local result = {}

for key, value in pairs(list) do		if (not hash[value]) then result[#result+1] = value hash[value] = true end end return result end

function p.removeFalseEntries(tbl, max) if not max then max = #tbl end local j = 0 for i = 1, max do		if tbl[i] then j = j + 1 tbl[j] = tbl[i] end end for i = j+1, max do		tbl[i] = nil end return tbl end

function p.compactNils(tbl) local result = {} local last = p.lastIndex(tbl) local j = 1 for i = 1, last do		if tbl[i] then result[j] = tbl[i] j = j + 1 end end return result end

function p.padNils(padValue, max) return function(tbl) padValue = padValue or '' max = max or p.lastIndex(tbl) local result = {} for i = 1, max do			if tbl[i] == nil then result[i] = padValue else result[i] = tbl[i] end end return result end end

-- returns a copy of tbl with the elements in opposite order (not a deep copy) function p.reverse(tbl) local tbl2 = {} local len = #tbl for i = len, 1, -1 do		tbl2[len - i + 1] = tbl[i] end return tbl2 end

function p.slice(s, e)	return function(tbl) local tbl2 = {} e = e or p.lastIndex(tbl) for k = s, e do			tbl2[#tbl2+1] = tbl[k] end return tbl2 end end

-- sorts tblToSort to be in the same order as the elements appear in lookup function p.sortByKeyOrder(tblToSort, values) local lookup = p.hash(values) table.sort(tblToSort, function (a,b)			return (lookup[a] or 0) < (lookup[b] or 0)		end	) return end

function p.sortUnique(tbl) table.sort(tbl) local tbl2 = {} local i = 0 for k, v in ipairs(tbl) do		if v ~= tbl2[i] then i = i + 1 tbl2[i] = v		end end return tbl2 end

function p.takeWhile(property, tbl) local results = {} local i = 1 while tbl[i] and tbl[i][property] do		table.insert(results, tbl[i]) i = i + 1 end return results end

function p.tail(tbl) local result = {} for i = 2, #tbl do		table.insert(result, tbl[i]) end return result end

function p.dropWhile(iteratee) iteratee = h.iteratee(iteratee) return function(tbl) local result = {} local i = 1 while tbl[i] and iteratee(tbl[i], i) do			i = i + 1 end return p.slice(i)(tbl) end end

function p.dropRightWhile(iteratee) iteratee = h.iteratee(iteratee) return function(tbl) local result = {} local i = #tbl while i > 0 and iteratee(tbl[i], i) do			i = i - 1 end return p.slice(1, i)(tbl) end end

function p.unique(tbl) local result = {} for _, v in ipairs(tbl) do		if not p.find(p.isEqual(v))(result) then table.insert(result, v)		end end return result end

function p.uniqueBy(iteratee) iteratee = h.iteratee(iteratee) return function(tbl) local result = {} for _, v in ipairs(tbl) do			local match = p.find(function(val)				return iteratee(val) == iteratee(v)			end)(result) if not match then table.insert(result, v)			end end return result end end

-- Based on https://github.com/lua-stdlib/functional -- See https://lua-stdlib.github.io/lua-stdlib/modules/std.functional.html#zip function p.zip(tbl, emptyValue) local result = {} for outerk, innerTbl in ipairs(tbl) do		innerTbl = p.padNils("NIL")(innerTbl) -- to properly handle sparse arrays for k, v in ipairs(innerTbl) do			result[k] = result[k] or {} if v ~= "NIL" then result[k][outerk] = v			end end if emptyValue then for k in ipairs(result) do				result[k] = p.padNils(emptyValue, #tbl)(result[k]) end end end return result end

return p