Modül:Array

Vikipedi, özgür ansiklopedi
Modül belgelemesi[oluştur]
require('Module:Module wikitext')._addText([[{{Sil|Kullanıcı talebi}}]]);
---
--

local Table = require('Module:Table')
local Logic = require('Module:Logic')

--
-- Array functions. Arrays are tables with numeric indexes that does not
-- have gaps. Functions in Array use ipairs() to iterate over tables.
--
-- For functions using pairs() instead of ipairs(), use Module:Table.
--
local Array = {}

---Modify an array in-place by shuffling its contents.
---@generic T
---@param tbl T[]
---@return T[]
function Array.randomize(tbl)
	math.randomseed(os.time())

	for i = #tbl, 2, -1 do
		local j = math.random(i)
		tbl[i], tbl[j] = tbl[j], tbl[i]
	end
	return tbl
end

---Return true if the input is a table in array format
---@param tbl any
---@return boolean
function Array.isArray(tbl)
	return type(tbl) == 'table' and Table.size(tbl) == #tbl
end

-- Creates a copy of an array with the same elements.
---@generic T
---@param tbl T[]
---@return T[]
function Array.copy(tbl)
	local copy = {}
	for _, element in ipairs(tbl) do
		table.insert(copy, element)
	end
	return copy
end

--[[
Returns a subarray given by its indexes.

Examples:
Array.sub({3, 5, 7, 11}, 2) = {5, 7, 11};
Array.sub({3, 5, 7, 11}, 2, 3) = {5, 7};
Array.sub({3, 5, 7, 11}, -2, -1) = {7, 11}
]]
---@generic T
---@param tbl T[]
---@param startIndex integer
---@param endIndex integer?
---@return T[]
function Array.sub(tbl, startIndex, endIndex)
	if startIndex < 0 then startIndex = #tbl + 1 + startIndex end
	if not endIndex then endIndex = #tbl end
	if endIndex < 0 then endIndex = #tbl + 1 + endIndex end

	local subArray = {}
	for index = startIndex, endIndex do
		table.insert(subArray, tbl[index])
	end
	return subArray
end

---Applies a function to each element in an array and places the results in a new array.
---@generic V, T
---@param elements V[]
---@param funct fun(element: V, index?: integer): T|nil
---@return T[]
function Array.map(elements, funct)
	local mappedArray = {}
	for index, element in ipairs(elements) do
		table.insert(mappedArray, funct(element, index))
	end
	return mappedArray
end

--[[
Filters an array based on a predicate.

Example:
Array.filter({1, 2, 3}, function(x) return x % 2 == 1 end)
-- returns {1, 3}
]]
---@generic T
---@param tbl T[]
---@param predicate fun(element?: T, index?: integer): boolean
---@return T[]
function Array.filter(tbl, predicate)
	local filteredArray = {}
	for index, element in ipairs(tbl) do
		if predicate(element, index) then
			table.insert(filteredArray, element)
		end
	end
	return filteredArray
end

---Flattens an array of arrays into an array.
---@generic T
---@param tbl T[]
---@return T[]
function Array.flatten(tbl)
	local flattenedArray = {}
	for _, x in ipairs(tbl) do
		if type(x) == 'table' then
			for _, y in ipairs(x) do
				table.insert(flattenedArray, y)
			end
		else
			table.insert(flattenedArray, x)
		end
	end
	return flattenedArray
end

---@generic T
---@param tbl T[]
---@param funct any
---@return T[]
function Array.flatMap(tbl, funct)
	return Array.flatten(Array.map(tbl, funct))
end

---Determnines whether all elements in an array satisfy a predicate.
---@generic T
---@param tbl T[]
---@param predicate fun(element: T): boolean
---@return boolean
function Array.all(tbl, predicate)
	for _, element in ipairs(tbl) do
		if not predicate(element) then
			return false
		end
	end
	return true
end

---Determnines whether any elements in an array satisfies a predicate.
---@generic T
---@param tbl T[]
---@param predicate fun(element: T): boolean
---@return boolean
function Array.any(tbl, predicate)
	for _, element in ipairs(tbl) do
		if predicate(element) then
			return true
		end
	end
	return false
end

---Finds the first element in an array satisfying a predicate. Returs nil if no element satisfies the predicate.
---@generic T
---@param tbl T[]
---@param predicate fun(element?: T, index?: integer): boolean
---@return T?
function Array.find(tbl, predicate)
	for index, element in ipairs(tbl) do
		if predicate(element, index) then
			return element
		end
	end
	return nil
end

--[[
Groups an array based on applying a transformation to the elements.

The function returns two values. The first is an array of groups. The second
is a table whose keys are the transformed values and whose values are the
groups.

Example:
Array.groupBy({2, 3, 5, 7, 11, 13}, function(x) return x % 4 end)
-- returns {{2}, {3, 7, 11}, {5, 13}},
-- {1 = {5, 13}, 2 = {2}, 3 = {3, 7, 11}}
]]
---@generic T, K
---@param tbl T[]
---@param funct fun(xValue: T): K?
---@return T[][]
---@return table<K, T[]>
function Array.groupBy(tbl, funct)
	local groupsByKey = {}
	local groups = {}
	for _, xValue in ipairs(tbl) do
		local yValue = funct(xValue)
		if yValue then
			local group = groupsByKey[yValue]
			if not group then
				group = {}
				groupsByKey[yValue] = group
				table.insert(groups, group)
			end
			table.insert(group, xValue)
		end
	end

	return groups, groupsByKey
end

--[[
Groups adjacent elements of an array based on applying a transformation to the
elements. The function returns an array of groups.

The optional equals parameter specifies the equality relation of the
transformed elements.

Example:
Array.groupAdjacentBy({2, 3, 5, 7, 14, 16}, function(x) return x % 2 end)
-- returns {{2}, {3, 5, 7}, {14, 16}}
]]
---@generic V, T
---@param array V[]
---@param f fun(elem: V): T
---@param equals? fun(key: T, currentKey: T): boolean
---@return V[][]
function Array.groupAdjacentBy(array, f, equals)
	equals = equals or Logic.deepEquals

	local groups = {}
	local currentKey
	for index, elem in ipairs(array) do
		local key = f(elem)
		if index == 1 or not equals(key, currentKey) then
			currentKey = key
			table.insert(groups, {})
		end
		table.insert(groups[#groups], elem)
	end

	return groups
end

---Lexicographically compare two arrays.
---@generic T
---@param tblX T[]
---@param tblY T[]
---@return boolean
function Array.lexicalCompare(tblX, tblY)
	for index = 1, math.min(#tblX, #tblY) do
		if tblX[index] < tblY[index] then
			return true
		elseif tblX[index] > tblY[index] then
			return false
		end
	end
	return #tblX < #tblY
end

---Lexicographically compare 2 values.
---If they are arrays (tables) compare them via `Array.lexicalCompare`.
---Else compare them as directly values.
---@generic T
---@param y1 T[]|T
---@param y2 T[]|T
---@return boolean
function Array.lexicalCompareIfTable(y1, y2)
	if type(y1) == 'table' and type(y2) == 'table' then
		return Array.lexicalCompare(y1, y2)
	else
		return y1 < y2
	end
end

--[[
Sorts an array by transforming its elements via a function and comparing the
transformed elements.

If the transformed elements are arrays, then they will be lexically compared.
This is useful for setting up tie-breaker criteria - put the main critera in
the first element, and subsequent tie-breakers in the remaining elements.

The optional third argument specifies a custom comparator for the transformed elements.

Examples:
Array.sortBy({-3, -1, 2, 4}, function(x) return x * x end)
-- returns {-1, 2, -3, 4}

Array.sortBy({
	{first='Neil', last='Armstrong'},
	{first='Louis', last='Armstrong'},
	{first='Buzz', last='Aldrin'},
}, function(x) return {x.last, x.first} end)
-- returns {
--	{first='Buzz', last='Aldrin'},
--	{first='Louis', last='Armstrong'},
--	{first='Neil', last='Armstrong'},
-- }

]]
---@generic T, V
---@param tbl T[]
---@param funct fun(element: T): V
---@param compare? fun(a: V, b: V): boolean
---@return T[]
function Array.sortBy(tbl, funct, compare)
	local copy = Table.copy(tbl)
	Array.sortInPlaceBy(copy, funct, compare)
	return copy
end

---Sorts an array by transforming its elements via a function and comparing the transformed elements.
---Similar to `Array.sortBy` but sorts in place, i.e. mutates the first argument.
---@generic T, V
---@param tbl T[]
---@param funct fun(element: T): V
---@param compare? fun(a: V, b: V): boolean
function Array.sortInPlaceBy(tbl, funct, compare)
	compare = compare or Array.lexicalCompareIfTable
	table.sort(tbl, function(x1, x2) return compare(funct(x1), funct(x2)) end)
end

-- Reverses the order of elements in an array.
---@generic T
---@param tbl T[]
---@return T[]
function Array.reverse(tbl)
	local reversedArray = {}
	for index = #tbl, 1, -1 do
		table.insert(reversedArray, tbl[index])
	end
	return reversedArray
end

--[[
Returns an array with elements append to the end. Does not mutate the inputs.

Example:
Array.append({2, 3}, 5, 7, 11)
-- returns {2, 3, 5, 7, 11}
]]
---@generic T
---@param tbl T[]
---@param ... any
---@return any[]
function Array.append(tbl, ...)
	return Array.appendWith(Array.copy(tbl), ...)
end

---Adds elements to the end of an array. The array is mutated in the process.
---@generic T
---@param tbl T[]
---@param ... any
---@return any[]
function Array.appendWith(tbl, ...)
	local elements = Table.pack(...)
	for index = 1, elements.n do
		if elements[index] ~= nil then
			table.insert(tbl, elements[index])
		end
	end
	return tbl
end

--[[
Returns an array with elements from one or more arrays append to the end. Does
not mutate the inputs.

Example:
Array.extend({2, 3}, {5, 7, 11}, {13})
-- returns {2, 3, 5, 7, 11, 13}

Array.extend({2, 3}, 5, 7, nil, {11, 13})
-- returns {2, 3, 5, 7, 11, 13}
]]
---@generic T
---@param tbl T[]|T
---@param ... T[]|T
---@return T[]
function Array.extend(tbl, ...)
	return Array.extendWith({}, tbl, ...)
end

--[[
Adds elements from one or more arrays to the end of a target array. The target
array is mutated in the process.
]]
---@generic T
---@param tbl T[]
---@param ... T[]|T
---@return T[]
function Array.extendWith(tbl, ...)
	local arrays = Table.pack(...)
	for index = 1, arrays.n do
		if type(arrays[index]) == 'table' then
			for _, element in ipairs(arrays[index]) do
				table.insert(tbl, element)
			end
		elseif arrays[index] ~= nil then
			table.insert(tbl, arrays[index])
		end
	end
	return tbl
end

--[[
Returns the array {funct(1), funct(2), funct(3), ...}. Stops before the first nil value returned by funct.

Example:
Array.mapIndexes(function(x) return x < 5 and x * x or nil end)
-- returns {1, 4, 9, 16}
]]
---@generic T
---@param funct fun(index: integer): T?
---@return T[]
function Array.mapIndexes(funct)
	local arr = {}
	for index = 1, math.huge do
		local y = funct(index)
		if y then
			table.insert(arr, y)
		else
			break
		end
	end
	return arr
end

---Returns the array {from, from + 1, from + 2, ..., to}.
---@param from integer
---@param to integer
---@return integer[]
function Array.range(from, to)
	local elements = {}
	for element = from, to do
		table.insert(elements, element)
	end
	return elements
end

---Extracts keys from a given table into an array. An order can be supplied via an iterator.
---@generic K, V
---@param tbl {[K]: V}
---@param iterator? fun(tbl: table, ...):fun(table: table<K, V>, index?: K):K, V, ...
---@param ... any
---@return K[]
function Array.extractKeys(tbl, iterator, ...)
	iterator = iterator or pairs
	local array = {}
	for key, _ in iterator(tbl, ...) do
		table.insert(array, key)
	end
	return array
end

---Extracts values from a given table into an array. An order can be supplied via an iterator.
---@generic K, V
---@param tbl {[K]: V}
---@param iterator? fun(tbl: table, ...):fun(table: table<K, V>, index?: K):K, V, ...
---@param ... any
---@return V[]
function Array.extractValues(tbl, iterator, ...)
	iterator = iterator or pairs
	local array = {}
	for _, item in iterator(tbl, ...) do
		table.insert(array, item)
	end
	return array
end

--[[
Applies a function to each element in an array.

Example:

Array.forEach({4, 6, 8}, mw.log)
-- Prints 4 1 6 2 8 3
]]
---@generic T
---@param elements T[]
---@param funct fun(element?: T, index?: integer)
function Array.forEach(elements, funct)
	for index, element in ipairs(elements) do
		funct(element, index)
	end
end

--[[
Reduces an array using the specified binary operation. Computes
operator(... operator(operator(operator(initialValue, array[1]), array[2]), array[3]), ... array[#array])

If initialValue is not provided then the operator(initialValue, array[1]) step is skipped, and
operator(array[1], array[2]) becomes the first step.

Example:

local function pow(x, y) return x ^ y end
Array.reduce({2, 3, 5}, pow)
-- Returns 32768
]]
---@generic T, V
---@param array T[]
---@param operator fun(aggregate: V, arrayValue: T): V
---@param initialValue V?
---@return V?
function Array.reduce(array, operator, initialValue)
	local aggregate
	if initialValue ~= nil then
		aggregate = initialValue
	else
		aggregate = array[1]
	end

	for index = initialValue ~= nil and 1 or 2, #array do
		aggregate = operator(aggregate, array[index])
	end
	return aggregate
end

---Computes the maximum element in an array according to a scoring function. Returns nil if the array is empty.
---@generic T
---@param array T[]
---@param funct fun(item: T): number
---@param compare? fun(maxScore: number, score: number): boolean
---@return number?
function Array.maxBy(array, funct, compare)
	compare = compare or Array.lexicalCompareIfTable

	local max, maxScore
	for _, item in ipairs(array) do
		local score = funct(item)
		if max == nil or compare(maxScore, score) then
			max = item
			maxScore = score
		end
	end
	return max
end

---Computes the maximum element in an array. Returns nil if the array is empty.
---@param array number[]
---@param compare? fun(maxScore: number, score: number): boolean
---@return number?
function Array.max(array, compare)
	return Array.maxBy(array, function(x) return x end, compare)
end

---Computes the minimum element in an array according to a scoring function. Returns nil if the array is empty.
---@generic T
---@param array T[]
---@param funct fun(item: T): number
---@param compare fun(maxScore: number, score: number): boolean
---@return number?
function Array.minBy(array, funct, compare)
	compare = compare or Array.lexicalCompareIfTable

	local min, minScore
	for _, item in ipairs(array) do
		local score = funct(item)
		if min == nil or compare(score, minScore) then
			min = item
			minScore = score
		end
	end
	return min
end

---Computes the minimum element in an array. Returns nil if the array is empty.
---@param array number[]
---@param compare fun(maxScore: number, score: number): boolean
---@return number?
function Array.min(array, compare)
	return Array.minBy(array, function(x) return x end, compare)
end

--[[
Finds the index of the element in an array satisfying a predicate. Returs 0
if no element satisfies the predicate.

Example:

Array.indexOf({3, 5, 4, 6, 7}, function(x) return x % 2 == 0 end)
-- returns 3
]]
---@generic V
---@param array V[]
---@param pred fun(elem: V, ix: integer?): boolean
---@return integer
function Array.indexOf(array, pred)
	for ix, elem in ipairs(array) do
		if pred(elem, ix) then
			return ix
		end
	end
	return 0
end

--[[
Returns distinct/unique elements of an array.

Example:

Array.unique({4, 5, 4, 3})
-- Returns {4, 5, 3}
]]
---@generic V
---@param elements V[]
---@return V[]
function Array.unique(elements)
	local elementCache = {}
	local uniqueElements = {}
	for _, element in ipairs(elements) do
		if elementCache[element] == nil then
			table.insert(uniqueElements, element)
			elementCache[element] = true
		end
	end
	return uniqueElements
end

return Array