Module:Array
This module is a helper module to be used by other modules; it may not designed to be invoked directly. See RuneScape:Lua/Helper modules for a full list and more information. For a full list of modules using this helper click here
Function | Type | Use |
---|---|---|
all( arr, [fn] ) | arr: any[] fn?: any -> boolean | Behaviour depends on the value of fn :
|
any( arr, [fn] ) | arr: any[] fn?: any -> boolean | Behaviour depends on the value of fn :
|
clean( arr ) | arr: any[] -> any[] | Recursively removes all metatables. |
clone( arr, [deep] ) | arr: any[] deep?: boolean -> any[] | Make a copy of the input table. Preserves metatables. |
contains( arr, val ) | arr: any[] val: any -> boolean | Check if arr contains val . |
containsAny( arr, t ) | arr: any[] t: any[] -> boolean | Check if arr contains any of the values in the table t . |
containsAll( arr, t ) | arr: any[] t: any[] -> boolean | Check if arr contains all values in the table t . |
convolve( x, y ) | x: number[] y: number[] -> number[] | Convolute two number arrays. |
condenseSparse( arr ) | arr: any[] -> any[] | Remove nil values from arr while preserving order. |
count( arr, fn ) | arr: any[] fn: any -> integer | Behaviour depends on value of val :
|
diff( arr, [order|1] ) | arr: number[] order?: number -> number[] | Differentiates arr . The length of the result is #arr - order long. |
each( arr, fn ) | arr: any[] fn: fun(elem: any, i?: integer) | Loops over the array part of arr and passes each element as the first argument to fn . This function returns nothing. |
filter( arr, fn ) | arr: any[] fn: fun(elem: any, i?: integer): boolean -> any[] | Makes a copy of arr with only elements for which fn returned true. |
find( arr, fn, [default] ) | arr: any[] fn: any default?: any -> any?, integer? | Behaviour depends on the value of fn :
|
find_index( arr, fn, [default] ) | arr: any[] fn: any default?: any -> integer? | Behaviour depends on the value of fn :
|
get( arr, indexes ) | arr: any[] indexes: integer|integer[] -> any[] | Extracts a subset of arr . |
int( arr, [start|1], [stop|#arr] ) | arr: number[] start?: number stop?: number -> number[] | Integrates arr from index start to stop . Effectively does . |
intersect( arr1, arr2 ) | arr1: any[] arr2: any[] -> any[] | Returns an array with elements that are present in both tables. |
intersects( arr1, arr2 ) | arr1: any[] arr2: any[] -> boolean | Checks if the two inputs have at least one element in common. |
insert( arr, val, [index], [unpackVal] ) insert( arr, val, [unpackVal] ) | arr: any[] val: any index?: integer unpackVal?: boolean -> any[] | Inserts values into arr . If val is an array and unpackVal is true then the individual elements of val are inserted. index is the location to start the insertion. Default is at the end of arr . |
last( arr ) | arr: any[] -> any | Returns the last element of arr . |
len( arr ) | arr: any[] -> integer | Returns the length of the array but it also works on proxy arrays like mw.loadData or mw.loadJsonData. |
map( arr, fn ) | arr: any[] fn: fun(elem: any, i?: integer): any -> any[] | Returns a new table were each element of arr is modified by fn . |
max_by( arr, fn ) | arr: any[] fn: fun(elem: any): any -> any, integer | Find the element for which fn returned the largest value. The returned value of fn needs to be comparable using the < operator. Returns three values: The element with the largest fn value, its fn result, and its index. |
max( arr ) | arr: any[] -> any, integer | Find the largest value in the array. The values need to be comparable using the < operator. Returns two values: the element and its index. |
min( arr ) | arr: any[] -> any, integer | Find the smallest value in the array. The values need to be comparable using the < operator. Returns two values: the element and its index. |
new( [arr|{}] ) | arr?: any[] -> any[] | 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
mw.logObject( -x ) --> { -1, -2, -3 }
mw.logObject( x + 2 ) --> { 3, 4, 5 }
mw.logObject( x - 2 ) --> { -1, 0, 1 }
mw.logObject( x * 2 ) --> { 2, 4, 6 }
mw.logObject( x / 2 ) --> { 0.5, 1, 1.5 }
mw.logObject( x ^ 2 ) --> { 1, 4, 9 }
mw.logObject( x + y ) --> { 5, 7, 9 }
mw.logObject( x .. y ) --> { 1, 2, 3, 4, 5, 6 }
mw.logObject( (x .. y):reject{3, 4, 5} ) --> { 1, 2, 6 }
mw.logObject( x:sum() ) --> 6
mw.logObject( x:update( {1, 3}, y:get{2, 3} * 2 ) ) --> { 10, 2, 12 }
|
newIncrementor( [start|1], [step|1] ) | start?: number step?: number -> Incrementor | Returns a new incrementor function. Every time this incrementor function is called it returns a number step higher than the previous call. The current value can be obtained with inc.n or set inc.n = number where inc is an incrementor function. The step size can be changed with inc.step = number . |
range( stop ) range( start, stop, [step|1] ) | start: number stop: number step?: number -> number[] | Returns a table containing a sequence of numbers from start to stop (both inclusive if ints, end-exclusive if floats) by step . range(4) produces {1, 2, 3, 4} (start defaults to 1 ). range(0, 4) produces {0, 1, 2, 3, 4} . When step is given, it specifies the increment (or decrement). |
reduce( arr, fn, [accumulator|arr[1]] ) | arr: any[] fn: fun(elem: any, acc: any, i?: integer): any accumulator?: any -> any | Condenses the array into a single value.
For each element If no local t = { 1, 2, 3, 4 }
local sum = arr.reduce( t, function(elem, acc) return acc + elem end ) -- sum == 10
|
reject( arr, val ) | arr: any[] val: any -> any[] | Make a copy off arr with certain values removed.
Behaviour for different values of
|
rep( val, n ) | val: any n: number -> any[] | Returns a table with n copies of val. |
scan( arr, fn, [accumulator|arr[1]] ) | arr: any[] fn: fun(elem: any, acc: any, i?: integer): any accumulator?: any -> any[] | Condenses the array into a single value while saving every accumulator value.
For each element If no local t = { 1, 2, 3, 4 }
local x = arr.scan( t, function(elem, acc) return acc + elem end ) -- x = { 1, 3, 6, 10 }
|
set( arr, indexes, values ) | arr: any[] indexes: integer|integer[] values: any|any[] -> any[] | 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. Ifvalues is a table then it must of the same length as indexes . |
slice( arr, [start|1], [stop|#arr] ) slice( arr, stop ) | arr: any[] start?: number stop?: number -> any[] | Returns a table containing all the elements of arr between the start and stop indices. The start and stop indices are inclusive. If start or stop are negative values then they are referenced to the end of the table. |
split( arr, index ) | arr: any[] index: number -> any[], any[] | Split arr into two arrays. Retuns two tables. The first contains elements from [1, index], and the second from [index + 1, #arr]. |
sum( arr ) | arr: number[] -> number | Returns the sum of all elements of arr . |
take( arr, count, [start|1] ) | arr: any[] count: number start?: number -> any[] | Extract a subtable from arr of count elements long starting from the start index. |
take_every( arr, n, [start|1], [count|#arr] ) | arr: any[] n: integer start?: integer count?: integer -> any[] | 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 = { 3, 5, 7, 9 }
local x = arr.take_every( t, 2, 3, 2 ) --> x = { 3, 5 }
|
unique( arr, [fn] ) | arr: any[] fn?: fun(elem: any): any -> any[] | Return a new table with all duplicates removed. fn is an optional function to generate an id for each element. The result will then contain elements that generated unique ids. The order of first occurance is preserved. |
zip( ... ) | ...any[] -> any[][] | 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 } }
|
local arr = require( 'Module:Array' )
local x = arr{1, 2, 3, 4, 10}
local y = arr{'a', 'b', 'b', 1}
arr.any( x, function( item ) return item == 3 end ) --> true
arr.all( y, function( item ) return type( item ) == 'string' end ) --> false
arr.map( x, function( item ) return item * 2 end ) --> { 2, 4, 6, 8, 20 }
arr.filter( y, function( item ) return type( item ) == 'string' end ) --> { "a", "b", "b" }
arr.reject( y, function( item ) return type( item ) == 'string' end ) --> { 1 }
arr.find( x, function( item ) return item > 5 end ) --> 10, 5
arr.find_index( y, function( item ) return type( item ) ~= 'string' end ) --> 4
arr.max_by( x, function( item ) return item * 2 end ) --> 10, 20, 5
arr.reduce( x, function( item, acc ) return acc + item*item end, 5 ) --> 135
arr.range( 10, 1, -3 ) --> { 10, 7, 4, 1 }
arr.scan( x, function( item, acc ) return acc + item*item end, 5 ) --> { 6, 10, 19, 35, 135 }
arr.slice( x, 2, 4 ) --> { 2, 3, 4 }
arr.split( x, 2 ) --> { 1, 2 }, { 3, 4, 10 }
arr.sum( x ) --> 20
arr.take( x, 2 ) --> { 1, 2 }
arr.take_every( x, 2 ) --> { 1, 3, 10 }
arr.unique( y ) --> { "a", "b", 1 }
arr.zip( x, y, {20, 30} ) --> { { 1, "a", 20 }, { 2, "b", 30 }, { 3, "b" }, { 4, 1 }, { 10 } }
arr.intersect( x, y ) --> { 1 }
arr.intersects( x, y ) --> true
arr.contains({ 1, 2, 3}, 3) --> true
arr.diff( x ) --> { 1, 1, 1, 6 }
arr.int( x ) --> { 1, 3, 6, 10, 20 }
arr.insert( x, y, 3 ) --> { 1, 2, { "a", "b", "b", 1 }, 3, 4, 10 }
inc = arr.newIncrementor( 10, 5 )
print( inc() ) --> 10
print( inc() ) --> 15
local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType
local checkTypeMulti = libraryUtil.checkTypeMulti
---Returns the length of the array but it also works on proxy arrays
---@param arr any[]
---@return integer
local function len(arr)
local l = #arr
if l == 0 then
if arr[1] ~= nil then
-- Exponential search to find length of proxy table
local low = 1
local high = 1
local ceil = math.ceil
while arr[high] ~= nil do
high = high * 2
end
while low ~= high do
local m = ceil((low + high) / 2)
if arr[m] == nil then
high = m - 1
else
low = m
end
end
return low
else
return 0
end
else
return l
end
end
---@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,
len = len
}
Array.__index = Array
setmetatable(Array, {
__index = table,
__call = function (_, arr)
return Array.new(arr)
end
})
function Array.__concat(lhs, rhs)
if type(lhs) == 'table' and type(rhs) == 'table' then
local res = {}
local l1 = len(lhs)
for i = 1, l1 do
res[i] = lhs[i]
end
for i = 1, len(rhs) do
res[l1 + i] = 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, len(rhs) do
res[i] = fun(lhs, rhs[i])
end
elseif type(rhs) == 'number' then
for i = 1, len(lhs) do
res[i] = fun(lhs[i], rhs)
end
else
assert(len(lhs) == len(rhs), string.format('Elementwise %s failed because arrays have different sizes (left: %d, right: %d)', opName, len(lhs), len(rhs)))
for i = 1, len(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 len(lhs) ~= len(rhs) then
return false
end
for i = 1, len(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
for i = 1, len(arr) do
---@diagnostic disable-next-line: redundant-parameter
if not fn(arr[i], i) then
return false
end
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
for i = 1, len(arr) do
---@diagnostic disable-next-line: redundant-parameter
if fn(arr[i], i) then
return true
end
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, len(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, len(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, len(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, len(t) do
lookupTbl[t[i]] = true
end
for i = 1, len(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 trueCount = 0
local l = len(t)
for i = 1, l do
lookupTbl[t[i]] = false
end
for i = 1, len(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 = len(x), len(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, len(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, len(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')
for i = 1, len(arr) do
fn(arr[i], i)
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 l = 0
for i = 1, len(arr) do
if fn(arr[i], i) then
l = l + 1
r[l] = arr[i]
end
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
for i = 1, len(arr) do
---@diagnostic disable-next-line: redundant-parameter
if fn(arr[i], i) then
return arr[i], i
end
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
for i = 1, len(arr) do
---@diagnostic disable-next-line: redundant-parameter
if val(arr[i], i) then
return i
end
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, len(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 len(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 l = 0
Array.each(arr2, function(item) arr2Elements[item] = true end)
Array.each(arr1, function(item)
if arr2Elements[item] then
l = l + 1
res[l] = 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 len(arr1) <= len(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 l = len(arr)
index = index or (l + 1)
local mt = getmetatable(arr)
setmetatable(arr, nil)
if unpackVal and type(val) == 'table' then
local l2 = len(val)
for i = 0, l - index do
arr[l + l2 - i] = arr[l - i]
end
for i = 0, l2 - 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[len(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 l = 0
local r = {}
for i = 1, len(arr) do
local tmp = fn(arr[i], i)
if tmp ~= nil then
l = l + 1
r[l] = tmp
end
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 tostring(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 start = 1
if acc == nil then
acc = arr[1]
start = 2
end
for i = start, len(arr) do
acc = fn(arr[i], acc, i)
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 l = 0
if type(val) == 'function' then
for i = 1, len(arr) do
if not val(arr[i], i) then
l = l + 1
r[l] = arr[i]
end
end
else
local rejectMap = {}
Array.each(val --[[@as any[] ]], function(item) rejectMap[item] = true end)
for i = 1, len(arr) do
if not rejectMap[arr[i]] then
l = l + 1
r[l] = arr[i]
end
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 = {}
for i = 1, len(arr) do
if i == 1 and not accumulator then
acc = arr[i]
else
acc = fn(arr[i], acc, i)
end
r[i] = acc
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(len(indexes) == len(values), string.format("Module:Array.set: 'indexes' and 'values' arrays are not equal length (#indexes = %d, #values = %d)", len(indexes), len(values)))
for i = 1, len(indexes) do
arr[indexes[i]] = values[i]
end
else
for i = 1, len(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 len(arr)
if start < 0 then
start = len(arr) + start
end
if stop == nil then
stop = start
start = 1
end
if stop < 0 then
stop = len(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, len(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, len(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(len(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 = { 3, 5, 7, 9 }
---local x = arr.take_every( t, 2, 3, 2 ) --> x = { 3, 5 }
--- ```
---@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 len(arr)
start = start or 1
local stop = math.min(len(arr), start + n * (count - 1))
local r = {}
local l = 0
for i = start, stop, n do
l = l + 1
r[l] = arr[i]
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 l = 0
local hash = {}
for i = 1, len(arr) do
local id = fn(arr[i])
if not hash[id] then
l = l + 1
r[l] = arr[i]
hash[id] = true
end
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 len(arr) end)
for i = 1, longest do
local q = {}
for j = 1, len(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, len(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, len(k) do
t[k[i]] = v[i]
end
else
for i = 1, len(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