Module:TableUtil

local lang = mw.getLanguage('en')

local p = {}

function p.generalLength(tbl) local n = 0 for _, _ in pairs(tbl) do		n = n + 1 end return n end

function p.getKeys(tbl, f)	local ret = {} for k, _ in pairs(tbl) do		ret[#ret+1] = k	end if f then table.sort(ret, f) end return ret 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.hash(tbl) if not tbl then return {} end local hash = {} for k, v in pairs(tbl) do		hash[v] = k	end return hash end

function p.arrayHash(tbl) if not tbl then return {} end local hash = {} for k, v in ipairs(tbl) do		hash[v] = k	end return hash end

function p.appendHash(parent, tbl) for k, v in pairs(tbl) do		parent[v] = k	end return parent 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.mergeArrays(tbl1, ...) -- tbl1 is modified to include the elements of tbl2 appended to the end. Order is preserved. if not tbl1 then tbl1 = {} end local newTables = {...} for _, tbl2 in ipairs(newTables) do		for _, v in ipairs(tbl2) do			tbl1[#tbl1+1] = v		end end return tbl1 end

function p.uniqueKeyMergeArrays(key, tbl1, ...) -- tbl1 is modified to include the elements of tbl2 appended to the end. Order is preserved. -- skips rows in later tables that have repeat values of a key. -- useful when unioning Cargo tables with a net groupBy. if not tbl1 then tbl1 = {} end local newTables = {...} local seenKeys = {} for _, tbl2 in ipairs(newTables) do		for _, row in ipairs(tbl2) do			if not seenKeys[row[key]] then seenKeys[row[key]] = true tbl1[#tbl1+1] = row end end end return tbl1 end

function p.merge(tbl1, ...) -- tbl1 is modified to include all the elements of tbl2. if not tbl1 then tbl1 = {} end local tables = {...} for _, tbl2 in ipairs(tables) do		for k, v in pairs(tbl2) do			tbl1[k] = v		end end return tbl1 end

function p.mergeDontOverwrite(tbl1, ...) -- tbl1 is modified to include all the elements of tbl2. if not tbl1 then tbl1 = {} end local tables = {...} for _, tbl2 in ipairs(tables) do		for k, v in pairs(tbl2) do			if tbl1[k] == nil then tbl1[k] = v			end end end return tbl1 end

function p.mergeAndConcat(tbl1, sep, ...) -- tbl1 is modified to include all the elements of tbl2. if not tbl1 then tbl1 = {} end local tables = {...} for _, tbl2 in ipairs(tables) do		for k, v in pairs(tbl2) do			if not tbl1[k] then tbl1[k] = v			else tbl1[k] = ('%s%s%s'):format(tbl1[k], sep, v)			end end end return tbl1 end

function p.mergeDicts(tbl1, ...) -- tbl1 is modified to include the elements of tbl2 appended to the end. Order is preserved. if not tbl1 then tbl1 = {} end local newTables = {...} for _, tbl2 in ipairs(newTables) do		for _, v in ipairs(tbl2) do			tbl1[#tbl1+1] = v			tbl1[v] = tbl2[v] end end return tbl1 end

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

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

function p.removeValueFromArray(tbl, val) local len = #tbl for i = len, 1, -1 do		if tbl[i] == val then table.remove(i) end end end

function p.removeDuplicates(tbl) local hash = {} local ret = {} for _, v in ipairs(tbl) do		if not hash[v] then hash[v] = true ret[#ret+1] = v		end end return ret 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.reverseInPlace(tbl) local len = #tbl local stop_at = len / 2 for i = 1, stop_at do		local temp = tbl[i] tbl[i] = tbl[len - i + 1] tbl[len - i + 1] = temp end 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.slice(tbl, s, e)	if s < 0 then s = #tbl + 1 + s	end if e < 0 then e = #tbl + 1 + e	end local tbl2 = {} for k = s, e do		tbl2[#tbl2+1] = tbl[k] end return tbl2 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

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.padFalseEntries(tbl, max, default) default = default or '' for i = 1, max do		if not tbl[i] then tbl[i] = default end end return tbl end

function p.concat(tbl, sep, f, ...) if not tbl then return end if not sep then sep = ',' end if not f then local tbl2 = mw.clone(tbl) p.removeFalseEntries(tbl2) return table.concat(tbl2, sep) end local tbl2 = {} for k, v in ipairs(tbl) do		if v then tbl2[#tbl2+1] = f(v, ...) end end return table.concat(tbl2, sep) end

function p.concatDict(tbl, sep, f, ...) local tbl2 = {} for _, v in ipairs(tbl) do		tbl2[#tbl2+1] = tbl[v] end return p.concat(tbl2, sep, f, ...) end

function p.concatNonempty(tbl, sep, f, ...) if not tbl then return end if not next(tbl) then return end return p.concat(tbl, sep, f, ...) end

function p.concatFromArgs(args, argname, sep, f)	-- if fields are saved in args as field1, field2, field3, etc local i = 1 local tbl = {} if args[argname] then tbl[1] = args[argname] i = 2 end while args[argname .. i] do tbl[i] = args[argname .. i]		i = i + 1 end return next(tbl) and p.concat(tbl, sep, f) end

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

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.guaranteeTable(c, f, ...) if not c then return nil end if type(c) == 'table' then return f and f(c, ...) or c	end return { f and f(c, ...) or c } end

function p.guaranteeIndex(tbl, i)	if type(i) == 'number' then return i end return p.keyOf(tbl, i) end

function p.concatIfTable(tbl, sep) if not tbl then return nil end return p.concat(p.guaranteeTable(tbl), sep) end

function p.nextNonFalse(tbl) for _, v in pairs(tbl) do		if v then return v end end end

function p.interlace(tbl) local ret = {} local _, keyList = next(tbl) for key, _ in pairs(keyList) do		ret[key] = {} end for k, v in pairs(tbl) do		for key, _ in pairs(keyList) do			ret[key][k] = v[key] end end return ret end

function p.initTable(tbl, key, val) if tbl[key] then return end tbl[key] = val or {} end

function p.initDict(tbl, key, val) if tbl[key] then return end tbl[key] = val or {} tbl[#tbl+1] = key end

function p.push(tbl, val) -- useful just to make code look cleaner when tbl is something like parent[row.key] tbl[#tbl+1] = val end

function p.pushDict(tbl, key, val) tbl[#tbl+1] = key tbl[key] = val end

function p.arrayToDict(tbl, key) for i, v in ipairs(tbl) do		tbl[v[key]] = v		tbl[i] = v	end end

function p.extractValueToList(tbl, key) local ret = {} for _, v in ipairs(tbl) do		ret[#ret+1] = v[key] end return ret end

function p.extractValueFromDictToList(tbl, key) local ret = {} for _, v in ipairs(tbl) do		ret[#ret+1] = tbl[v][key] end return ret end

function p.arrayFromField(tbl, key) local ret = {} for k, row in ipairs(tbl) do		ret[#ret+1] = row[key] end return ret end

return p