[modified] renamed to batteries, added global export suppression and more documentation

This commit is contained in:
Max Cahill 2020-03-15 20:28:50 +11:00
parent 0dd92b2c2e
commit 66a9f41474
11 changed files with 462 additions and 224 deletions

View File

@ -5,7 +5,7 @@
todo: collect some stats on classes/optional global class registry
]]
function class(inherits)
return local function class(inherits)
local c = {}
c.__mt = {__index = c}
--handle single inheritence

62
colour.lua Normal file
View File

@ -0,0 +1,62 @@
--[[
colour handling stuff
feel free to alias to colour
]]
local bit = require("bit")
local band, bor = bit.band, bit.bor
local lshift, rshift = bit.lshift, bit.rshift
local colour = {}
function colour.packRGB(r, g, b)
local br = lshift(band(0xff, r * 255), 16)
local bg = lshift(band(0xff, g * 255), 8)
local bb = lshift(band(0xff, b * 255), 0)
return bor( br, bg, bb )
end
function colour.packARGB(r, g, b, a)
local ba = lshift(band(0xff, a * 255), 24)
local br = lshift(band(0xff, r * 255), 16)
local bg = lshift(band(0xff, g * 255), 8)
local bb = lshift(band(0xff, b * 255), 0)
return bor( br, bg, bb, ba )
end
function colour.packRGBA(r, g, b, a)
local br = lshift(band(0xff, r * 255), 24)
local bg = lshift(band(0xff, g * 255), 16)
local bb = lshift(band(0xff, b * 255), 8)
local ba = lshift(band(0xff, a * 255), 0)
return bor( br, bg, bb, ba )
end
function colour.unpackARGB(argb)
local r = rshift(band(argb, 0x00ff0000), 16) / 255.0
local g = rshift(band(argb, 0x0000ff00), 8) / 255.0
local b = rshift(band(argb, 0x000000ff), 0) / 255.0
local a = rshift(band(argb, 0xff000000), 24) / 255.0
return r, g, b, a
end
function colour.unpackRGBA(rgba)
local r = rshift(band(rgba, 0xff000000), 24) / 255.0
local g = rshift(band(rgba, 0x00ff0000), 16) / 255.0
local b = rshift(band(rgba, 0x0000ff00), 8) / 255.0
local a = rshift(band(rgba, 0x000000ff), 0) / 255.0
return r, g, b, a
end
function colour.unpackRGB(rgb)
local r = rshift(band(rgb, 0x00ff0000), 16) / 255.0
local g = rshift(band(rgb, 0x0000ff00), 8) / 255.0
local b = rshift(band(rgb, 0x000000ff), 0) / 255.0
local a = 1.0
return r, g, b, a
end
--todo: hsl, hsv, other colour spaces
return colour

View File

@ -1,29 +1,25 @@
--[[
functional programming facilities
notes:
be careful about creating closures in hot loops.
this is this module's achilles heel - there's no special
syntax for closures so it's not apparent that you're suddenly
allocating at every call
reduce has a similar problem, but at least arguments
there are clear!
optional:
set BATTERIES_FUNCTIONAL_MODULE to a table before requiring
if you don't want this to modify the global `table` table
]]
--collect all keys of a table into a sequence
function table.keys(t)
local r = {}
for k,v in pairs(t) do
table.insert(r, k)
end
return r
end
--collect all values of a table into a sequence
--(shallow copy if it's already a sequence)
function table.values(t)
local r = sequence:new()
for k,v in pairs(t) do
table.insert(r, v)
end
return r
end
local _table = BATTERIES_FUNCTIONAL_MODULE or table
--simple sequential iteration, f is called for all elements of t
--f can return non-nil to break the loop (and return the value)
function table.foreach(t, f)
function _table.foreach(t, f)
for i,v in ipairs(t) do
local r = f(v, i)
if r ~= nil then
@ -35,7 +31,7 @@ end
--performs a left to right reduction of t using f, with o as the initial value
-- reduce({1, 2, 3}, f, 0) -> f(f(f(0, 1), 2), 3)
-- (but performed iteratively, so no stack smashing)
function table.reduce(t, f, o)
function _table.reduce(t, f, o)
for i,v in ipairs(t) do
o = f(o, v)
end
@ -44,7 +40,7 @@ end
--maps a sequence {a, b, c} -> {f(a), f(b), f(c)}
-- (automatically drops any nils due to table.insert, which can be used to simultaneously map and filter)
function table.map(t, f)
function _table.map(t, f)
local r = {}
for i,v in ipairs(t) do
local mapped = f(v, i)
@ -55,8 +51,25 @@ function table.map(t, f)
return r
end
--maps a sequence inplace, modifying it {a, b, c} -> {f(a), f(b), f(c)}
-- (automatically drops any nils, which can be used to simultaneously map and filter,
-- but this results in a linear table.remove so "careful" for big working sets)
function _table.remap(t, f)
local i = 1
while i <= #t do
local mapped = f(t[i])
if mapped ~= nil then
t[i] = mapped
i = i + 1
else
table.remove(t, i)
end
end
return t
end
--filters a sequence
function table.filter(t, f)
function _table.filter(t, f)
local r = {}
for i,v in ipairs(t) do
if f(v, i) then
@ -67,7 +80,7 @@ function table.filter(t, f)
end
--partitions a sequence based on filter criteria
function table.partition(t, f)
function _table.partition(t, f)
local a = {}
local b = {}
for i,v in ipairs(t) do
@ -84,7 +97,7 @@ end
--iteration limited by min(#t1, #t2)
--function receives arguments (t1, t2, i)
--nil results ignored
function table.zip(t1, t2, f)
function _table.zip(t1, t2, f)
local ret = {}
local limit = math.min(#t2, #t2)
for i=1, limit do
@ -101,9 +114,9 @@ end
--return a copy of a sequence with all duplicates removed
-- causes a little "extra" gc churn; one table and one closure
-- as well as the copied deduped table
function table.dedupe(t)
function _table.dedupe(t)
local seen = {}
return table.filter(t, function(v)
return _table.filter(t, function(v)
if seen[v] then
return false
end
@ -113,15 +126,15 @@ function table.dedupe(t)
end
--append sequence t2 into t1, modifying t1
function table.append_inplace(t1, t2)
table.foreach(t2, function(v)
function _table.append_inplace(t1, t2)
for i,v in ipairs(t2) do
table.insert(t1, v)
end)
end
return t1
end
--return a new sequence with the elements of both t1 and t2
function table.append(t1, t2)
function _table.append(t1, t2)
local r = {}
append_inplace(r, t1)
append_inplace(r, t2)
@ -133,7 +146,7 @@ end
-----------------------------------------------------------
--true if any element of the table matches f
function table.any(t, f)
function _table.any(t, f)
for i,v in ipairs(t) do
if f(v) then
return true
@ -143,7 +156,7 @@ function table.any(t, f)
end
--true if no element of the table matches f
function table.none(t, f)
function _table.none(t, f)
for i,v in ipairs(t) do
if f(v) then
return false
@ -153,7 +166,7 @@ function table.none(t, f)
end
--true if all elements of the table match f
function table.all(t, f)
function _table.all(t, f)
for i,v in ipairs(t) do
if not f(v) then
return false
@ -163,7 +176,7 @@ function table.all(t, f)
end
--counts the elements of t that match f
function table.count(t, f)
function _table.count(t, f)
local c = 0
for i,v in ipairs(t) do
if f(v) then
@ -174,7 +187,7 @@ function table.count(t, f)
end
--true if the table contains element e
function table.contains(t, e)
function _table.contains(t, e)
for i, v in ipairs(t) do
if v == e then
return true
@ -184,48 +197,53 @@ function table.contains(t, e)
end
--return the numeric sum of all elements of t
function table.sum(t)
return table.reduce(t, function(a, b)
function _table.sum(t)
return _table.reduce(t, function(a, b)
return a + b
end, 0)
end
--return the numeric mean of all elements of t
function table.mean(t)
function _table.mean(t)
local len = #t
if len == 0 then
return 0
end
return table.sum(t) / len
return _table.sum(t) / len
end
--return the minimum and maximum of t in one pass
function table.minmax(t)
local a = table.reduce(t, function(a, b)
a.min = a.min and math.min(a.min, b) or b
a.max = a.max and math.max(a.max, b) or b
return a
end, {})
if a.min == nil then
a.min = 0
a.max = 0
--or zero for both if t is empty
-- (would perhaps more correctly be math.huge, -math.huge
-- but that tends to be surprising/annoying in practice)
function _table.minmax(t)
local max, min
for i,v in ipairs(t) do
min = not min and v or math.min(min, v)
max = not max and v or math.max(max, v)
end
return a.min, a.max
if min == nil then
min = 0
max = 0
end
return min, max
end
function table.max(t)
local min, max = table.minmax(t)
--return the maximum element of t or zero if t is empty
function _table.max(t)
local min, max = _table.minmax(t)
return max
end
function table.min(t)
local min, max = table.minmax(t)
--return the minimum element of t or zero if t is empty
function _table.min(t)
local min, max = _table.minmax(t)
return min
end
--return the element of the table that results in the greatest numeric value
--(function receives element and key respectively, table evaluated in pairs order)
function table.find_best(t, f)
function _table.find_best(t, f)
local current = nil
local current_best = -math.huge
for k,e in pairs(t) do
@ -239,14 +257,15 @@ function table.find_best(t, f)
end
--return the element of the table that results in the value nearest to the passed value
function table.find_nearest(t, f, v)
return table.find_best(t, function(e)
--todo: optimise as this generates a closure each time
function _table.find_nearest(t, f, v)
return _table.find_best(t, function(e)
return -math.abs(f(e) - v)
end)
end
--return the first element of the table that results in a true filter
function table.find_match(t, f)
function _table.find_match(t, f)
for i,v in ipairs(t) do
if f(v) then
return v
@ -254,3 +273,5 @@ function table.find_match(t, f)
end
return nil
end
return _table

View File

@ -1,35 +1,92 @@
--[[
core modules
batteries for lua
if required as the "entire library" (ie by this file), puts everything into
global namespace as it'll presumably be commonly used
global namespace by default as it'll presumably be commonly used
if not, several of the modules work as "normal" modules and return a table
if not, several of the modules work as normal lua modules and return a table
for local-friendly use
the others that modify some global table can be talked into behaving as normal
lua modules as well by setting appropriate globals prior to inclusion
you can avoid modifying any global namespace by setting
BATTERIES_NO_GLOBALS = true
before requiring, then everything can be accessed as eg
batteries.table.stable_sort
]]
local path = ...
local function relative_file(p)
return table.concat({path, p}, ".")
local function require_relative(p)
return require(table.concat({path, p}, "."))
end
require(relative_file("oo"))
if BATTERIES_NO_GLOBALS then
--define local tables for everything to go into
BATTERIES_MATH_MODULE = {}
BATTERIES_TABLE_MODULE = {}
BATTERIES_FUNCTIONAL_MODULE = {}
end
require(relative_file("math"))
local _class = require_relative("class")
require(relative_file("table"))
require(relative_file("stable_sort"))
local _math = require_relative("math")
require(relative_file("functional"))
sequence = require(relative_file("sequence"))
unique_mapping = require(relative_file("unique_mapping"))
local _table = require_relative("table")
local _stable_sort = require_relative("stable_sort")
vec2 = require(relative_file("vec2"))
vec3 = require(relative_file("vec3"))
intersect = require(relative_file("intersect"))
local _functional = require_relative("functional")
local _sequence = require_relative("sequence")
state_machine = require(relative_file("state_machine"))
local _vec2 = require_relative("vec2")
local _vec3 = require_relative("vec3")
local _intersect = require_relative("intersect")
async = require(relative_file("async"))
local _unique_mapping = require_relative("unique_mapping")
local _state_machine = require_relative("state_machine")
manual_gc = require(relative_file("manual_gc"))
local _async = require_relative("async")
local _manual_gc = require_relative("manual_gc")
local _colour = require_relative("colour")
--export globally if required
if not BATTERIES_NO_GLOBALS then
class = _class
sequence = _sequence
vec2 = _vec2
vec3 = _vec3
intersect = _intersect
unique_mapping = _unique_mapping
state_machine = _state_machine
async = _async
manual_gc = _manual_gc
--support both spellings
colour = _colour
color = _colour
end
--either way, export to package registry
return {
class = _class,
math = _math,
table = _table,
stable_sort = _stable_sort,
functional = _functional,
sequence = _sequence,
vec2 = _vec2,
vec3 = _vec3,
intersect = _intersect,
unique_mapping = _unique_mapping,
state_machine = _state_machine,
async = _async,
manual_gc = _manual_gc,
colour = _colour,
}

109
math.lua
View File

@ -1,11 +1,15 @@
--[[
extra mathematical functions
optional:
set BATTERIES_MATH_MODULE to a table before requiring
if you don't want this to modify the global `math` table
]]
local bit = require("bit")
local _math = BATTERIES_MATH_MODULE or math
--wrap v around range [lo, hi)
function math.wrap(v, lo, hi)
function _math.wrap(v, lo, hi)
local range = hi - lo
local relative = v - lo
local relative_wrapped = relative % range
@ -15,51 +19,51 @@ function math.wrap(v, lo, hi)
end
--clamp v to range [lo, hi]
function math.clamp(v, lo, hi)
function _math.clamp(v, lo, hi)
return math.max(lo, math.min(v, hi))
end
function math.clamp01(v)
return math.clamp(v, 0, 1)
function _math.clamp01(v)
return _math.clamp(v, 0, 1)
end
--round v to nearest whole
function math.round(v)
function _math.round(v)
return math.floor(v + 0.5)
end
--round v to one-in x
-- (eg x = 2, v rounded to increments of 0.5)
function math.to_one_in(v, x)
return math.round(v * x) / x
function _math.to_one_in(v, x)
return _math.round(v * x) / x
end
--round v to a given decimal precision
function math.to_precision(v, decimal_points)
return math.to_one_in(v, math.pow(10, decimal_points))
function _math.to_precision(v, decimal_points)
return _math.to_one_in(v, math.pow(10, decimal_points))
end
--0, 1, -1 sign of a scalar
function math.sign(v)
function _math.sign(v)
if v < 0 then return -1 end
if v > 0 then return 1 end
return 0
end
--linear interpolation between a and b
function math.lerp(a, b, t)
function _math.lerp(a, b, t)
return a * (1.0 - t) + b * t
end
--classic smoothstep
--(only "safe" for 0-1 range)
function math.smoothstep(v)
function _math.smoothstep(v)
return v * v * (3 - 2 * v)
end
--classic smootherstep; zero 2nd order derivatives at 0 and 1
--(only safe for 0-1 range)
function math.smootherstep(v)
function _math.smootherstep(v)
return v * v * v * (v * (v * 6 - 15) + 10)
end
@ -125,7 +129,7 @@ local sparse_primes_1k = {
6689, 7039, 7307, 7559, 7573, 7919,
}
function math.first_above(v, t)
function _math.first_above(v, t)
for _,p in ipairs(t) do
if p > v then
return p
@ -134,79 +138,30 @@ function math.first_above(v, t)
return t[#t]
end
function math.next_prime_1k(v)
return math.first_above(v, primes_1k)
function _math.next_prime_1k(v)
return _math.first_above(v, primes_1k)
end
function math.next_prime_1k_sparse(v)
return math.first_above(v, sparse_primes_1k)
end
--color handling stuff
local band, bor = bit.band, bit.bor
local lshift, rshift = bit.lshift, bit.rshift
function math.colorToRGB(r, g, b)
local br = lshift(band(0xff, r * 255), 16)
local bg = lshift(band(0xff, g * 255), 8)
local bb = lshift(band(0xff, b * 255), 0)
return bor( br, bg, bb )
end
function math.colorToARGB(r, g, b, a)
local ba = lshift(band(0xff, a * 255), 24)
local br = lshift(band(0xff, r * 255), 16)
local bg = lshift(band(0xff, g * 255), 8)
local bb = lshift(band(0xff, b * 255), 0)
return bor( br, bg, bb, ba )
end
function math.colorToRGBA(r, g, b, a)
local br = lshift(band(0xff, r * 255), 24)
local bg = lshift(band(0xff, g * 255), 16)
local bb = lshift(band(0xff, b * 255), 8)
local ba = lshift(band(0xff, a * 255), 0)
return bor( br, bg, bb, ba )
end
function math.ARGBToColor(argb)
local r = rshift(band(argb, 0x00ff0000), 16) / 255.0
local g = rshift(band(argb, 0x0000ff00), 8) / 255.0
local b = rshift(band(argb, 0x000000ff), 0) / 255.0
local a = rshift(band(argb, 0xff000000), 24) / 255.0
return r, g, b, a
end
function math.RGBAToColor(rgba)
local r = rshift(band(rgba, 0xff000000), 24) / 255.0
local g = rshift(band(rgba, 0x00ff0000), 16) / 255.0
local b = rshift(band(rgba, 0x0000ff00), 8) / 255.0
local a = rshift(band(rgba, 0x000000ff), 0) / 255.0
return r, g, b, a
end
function math.RGBToColor(rgb)
local r = rshift(band(rgb, 0x00ff0000), 16) / 255.0
local g = rshift(band(rgb, 0x0000ff00), 8) / 255.0
local b = rshift(band(rgb, 0x000000ff), 0) / 255.0
local a = 1.0
return r, g, b, a
function _math.next_prime_1k_sparse(v)
return _math.first_above(v, sparse_primes_1k)
end
--angle handling stuff
function math.normalise_angle(a)
return math.wrap(a, -math.pi, math.pi)
function _math.normalise_angle(a)
return _math.wrap(a, -math.pi, math.pi)
end
function math.relative_angle(a1, a2)
a1 = math.normalise_angle(a1)
a2 = math.normalise_angle(a2)
return math.normalise_angle(a1 - a2)
function _math.relative_angle(a1, a2)
a1 = _math.normalise_angle(a1)
a2 = _math.normalise_angle(a2)
return _math.normalise_angle(a1 - a2)
end
--geometric rotation multi-return
function math.rotate(x, y, r)
function _math.rotate(x, y, r)
local s = math.sin(r)
local c = math.cos(r)
return c * x - s * y, s * x + c * y
end
return _math

View File

@ -1,5 +1,38 @@
# lua-core
# batteries
Core dependencies for making games with lua, especially with [love](https://love2d.org).
Definitely needs a catchier name, but does a lot of useful stuff to get projects off the ground faster.
Does a lot to get projects off the ground faster, filling out lua's standard library and providing implementations of common algorithms and data structures useful for games.
# Module Overview
- `class` - single-inheritance oo in a single function
- `math` - mathematical extensions
- `table` - table handling extensions
- `stable_sort` - a stable sorting algorithm that is also faster than table.sort under luajit
- `functional` - functional programming facilities. `map`, `reduce`, `any`, `match`, `minmax`, `mean`...
- `sequence` - an oo wrapper on sequential tables so you can do `t:insert(i, v)` instead of `table.insert(t, i, v)`. Also supports the functional interfance.
- `vec2` - 2d vectors with method chaining, garbage saving interface
- `vec3` - 3d vectors as above
- `intersect` - 2d intersection routines, a bit sparse at the moment
- `unique_mapping` - generate a unique mapping from arbitrary lua values to numeric keys - essentially making up a consistent ordering for unordered data. niche, but useful for optimising draw batches for example, as you can't sort on textures without it.
- `state_machine` - finite state machine implementation with state transitions and all the rest. useful for game states, ai, cutscenes...
- `async` - async operations as coroutines.
- `manual_gc` - get gc out of your update/draw calls. really good when trying to get accurate profiling information. requires you to think a bit about your garbage budgets though.
- `colour` - colour conversion routines. can also be spelled `color` if you're into that.
# PRs
Pull requests are welcome. If you have something "big" to add please get in touch before starting work, but I'm quite open minded!
# Globals?
By default `batteries` will modify builtin lua tables (such as `table` and `math`) as it sees fit, as this eases consumption later on - you don't have to remember if say, `table.remove_value` is built in to lua or not. As the intended use case is fairly pervasive, required early, and "fire and forget", this sits with me just fine.
If however, you'd prefer to require things locally, you can (rather ironically) set a few globals at boot time as documented in each module to change this behaviour, or set `BATTERIES_NO_GLOBALS = true` to make none of them modify anything global. If you really want to you can undefine the behaviour-changing globals after the module is required, as the results are cached.
I'd strongly recommend that if you find yourself defining the above, stop and think why/if you really want to avoid globals for a library intended to be commonly used across your entire codebase!
Some folks will have good reasons, which is why the functionality is present!
Others may wish to reconsider and save themselves typing batteries a few hundred times :)

View File

@ -7,6 +7,7 @@
in that case, you can still use the table methods that accept a table
first as method calls.
]]
local sequence = {}
sequence.mt = {__index = sequence}
@ -20,52 +21,59 @@ function sequence:new(t)
return setmetatable(t or {}, sequence.mt)
end
--import table functions to sequence as-is
sequence.join = table.concat --alias
--alias
sequence.join = table.concat
--sorting default to stable if present
sequence.sort = table.stable_sort or table.sort
--import functional interface to sequence in a sequence preserving way
--(handle functional module delegation correctly)
local _func = BATTERIES_FUNCTIONAL_MODULE or table
--import functional interface to sequence in a type preserving way
function sequence:keys()
return sequence:new(table.keys(self))
return sequence:new(_func.keys(self))
end
function sequence:values()
return sequence:new(table.values(self))
return sequence:new(_func.values(self))
end
function sequence:foreach(f)
return table.foreach(self, f)
return _func.foreach(self, f)
end
function sequence:reduce(f, o)
return table.foreach(self, f, o)
return _func.foreach(self, f, o)
end
function sequence:map(f)
return sequence:new(table.map(self, f))
return sequence:new(_func.map(self, f))
end
function sequence:remap(f)
return _func.remap(self, f)
end
function sequence:filter(f)
return sequence:new(table.filter(self, f))
return sequence:new(_func.filter(self, f))
end
function sequence:partition(f)
local a, b = table.partition(self, f)
local a, b = _func.partition(self, f)
return sequence:new(a), sequence:new(b)
end
function sequence:zip(other, f)
return sequence:new(table.zip(self, other, f))
return sequence:new(_func.zip(self, other, f))
end
function sequence:dedupe()
return table.dedupe(self)
return _func.dedupe(self)
end
function sequence:append_inplace(other)
return table.append_inplace(self, other)
return _func.append_inplace(self, other)
end
function sequence:append(other)
@ -73,7 +81,7 @@ function sequence:append(other)
end
function sequence:copy(deep)
return sequence:new(table.copy(self, deep))
return sequence:new(_func.copy(self, deep))
end
return sequence

View File

@ -1,6 +1,6 @@
-- stable sorting routines for lua
--
-- modifies the global table namespace so you don't have
-- by default modifies the global table namespace so you don't have
-- to re-require it everywhere.
--
-- table.stable_sort
@ -10,6 +10,11 @@
-- table.insertion_sort
-- an insertion sort, should you prefer it
--
-- alternatively, adds them to either BATTERIES_SORT_MODULE or
-- BATTERIES_TABLE_MODULE if defined
--
-- also returns just the interface above as a module either way
--
--this is based on MIT licensed code from Dirk Laurie and Steve Fisher
--license as follows:
@ -36,14 +41,15 @@
DEALINGS IN THE SOFTWARE.
]]
-- (modifications by Max Cahill 2018)
-- (modifications by Max Cahill 2018, 2020)
local _sort_core = {}
--tunable size for
_sort_core.max_chunk_size = 24
--tunable size for insertion sort "bottom out"
_sort_core.max_chunk_size = 32
function _sort_core.insertion_sort_impl(array, first, last, less)
--insertion sort on a section of array
function _sort_core._insertion_sort_impl(array, first, last, less)
for i = first + 1, last do
local k = first
local v = array[i]
@ -59,7 +65,8 @@ function _sort_core.insertion_sort_impl(array, first, last, less)
end
end
function _sort_core.merge(array, workspace, low, middle, high, less)
--merge sorted adjacent sections of array
function _sort_core._merge(array, workspace, low, middle, high, less)
local i, j, k
i = 1
-- copy first half of array to auxiliary array
@ -91,24 +98,25 @@ function _sort_core.merge(array, workspace, low, middle, high, less)
end
end
function _sort_core.merge_sort_impl(array, workspace, low, high, less)
--implementation for the merge sort
function _sort_core._merge_sort_impl(array, workspace, low, high, less)
if high - low <= _sort_core.max_chunk_size then
_sort_core.insertion_sort_impl(array, low, high, less)
_sort_core._insertion_sort_impl(array, low, high, less)
else
local middle = math.floor((low + high) / 2)
_sort_core.merge_sort_impl(array, workspace, low, middle, less)
_sort_core.merge_sort_impl(array, workspace, middle + 1, high, less)
_sort_core.merge(array, workspace, low, middle, high, less)
_sort_core._merge_sort_impl(array, workspace, low, middle, less)
_sort_core._merge_sort_impl(array, workspace, middle + 1, high, less)
_sort_core._merge(array, workspace, low, middle, high, less)
end
end
function default_less(a, b)
--default comparison; hoisted for clarity
local function default_less(a, b)
return a < b
end
--inline common setup stuff
function _sort_core.sort_setup(array, less)
function _sort_core._sort_setup(array, less)
--default less
less = less or default_less
--
@ -127,28 +135,35 @@ end
function _sort_core.stable_sort(array, less)
--setup
local trivial, n, less = _sort_core.sort_setup(array, less)
local trivial, n, less = _sort_core._sort_setup(array, less)
if not trivial then
--temp storage; allocate ahead of time
local workspace = {}
local middle = math.ceil(n / 2)
workspace[middle] = array[1]
--dive in
_sort_core.merge_sort_impl( array, workspace, 1, n, less )
_sort_core._merge_sort_impl( array, workspace, 1, n, less )
end
return array
end
function _sort_core.insertion_sort(array, less)
--setup
local trivial, n, less = _sort_core.sort_setup(array, less)
local trivial, n, less = _sort_core._sort_setup(array, less)
if not trivial then
_sort_core.insertion_sort_impl(array, 1, n, less)
_sort_core._insertion_sort_impl(array, 1, n, less)
end
return array
end
_sort_core.unstable_sort = table.sort
--export sort core
table.insertion_sort = _sort_core.insertion_sort
table.stable_sort = _sort_core.stable_sort
table.unstable_sort = table.sort
local _table = BATTERIES_SORT_MODULE or BATTERIES_TABLE_MODULE or table
_table.insertion_sort = _sort_core.insertion_sort
_table.stable_sort = _sort_core.stable_sort
_table.unstable_sort = _sort_core.unstable_sort
return _sort_core

151
table.lua
View File

@ -1,36 +1,52 @@
--[[
extra table routines
optional:
set BATTERIES_TABLE_MODULE to a table before requiring
if you don't want this to modify the global `table` table
]]
local _table = BATTERIES_TABLE_MODULE or table
--apply prototype to module if it isn't the global table
if BATTERIES_TABLE_MODULE ~= table then
setmetatable(BATTERIES_TABLE_MODULE, {
__index = table,
})
end
--alias
_table.join = _table.concat
--return the back element of a table
function table.back(t)
function _table.back(t)
return t[#t]
end
--remove the back element of a table and return it
function table.pop(t)
function _table.pop(t)
return table.remove(t)
end
--insert to the back of a table
function table.push(t, v)
function _table.push(t, v)
return table.insert(t, v)
end
--remove the front element of a table and return it
function table.shift(t)
function _table.shift(t)
return table.remove(t, 1)
end
--insert to the front of a table
function table.unshift(t, v)
function _table.unshift(t, v)
return table.insert(t, 1, v)
end
--find the index in a sequential table that a resides at
--or nil if nothing was found
--(todo: consider pairs version?)
function table.index_of(t, a)
function _table.index_of(t, a)
if a == nil then return nil end
for i,b in ipairs(t) do
if a == b then
@ -42,8 +58,8 @@ end
--remove the first instance of value from a table (linear search)
--returns true if the value was removed, else false
function table.remove_value(t, a)
local i = table.index_of(t, a)
function _table.remove_value(t, a)
local i = _table.index_of(t, a)
if i then
table.remove(t, i)
return true
@ -53,8 +69,8 @@ end
--add a value to a table if it doesn't already exist (linear search)
--returns true if the value was added, else false
function table.add_value(t, a)
local i = table.index_of(t, a)
function _table.add_value(t, a)
local i = _table.index_of(t, a)
if not i then
table.insert(t, a)
return true
@ -70,7 +86,7 @@ local function _random(min, max, r)
end
--pick a random value from a table (or nil if it's empty)
function table.pick_random(t, r)
function _table.pick_random(t, r)
if #t == 0 then
return nil
end
@ -78,7 +94,7 @@ function table.pick_random(t, r)
end
--shuffle the order of a table
function table.shuffle(t, r)
function _table.shuffle(t, r)
for i = 1, #t do
local j = _random(1, #t, r)
t[i], t[j] = t[j], t[i]
@ -87,7 +103,7 @@ function table.shuffle(t, r)
end
--reverse the order of a table
function table.reverse(t)
function _table.reverse(t)
for i = 1, #t / 2 do
local j = #t - i + 1
t[i], t[j] = t[j], t[i]
@ -95,28 +111,57 @@ function table.reverse(t)
return t
end
--collect all keys of a table into a sequential table
--(useful if you need to iterate non-changing keys often and want an nyi tradeoff;
-- this call will be slow but then following iterations can use ipairs)
function _table.keys(t)
local r = {}
for k,v in pairs(t) do
table.insert(r, k)
end
return r
end
--collect all values of a keyed table into a sequential table
--(shallow copy if it's already sequential)
function _table.values(t)
local r = {}
for k,v in pairs(t) do
table.insert(r, v)
end
return r
end
--(might already exist depending on luajit)
if table.clear == nil then
--destructively clear a numerically keyed table
--useful when multiple references are floating around
--so you cannot just pop a new table out of nowhere
function table.clear(t)
assert(type(to) == "table", "table.clear - argument 't' must be a table")
while t[1] ~= nil do
table.remove(t)
if _table.clear == nil then
if _table ~= table and table.clear then
--import from global if it exists
_table.clear = table.clear
else
--remove all values from a table
--useful when multiple references are floating around
--so you cannot just pop a new table out of nowhere
function _table.clear(t)
assert(type(to) == "table", "table.clear - argument 't' must be a table")
local k = next(t)
while k ~= nil do
t[k] = nil
k = next(t)
end
end
end
end
--overlay one table directly onto another, shallow only
function table.overlay(to, from)
assert(type(to) == "table", "table.overlay - argument 'to' must be a table")
assert(type(from) == "table", "table.overlay - argument 'from' must be a table")
for k,v in pairs(from) do
to[k] = v
end
return to
end
--note:
-- copies and overlays are currently not satisfactory
--
-- i feel that copy especially tries to do too much and
-- probably they should be split into separate functions
-- to be both more explicit and performant, ie
--
-- shallow_copy, deep_copy, shallow_overlay, deep_overlay
--
-- input is welcome on this :)
--copy a table
-- deep_or_into is either:
@ -128,7 +173,7 @@ end
-- if into specified, copies into that table
-- but doesn't clear anything out
-- (useful for deep overlays and avoiding garbage)
function table.copy(t, deep_or_into)
function _table.copy(t, deep_or_into)
assert(type(t) == "table", "table.copy - argument 't' must be a table")
local is_bool = type(deep_or_into) == "boolean"
local is_table = type(deep_or_into) == "table"
@ -140,7 +185,7 @@ function table.copy(t, deep_or_into)
if type(v.copy) == "function" then
v = v:copy()
else
v = table.copy(v, deep)
v = _table.copy(v, deep)
end
end
into[k] = v
@ -148,3 +193,45 @@ function table.copy(t, deep_or_into)
return into
end
--overlay one table directly onto another, shallow only
function _table.overlay(to, from)
assert(type(to) == "table", "table.overlay - argument 'to' must be a table")
assert(type(from) == "table", "table.overlay - argument 'from' must be a table")
for k,v in pairs(from) do
to[k] = v
end
return to
end
--faster unpacking for known-length tables up to 8
--gets around nyi in luajit
--note: you can use a larger unpack than you need as the rest
-- can be discarded, but it "feels dirty" :)
function _table.unpack2(t)
return t[1], t[2],
end
function _table.unpack3(t)
return t[1], t[2], t[3]
end
function _table.unpack4(t)
return t[1], t[2], t[3], t[4]
end
function _table.unpack5(t)
return t[1], t[2], t[3], t[4], t[5]
end
function _table.unpack6(t)
return t[1], t[2], t[3], t[4], t[5], t[6]
end
function _table.unpack7(t)
return t[1], t[2], t[3], t[4], t[5], t[6], t[7]
end
function _table.unpack8(t)
return t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8]
end

View File

@ -5,7 +5,7 @@
--[[
notes:
depends on a class() function as in oo.lua
depends on a class() function as in batteries class.lua
some methods depend on math library extensions

View File

@ -5,7 +5,7 @@
--[[
notes:
depends on a class() function as in oo.lua
depends on a class() function as in batteries class.lua
some methods depend on math library extensions