Returns an array constructor that produces a table that has a number of functions available as methods: the table
library functions, and various vanilla Lua functions and functions from Module:table and Module:fun that operate on arrays or on tables with integer keys.
local Array = require("Module:array")
local nums = Array() -- or Array:new()
nums:type() --> "array"
for i = 1, 5 do
nums:insert(i)
end
nums:concat(", ") --> "1, 2, 3, 4, 5"
local squares = nums:map(function (num) return num ^ 2 end) -- Returns new array.
squares:concat(', ') --> "1, 4, 9, 16, 25"
local even_squares = squares:filter(function (square) return square % 2 == 0 end)
even_squares:concat(", ") --> "4, 16"
The functions from Module:table and Module:fun are loaded as needed.
Functions from Module:table:
compressSparseArray
(alias compress
), contains
, invert
, isArray
, length
, listToSet
(alias toSet
), maxIndex
, numKeys
, removeDuplicates
, reverse
, reverseIpairs
, serialCommaJoin
, sparseIpairs
Functions from Module:fun. These have a function as the second argument:
all
, filter
, map
, some
These functions are included in a funcs
subtable of an array (awkward):
affixNums
, keysToList
, numKeys
The following functions return an array (with the array metatable):
affixNums
, compressSparseArray
, keysToList
, numKeys
, removeDuplicates
, reverse
The names with underscores instead of camel case can be used as aliases: for instance, arr:to_set()
instead of arr:toSet()
.
The array constructor behaves differently depending on the arguments supplied to it. Without arguments, it creates an empty table. Given a single table, it adds the metatable to it. If the table has been loaded with mw.loadData
, it duplicates the table, removing the metatable that is found in tables loaded with mw.loadData
. Otherwise, it creates a new table (array) containing the arguments.
The array constructor does this by adding a metatable. This is similar to how all strings have a metatable that allows the string
library functions to be used as methods: for instance, ("abc"):sub(1, 1)
for string.sub("abc", 1, 1)
.
local Array = {}
local array_constructor
-- Copy table library so as not to unexpectedly change the behavior of code that
-- uses it.
local array_methods = mw.clone(table)
-- Create version of table.sort that returns the table.
array_methods.sort = function (t, comp)
table.sort(t, comp)
return t
end
-- ipairs and unpack operate on arrays.
array_methods.ipairs = ipairs
array_methods.unpack = unpack
function array_methods:type()
local mt = getmetatable(self)
return type(mt) == "table" and mt.__type or nil
end
function array_methods:adjustIndex(index)
index = math.floor(index)
if index < 0 then
index = #self + index + 1
end
return index
end
-- string.sub-style slicing.
function array_methods:slice(i, j)
if i == nil then
i = 1
elseif type(i) == "number" then
i = self:adjust_index(i)
else
error("Expected number, got " .. type(i))
end
if j == nil or type(j) == "number" then
j = self:adjust_index(j or -1)
else
error("Expected number, got " .. type(j))
end
local new_arr = array_constructor()
local k = 0
for index = i, j do
k = k + 1
new_arr = self
end
return new_arr
end
-- A function to convert string key-table modules such
-- as ] into arrays.
-- "from" is a bad name.
-- field_for_key supplies the field name in which the
-- key will be stored.
local function to_array(map, field_for_key)
m_table = m_table or require "Module:table"
local arr = {}
local i = 0
for key, val in pairs(map) do
i = i + 1
local new_val = m_table.shallowcopy(val)
if field_for_key then
new_val = key
end
arr = new_val
end
return array_constructor(arr)
end
-- Functions from ] that operate on arrays or sparse arrays.
-- List copied from ].
local operate_on_array = {
-- non-sparse
"removeDuplicates", "length", "contains", "serialCommaJoin",
"reverseIpairs", "reverse", "invert", "listToSet", "isArray",
-- sparse
"numKeys", "maxIndex", "compressSparseArray", "sparseIpairs",
-- tables in general
"shallowcopy", "deepcopy",
}
-- Not all of these operate on arrays.
local create_new_array = {
-- Functions from ] that create an array.
-- List copied from ].
"removeDuplicates", "numKeys", "affixNums", "compressSparseArray",
"keysToList", "reverse",
-- Functions from ] that create an table.
"shallowcopy", "deepcopy",
-- Functions from ] that create an array.
"map", "filter",
}
-- Functions from ] that take an array in the second argument.
-- They just have to have the argument order reversed to work as methods of the
-- array object.
local second_argument_is_array = { "map", "some", "all", "filter" }
-- Add aliases for the functions from ] whose names
-- contain "array" or "list", which is redundant, and whose names don't conform
-- to the usual camel case.
-- The key redirects to the value.
local alias_of = {
compress = "compressSparseArray", keys = "keysToList", toSet = "listToSet",
deepCopy = "deepcopy", shallowCopy = "shallowcopy",
}
local function get_module_function(key, module, module_name)
return module
or error("No function named " .. tostring(key) .. " in Module:" .. module_name)
end
local function wrap_in_array_constructor(func)
return function (...)
return array_constructor(func(...))
end
end
local function create_array_generating_func(key, module, module_name)
return wrap_in_array_constructor(get_module_function(key, module, module_name))
end
local function reverse_arguments(func)
return function (a, b)
return func(b, a, true)
end
end
local function underscore_to_camel_case(str)
if type(str) ~= "string" then return str end
str = str:gsub("_(.)", string.upper)
return str
end
local m_table, m_fun
local Array = {}
Array.__type = "array"
function Array:__index(key)
if type(key) ~= "string" then
return nil
end
-- Convert underscores to camel case: num_keys -> numKeys.
key = underscore_to_camel_case(key)
local val = array_methods
if val then
return val
end
key = alias_of or key
local func
m_table = m_table or require "Module:table"
if m_table.contains(operate_on_array, key) then
if m_table.contains(create_new_array, key) then
func = create_array_generating_func(key, m_table, "table")
else
func = m_table
end
elseif m_table.contains(second_argument_is_array, key) then
m_fun = m_fun or require "Module:fun"
local raw_func = reverse_arguments(get_module_function(key, m_fun, "fun"))
if m_table.contains(create_new_array, key) then
func = wrap_in_array_constructor(raw_func)
else
func = raw_func
end
elseif key == "fold" then
m_fun = m_fun or require "Module:fun"
local raw_func = get_module_function(key, m_fun, "fun")
func = function(t, func, accum)
return raw_func(func, t, accum)
end
end
if func then
array_methods = func
return func
end
end
function Array.__add(a, b)
if type(a) == 'table' and type(b) == 'table' then
m_table = m_table or require "Module:table"
local new_arr = array_constructor(m_table.shallowcopy(a))
for _, val in ipairs(b) do
new_arr:insert(val)
end
return new_arr
end
end
function Array:new(...)
local arr
if select("#", ...) == 1 and type((...)) == "table" then
arr = ...
local mt = getmetatable(arr)
-- If table has been loaded with mw.loadData, copy it to avoid the
-- limitations of it being a virtual table.
if mt and mt.mw_loadData then
m_table = m_table or require "Module:table"
arr = m_table.shallowcopy(arr)
end
else
arr = { ... }
end
return setmetatable(arr, self)
end
-- Declared as local above.
function array_constructor(...)
return Array:new(...)
end
local array_generating_funcs = { from = to_array }
local Array_library_mt = {
__call = Array.new, __index = array_generating_funcs
}
setmetatable(Array, Array_library_mt)
function Array_library_mt:__index(key)
key = underscore_to_camel_case(key)
key = alias_of or key
if array_generating_funcs then
return array_generating_funcs
end
m_table = m_table or require "Module:table"
if m_table.contains(create_new_array, key) then
local func = create_array_generating_func(key, m_table, "table")
array_generating_funcs = func
return func
end
end
return Array