Module:Array: Difference between revisions

m (1 revision imported: update)
m (1 revision imported)
 
(One intermediate revision by one other user not shown)
Line 1: Line 1:
-- <nowiki> awawa
-- Imported from: https://runescape.wiki/w/Module:Array
 
local libraryUtil = require('libraryUtil')
local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType
local checkType = libraryUtil.checkType
local checkTypeMulti = libraryUtil.checkTypeMulti
local checkTypeMulti = libraryUtil.checkTypeMulti
local arr = {}


setmetatable(arr, {
---@class Array
__call = function (_, array)
---@operator call(any[]): Array
return arr.new(array)
---@operator concat(any[]): Array
---@operator concat(number|string|function): string
---@operator unm: Array
---@operator add(number|number[]|Array): Array
---@operator sub(number|number[]|Array): Array
---@operator mul(number|number[]|Array): Array
---@operator div(number|number[]|Array): Array
---@operator pow(number|number[]|Array): Array
local Array = {
pop = table.remove
}
Array.__index = Array
 
setmetatable(Array, {
__index = table,
__call = function (_, arr)
return Array.new(arr)
end
end
})
})


function arr.__index(t, k)
-- function Array.__tostring(arr)
if type(k) == 'table' then
-- -- local dumpObject = require('Module:Logger').dumpObject
local res = arr.new()
-- require 'log'
for i = 1, #t do
-- local dumpObject = dumpObject
res[i] = t[k[i]]
-- local mt = getmetatable(arr)
end
-- setmetatable(arr, nil)
return res
-- local str = dumpObject(arr, {clean=true, collapseLimit=100})
else
-- setmetatable(arr, mt)
return arr[k]
-- return str
end
-- end
end


function arr.__tostring(array)
function Array.__concat(lhs, rhs)
local dumpObject = require('Module:Logger').dumpObject
setmetatable(array, nil)
local str = dumpObject(array, {clean=true, collapseLimit=100})
setmetatable(array, arr)
return str
end
 
function arr.__concat(lhs, rhs)
if type(lhs) == 'table' and type(rhs) == 'table' then
if type(lhs) == 'table' and type(rhs) == 'table' then
local res = setmetatable({}, getmetatable(lhs) or getmetatable(rhs))
local res = {}
for i = 1, #lhs do
for i = 1, #lhs do
res[i] = lhs[i]
res[i] = lhs[i]
Line 41: Line 48:
res[i + l] = rhs[i]
res[i + l] = rhs[i]
end
end
return res
return setmetatable(res, getmetatable(lhs) or getmetatable(rhs))
else
else
return tostring(lhs) .. tostring(rhs)
return tostring(lhs) .. tostring(rhs)
Line 47: Line 54:
end
end


function arr.__unm(array)
function Array.__unm(arr)
return arr.map(array, function(x) return -x end)
return Array.map(arr, function(x) return -x end)
end
end


local function mathTemplate(lhs, rhs, funName, fun)
---@param lhs number|number[]|Array
---@param rhs number|number[]|Array
---@param funName string
---@param opName string
---@param fun fun(lhs: number, rhs: number): number
---@return Array
local function mathTemplate(lhs, rhs, funName, opName, fun)
checkTypeMulti('Module:Array.' .. funName, 1, lhs, {'number', 'table'})
checkTypeMulti('Module:Array.' .. funName, 1, lhs, {'number', 'table'})
checkTypeMulti('Module:Array.' .. funName, 2, rhs, {'number', 'table'})
checkTypeMulti('Module:Array.' .. funName, 2, rhs, {'number', 'table'})
local res = setmetatable({}, getmetatable(lhs) or getmetatable(rhs))
local res = {}


if type(lhs) == 'number' then
if type(lhs) == 'number' then
Line 65: Line 78:
end
end
else
else
assert(#lhs == #rhs, string.format('Tables are not equal length (lhs=%d, rhs=%d)', #lhs, #rhs))
assert(#lhs == #rhs, string.format('Elementwise %s failed because arrays have different sizes (left: %d, right: %d)', opName, #lhs, #rhs))
for i = 1, #lhs do
for i = 1, #lhs do
res[i] = fun(lhs[i], rhs[i])
res[i] = fun(lhs[i], rhs[i])
Line 71: Line 84:
end
end


return res
return setmetatable(res, getmetatable(lhs) or getmetatable(rhs))
end
end


function arr.__add(lhs, rhs)
function Array.__add(lhs, rhs)
return mathTemplate(lhs, rhs, '__add', function(x, y) return x + y end)
return mathTemplate(lhs, rhs, '__add', 'addition', function(x, y) return x + y end)
end
end


function arr.__sub(lhs, rhs)
function Array.__sub(lhs, rhs)
return mathTemplate(lhs, rhs, '__sub', function(x, y) return x - y end)
return mathTemplate(lhs, rhs, '__sub', 'substraction', function(x, y) return x - y end)
end
end


function arr.__mul(lhs, rhs)
function Array.__mul(lhs, rhs)
return mathTemplate(lhs, rhs, '__mul', function(x, y) return x * y end)
return mathTemplate(lhs, rhs, '__mul', 'multiplication', function(x, y) return x * y end)
end
end


function arr.__div(lhs, rhs)
function Array.__div(lhs, rhs)
return mathTemplate(lhs, rhs, '__div', function(x, y) return x / y end)
return mathTemplate(lhs, rhs, '__div', 'division', function(x, y) return x / y end)
end
end


function arr.__pow(lhs, rhs)
function Array.__pow(lhs, rhs)
return mathTemplate(lhs, rhs, '__pow', function(x, y) return x ^ y end)
return mathTemplate(lhs, rhs, '__pow', 'exponentiation', function(x, y) return x ^ y end)
end
 
function arr.__lt(lhs, rhs)
for i = 1, math.min(#lhs, #rhs) do
if lhs[i] >= rhs[i] then
return false
end
end
return true
end
 
function arr.__le(lhs, rhs)
for i = 1, math.min(#lhs, #rhs) do
if lhs[i] > rhs[i] then
return false
end
end
return true
end
end


function arr.__eq(lhs, rhs)
function Array.__eq(lhs, rhs)
if #lhs ~= #rhs then
if #lhs ~= #rhs then
return false
return false
Line 124: Line 119:
end
end


function arr.all(array, fn)
---Behaviour depends on the value of `fn`:
checkType('Module:Array.all', 1, array, 'table')
---* `nil` - Checks that the array doesn't contain any **false** elements.
---* `fun(elem: any, i?: integer): boolean` - Returns **true** if `fn` returns **true** for every element.
---* `number` | `table` | `boolean` - Checks that all elements in `arr` are equal to this value.
---@param arr any[]
---@param fn? any
---@return boolean
function Array.all(arr, fn)
checkType('Module:Array.all', 1, arr, 'table')
if fn == nil then fn = function(item) return item end end
if fn == nil then fn = function(item) return item end end
if type(fn) ~= 'function' then
if type(fn) ~= 'function' then
Line 132: Line 134:
end
end
local i = 1
local i = 1
while array[i] ~= nil do
while arr[i] ~= nil do
if not fn(array[i], i) then
---@diagnostic disable-next-line: redundant-parameter
if not fn(arr[i], i) then
return false
return false
end
end
Line 141: Line 144:
end
end


function arr.any(array, fn)
---Behaviour depends on the value of `fn`:
checkType('Module:Array.any', 1, array, 'table')
---* `nil` - Checks that the array contains at least one non **false** element.
---* `fun(elem: any, i?: integer): boolean` - Returns **true** if `fn` returns **true** for at least one element.
---* `number` | `table` | `boolean` - Checks that `arr` contains this value.
---@param arr any[]
---@param fn? any
---@return boolean
function Array.any(arr, fn)
checkType('Module:Array.any', 1, arr, 'table')
if fn == nil then fn = function(item) return item end end
if fn == nil then fn = function(item) return item end end
if type(fn) ~= 'function' then
if type(fn) ~= 'function' then
Line 149: Line 159:
end
end
local i = 1
local i = 1
while array[i] ~= nil do
while arr[i] ~= nil do
if fn(array[i], i) then
---@diagnostic disable-next-line: redundant-parameter
if fn(arr[i], i) then
return true
return true
end
end
Line 158: Line 169:
end
end


function arr.clean(array)
---Recursively removes all metatables.
checkType('Module:Array.clean', 1, array, 'table')
---@param arr any[]
for i = 1, #array do
---@return any[]
if type(array[i]) == 'table' then
function Array.clean(arr)
arr.clean(array[i])
checkType('Module:Array.clean', 1, arr, 'table')
for i = 1, #arr do
if type(arr[i]) == 'table' then
Array.clean(arr[i])
end
end
end
end
setmetatable(array, nil)
setmetatable(arr, nil)
return array
return arr
end
end


function arr.contains(array, elem, useElemTableContent)
---Make a copy of the input table. Preserves metatables.
checkType('Module:Array.contains', 1, array, 'table')
---@generic T: any[]
if type(elem) == 'table' and useElemTableContent ~= false then
---@param arr T
local elemMap = {}
---@param deep? boolean # Recursively clone subtables if **true**.
local isFound = {}
---@return T
arr.each(elem, function(x, i) elemMap[x] = i; isFound[i] = false end)
function Array.clone(arr, deep)
for i = 1, #array do
checkType('Module:Array.clone', 1, arr, 'table')
local j = elemMap[array[i]]
checkType('Module:Array.clone', 2, deep, 'boolean', true)
if j then
local res = {}
isFound[j] = true
for i = 1, #arr do
end
if deep == true and type(arr[i]) == 'table' then
res[i] = Array.clone(arr[i], true)
else
res[i] = arr[i]
end
end
return setmetatable(res, getmetatable(arr))
end
 
---Check if `arr` contains `val`.
---@param arr any[]
---@param val any
---@return boolean
function Array.contains(arr, val)
checkType('Module:Array.contains', 1, arr, 'table')
for i = 1, #arr do
if arr[i] == val then
return true
end
end
return false
end
 
---Check if `arr` contains any of the values in the table `t`.
---@param arr any[]
---@param t any[]
---@return boolean
function Array.containsAny(arr, t)
checkType('Module:Array.containsAny', 1, arr, 'table')
checkType('Module:Array.containsAny', 2, t, 'table')
local lookupTbl = {}
for i = 1, #t do
lookupTbl[t[i]] = true
end
for i = 1, #arr do
if lookupTbl[arr[i]] then
return true
end
end
return false
end
 
---Check if `arr` contains all values in the table `t`.
---@param arr any[]
---@param t any[]
---@return boolean
function Array.containsAll(arr, t)
checkType('Module:Array.containsAll', 1, arr, 'table')
checkType('Module:Array.containsAll', 2, t, 'table')
local lookupTbl = {}
local l = #t
local trueCount = 0
for i = 1, l do
lookupTbl[t[i]] = false
end
for i = 1, #arr do
if lookupTbl[arr[i]] == false then
lookupTbl[arr[i]] = true
trueCount = trueCount + 1
end
if trueCount == l then
return true
end
end
return arr.all(isFound, true)
else
return arr.any(array, function(item) return item == elem end)
end
end
return false
end
---Convolute two number arrays.
---@generic T: number[]
---@param x T
---@param y T
---@return T
function Array.convolve(x, y)
checkType('Module:Array.convolve', 1, x, 'table')
checkType('Module:Array.convolve', 2, y, 'table')
local z = {}
    local xLen, yLen = #x, #y
    for j = 1, (xLen + yLen - 1) do
        local sum = 0
        for k = math.max(1, j - yLen + 1), math.min(xLen, j) do
            sum = sum + x[k] * y[j-k+1]
        end
        z[j] = sum
    end
    return setmetatable(z, getmetatable(x) or getmetatable(y))
end
---Remove **nil** values from `arr` while preserving order.
---@generic T: any[]
---@param arr T
---@return T
function Array.condenseSparse(arr)
checkType('Module:Array.condenseSparse', 1, arr, 'table')
local keys = {}
local res = {}
local l = 0
for k in pairs(arr) do
l = l + 1
keys[l] = k
end
table.sort(keys)
for i =  1, l do
res[i] = arr[keys[i]]
end
return setmetatable(res, getmetatable(arr))
end
end


function arr.count(array, fn)
---Behaviour depends on value of `val`:
checkType('Module:Array.count', 1, array, 'table')
---* `nil` - Counts the number of non **false** elements.
if fn == nil then fn = function(item) return item end end
---* `fun(elem: any): boolean` - Count the number of times the function returned **true**.
if type(fn) ~= 'function' then
---* `boolean` | `number` | `table` - Counts the number of times this value occurs in `arr`.
local val = fn
---@param arr any[]
fn = function(item) return item == val end
---@param val? any
---@return integer
function Array.count(arr, val)
checkType('Module:Array.count', 1, arr, 'table')
if val == nil then val = function(item) return item end end
if type(val) ~= 'function' then
local _val = val
val = function(item) return item == _val end
end
end
local count = 0
local count = 0
for i = 1, #array do
for i = 1, #arr do
if fn(array[i]) then
if val(arr[i]) then
count = count + 1
count = count + 1
end
end
Line 203: Line 323:
end
end


function arr.diff(array, order)
---Differentiate the array
checkType('Module:Array.diff', 1, array, 'table')
---@generic T: number[]
---@param arr T
---@param order number? # Oder of the differentiation. Default is 1.
---@return T # Length is `#arr - order`
function Array.diff(arr, order)
checkType('Module:Array.diff', 1, arr, 'table')
checkType('Module:Array.diff', 2, order, 'number', true)
checkType('Module:Array.diff', 2, order, 'number', true)
local res = setmetatable({}, getmetatable(array))
local res = {}
for i = 1, #array - 1 do
for i = 1, #arr - 1 do
res[i] = array[i+1] - array[i]
res[i] = arr[i+1] - arr[i]
end
end
if order and order > 1 then
if order and order > 1 then
return arr.diff(res, order - 1)
return Array.diff(res, order - 1)
end
end
return res
return setmetatable(res, getmetatable(arr))
end
end


function arr.each(array, fn)
---Loops over `arr` and passes each element as the first argument to `fn`. This function returns nothing.
checkType('Module:Array.each', 1, array, 'table')
---@param arr any[]
---@param fn fun(elem: any, i?: integer)
function Array.each(arr, fn)
checkType('Module:Array.each', 1, arr, 'table')
checkType('Module:Array.each', 2, fn, 'function')
checkType('Module:Array.each', 2, fn, 'function')
local i = 1
local i = 1
while array[i] ~= nil do
while arr[i] ~= nil do
fn(array[i], i)
fn(arr[i], i)
i = i + 1
i = i + 1
end
end
end
end


function arr.filter(array, fn)
---Makes a copy of `arr` with only elements for which `fn` returned **true**.
checkType('Module:Array.filter', 1, array, 'table')
---@generic T: any[]
if fn == nil then fn = function(item) return item end end
---@param arr T
if type(fn) ~= 'function' then
---@param fn fun(elem: any, i?: integer): boolean
local val = fn
---@return T
fn = function(item) return item == val end
function Array.filter(arr, fn)
end
checkType('Module:Array.filter', 1, arr, 'table')
local r = setmetatable({}, getmetatable(array))
checkType('Module:Array.filter', 2, fn, 'function')
local r = {}
local len = 0
local len = 0
local i = 1
local i = 1
while array[i] ~= nil do
while arr[i] ~= nil do
if fn(array[i], i) then
if fn(arr[i], i) then
len = len + 1
len = len + 1
r[len] = array[i]
r[len] = arr[i]
end
end
i = i + 1
i = i + 1
end
end
return r
return setmetatable(r, getmetatable(arr))
end
end


function arr.find(array, fn, default)
---Find the first elements for which `fn` returns **true**.
checkType('Module:Array.find', 1, array, 'table')
---@param arr any[]
---@param fn any # A value to look for or a function of the form `fun(elem: any, i?: integer): boolean`.
---@param default? any # Value to return if no element passes the test.
---@return any? elem # The first element that passed the test.
---@return integer? i # The index of the item that passed the test.
function Array.find(arr, fn, default)
checkType('Module:Array.find', 1, arr, 'table')
checkTypeMulti('Module:Array.find_index', 2, fn, {'function', 'table', 'number', 'boolean'})
checkTypeMulti('Module:Array.find_index', 2, fn, {'function', 'table', 'number', 'boolean'})
if type(fn) ~= 'function' then
if type(fn) ~= 'function' then
local val = fn
local _val = fn
fn = function(item) return item == val end
fn = function(item) return item == _val end
end
end
local i = 1
local i = 1
while array[i] ~= nil do
while arr[i] ~= nil do
if fn(array[i], i) then
---@diagnostic disable-next-line: redundant-parameter
return array[i], i
if fn(arr[i], i) then
return arr[i], i
end
end
i = i + 1
i = i + 1
end
end
return default
return default, nil
end
end


function arr.find_index(array, fn, default)
---Find the index of `val`.
checkType('Module:Array.find_index', 1, array, 'table')
---@param arr any[]
checkTypeMulti('Module:Array.find_index', 2, fn, {'function', 'table', 'number', 'boolean'})
---@param val any # A value to look for or a function of the form `fun(elem: any, i?: integer): boolean`.
if type(fn) ~= 'function' then
---@param default? any # Value to return if no element passes the test.
local val = fn
---@return integer?
fn = function(item) return item == val end
function Array.find_index(arr, val, default)
checkType('Module:Array.find_index', 1, arr, 'table')
checkTypeMulti('Module:Array.find_index', 2, val, {'function', 'table', 'number', 'boolean'})
if type(val) ~= 'function' then
local _val = val
val = function(item) return item == _val end
end
end
local i = 1
local i = 1
while array[i] ~= nil do
while arr[i] ~= nil do
if fn(array[i], i) then
---@diagnostic disable-next-line: redundant-parameter
if val(arr[i], i) then
return i
return i
end
end
Line 280: Line 422:
end
end


function arr.newIncrementor(start, step)
---Extracts a subset of `arr`.
checkType('Module:Array.newIncrementor', 1, start, 'number', true)
---@generic T: any[]
checkType('Module:Array.newIncrementor', 2, step, 'number', true)
---@param arr T
step = step or 1
---@param indexes integer|integer[] # Indexes of the elements.
local n = (start or 1) - step
---@return T
local obj = {}
function Array.get(arr, indexes)
return setmetatable(obj, {
checkType('Module:Array.set', 1, arr, 'table')
__call = function() n = n + step return n end,
checkTypeMulti('Module:Array.set', 2, indexes, {'table', 'number'})
__tostring = function() return n end,
if type(indexes) == 'number' then
__index = function() return n end,
indexes = {indexes}
__newindex = function(self, k, v)
end
if k == 'step' and type(v) == 'number' then
local res = {}
step = v
for i = 1, #indexes do
elseif type(v) == 'number' then
res[i] = arr[indexes[i]]
n = v
end
end
return setmetatable(res, getmetatable(arr))
end,
__concat = function(x, y) return tostring(x) .. tostring(y) end
})
end
end


function arr.int(array, start, stop)
---Integrates the array. Effectively does $\left\{\sum^{n}_{start}{arr[n]} \,\Bigg|\, n \in [start, stop]\right\}$.
checkType('Module:Array.int', 1, array, 'table')
---@generic T: number[]
---@param arr T # number[]
---@param start? integer # Index where to start the summation. Defaults to 1.
---@param stop? integer # Index where to stop the summation. Defaults to #arr.
---@return T
function Array.int(arr, start, stop)
checkType('Module:Array.int', 1, arr, 'table')
checkType('Module:Array.int', 2, start, 'number', true)
checkType('Module:Array.int', 2, start, 'number', true)
checkType('Module:Array.int', 3, stop, 'number', true)
checkType('Module:Array.int', 3, stop, 'number', true)
local res = setmetatable({}, getmetatable(array))
local res = {}
start = start or 1
start = start or 1
stop = stop or #array
stop = stop or #arr
res[1] = array[start]
res[1] = arr[start]
for i = 1, stop - start do
for i = 1, stop - start do
res[i+1] = res[i] + array[start + i]
res[i+1] = res[i] + arr[start + i]
end
end
return res
return setmetatable(res, getmetatable(arr))
end
end


function arr.intersect(array1, array2)
---Returns an array with elements that are present in both tables.
checkType('Module:Array.intersect', 1, array1, 'table')
---@generic T: any[]
checkType('Module:Array.intersect', 2, array2, 'table')
---@param arr1 T
local array2Elements = {}
---@param arr2 T
local res = setmetatable({}, getmetatable(array1) or getmetatable(array2))
---@return T
function Array.intersect(arr1, arr2)
checkType('Module:Array.intersect', 1, arr1, 'table')
checkType('Module:Array.intersect', 2, arr2, 'table')
local arr2Elements = {}
local res = {}
local len = 0
local len = 0
arr.each(array2, function(item) array2Elements[item] = true end)
Array.each(arr2, function(item) arr2Elements[item] = true end)
arr.each(array1, function(item)
Array.each(arr1, function(item)
if array2Elements[item] then
if arr2Elements[item] then
len = len + 1
len = len + 1
res[len] = item
res[len] = item
end
end
end)
end)
return res
return setmetatable(res, getmetatable(arr1) or getmetatable(arr2))
end
end


function arr.intersects(array1, array2)
---Checks if the two inputs have at least one element in common.
checkType('Module:Array.intersects', 1, array1, 'table')
---@param arr1 any[]
checkType('Module:Array.intersects', 2, array2, 'table')
---@param arr2 any[]
---@return boolean
function Array.intersects(arr1, arr2)
checkType('Module:Array.intersects', 1, arr1, 'table')
checkType('Module:Array.intersects', 2, arr2, 'table')
local small = {}
local small = {}
local large
local large
if #array1 <= #array2 then
if #arr1 <= #arr2 then
arr.each(array1, function(item) small[item] = true end)
Array.each(arr1, function(item) small[item] = true end)
large = array2
large = arr2
else
else
arr.each(array2, function(item) small[item] = true end)
Array.each(arr2, function(item) small[item] = true end)
large = array1
large = arr1
end
end
return arr.any(large, function(item) return small[item] end)
return Array.any(large, function(item) return small[item] end)
end
end


function arr.insert(array, val, index, unpackVal)
---Inserts values into `arr`.
checkType('Module:Array.insert', 1, array, 'table')
---@generic T: any[]
checkType('Module:Array.insert', 3, index, 'number', true)
---@param arr T
---@param val any # If `val` is an array and `unpackVal` is **true** then the individual elements of `val` are inserted.
---@param index? integer # Location to start the insertion. Default is at the end of `arr`.
---@param unpackVal? boolean # Default is **false**.
---@return T
---@overload fun(arr: T, val: any, unpackVal: boolean): T
function Array.insert(arr, val, index, unpackVal)
checkType('Module:Array.insert', 1, arr, 'table')
checkTypeMulti('Module:Array.insert', 3, index, {'number', 'boolean', 'nil'})
checkType('Module:Array.insert', 4, unpackVal, 'boolean', true)
checkType('Module:Array.insert', 4, unpackVal, 'boolean', true)
local len = #array
if type(index) == 'boolean'  then
unpackVal, index = index, nil
end
local len = #arr
index = index or (len + 1)
index = index or (len + 1)
local mt = getmetatable(arr)
setmetatable(arr, nil)


if type(val) == 'table' and unpackVal ~= false then
if type(val) == 'table' and unpackVal then
local len2 = #val
local len2 = #val
for i = 0, len - index do
for i = 0, len - index do
array[len + len2 - i] = array[len - i]
arr[len + len2 - i] = arr[len - i]
end
end
for i = 0, len2 - 1 do
for i = 0, len2 - 1 do
array[index + i] = val[i + 1]
arr[index + i] = val[i + 1]
end
end
else
else
table.insert(array, index, val)
table.insert(arr, index, val)
end
end


return array
return setmetatable(arr, mt)
end
end


function arr.map(array, fn)
---Returns the last element of `arr`.
checkType('Module:Array.map', 1, array, 'table')
---@param arr any[]
---@param offset? integer
---@return any
function Array.last(arr, offset)
checkType('Module:Array.last', 1, arr, 'table')
checkType('Module:Array.last', 2, offset, 'number', true)
return arr[#arr + offset]
end
 
---Returns a new table were each element of `arr` is modified by `fn`.
---@generic T: any[]
---@param arr T
---@param fn fun(elem: any, i?: integer): any # First argument is the current element, the second argument is the index of the current element.
---@return T
function Array.map(arr, fn)
checkType('Module:Array.map', 1, arr, 'table')
checkType('Module:Array.map', 2, fn, 'function')
checkType('Module:Array.map', 2, fn, 'function')
local len = 0
local len = 0
local r = setmetatable({}, getmetatable(array))
local r = {}
local i = 1
local i = 1
while array[i] ~= nil do
while arr[i] ~= nil do
local tmp = fn(array[i], i)
local tmp = fn(arr[i], i)
if tmp ~= nil then
if tmp ~= nil then
len = len + 1
len = len + 1
Line 382: Line 564:
i = i + 1
i = i + 1
end
end
return r
return setmetatable(r, getmetatable(arr))
end
end


function arr.max_by(array, fn)
---Find the element for which `fn` returned the largest value.
checkType('Module:Array.max_by', 1, array, 'table')
---@param arr any[]
---@param fn fun(elem: any): any # The returned value needs to be comparable using the `<` operator.
---@return any elem # The element with the largest `fn` value.
---@return integer i # The index of this element.
function Array.max_by(arr, fn)
checkType('Module:Array.max_by', 1, arr, 'table')
checkType('Module:Array.max_by', 2, fn, 'function')
checkType('Module:Array.max_by', 2, fn, 'function')
return unpack(arr.reduce(array, function(new, old, i)
return unpack(Array.reduce(arr, function(new, old, i)
local y = fn(new)
local y = fn(new)
return y > old[2] and {new, y, i} or old
return y > old[2] and {new, y, i} or old
Line 394: Line 581:
end
end


function arr.max(array)
---Find the largest value in the array.
checkType('Module:Array.max', 1, array, 'table')
---@param arr any[] # The values need to be comparable using the `<` operator.
local val, _, i = arr.max_by(array, function(x) return x end)
---@return any elem
---@return integer i # The index of the largest value.
function Array.max(arr)
checkType('Module:Array.max', 1, arr, 'table')
local val, _, i = Array.max_by(arr, function(x) return x end)
return val, i
return val, i
end
end


function arr.min(array)
---Find the smallest value in the array.
checkType('Module:Array.min', 1, array, 'table')
---@param arr any[] # The values need to be comparable using the `<` operator.
local val, _, i = arr.max_by(array, function(x) return -x end)
---@return any elem
---@return integer i # The index of the smallest value.
function Array.min(arr)
checkType('Module:Array.min', 1, arr, 'table')
local val, _, i = Array.max_by(arr, function(x) return -x end)
return val, i
return val, i
end
end


function arr.new(array)
---Turn the input table into an Array. This makes it possible to use the colon `:` operator to access the Array methods.
array = array or {}
---
for _, v in pairs(array) do
---It also enables the use of math operators with the array.
---```
---local x = arr.new{ 1, 2, 3 }
---local y = arr{ 4, 5, 6 } -- Alternative notation
---
---print( -x ) --> { -1, -2, -3 }
---print( x + 2 ) --> { 3, 4, 5 }
---print( x - 2 ) --> { -1, 0, 1 }
---print( x * 2 ) --> { 2, 4, 6 }
---print( x / 2 ) --> { 0.5, 1, 1.5 }
---print( x ^ 2 ) --> { 1, 4, 9 }
---
---print( x + y ) --> { 5, 7, 9 }
---print( x .. y ) --> { 1, 2, 3, 4, 5, 6 }
---print( (x .. y):reject{3, 4, 5} ) --> { 1, 2, 6 }
---print( x:sum() ) --> 6
---
---print( x:update( {1, 3}, y:get{2, 3} * 2 ) ) --> { 10, 2, 12 }
---```
---@param arr? any[]
---@return Array
function Array.new(arr)
local obj = arr or {}
for _, v in pairs(obj) do
if type(v) == 'table' then
if type(v) == 'table' then
arr.new(v)
Array.new(v)
end
end
end
end


if getmetatable(array) == nil then
if getmetatable(obj) == nil then
setmetatable(array, arr)
setmetatable(obj, Array)
end
end


return array
return obj
end
 
---Creates an object that returns a value that is `step` higher than the previous value each time it gets called.
---
---The stored value can be read without incrementing by reading the `val` field.
---
---A new stored value can be set through the `val` field.
---
---A new step size can be set through the `step` field.
---```
---local inc = arr.newIncrementor(10, 5)
---print( inc() ) --> 10
---print( inc() ) --> 15
---print( inc.val ) --> 15
---inc.val = 100
---inc.step = 20
---print( inc.val ) --> 100
---print( inc() ) --> 120
---```
---@param start? number # Default is 1.
---@param step? number # Default is 1.
---@return Incrementor
function Array.newIncrementor(start, step)
checkType('Module:Array.newIncrementor', 1, start, 'number', true)
checkType('Module:Array.newIncrementor', 2, step, 'number', true)
step = step or 1
local n = (start or 1) - step
---@class Incrementor
local obj = {}
return setmetatable(obj, {
__call = function() n = n + step return n end,
__tostring = function() return n end,
__index = function() return n end,
__newindex = function(self, k, v)
if k == 'step' and type(v) == 'number' then
step = v
elseif type(v) == 'number' then
n = v
end
end,
__concat = function(x, y) return tostring(x) .. tostring(y) end
})
end
end


function arr.range(start, stop, step)
---Returns a range of numbers.
---@param start number # Start value inclusive.
---@param stop number # Stop value inclusive for integers, exclusive for floats.
---@param step? number # Default is 1.
---@return Array
---@overload fun(stop: number): Array
function Array.range(start, stop, step)
checkType('Module:Array.range', 1, start, 'number')
checkType('Module:Array.range', 1, start, 'number')
checkType('Module:Array.range', 2, stop, 'number', true)
checkType('Module:Array.range', 2, stop, 'number', true)
checkType('Module:Array.range', 3, step, 'number', true)
checkType('Module:Array.range', 3, step, 'number', true)
local array = setmetatable({}, arr)
local arr = {}
local len = 0
local len = 0
if not stop then
if not stop then
Line 433: Line 699:
for i = start, stop, step or 1 do
for i = start, stop, step or 1 do
len = len + 1
len = len + 1
array[len] = i
arr[len] = i
end
end
return array
return setmetatable(arr, Array)
end
end


function arr.reduce(array, fn, accumulator)
---Condenses the array into a single value.
checkType('Module:Array.reduce', 1, array, 'table')
---
---For each element `fn` is called with the current element, the current accumulator, and the current element index. The returned value of `fn` becomes the accumulator for the next element.
---
---If no `accumulator` value is given at the start then the first element off `arr` becomes the accumulator and the iteration starts from the second element.
---```
---local t = { 1, 2, 3, 4 }
---local sum = arr.reduce( t, function(elem, acc) return acc + elem end ) -- sum == 10
---```
---@param arr any[]
---@param fn fun(elem: any, acc: any, i?: integer): any # The result of this function becomes the `acc` for the next element.
---@param accumulator? any
---@return any # This is the last accumulator value.
function Array.reduce(arr, fn, accumulator)
checkType('Module:Array.reduce', 1, arr, 'table')
checkType('Module:Array.reduce', 2, fn, 'function')
checkType('Module:Array.reduce', 2, fn, 'function')
local acc = accumulator
local acc = accumulator
local i = 1
local i = 1
if acc == nil then
if acc == nil then
acc = array[1]
acc = arr[1]
i = 2
i = 2
end
end
while array[i] ~= nil do
while arr[i] ~= nil do
acc = fn(array[i], acc, i)
acc = fn(arr[i], acc, i)
i = i + 1
i = i + 1
end
end
Line 454: Line 733:
end
end


function arr.reject(array, fn)
---Make a copy off `arr` with certain values removed.
checkType('Module:Array.reject', 1, array, 'table')
---
checkTypeMulti('Module:Array.reject', 2, fn, {'function', 'table', 'number', 'boolean'})
---Behaviour for different values of `val`:
if fn == nil then fn = function(item) return item end end
---* `boolean` | `number` - Remove values equal to this.
if type(fn) ~= 'function' and type(fn) ~= 'table' then
---* `table` - Remove all values in this table.
fn = {fn}
---* `fun(elem: any, i?: integer): boolean` - Remove elements for which the functions returns **true**.
---@generic T: any[]
---@param arr T
---@param val table|function|number|boolean
---@return T
function Array.reject(arr, val)
checkType('Module:Array.reject', 1, arr, 'table')
checkTypeMulti('Module:Array.reject', 2, val, {'function', 'table', 'number', 'boolean'})
if type(val) ~= 'function' and type(val) ~= 'table' then
val = {val}
end
end
local r = setmetatable({}, getmetatable(array))
local r = {}
local len = 0
local len = 0
if type(fn) == 'function' then
if type(val) == 'function' then
local i = 1
local i = 1
while array[i] ~= nil do
while arr[i] ~= nil do
if not fn(array[i], i) then
if not val(arr[i], i) then
len = len + 1
len = len + 1
r[len] = array[i]
r[len] = arr[i]
end
end
i = i + 1
i = i + 1
Line 474: Line 762:
else
else
local rejectMap = {}
local rejectMap = {}
arr.each(fn, function(item) rejectMap[item] = true end)
Array.each(val --[[@as any[] ]], function(item) rejectMap[item] = true end)
local i = 1
local i = 1
while array[i] ~= nil do
while arr[i] ~= nil do
if not rejectMap[array[i]] then
if not rejectMap[arr[i]] then
len = len + 1
len = len + 1
r[len] = array[i]
r[len] = arr[i]
end
end
i = i + 1
i = i + 1
end
end
end
end
return r
return setmetatable(r, getmetatable(arr))
end
end


function arr.rep(val, n)
---Returns an Array with `val` repeated `n` times.
---@param val any
---@param n integer
---@return Array
function Array.rep(val, n)
checkType('Module:Array.rep', 2, n, 'number')
checkType('Module:Array.rep', 2, n, 'number')
local r = setmetatable({}, arr)
local r = {}
for i = 1, n do
for i = 1, n do
r[i] = val
r[i] = val
end
end
return r
return setmetatable(r, Array)
end
end


function arr.scan(array, fn, accumulator)
---Condenses the array into a single value while saving every accumulator value.
checkType('Module:Array.scan', 1, array, 'table')
---
---For each element `fn` is called with the current element, the current accumulator, and the current element index. The returned value of `fn` becomes the accumulator for the next element.
---
---If no `accumulator` value is given at the start then the first element off `arr` becomes the accumulator and the iteration starts from the second element.
---```
---local t = { 1, 2, 3, 4 }
---local x = arr.scan( t, function(elem, acc) return acc + elem end ) -- x = { 1, 3, 6, 10 }
---```
---@generic T: any[]
---@param arr T
---@param fn fun(elem: any, acc: any, i?: integer): any # Returned value becomes the accumulator for the next element.
---@param accumulator? any
---@return T
function Array.scan(arr, fn, accumulator)
checkType('Module:Array.scan', 1, arr, 'table')
checkType('Module:Array.scan', 2, fn, 'function')
checkType('Module:Array.scan', 2, fn, 'function')
local acc = accumulator
local acc = accumulator
local r = setmetatable({}, getmetatable(array))
local r = {}
local i = 1
local i = 1
while array[i] ~= nil do
while arr[i] ~= nil do
if i == 1 and not accumulator then
if i == 1 and not accumulator then
acc = array[i]
acc = arr[i]
else
else
acc = fn(array[i], acc)
acc = fn(arr[i], acc, i)
end
end
r[i] = acc
r[i] = acc
i = i + 1
i = i + 1
end
end
return r
return setmetatable(r, getmetatable(arr))
end
 
---Update a range of index with a range of values.
---
---If if only one value is given but multiple indexes than that value is set for all those indexes.
---
---If `values` is a table then it must of the same length as `indexes`.
---@generic T: any[]
---@param arr T
---@param indexes integer|integer[]
---@param values any|any[]
---@return T
function Array.set(arr, indexes, values)
checkType('Module:Array.set', 1, arr, 'table')
checkTypeMulti('Module:Array.set', 2, indexes, {'table', 'number'})
local mt = getmetatable(arr)
setmetatable(arr, nil)
if type(indexes) == 'number' then
indexes = {indexes}
end
if type(values) == 'table' then
assert(#indexes == #values, string.format("Module:Array.set: 'indexes' and 'values' arrays are not equal length (#indexes = %d, #values = %d)", #indexes, #values))
for i = 1, #indexes do
arr[indexes[i]] = values[i]
end
else
for i = 1, #indexes do
arr[indexes[i]] = values
end
end
return setmetatable(arr, mt)
end
end


function arr.slice(array, start, finish)
---Extract a subtable from `arr`.
checkType('Module:Array.slice', 1, array, 'table')
---@generic T: any[]
---@param arr T
---@param start integer # Start index. Use negative values to count form the end of the array.
---@param stop integer # Stop index. Use negative values to count form the end of the array.
---@return T
---@overload fun(arr: T, stop: integer): T
function Array.slice(arr, start, stop)
checkType('Module:Array.slice', 1, arr, 'table')
checkType('Module:Array.slice', 2, start, 'number', true)
checkType('Module:Array.slice', 2, start, 'number', true)
checkType('Module:Array.slice', 3, finish, 'number', true)
checkType('Module:Array.slice', 3, stop, 'number', true)
start = start or 1
start = start or #arr
finish = finish or #array
if start < 0 then
if start < 0 and finish == nil then
start = #arr + start
finish = #array + start
end
if stop == nil then
stop = start
start = 1
start = 1
elseif start < 0 then
start = #array + start
end
end
if finish < 0 then
if stop < 0 then
finish = #array + finish
stop = #arr + stop
end
end
local r = setmetatable({}, getmetatable(array))
local r = {}
local len = 0
local len = 0
for i = start, finish do
for i = start, stop do
len = len + 1
len = len + 1
r[len] = array[i]
r[len] = arr[i]
end
end
return r
return setmetatable(r, getmetatable(arr))
end
end


function arr.split(array, count)
---Split `arr` into two arrays.
checkType('Module:Array.split', 1, array, 'table')
---@generic T: any[]
checkType('Module:Array.split', 2, count, 'number')
---@param arr T
local x = setmetatable({}, getmetatable(array))
---@param index integer # Index to split on.
local y = setmetatable({}, getmetatable(array))
---@return T x # [1, index]
for i = 1, #array do
---@return T y # [index + 1, #arr]
table.insert(i <= count and x or y, array[i])
function Array.split(arr, index)
checkType('Module:Array.split', 1, arr, 'table')
checkType('Module:Array.split', 2, index, 'number')
local x = {}
local y = {}
for i = 1, #arr do
table.insert(i <= index and x or y, arr[i])
end
end
return x, y
return setmetatable(x, getmetatable(arr)), setmetatable(y, getmetatable(arr))
end
end


function arr.sum(array)
---Returns the sum of all elements of `arr`.
checkType('Module:Array.sum', 1, array, 'table')
---@param arr number[]
---@return number
function Array.sum(arr)
checkType('Module:Array.sum', 1, arr, 'table')
local res = 0
local res = 0
for i = 1, #array do
for i = 1, #arr do
res = res + array[i]
res = res + arr[i]
end
end
return res
return res
end
end


function arr.take(array, count, offset)
---Extract a subtable from `arr`.
checkType('Module:Array.take', 1, array, 'table')
---@generic T: any[]
---@param arr T
---@param count integer # Length of the subtable.
---@param start? integer # Start index. Default is 1.
---@return T
function Array.take(arr, count, start)
checkType('Module:Array.take', 1, arr, 'table')
checkType('Module:Array.take', 2, count, 'number')
checkType('Module:Array.take', 2, count, 'number')
checkType('Module:Array.take', 3, offset, 'number', true)
checkType('Module:Array.take', 3, start, 'number', true)
local x = setmetatable({}, getmetatable(array))
local x = {}
for i = offset or 1, #array do
start = start or 1
if i <= count then
for i = start, math.min(#arr, count + start - 1) do
table.insert(x, array[i])
table.insert(x, arr[i])
end
end
end
return x
return setmetatable(x, getmetatable(arr))
end
end


function arr.take_every(array, n, offset)
---Extract a subtable from `arr`.
checkType('Module:Array.take_every', 1, array, 'table')
---```
---local t = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
---local x = arr.take_every( t, 2 )      --> x = { 1, 3, 5, 7, 9 }
---local x = arr.take_every( t, 2, 3 )    --> x = { 1, 3, 5 }
---local x = arr.take_every( t, 2, 3, 2 ) --> x = { 2, 4, 6 }
--- ```
---@generic T: any[]
---@param arr T
---@param n integer # Step size.
---@param start? integer # Start index.
---@param count? integer # Max amount of elements to get.
---@return T
function Array.take_every(arr, n, start, count)
checkType('Module:Array.take_every', 1, arr, 'table')
checkType('Module:Array.take_every', 2, n, 'number')
checkType('Module:Array.take_every', 2, n, 'number')
checkType('Module:Array.take_every', 3, offset, 'number', true)
checkType('Module:Array.take_every', 3, start, 'number', true)
local r = setmetatable({}, getmetatable(array))
checkType('Module:Array.take_every', 4, count, 'number', true)
count = count or #arr
local r = {}
local len = 0
local len = 0
local i = offset or 1
local i = start or 1
while array[i] ~= nil do
while arr[i] ~= nil and len < count do
len = len + 1
len = len + 1
r[len] = array[i]
r[len] = arr[i]
i = i + n
i = i + n
end
end
return r
return setmetatable(r, getmetatable(arr))
end
end


function arr.unique(array, fn)
---Return a new table with all duplicates removed.
checkType('Module:Array.unique', 1, array, 'table')
---@generic T: any[]
---@param arr T
---@param fn? fun(elem: any): any # Function to generate an id for each element. The result will then contain elements that generated unique ids.
---@return T
function Array.unique(arr, fn)
checkType('Module:Array.unique', 1, arr, 'table')
checkType('Module:Array.unique', 2, fn, 'function', true)
checkType('Module:Array.unique', 2, fn, 'function', true)
fn = fn or function(item) return item end
fn = fn or function(item) return item end
local r = setmetatable({}, getmetatable(array))
local r = {}
local len = 0
local len = 0
local hash = {}
local hash = {}
local i = 1
local i = 1
while array[i] ~= nil do
while arr[i] ~= nil do
local id = fn(array[i])
local id = fn(arr[i])
if not hash[id] then
if not hash[id] then
len = len + 1
len = len + 1
r[len] = array[i]
r[len] = arr[i]
hash[id] = true
hash[id] = true
end
end
i = i + 1
i = i + 1
end
end
return r
return setmetatable(r, getmetatable(arr))
end
end


function arr.update(array, indexes, values)
---Combine elements with the same index from multiple arrays.
checkType('Module:Array.update', 1, array, 'table')
---```
checkTypeMulti('Module:Array.update', 2, indexes, {'table', 'number'})
---local x = {1, 2, 3}
if type(indexes) == 'number' then
---local y = {4, 5, 6, 7}
indexes = {indexes}
---local z = arr.zip( x, y ) --> z = { { 1, 4 }, { 2, 5 }, { 3, 6 }, { 7 } }
---```
---@param ... any[]
---@return Array
function Array.zip(...)
local arrs = { ... }
checkType('Module:Array.zip', 1, arrs[1], 'table')
local r = {}
local _, longest = Array.max_by(arrs, function(arr) return #arr end)
for i = 1, longest do
local q = {}
for j = 1, #arrs do
table.insert(q, arrs[j][i])
end
table.insert(r, setmetatable(q, Array))
end
end
if type(values) == 'table' then
return setmetatable(r, Array)
assert(#indexes == #values, 'Values array must be of equal length as index array')
end
for i = 1, #indexes do
 
array[indexes[i]] = values[i]
-- Range indexing has a performance impact so this is placed in a separate subclass
Array.RI_mt = {}
for k, v in pairs(Array) do
Array.RI_mt[k] = v
end
 
function Array.RI_mt.__index(t, k)
if type(k) == 'table' then
local res = {}
for i = 1, #k do
res[i] = t[k[i]]
end
end
return setmetatable(res, Array)
else
else
for i = 1, #indexes do
return Array[k]
array[indexes[i]] = values
end
end
 
function Array.RI_mt.__newindex(t, k, v)
if type(k) == 'table' then
if type(v) == 'table' then
for i = 1, #k do
t[k[i]] = v[i]
end
else
for i = 1, #k do
t[k[i]] = v
end
end
end
else
rawset(t, k, v)
end
end
return array
end
end


function arr.zip(...)
---Enable range indexing on the input array.
local arrays = { ... }
---
checkType('Module:Array.zip', 1, arrays[1], 'table')
---This has a performance impact on reads and writes to the table.
local r = setmetatable({}, getmetatable(arrays[1]))
---```
local _, longest = arr.max_by(arrays, function(array) return #array end)
---local t = arr{10, 11, 12, 13, 14, 15}:ri()
for i = 1, longest do
---print( t[{2, 3}] ) --> { 11, 12 }
local q = {}
---```
for j = 1, #arrays do
---@param arr any[]
table.insert(q, arrays[j][i])
---@param recursive? boolean # Default is false.
---@return Array
function Array.ri(arr, recursive)
checkType('Module:Array.ri', 1, arr, 'table')
checkType('Module:Array.ri', 2, recursive, 'boolean', true)
arr = arr or {}
if recursive then
for _, v in pairs(arr) do
if type(v) == 'table' then
Array.ri(v, true)
end
end
end
table.insert(r, q)
end
end
return r
 
if getmetatable(arr) == nil or getmetatable(arr) == Array then
setmetatable(arr, Array.RI_mt)
end
 
return arr
end
 
---Globally enable range indexing on all Array objects by default.
---@param set boolean
function Array.allwaysAllowRangeIndexing(set)
checkType('Module:Array.allwaysAllowRangeIndexing', 1, set, 'boolean')
if set then
Array.__index = Array.RI_mt.__index
Array.__newindex = Array.RI_mt.__newindex
else
Array.__index = Array
Array.__newindex = nil
end
end
end


return arr
return Array
-- </nowiki>

Latest revision as of 09:52, 22 December 2023

Module documentation[view][edit][history][purge]
This documentation is transcluded from Module:Array/doc. Changes can be proposed in the talk page.
Function list
L 40 — Array.__concat
L 56 — Array.__unm
L 66 — mathTemplate
L 89 — Array.__add
L 93 — Array.__sub
L 97 — Array.__mul
L 101 — Array.__div
L 105 — Array.__pow
L 109 — Array.__eq
L 128 — Array.all
L 153 — Array.any
L 174 — Array.clean
L 190 — Array.clone
L 208 — Array.contains
L 222 — Array.containsAny
L 241 — Array.containsAll
L 267 — Array.convolve
L 286 — Array.condenseSparse
L 309 — Array.count
L 330 — Array.diff
L 346 — Array.each
L 361 — Array.filter
L 383 — Array.find
L 406 — Array.find_index
L 429 — Array.get
L 448 — Array.int
L 467 — Array.intersect
L 487 — Array.intersects
L 510 — Array.insert
L 541 — Array.last
L 552 — Array.map
L 574 — Array.max_by
L 587 — Array.max
L 597 — Array.min
L 626 — Array.new
L 661 — Array.newIncrementor
L 689 — Array.range
L 719 — Array.reduce
L 745 — Array.reject
L 781 — Array.rep
L 804 — Array.scan
L 832 — Array.set
L 860 — Array.slice
L 890 — Array.split
L 904 — Array.sum
L 919 — Array.take
L 944 — Array.take_every
L 966 — Array.unique
L 994 — Array.zip
L 1015 — Array.RI_mt.__index
L 1027 — Array.RI_mt.__newindex
L 1053 — Array.ri
L 1074 — Array.allwaysAllowRangeIndexing

This module is a helper module to be used by other modules; it may not designed to be invoked directly. See Module:Array on RuneScape Wiki for more details.


-- Imported from: https://runescape.wiki/w/Module:Array

local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType
local checkTypeMulti = libraryUtil.checkTypeMulti

---@class Array
---@operator call(any[]): Array
---@operator concat(any[]): Array
---@operator concat(number|string|function): string
---@operator unm: Array
---@operator add(number|number[]|Array): Array
---@operator sub(number|number[]|Array): Array
---@operator mul(number|number[]|Array): Array
---@operator div(number|number[]|Array): Array
---@operator pow(number|number[]|Array): Array
local Array = {
	pop = table.remove
}
Array.__index = Array

setmetatable(Array, {
	__index = table,
	__call = function (_, arr)
		return Array.new(arr)
	end
})

-- function Array.__tostring(arr)
-- 	-- local dumpObject = require('Module:Logger').dumpObject
-- 	require 'log'
-- 	local dumpObject = dumpObject
-- 	local mt = getmetatable(arr)
-- 	setmetatable(arr, nil)
-- 	local str = dumpObject(arr, {clean=true, collapseLimit=100})
-- 	setmetatable(arr, mt)
-- 	return str
-- end

function Array.__concat(lhs, rhs)
	if type(lhs) == 'table' and type(rhs) == 'table' then
		local res = {}
		for i = 1, #lhs do
			res[i] = lhs[i]
		end
		local l = #lhs
		for i = 1, #rhs do
			res[i + l] = rhs[i]
		end
		return setmetatable(res, getmetatable(lhs) or getmetatable(rhs))
	else
		return tostring(lhs) .. tostring(rhs)
	end
end

function Array.__unm(arr)
	return Array.map(arr, function(x) return -x end)
end

---@param lhs number|number[]|Array
---@param rhs number|number[]|Array
---@param funName string
---@param opName string
---@param fun fun(lhs: number, rhs: number): number
---@return Array
local function mathTemplate(lhs, rhs, funName, opName, fun)
	checkTypeMulti('Module:Array.' .. funName, 1, lhs, {'number', 'table'})
	checkTypeMulti('Module:Array.' .. funName, 2, rhs, {'number', 'table'})
	local res = {}

	if type(lhs) == 'number' then
		for i = 1, #rhs do
			res[i] = fun(lhs, rhs[i])
		end
	elseif type(rhs) == 'number' then
		for i = 1, #lhs do
			res[i] = fun(lhs[i], rhs)
		end
	else
		assert(#lhs == #rhs, string.format('Elementwise %s failed because arrays have different sizes (left: %d, right: %d)', opName, #lhs, #rhs))
		for i = 1, #lhs do
			res[i] = fun(lhs[i], rhs[i])
		end
	end

	return setmetatable(res, getmetatable(lhs) or getmetatable(rhs))
end

function Array.__add(lhs, rhs)
	return mathTemplate(lhs, rhs, '__add', 'addition', function(x, y) return x + y end)
end

function Array.__sub(lhs, rhs)
	return mathTemplate(lhs, rhs, '__sub', 'substraction', function(x, y) return x - y end)
end

function Array.__mul(lhs, rhs)
	return mathTemplate(lhs, rhs, '__mul', 'multiplication', function(x, y) return x * y end)
end

function Array.__div(lhs, rhs)
	return mathTemplate(lhs, rhs, '__div', 'division', function(x, y) return x / y end)
end

function Array.__pow(lhs, rhs)
	return mathTemplate(lhs, rhs, '__pow', 'exponentiation', function(x, y) return x ^ y end)
end

function Array.__eq(lhs, rhs)
	if #lhs ~= #rhs then
		return false
	end
	for i = 1, #lhs do
		if lhs[i] ~= rhs[i] then
			return false
		end
	end
	return true
end

---Behaviour depends on the value of `fn`:
---* `nil` - Checks that the array doesn't contain any **false** elements.
---* `fun(elem: any, i?: integer): boolean` - Returns **true** if `fn` returns **true** for every element.
---* `number` | `table` | `boolean` - Checks that all elements in `arr` are equal to this value.
---@param arr any[]
---@param fn? any
---@return boolean
function Array.all(arr, fn)
	checkType('Module:Array.all', 1, arr, 'table')
	if fn == nil then fn = function(item) return item end end
	if type(fn) ~= 'function' then
		local val = fn
		fn = function(item) return item == val end
	end
	local i = 1
	while arr[i] ~= nil do
		---@diagnostic disable-next-line: redundant-parameter
		if not fn(arr[i], i) then
			return false
		end
		i = i + 1
	end
	return true
end

---Behaviour depends on the value of `fn`:
---* `nil` - Checks that the array contains at least one non **false** element.
---* `fun(elem: any, i?: integer): boolean` - Returns **true** if `fn` returns **true** for at least one element.
---* `number` | `table` | `boolean` - Checks that `arr` contains this value.
---@param arr any[]
---@param fn? any
---@return boolean
function Array.any(arr, fn)
	checkType('Module:Array.any', 1, arr, 'table')
	if fn == nil then fn = function(item) return item end end
	if type(fn) ~= 'function' then
		local val = fn
		fn = function(item) return item == val end
	end
	local i = 1
	while arr[i] ~= nil do
		---@diagnostic disable-next-line: redundant-parameter
		if fn(arr[i], i) then
			return true
		end
		i = i + 1
	end
	return false
end

---Recursively removes all metatables.
---@param arr any[]
---@return any[]
function Array.clean(arr)
	checkType('Module:Array.clean', 1, arr, 'table')
	for i = 1, #arr do
		if type(arr[i]) == 'table' then
			Array.clean(arr[i])
		end
	end
	setmetatable(arr, nil)
	return arr
end

---Make a copy of the input table. Preserves metatables.
---@generic T: any[]
---@param arr T
---@param deep? boolean # Recursively clone subtables if **true**.
---@return T
function Array.clone(arr, deep)
	checkType('Module:Array.clone', 1, arr, 'table')
	checkType('Module:Array.clone', 2, deep, 'boolean', true)
	local res = {}
	for i = 1, #arr do
		if deep == true and type(arr[i]) == 'table' then
			res[i] = Array.clone(arr[i], true)
		else
			res[i] = arr[i]
		end
	end
	return setmetatable(res, getmetatable(arr))
end

---Check if `arr` contains `val`.
---@param arr any[]
---@param val any
---@return boolean
function Array.contains(arr, val)
	checkType('Module:Array.contains', 1, arr, 'table')
	for i = 1, #arr do
		if arr[i] == val then
			return true
		end
	end
	return false
end

---Check if `arr` contains any of the values in the table `t`.
---@param arr any[]
---@param t any[]
---@return boolean
function Array.containsAny(arr, t)
	checkType('Module:Array.containsAny', 1, arr, 'table')
	checkType('Module:Array.containsAny', 2, t, 'table')
	local lookupTbl = {}
	for i = 1, #t do
		lookupTbl[t[i]] = true
	end
	for i = 1, #arr do
		if lookupTbl[arr[i]] then
			return true
		end
	end
	return false
end

---Check if `arr` contains all values in the table `t`.
---@param arr any[]
---@param t any[]
---@return boolean
function Array.containsAll(arr, t)
	checkType('Module:Array.containsAll', 1, arr, 'table')
	checkType('Module:Array.containsAll', 2, t, 'table')
	local lookupTbl = {}
	local l = #t
	local trueCount = 0
	for i = 1, l do
		lookupTbl[t[i]] = false
	end
	for i = 1, #arr do
		if lookupTbl[arr[i]] == false then
			lookupTbl[arr[i]] = true
			trueCount = trueCount + 1
		end
		if trueCount == l then
			return true
		end
	end
	return false
end

---Convolute two number arrays.
---@generic T: number[]
---@param x T
---@param y T
---@return T
function Array.convolve(x, y)
	checkType('Module:Array.convolve', 1, x, 'table')
	checkType('Module:Array.convolve', 2, y, 'table')
	local z = {}
    local xLen, yLen = #x, #y
    for j = 1, (xLen + yLen - 1) do
        local sum = 0
        for k = math.max(1, j - yLen + 1), math.min(xLen, j) do
            sum = sum + x[k] * y[j-k+1]
        end
        z[j] = sum
    end
    return setmetatable(z, getmetatable(x) or getmetatable(y))
end

---Remove **nil** values from `arr` while preserving order.
---@generic T: any[]
---@param arr T
---@return T
function Array.condenseSparse(arr)
	checkType('Module:Array.condenseSparse', 1, arr, 'table')
	local keys = {}
	local res = {}
	local l = 0
	for k in pairs(arr) do
		l = l + 1
		keys[l] = k
	end
	table.sort(keys)
	for i =  1, l do
		res[i] = arr[keys[i]]
	end
	return setmetatable(res, getmetatable(arr))
end

---Behaviour depends on value of `val`:
---* `nil` - Counts the number of non **false** elements.
---* `fun(elem: any): boolean` - Count the number of times the function returned **true**.
---* `boolean` | `number` | `table` - Counts the number of times this value occurs in `arr`.
---@param arr any[]
---@param val? any
---@return integer
function Array.count(arr, val)
	checkType('Module:Array.count', 1, arr, 'table')
	if val == nil then val = function(item) return item end end
	if type(val) ~= 'function' then
		local _val = val
		val = function(item) return item == _val end
	end
	local count = 0
	for i = 1, #arr do
		if val(arr[i]) then
			count = count + 1
		end
	end
	return count
end

---Differentiate the array
---@generic T: number[]
---@param arr T
---@param order number? # Oder of the differentiation. Default is 1.
---@return T # Length is `#arr - order`
function Array.diff(arr, order)
	checkType('Module:Array.diff', 1, arr, 'table')
	checkType('Module:Array.diff', 2, order, 'number', true)
	local res = {}
	for i = 1, #arr - 1 do
		res[i] = arr[i+1] - arr[i]
	end
	if order and order > 1 then
		return Array.diff(res, order - 1)
	end
	return setmetatable(res, getmetatable(arr))
end

---Loops over `arr` and passes each element as the first argument to `fn`. This function returns nothing.
---@param arr any[]
---@param fn fun(elem: any, i?: integer)
function Array.each(arr, fn)
	checkType('Module:Array.each', 1, arr, 'table')
	checkType('Module:Array.each', 2, fn, 'function')
	local i = 1
	while arr[i] ~= nil do
		fn(arr[i], i)
		i = i + 1
	end
end

---Makes a copy of `arr` with only elements for which `fn` returned **true**.
---@generic T: any[]
---@param arr T
---@param fn fun(elem: any, i?: integer): boolean
---@return T
function Array.filter(arr, fn)
	checkType('Module:Array.filter', 1, arr, 'table')
	checkType('Module:Array.filter', 2, fn, 'function')
	local r = {}
	local len = 0
	local i = 1
	while arr[i] ~= nil do
		if fn(arr[i], i) then
			len = len + 1
			r[len] = arr[i]
		end
		i = i + 1
	end
	return setmetatable(r, getmetatable(arr))
end

---Find the first elements for which `fn` returns **true**.
---@param arr any[]
---@param fn any # A value to look for or a function of the form `fun(elem: any, i?: integer): boolean`.
---@param default? any # Value to return if no element passes the test.
---@return any? elem # The first element that passed the test.
---@return integer? i # The index of the item that passed the test.
function Array.find(arr, fn, default)
	checkType('Module:Array.find', 1, arr, 'table')
	checkTypeMulti('Module:Array.find_index', 2, fn, {'function', 'table', 'number', 'boolean'})
	if type(fn) ~= 'function' then
		local _val = fn
		fn = function(item) return item == _val end
	end
	local i = 1
	while arr[i] ~= nil do
		---@diagnostic disable-next-line: redundant-parameter
		if fn(arr[i], i) then
			return arr[i], i
		end
		i = i + 1
	end
	return default, nil
end

---Find the index of `val`.
---@param arr any[]
---@param val any # A value to look for or a function of the form `fun(elem: any, i?: integer): boolean`.
---@param default? any # Value to return if no element passes the test.
---@return integer?
function Array.find_index(arr, val, default)
	checkType('Module:Array.find_index', 1, arr, 'table')
	checkTypeMulti('Module:Array.find_index', 2, val, {'function', 'table', 'number', 'boolean'})
	if type(val) ~= 'function' then
		local _val = val
		val = function(item) return item == _val end
	end
	local i = 1
	while arr[i] ~= nil do
		---@diagnostic disable-next-line: redundant-parameter
		if val(arr[i], i) then
			return i
		end
		i = i + 1
	end
	return default
end

---Extracts a subset of `arr`.
---@generic T: any[]
---@param arr T
---@param indexes integer|integer[] # Indexes of the elements.
---@return T
function Array.get(arr, indexes)
	checkType('Module:Array.set', 1, arr, 'table')
	checkTypeMulti('Module:Array.set', 2, indexes, {'table', 'number'})
	if type(indexes) == 'number' then
		indexes = {indexes}
	end
	local res = {}
	for i = 1, #indexes do
		 res[i] = arr[indexes[i]]
	end
	return setmetatable(res, getmetatable(arr))
end

---Integrates the array. Effectively does $\left\{\sum^{n}_{start}{arr[n]} \,\Bigg|\, n \in [start, stop]\right\}$.
---@generic T: number[]
---@param arr T # number[]
---@param start? integer # Index where to start the summation. Defaults to 1.
---@param stop? integer # Index where to stop the summation. Defaults to #arr.
---@return T
function Array.int(arr, start, stop)
	checkType('Module:Array.int', 1, arr, 'table')
	checkType('Module:Array.int', 2, start, 'number', true)
	checkType('Module:Array.int', 3, stop, 'number', true)
	local res = {}
	start = start or 1
	stop = stop or #arr
	res[1] = arr[start]
	for i = 1, stop - start do
		res[i+1] = res[i] + arr[start + i]
	end
	return setmetatable(res, getmetatable(arr))
end

---Returns an array with elements that are present in both tables.
---@generic T: any[]
---@param arr1 T
---@param arr2 T
---@return T
function Array.intersect(arr1, arr2)
	checkType('Module:Array.intersect', 1, arr1, 'table')
	checkType('Module:Array.intersect', 2, arr2, 'table')
	local arr2Elements = {}
	local res = {}
	local len = 0
	Array.each(arr2, function(item) arr2Elements[item] = true end)
	Array.each(arr1, function(item)
		if arr2Elements[item] then
			len = len + 1
			res[len] = item
		end
	end)
	return setmetatable(res, getmetatable(arr1) or getmetatable(arr2))
end

---Checks if the two inputs have at least one element in common.
---@param arr1 any[]
---@param arr2 any[]
---@return boolean
function Array.intersects(arr1, arr2)
	checkType('Module:Array.intersects', 1, arr1, 'table')
	checkType('Module:Array.intersects', 2, arr2, 'table')
	local small = {}
	local large
	if #arr1 <= #arr2 then
		Array.each(arr1, function(item) small[item] = true end)
		large = arr2
	else
		Array.each(arr2, function(item) small[item] = true end)
		large = arr1
	end
	return Array.any(large, function(item) return small[item] end)
end

---Inserts values into `arr`.
---@generic T: any[]
---@param arr T
---@param val any # If `val` is an array and `unpackVal` is **true** then the individual elements of `val` are inserted.
---@param index? integer # Location to start the insertion. Default is at the end of `arr`.
---@param unpackVal? boolean # Default is **false**.
---@return T
---@overload fun(arr: T, val: any, unpackVal: boolean): T
function Array.insert(arr, val, index, unpackVal)
	checkType('Module:Array.insert', 1, arr, 'table')
	checkTypeMulti('Module:Array.insert', 3, index, {'number', 'boolean', 'nil'})
	checkType('Module:Array.insert', 4, unpackVal, 'boolean', true)
	if type(index) == 'boolean'  then
		unpackVal, index = index, nil
	end
	local len = #arr
	index = index or (len + 1)
	local mt = getmetatable(arr)
	setmetatable(arr, nil)

	if type(val) == 'table' and unpackVal then
		local len2 = #val
		for i = 0, len - index do
			arr[len + len2 - i] = arr[len - i]
		end
		for i = 0, len2 - 1 do
			arr[index + i] = val[i + 1]
		end
	else
		table.insert(arr, index, val)
	end

	return setmetatable(arr, mt)
end

---Returns the last element of `arr`.
---@param arr any[]
---@param offset? integer
---@return any
function Array.last(arr, offset)
	checkType('Module:Array.last', 1, arr, 'table')
	checkType('Module:Array.last', 2, offset, 'number', true)
	return arr[#arr + offset]
end

---Returns a new table were each element of `arr` is modified by `fn`.
---@generic T: any[]
---@param arr T
---@param fn fun(elem: any, i?: integer): any # First argument is the current element, the second argument is the index of the current element.
---@return T
function Array.map(arr, fn)
	checkType('Module:Array.map', 1, arr, 'table')
	checkType('Module:Array.map', 2, fn, 'function')
	local len = 0
	local r = {}
	local i = 1
	while arr[i] ~= nil do
		local tmp = fn(arr[i], i)
		if tmp ~= nil then
			len = len + 1
			r[len] = tmp
		end
		i = i + 1
	end
	return setmetatable(r, getmetatable(arr))
end

---Find the element for which `fn` returned the largest value.
---@param arr any[]
---@param fn fun(elem: any): any # The returned value needs to be comparable using the `<` operator.
---@return any elem # The element with the largest `fn` value.
---@return integer i # The index of this element.
function Array.max_by(arr, fn)
	checkType('Module:Array.max_by', 1, arr, 'table')
	checkType('Module:Array.max_by', 2, fn, 'function')
	return unpack(Array.reduce(arr, function(new, old, i)
		local y = fn(new)
		return y > old[2] and {new, y, i} or old
	end, {nil, -math.huge}))
end

---Find the largest value in the array.
---@param arr any[] # The values need to be comparable using the `<` operator.
---@return any elem
---@return integer i # The index of the largest value.
function Array.max(arr)
	checkType('Module:Array.max', 1, arr, 'table')
	local val, _, i = Array.max_by(arr, function(x) return x end)
	return val, i
end

---Find the smallest value in the array.
---@param arr any[] # The values need to be comparable using the `<` operator.
---@return any elem
---@return integer i # The index of the smallest value.
function Array.min(arr)
	checkType('Module:Array.min', 1, arr, 'table')
	local val, _, i = Array.max_by(arr, function(x) return -x end)
	return val, i
end

---Turn the input table into an Array. This makes it possible to use the colon `:` operator to access the Array methods.
---
---It also enables the use of math operators with the array.
---```
---local x = arr.new{ 1, 2, 3 }
---local y = arr{ 4, 5, 6 } -- Alternative notation
---
---print( -x ) --> { -1, -2, -3 }
---print( x + 2 ) --> { 3, 4, 5 }
---print( x - 2 ) --> { -1, 0, 1 }
---print( x * 2 ) --> { 2, 4, 6 }
---print( x / 2 ) --> { 0.5, 1, 1.5 }
---print( x ^ 2 ) --> { 1, 4, 9 }
---
---print( x + y ) --> { 5, 7, 9 }
---print( x .. y ) --> { 1, 2, 3, 4, 5, 6 }
---print( (x .. y):reject{3, 4, 5} ) --> { 1, 2, 6 }
---print( x:sum() ) --> 6
---
---print( x:update( {1, 3}, y:get{2, 3} * 2 ) ) --> { 10, 2, 12 }
---```
---@param arr? any[]
---@return Array
function Array.new(arr)
	local obj = arr or {}
	for _, v in pairs(obj) do
		if type(v) == 'table' then
			Array.new(v)
		end
	end

	if getmetatable(obj) == nil then
		setmetatable(obj, Array)
	end

	return obj
end

---Creates an object that returns a value that is `step` higher than the previous value each time it gets called.
---
---The stored value can be read without incrementing by reading the `val` field.
---
---A new stored value can be set through the `val` field.
---
---A new step size can be set through the `step` field.
---```
---local inc = arr.newIncrementor(10, 5)
---print( inc() ) --> 10
---print( inc() ) --> 15
---print( inc.val ) --> 15
---inc.val = 100
---inc.step = 20
---print( inc.val ) --> 100
---print( inc() ) --> 120
---```
---@param start? number # Default is 1.
---@param step? number # Default is 1.
---@return Incrementor
function Array.newIncrementor(start, step)
	checkType('Module:Array.newIncrementor', 1, start, 'number', true)
	checkType('Module:Array.newIncrementor', 2, step, 'number', true)
	step = step or 1
	local n = (start or 1) - step
	---@class Incrementor
	local obj = {}
	return setmetatable(obj, {
		__call = function() n = n + step return n end,
		__tostring = function() return n end,
		__index = function() return n end,
		__newindex = function(self, k, v)
			if k == 'step' and type(v) == 'number' then
				step = v
			elseif type(v) == 'number' then
				n = v
			end
		end,
		__concat = function(x, y) return tostring(x) .. tostring(y) end
	})
end

---Returns a range of numbers.
---@param start number # Start value inclusive.
---@param stop number # Stop value inclusive for integers, exclusive for floats.
---@param step? number # Default is 1.
---@return Array
---@overload fun(stop: number): Array
function Array.range(start, stop, step)
	checkType('Module:Array.range', 1, start, 'number')
	checkType('Module:Array.range', 2, stop, 'number', true)
	checkType('Module:Array.range', 3, step, 'number', true)
	local arr = {}
	local len = 0
	if not stop then
		stop = start
		start = 1
	end
	for i = start, stop, step or 1 do
		len = len + 1
		arr[len] = i
	end
	return setmetatable(arr, Array)
end

---Condenses the array into a single value.
---
---For each element `fn` is called with the current element, the current accumulator, and the current element index. The returned value of `fn` becomes the accumulator for the next element.
---
---If no `accumulator` value is given at the start then the first element off `arr` becomes the accumulator and the iteration starts from the second element.
---```
---local t = { 1, 2, 3, 4 }
---local sum = arr.reduce( t, function(elem, acc) return acc + elem end ) -- sum == 10
---```
---@param arr any[]
---@param fn fun(elem: any, acc: any, i?: integer): any # The result of this function becomes the `acc` for the next element.
---@param accumulator? any
---@return any # This is the last accumulator value.
function Array.reduce(arr, fn, accumulator)
	checkType('Module:Array.reduce', 1, arr, 'table')
	checkType('Module:Array.reduce', 2, fn, 'function')
	local acc = accumulator
	local i = 1
	if acc == nil then
		acc = arr[1]
		i = 2
	end
	while arr[i] ~= nil do
		acc = fn(arr[i], acc, i)
		i = i + 1
	end
	return acc
end

---Make a copy off `arr` with certain values removed.
---
---Behaviour for different values of `val`:
---* `boolean` | `number` - Remove values equal to this.
---* `table` - Remove all values in this table.
---* `fun(elem: any, i?: integer): boolean` - Remove elements for which the functions returns **true**.
---@generic T: any[]
---@param arr T
---@param val table|function|number|boolean
---@return T
function Array.reject(arr, val)
	checkType('Module:Array.reject', 1, arr, 'table')
	checkTypeMulti('Module:Array.reject', 2, val, {'function', 'table', 'number', 'boolean'})
	if type(val) ~= 'function' and type(val) ~= 'table' then
		val = {val}
	end
	local r = {}
	local len = 0
	if type(val) == 'function' then
		local i = 1
		while arr[i] ~= nil do
			if not val(arr[i], i) then
				len = len + 1
				r[len] = arr[i]
			end
			i = i + 1
		end
	else
		local rejectMap = {}
		Array.each(val --[[@as any[] ]], function(item) rejectMap[item] = true end)
		local i = 1
		while arr[i] ~= nil do
			if not rejectMap[arr[i]] then
				len = len + 1
				r[len] = arr[i]
			end
			i = i + 1
		end
	end
	return setmetatable(r, getmetatable(arr))
end

---Returns an Array with `val` repeated `n` times.
---@param val any
---@param n integer
---@return Array
function Array.rep(val, n)
	checkType('Module:Array.rep', 2, n, 'number')
	local r = {}
	for i = 1, n do
		r[i] = val
	end
	return setmetatable(r, Array)
end

---Condenses the array into a single value while saving every accumulator value.
---
---For each element `fn` is called with the current element, the current accumulator, and the current element index. The returned value of `fn` becomes the accumulator for the next element.
---
---If no `accumulator` value is given at the start then the first element off `arr` becomes the accumulator and the iteration starts from the second element.
---```
---local t = { 1, 2, 3, 4 }
---local x = arr.scan( t, function(elem, acc) return acc + elem end ) -- x = { 1, 3, 6, 10 }
---```
---@generic T: any[]
---@param arr T
---@param fn fun(elem: any, acc: any, i?: integer): any # Returned value becomes the accumulator for the next element.
---@param accumulator? any
---@return T
function Array.scan(arr, fn, accumulator)
	checkType('Module:Array.scan', 1, arr, 'table')
	checkType('Module:Array.scan', 2, fn, 'function')
	local acc = accumulator
	local r = {}
	local i = 1
	while arr[i] ~= nil do
		if i == 1 and not accumulator then
			acc = arr[i]
		else
			acc = fn(arr[i], acc, i)
		end
		r[i] = acc
		i = i + 1
	end
	return setmetatable(r, getmetatable(arr))
end

---Update a range of index with a range of values.
---
---If if only one value is given but multiple indexes than that value is set for all those indexes.
---
---If `values` is a table then it must of the same length as `indexes`.
---@generic T: any[]
---@param arr T
---@param indexes integer|integer[]
---@param values any|any[]
---@return T
function Array.set(arr, indexes, values)
	checkType('Module:Array.set', 1, arr, 'table')
	checkTypeMulti('Module:Array.set', 2, indexes, {'table', 'number'})
	local mt = getmetatable(arr)
	setmetatable(arr, nil)
	if type(indexes) == 'number' then
		indexes = {indexes}
	end
	if type(values) == 'table' then
		assert(#indexes == #values, string.format("Module:Array.set: 'indexes' and 'values' arrays are not equal length (#indexes = %d, #values = %d)", #indexes, #values))
		for i = 1, #indexes do
			arr[indexes[i]] = values[i]
		end
	else
		for i = 1, #indexes do
			arr[indexes[i]] = values
		end
	end
	return setmetatable(arr, mt)
end

---Extract a subtable from `arr`.
---@generic T: any[]
---@param arr T
---@param start integer # Start index. Use negative values to count form the end of the array.
---@param stop integer # Stop index. Use negative values to count form the end of the array.
---@return T
---@overload fun(arr: T, stop: integer): T
function Array.slice(arr, start, stop)
	checkType('Module:Array.slice', 1, arr, 'table')
	checkType('Module:Array.slice', 2, start, 'number', true)
	checkType('Module:Array.slice', 3, stop, 'number', true)
	start = start or #arr
	if start < 0 then
		start = #arr + start
	end
	if stop == nil then
		stop = start
		start = 1
	end
	if stop < 0 then
		stop = #arr + stop
	end
	local r = {}
	local len = 0
	for i = start, stop do
		len = len + 1
		r[len] = arr[i]
	end
	return setmetatable(r, getmetatable(arr))
end

---Split `arr` into two arrays.
---@generic T: any[]
---@param arr T
---@param index integer # Index to split on.
---@return T x # [1, index]
---@return T y # [index + 1, #arr]
function Array.split(arr, index)
	checkType('Module:Array.split', 1, arr, 'table')
	checkType('Module:Array.split', 2, index, 'number')
	local x = {}
	local y = {}
	for i = 1, #arr do
		table.insert(i <= index and x or y, arr[i])
	end
	return setmetatable(x, getmetatable(arr)), setmetatable(y, getmetatable(arr))
end

---Returns the sum of all elements of `arr`.
---@param arr number[]
---@return number
function Array.sum(arr)
	checkType('Module:Array.sum', 1, arr, 'table')
	local res = 0
	for i = 1, #arr do
		res = res + arr[i]
	end
	return res
end

---Extract a subtable from `arr`.
---@generic T: any[]
---@param arr T
---@param count integer # Length of the subtable.
---@param start? integer # Start index. Default is 1.
---@return T
function Array.take(arr, count, start)
	checkType('Module:Array.take', 1, arr, 'table')
	checkType('Module:Array.take', 2, count, 'number')
	checkType('Module:Array.take', 3, start, 'number', true)
	local x = {}
	start = start or 1
	for i = start, math.min(#arr, count + start - 1) do
		table.insert(x, arr[i])
	end
	return setmetatable(x, getmetatable(arr))
end

---Extract a subtable from `arr`.
---```
---local t = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
---local x = arr.take_every( t, 2 )       --> x = { 1, 3, 5, 7, 9 }
---local x = arr.take_every( t, 2, 3 )    --> x = { 1, 3, 5 }
---local x = arr.take_every( t, 2, 3, 2 ) --> x = { 2, 4, 6 }
--- ```
---@generic T: any[]
---@param arr T
---@param n integer # Step size.
---@param start? integer # Start index.
---@param count? integer # Max amount of elements to get.
---@return T
function Array.take_every(arr, n, start, count)
	checkType('Module:Array.take_every', 1, arr, 'table')
	checkType('Module:Array.take_every', 2, n, 'number')
	checkType('Module:Array.take_every', 3, start, 'number', true)
	checkType('Module:Array.take_every', 4, count, 'number', true)
	count = count or #arr
	local r = {}
	local len = 0
	local i = start or 1
	while arr[i] ~= nil and len < count do
		len = len + 1
		r[len] = arr[i]
		i = i + n
	end
	return setmetatable(r, getmetatable(arr))
end

---Return a new table with all duplicates removed.
---@generic T: any[]
---@param arr T
---@param fn? fun(elem: any): any # Function to generate an id for each element. The result will then contain elements that generated unique ids.
---@return T
function Array.unique(arr, fn)
	checkType('Module:Array.unique', 1, arr, 'table')
	checkType('Module:Array.unique', 2, fn, 'function', true)
	fn = fn or function(item) return item end
	local r = {}
	local len = 0
	local hash = {}
	local i = 1
	while arr[i] ~= nil do
		local id = fn(arr[i])
		if not hash[id] then
			len = len + 1
			r[len] = arr[i]
			hash[id] = true
		end
		i = i + 1
	end
	return setmetatable(r, getmetatable(arr))
end

---Combine elements with the same index from multiple arrays.
---```
---local x = {1, 2, 3}
---local y = {4, 5, 6, 7}
---local z = arr.zip( x, y ) --> z = { { 1, 4 }, { 2, 5 }, { 3, 6 }, { 7 } }
---```
---@param ... any[]
---@return Array
function Array.zip(...)
	local arrs = { ... }
	checkType('Module:Array.zip', 1, arrs[1], 'table')
	local r = {}
	local _, longest = Array.max_by(arrs, function(arr) return #arr end)
	for i = 1, longest do
		local q = {}
		for j = 1, #arrs do
			table.insert(q, arrs[j][i])
		end
		table.insert(r, setmetatable(q, Array))
	end
	return setmetatable(r, Array)
end

-- Range indexing has a performance impact so this is placed in a separate subclass
Array.RI_mt = {}
for k, v in pairs(Array) do
	Array.RI_mt[k] = v
end

function Array.RI_mt.__index(t, k)
	if type(k) == 'table' then
		local res = {}
		for i = 1, #k do
			res[i] = t[k[i]]
		end
		return setmetatable(res, Array)
	else
		return Array[k]
	end
end

function Array.RI_mt.__newindex(t, k, v)
	if type(k) == 'table' then
		if type(v) == 'table' then
			for i = 1, #k do
				t[k[i]] = v[i]
			end
		else
			for i = 1, #k do
				t[k[i]] = v
			end
		end
	else
		rawset(t, k, v)
	end
end

---Enable range indexing on the input array.
---
---This has a performance impact on reads and writes to the table.
---```
---local t = arr{10, 11, 12, 13, 14, 15}:ri()
---print( t[{2, 3}] ) --> { 11, 12 }
---```
---@param arr any[]
---@param recursive? boolean # Default is false.
---@return Array
function Array.ri(arr, recursive)
	checkType('Module:Array.ri', 1, arr, 'table')
	checkType('Module:Array.ri', 2, recursive, 'boolean', true)
	arr = arr or {}
	if recursive then
		for _, v in pairs(arr) do
			if type(v) == 'table' then
				Array.ri(v, true)
			end
		end
	end

	if getmetatable(arr) == nil or getmetatable(arr) == Array then
		setmetatable(arr, Array.RI_mt)
	end

	return arr
end

---Globally enable range indexing on all Array objects by default.
---@param set boolean
function Array.allwaysAllowRangeIndexing(set)
	checkType('Module:Array.allwaysAllowRangeIndexing', 1, set, 'boolean')
	if set then
		Array.__index = Array.RI_mt.__index
		Array.__newindex = Array.RI_mt.__newindex
	else
		Array.__index = Array
		Array.__newindex = nil
	end
end

return Array