Module:Table utilities
Appearance
Documentation for this module may be created at Module:Table utilities/doc
--[[
Module:Table utilities
Part of the Module:Utilities family.
Generic Lua table manipulation - no Cargo dependency, no wiki output.
All functions are re-exported through Module:Utilities for backward compatibility.
Functions:
unpack_table Flatten a nested table into a single key/value table
remove_dupes_from_flat_list Remove duplicates from a flat list (returns seen-set; carried forward as-is)
filter_by_intersection Keep only elements of table_a that appear in table_b
in_a_not_b Return elements in array a that are not in array b
table_has_value Check if an array contains a specific value
get_downstream_items Return all elements after a focal item in an ordered list
key_value_table_to_csv Extract unique values of a field from a cargo-like table → CSV string
deduplicate_rows Remove duplicate rows from a cargo result based on key fields
table_to_string Join an array into a comma-delimited string
merge_schemas Merge two or more field-schema tables into one
--]]
local tbl = {}
-- ============================================================
-- Flattening / unpacking
-- ============================================================
--[[
Flattens a nested table (e.g. a raw Cargo results object) into a single
key/value table. Prints a warning on key conflicts.
tbl.unpack_table({{a=1},{b=2}}) → {a=1, b=2}
--]]
function tbl.unpack_table(nested_table)
local flat_table = {}
for _, inner_table in pairs(nested_table) do
if type(inner_table) == "table" then
for key, value in pairs(inner_table) do
if flat_table[key] then
print("Warning: Key conflict for '" .. key .. "'. Existing value will be overwritten.")
end
flat_table[key] = value
end
end
end
return flat_table
end
-- ============================================================
-- Deduplication
-- ============================================================
--[[
Removes duplicates from a flat list in-place.
NOTE: Carried forward as-is. Returns the `seen` set (a dict), not a clean
array - this is a known quirk of the original implementation.
--]]
function tbl.remove_dupes_from_flat_list(flat_table)
local seen = {}
for index, item in ipairs(flat_table) do
if seen[item] then
table.remove(flat_table, index)
else
seen[item] = true
end
end
local simple_list = {}
return seen
end
--[[
Removes duplicate rows from a Cargo results array based on one or more key fields.
Preserves first-encounter order.
tbl.deduplicate_rows(rows, {"Name", "Family"})
--]]
function tbl.deduplicate_rows(rows, key_fields)
if not rows or type(rows) ~= "table" then return {} end
if not key_fields or type(key_fields) ~= "table" then
key_fields = { "Name" }
end
if #rows == 0 then return {} end
if #key_fields == 0 then return rows end
local unique_rows = {}
local seen = {}
for _, row in ipairs(rows) do
if type(row) == "table" then
local row_key = ""
for _, field in ipairs(key_fields) do
local v = row[field]
row_key = row_key .. "|" .. (v ~= nil and tostring(v) or "")
end
if not seen[row_key] then
table.insert(unique_rows, row)
seen[row_key] = true
end
end
end
return unique_rows
end
-- ============================================================
-- Set operations
-- ============================================================
-- Returns a new array containing only elements of table_a that also appear in table_b.
function tbl.filter_by_intersection(table_a, table_b)
local filtered = {}
for _, value in ipairs(table_a) do
for _, b in ipairs(table_b) do
if value == b then
table.insert(filtered, value)
break
end
end
end
return filtered
end
-- Returns elements that are in array a but not in array b.
function tbl.in_a_not_b(a, b)
local b_set = {}
for _, value in ipairs(b) do
b_set[value] = true
end
local result = {}
for _, value in ipairs(a) do
if not b_set[value] then
table.insert(result, value)
end
end
return result
end
-- Returns true if the array tbl contains value.
function tbl.table_has_value(t, value)
for _, v in ipairs(t) do
if v == value then return true end
end
return false
end
-- ============================================================
-- Ordered list operations
-- ============================================================
--[[
Returns all elements that appear after focal_item in list.
Raises an error if focal_item is not found.
tbl.get_downstream_items({"family","genus","species"}, "genus") → {"species"}
--]]
function tbl.get_downstream_items(list, focal_item)
if not list or type(list) ~= "table" or #list == 0 then
error("Error: The rank list is empty or invalid.")
end
if not focal_item or type(focal_item) ~= "string" then
error("Error: The focal rank is missing or invalid.")
end
local start_index = nil
for i, item in ipairs(list) do
if item == focal_item then
start_index = i
break
end
end
if not start_index then
error("Error: The focal rank '" .. focal_item .. "' is not found in the rank list. Rank list: {" ..
table.concat(list, ", ") .. "}")
end
local downstream = {}
for i = start_index + 1, #list do
table.insert(downstream, list[i])
end
return downstream
end
-- ============================================================
-- Cargo-like table → string conversions
-- ============================================================
-- Extracts unique values of field_name from a cargo-like array of row tables,
-- sorts them, and returns a comma-separated string.
-- format = "string" (default) - plain text
-- format = "page" - wrap in wiki page links (italicised for species/genus)
function tbl.key_value_table_to_csv(dict, field_name, format)
format = format or "string"
local uniques = {}
for i = 1, #dict do
local entry = dict[i]
if entry and entry[field_name] then
uniques[entry[field_name]] = true
else
mw.log("Invalid entry at index " .. i .. " in dict")
end
end
local sorted = {}
for v, _ in pairs(uniques) do
table.insert(sorted, v)
end
table.sort(sorted)
local list = {}
for i = 1, #sorted do
local item = sorted[i]:gsub("%**$", "")
if format == "page" then
if string.lower(field_name) == "species" or string.lower(field_name) == "genus" then
table.insert(list, "''" .. "[[" .. item .. "]]" .. "''")
else
table.insert(list, "[[" .. item .. "]]")
end
else
table.insert(list, item)
end
end
return #list > 0 and table.concat(list, ", ") or nil
end
-- Joins an array into a comma-delimited string.
-- Intended for simple rank lists and similar 1-D arrays.
function tbl.table_to_string(rank_table)
return table.concat(rank_table, ", ")
end
-- ============================================================
-- Schema helpers
-- ============================================================
--[[
Merges two or more field-schema tables into one.
Later arguments take precedence on key conflicts.
tbl.merge_schemas(schema_a, schema_b, schema_c)
--]]
function tbl.merge_schemas(...)
local merged = {}
for i = 1, select("#", ...) do
local schema = select(i, ...)
if schema and type(schema) == "table" then
for field, config in pairs(schema) do
merged[field] = config
end
end
end
return merged
end
return tbl