Lua runner with rich builtin stdlib
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
a/lua/stdlib/table.lua

260 lines
10 KiB

--- Make a read-only proxy of a table. Reads pass through; writes raise an error.
--- Caveat: pairs(), next() and # see no contents (proxy is empty). Intended for API namespaces.
function table.readonly(tbl, name)
if type(tbl) ~= "table" then
error("table.readonly: argument is not a table (got " .. type(tbl) .. ")")
end
if name ~= nil and type(name) ~= "string" then
error("table.readonly: name is not a string (got " .. type(name) .. ")")
end
local message = "Attempt to update a read-only table"
if name ~= nil then message = message .. " " .. name end
return setmetatable({}, {
__index = tbl,
__newindex = function() error(message, 2) end,
__metatable = false,
})
end
--- Filter a table by predicate, preserving keys. May leave gaps in a sequence table.
function table.filter(tbl, predicate)
if type(tbl) ~= "table" then error("table.filter: table argument is not a table (got " .. type(tbl) .. ")") end
if type(predicate) ~= "function" then error("table.filter: predicate is not a function (got " .. type(predicate) .. ")") end
local result = {}
for k, v in pairs(tbl) do
if predicate(v) then result[k] = v end
end
return result
end
--- Filter a sequence table by predicate, producing a new gapless sequence.
function table.ifilter(tbl, predicate)
if type(tbl) ~= "table" then error("table.ifilter: argument is not a table (got " .. type(tbl) .. ")") end
if type(predicate) ~= "function" then error("table.ifilter: predicate is not a function (got " .. type(predicate) .. ")") end
local result = {}
for i = 1, #tbl do
local v = tbl[i]
if predicate(v) then result[#result + 1] = v end
end
return result
end
--- Map values through a function, preserving keys.
function table.map(tbl, mapper)
if type(tbl) ~= "table" then error("table.map: table argument is not a table (got " .. type(tbl) .. ")") end
if type(mapper) ~= "function" then error("table.map: mapper is not a function (got " .. type(mapper) .. ")") end
local result = {}
for k, v in pairs(tbl) do result[k] = mapper(v) end
return result
end
--- Map values through a function, dropping nils. May leave gaps in a sequence table.
function table.filterMap(tbl, mapper)
if type(tbl) ~= "table" then error("table.filterMap: table argument is not a table (got " .. type(tbl) .. ")") end
if type(mapper) ~= "function" then error("table.filterMap: mapper is not a function (got " .. type(mapper) .. ")") end
local result = {}
for k, v in pairs(tbl) do
local res = mapper(v)
if res ~= nil then result[k] = res end
end
return result
end
--- Map a sequence through a function, dropping nils, producing a gapless sequence.
function table.ifilterMap(tbl, mapper)
if type(tbl) ~= "table" then error("table.ifilterMap: argument is not a table (got " .. type(tbl) .. ")") end
if type(mapper) ~= "function" then error("table.ifilterMap: mapper is not a function (got " .. type(mapper) .. ")") end
local result = {}
for i = 1, #tbl do
local res = mapper(tbl[i])
if res ~= nil then result[#result + 1] = res end
end
return result
end
--- Merge tables into a new table. Sequence parts are appended in order; other keys are
--- copied with later arguments overwriting earlier ones. Not recursive; not deep.
function table.merge(...)
local result = {}
local n = 0
for argi = 1, select("#", ...) do
local tbl = select(argi, ...)
if type(tbl) ~= "table" then
error("table.merge: argument #" .. argi .. " is not a table (got " .. type(tbl) .. ")")
end
local len = 0
for i, v in ipairs(tbl) do
n = n + 1
result[n] = v
len = i
end
for k, v in pairs(tbl) do
if not (math.type(k) == "integer" and k >= 1 and k <= len) then
result[k] = v
end
end
end
return result
end
local MERGE_DEPTH_LIMIT = 100
local function deepCopy(value, level)
if type(value) ~= "table" then return value end
if level > MERGE_DEPTH_LIMIT then error("table.deepMerge/deepCopy recursion too deep") end
local result = {}
for k, v in pairs(value) do result[k] = deepCopy(v, level + 1) end
return result
end
local function mergeRecurse(level, ...)
if level > MERGE_DEPTH_LIMIT then error("table.deepMerge recursion too deep") end
local result = {}
local n = 0
for argi = 1, select('#', ...) do
local tbl = select(argi, ...)
if type(tbl) ~= "table" then
error("table.deepMerge: argument #" .. argi .. " is not a table (got " .. type(tbl) .. ")")
end
local len = 0
for i, v in ipairs(tbl) do
n = n + 1
result[n] = deepCopy(v, level)
len = i
end
for k, v in pairs(tbl) do
if not (math.type(k) == "integer" and k >= 1 and k <= len) then
if type(v) == "table" and type(result[k]) == "table" then
result[k] = mergeRecurse(level + 1, result[k], v)
else
result[k] = deepCopy(v, level)
end
end
end
end
return result
end
--- Recursively merge tables. Nested tables on both sides are merged; other values are overwritten.
--- The result shares no tables with the inputs (everything is deep-copied).
function table.deepMerge(...)
return mergeRecurse(1, ...)
end
--- Deep-copy a table at all levels (excluding metatables).
function table.deepCopy(value)
if type(value) ~= "table" then
error("table.deepCopy: argument is not a table (got " .. type(value) .. ")")
end
return deepCopy(value, 1)
end
--- Reduce all values in a table to one using a function. Iteration order is unspecified.
function table.reduce(tbl, reducer, initialValue)
if type(tbl) ~= "table" then error("table.reduce: table argument is not a table (got " .. type(tbl) .. ")") end
if type(reducer) ~= "function" then error("table.reduce: reducer is not a function (got " .. type(reducer) .. ")") end
local accumulator = initialValue
for _, v in pairs(tbl) do accumulator = reducer(accumulator, v) end
return accumulator
end
--- Reduce a sequence table to one value, iterating in order.
function table.ireduce(tbl, reducer, initialValue)
if type(tbl) ~= "table" then error("table.ireduce: table argument is not a table (got " .. type(tbl) .. ")") end
if type(reducer) ~= "function" then error("table.ireduce: reducer is not a function (got " .. type(reducer) .. ")") end
local accumulator = initialValue
for _, v in ipairs(tbl) do accumulator = reducer(accumulator, v) end
return accumulator
end
--- Return the minimum value in a table. Returns nil for an empty table. NaN propagates.
function table.min(tbl)
if type(tbl) ~= "table" then error("table.min: argument is not a table (got " .. type(tbl) .. ")") end
local min
for _, val in pairs(tbl) do
if val ~= val then return val end
if min == nil or val < min then min = val end
end
return min
end
--- Return the maximum value in a table. Returns nil for an empty table. NaN propagates.
function table.max(tbl)
if type(tbl) ~= "table" then error("table.max: argument is not a table (got " .. type(tbl) .. ")") end
local max
for _, val in pairs(tbl) do
if val ~= val then return val end
if max == nil or val > max then max = val end
end
return max
end
--- Return the arithmetic mean of all values. Returns nil for an empty table. NaN propagates.
function table.mean(tbl)
if type(tbl) ~= "table" then error("table.mean: argument is not a table (got " .. type(tbl) .. ")") end
local sum
local count = 0
for _, val in pairs(tbl) do
count = count + 1
sum = sum and sum + val or val
end
return count > 0 and sum / count or nil
end
--- Return the sum of all values. Returns 0 for an empty table. NaN propagates.
function table.sum(tbl)
if type(tbl) ~= "table" then error("table.sum: argument is not a table (got " .. type(tbl) .. ")") end
local sum = 0
for _, val in pairs(tbl) do sum = sum + val end
return sum
end
--- Return an array of all keys. Order is unspecified.
function table.keys(tbl)
if type(tbl) ~= "table" then error("table.keys: argument is not a table (got " .. type(tbl) .. ")") end
local result = {}
for k in pairs(tbl) do result[#result + 1] = k end
return result
end
--- Return an array of all values. Order is unspecified.
function table.values(tbl)
if type(tbl) ~= "table" then error("table.values: argument is not a table (got " .. type(tbl) .. ")") end
local result = {}
for _, v in pairs(tbl) do result[#result + 1] = v end
return result
end
--- Return true if the table contains the given value (compared with ==).
function table.contains(tbl, value)
if type(tbl) ~= "table" then error("table.contains: argument is not a table (got " .. type(tbl) .. ")") end
for _, v in pairs(tbl) do
if v == value then return true end
end
return false
end
--- Find a value matching a predicate. Returns (value, key) or (nil, nil) if not found.
--- Check the returned key for nil to distinguish a stored false from "not found".
function table.find(tbl, predicate)
if type(tbl) ~= "table" then error("table.find: table argument is not a table (got " .. type(tbl) .. ")") end
if type(predicate) ~= "function" then error("table.find: predicate is not a function (got " .. type(predicate) .. ")") end
for k, v in pairs(tbl) do
if predicate(v) then return v, k end
end
return nil, nil
end
--- Return true if the table has no elements.
function table.isEmpty(tbl)
if type(tbl) ~= "table" then error("table.isEmpty: argument is not a table (got " .. type(tbl) .. ")") end
return next(tbl) == nil
end
--- Return a new array with elements in reverse order.
function table.reverse(tbl)
if type(tbl) ~= "table" then error("table.reverse: argument is not a table (got " .. type(tbl) .. ")") end
local result = {}
for i = #tbl, 1, -1 do result[#result + 1] = tbl[i] end
return result
end