|
|
|
--- General stateless utility algorithms
|
|
|
|
--
|
|
|
|
-- @module gear.algo
|
|
|
|
-- @copyright 2022 The DoubleFourteen Code Forge
|
|
|
|
-- @author Lorenzo Cogotti
|
|
|
|
|
|
|
|
local floor = math.floor
|
|
|
|
local min, max = math.min, math.max
|
|
|
|
|
|
|
|
local algo = {}
|
|
|
|
|
|
|
|
|
|
|
|
--- Clamp x within range [a,b] (where b >= a).
|
|
|
|
--
|
|
|
|
-- @number x value to clamp.
|
|
|
|
-- @number a interval lower bound (inclusive).
|
|
|
|
-- @number b interval upper bound (inclusive).
|
|
|
|
-- @treturn number clamped value.
|
|
|
|
function algo.clamp(x, a, b) return min(max(x, a), b) end
|
|
|
|
|
|
|
|
--- Fast remove from array.
|
|
|
|
--
|
|
|
|
-- Replace 'array[i]' with last array's element and
|
|
|
|
-- discard array's tail.
|
|
|
|
--
|
|
|
|
-- @tparam table array
|
|
|
|
-- @int i
|
|
|
|
function algo.removefast(array, i)
|
|
|
|
local n = #array
|
|
|
|
|
|
|
|
array[i] = array[n] -- NOP if i == n
|
|
|
|
array[n] = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
local function lt(a, b) return a < b end
|
|
|
|
|
|
|
|
local function _insertionsort(array, lo, hi, less)
|
|
|
|
for i = lo+1,hi do
|
|
|
|
local k = lo
|
|
|
|
local v = array[i]
|
|
|
|
|
|
|
|
for j = i,lo+1,-1 do
|
|
|
|
local el = array[j-1]
|
|
|
|
if less(v, el) then
|
|
|
|
array[j] = el
|
|
|
|
else
|
|
|
|
k = j
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
array[k] = v
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
--- Sort array using Insertion Sort - O(n^2).
|
|
|
|
--
|
|
|
|
-- Provides the most basic sorting algorithm around.
|
|
|
|
-- Performs better than regular table.sort() for small arrays
|
|
|
|
-- (~100 elements).
|
|
|
|
--
|
|
|
|
-- @tparam table array array to be sorted.
|
|
|
|
-- @tparam[opt=operator <] function less comparison function, takes 2 arguments,
|
|
|
|
-- returns true if its first argument is
|
|
|
|
-- less than its second argument,
|
|
|
|
-- false otherwise.
|
|
|
|
function algo.insertionsort(array, less)
|
|
|
|
_insertionsort(array, 1, #array, less or lt)
|
|
|
|
end
|
|
|
|
|
|
|
|
--- Binary search last element where
|
|
|
|
-- what <= array[i] - also known as lower bound.
|
|
|
|
--
|
|
|
|
-- @tparam table array an array sorted according to the less function.
|
|
|
|
-- @param what the comparison argument.
|
|
|
|
-- @tparam[opt=operator <] function less sorting criterium, a function taking 2 arguments,
|
|
|
|
-- returns true if the first argument
|
|
|
|
-- is less than the second argument,
|
|
|
|
-- false otherwise.
|
|
|
|
--
|
|
|
|
-- @treturn int the greatest index i, where what <= array[i].
|
|
|
|
-- If no such element exists, it returns an out of bounds index
|
|
|
|
-- such that array[i] == nil.
|
|
|
|
function algo.bsearchl(array, what, less)
|
|
|
|
less = less or lt
|
|
|
|
|
|
|
|
local lo, hi = 1, #array
|
|
|
|
local ofs, mid = -1, hi
|
|
|
|
|
|
|
|
while mid > 0 do
|
|
|
|
mid = floor(hi / 2)
|
|
|
|
|
|
|
|
-- array[lo+mid] <= what <-> what >= array[lo+mid]
|
|
|
|
-- <-> not what < array[lo+mid]
|
|
|
|
if not less(what, array[lo+mid]) then
|
|
|
|
lo = lo + mid
|
|
|
|
ofs = 0 -- at least one element where array[lo+mid] <= what
|
|
|
|
end
|
|
|
|
|
|
|
|
hi = hi - mid
|
|
|
|
end
|
|
|
|
|
|
|
|
return lo + ofs
|
|
|
|
end
|
|
|
|
|
|
|
|
--- Binary search first element where
|
|
|
|
-- what >= array[i] - also known as upper bound.
|
|
|
|
--
|
|
|
|
-- @tparam table array an array sorted according to the less function.
|
|
|
|
-- @param what the comparison argument.
|
|
|
|
-- @tparam[opt=operator <] function less sorting criterium,
|
|
|
|
-- a function taking 2 arguments,
|
|
|
|
-- returns true if the first argument
|
|
|
|
-- is less than the second argument,
|
|
|
|
-- false otherwise.
|
|
|
|
--
|
|
|
|
-- @treturn int the smallest index i, where what >= array[i].
|
|
|
|
-- If no such element exists, it returns an out of bounds index
|
|
|
|
-- such that array[i] == nil.
|
|
|
|
function algo.bsearchr(array, what, less)
|
|
|
|
less = less or lt
|
|
|
|
|
|
|
|
local lo, hi = 1, #array
|
|
|
|
local ofs, mid = -1, hi
|
|
|
|
|
|
|
|
while mid > 0 do
|
|
|
|
mid = floor(hi / 2)
|
|
|
|
|
|
|
|
-- array[lo+mid] >= what <-> not array[lo+mid] < what
|
|
|
|
if not less(array[lo+mid], what) then
|
|
|
|
ofs = 0
|
|
|
|
else
|
|
|
|
lo = lo + mid
|
|
|
|
ofs = 1
|
|
|
|
end
|
|
|
|
|
|
|
|
hi = hi - mid
|
|
|
|
end
|
|
|
|
|
|
|
|
return lo + ofs
|
|
|
|
end
|
|
|
|
|
|
|
|
local function merge(array, lo, mid, hi, less, workspace)
|
|
|
|
local i, j, k
|
|
|
|
i = 1
|
|
|
|
|
|
|
|
-- Copy first half of array to auxiliary array
|
|
|
|
for j = lo,mid do
|
|
|
|
workspace[i] = array[j]
|
|
|
|
i = i + 1
|
|
|
|
end
|
|
|
|
|
|
|
|
i = 1
|
|
|
|
j = mid + 1
|
|
|
|
k = lo
|
|
|
|
while k < j and j <= hi do
|
|
|
|
local v = array[j]
|
|
|
|
local el = workspace[i]
|
|
|
|
|
|
|
|
if less(v, el) then
|
|
|
|
array[k] = v
|
|
|
|
j = j + 1
|
|
|
|
else
|
|
|
|
array[k] = el
|
|
|
|
i = i + 1
|
|
|
|
end
|
|
|
|
k = k + 1
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Copy back any remaining elements of first half
|
|
|
|
for k = k,j-1 do
|
|
|
|
array[k] = workspace[i]
|
|
|
|
i = i + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function mergesort(array, lo, hi, less, workspace)
|
|
|
|
if hi - lo < 72 then
|
|
|
|
_insertionsort(array, lo, hi, less)
|
|
|
|
else
|
|
|
|
local mid = floor((lo + hi)/2)
|
|
|
|
|
|
|
|
mergesort(array, lo, mid, less, workspace)
|
|
|
|
mergesort(array, mid + 1, hi, less, workspace)
|
|
|
|
merge(array, lo, mid, hi, less, workspace)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
--- Array stable sort, uses Merge Sort - O(n*log(n)).
|
|
|
|
--
|
|
|
|
-- Algorithm is a slightly altered version of stable_sort(),
|
|
|
|
-- posted on Lua-L by Steve Fisher on 02 May 2013 22:10:50
|
|
|
|
-- https://lua-users.org/lists/lua-l/2013-05/msg00038.html
|
|
|
|
--
|
|
|
|
-- This implementation uses an auxiliary buffer of size O(n/2).
|
|
|
|
--
|
|
|
|
-- @tparam table array Array to be sorted.
|
|
|
|
-- @tparam[opt=operator <] function less comparison function, takes 2 arguments,
|
|
|
|
-- returns true if its first argument is
|
|
|
|
-- less than its second argument,
|
|
|
|
-- false otherwise.
|
|
|
|
-- @tparam[opt={}] table use an existing workspace buffer instead of allocating one.
|
|
|
|
function algo.stablesort(array, less, workspace)
|
|
|
|
local n = #array
|
|
|
|
|
|
|
|
less = less or lt
|
|
|
|
workspace = workspace or {}
|
|
|
|
workspace[floor((n+1)/2)] = array[1] -- preallocate some room
|
|
|
|
|
|
|
|
mergesort(array, 1, n, less, workspace)
|
|
|
|
end
|
|
|
|
|
|
|
|
return algo
|