modlib/table.lua

668 lines
14 KiB
Lua
Raw Normal View History

-- Localize globals
2021-11-13 13:15:58 +01:00
local assert, ipairs, math, next, pairs, rawget, rawset, setmetatable, string, table, type = assert, ipairs, math, next, pairs, rawget, rawset, setmetatable, string, table, type
2021-06-17 19:45:08 +02:00
-- Set environment
local _ENV = {}
setfenv(1, _ENV)
2021-11-13 16:29:00 +01:00
-- Empty table
empty = {}
2020-02-09 01:39:54 +01:00
-- Table helpers
2020-03-24 23:32:56 +01:00
2021-11-13 16:30:23 +01:00
function from_iterator(...)
local table = {}
for key, value in ... do
table[key] = value
end
return table
end
2020-04-05 10:42:44 +02:00
function map_index(table, func)
2021-03-27 20:10:49 +01:00
local mapping_metatable = {
__index = function(table, key)
return rawget(table, func(key))
end,
__newindex = function(table, key, value)
rawset(table, func(key), value)
end
}
return setmetatable(table, mapping_metatable)
2020-04-05 10:42:44 +02:00
end
function set_case_insensitive_index(table)
2021-03-27 20:10:49 +01:00
return map_index(table, string.lower)
2020-04-05 10:42:44 +02:00
end
2021-01-19 13:18:25 +01:00
--+ nilget(a, "b", "c") == a?.b?.c
function nilget(value, key, ...)
2021-03-27 20:10:49 +01:00
if value == nil or key == nil then
return value
end
return nilget(value[key], ...)
2020-12-12 15:55:09 +01:00
end
2020-03-24 23:32:56 +01:00
-- Fisher-Yates
function shuffle(table)
2021-03-27 20:10:49 +01:00
for index = 1, #table - 1 do
2021-08-30 23:02:01 +02:00
local index_2 = math.random(index, #table)
2021-03-27 20:10:49 +01:00
table[index], table[index_2] = table[index_2], table[index]
end
return table
2020-03-24 23:32:56 +01:00
end
local rope_metatable = {__index = {
2021-03-27 20:10:49 +01:00
write = function(self, text)
table.insert(self, text)
end,
to_text = function(self)
return table.concat(self)
end
}}
--> rope with simple metatable (:write(text) and :to_text())
function rope(table)
2021-03-27 20:10:49 +01:00
return setmetatable(table or {}, rope_metatable)
end
local rope_len_metatable = {__index = {
2021-03-27 20:10:49 +01:00
write = function(self, text)
self.len = self.len + text:len()
end
}}
2021-11-13 13:15:58 +01:00
--> rope for determining length of text supporting `:write(text)` and `.len` to get the length of written text
function rope_len(len)
2021-03-27 20:10:49 +01:00
return setmetatable({len = len or 0}, rope_len_metatable)
end
2020-12-25 17:54:36 +01:00
function is_circular(table)
2021-03-27 20:10:49 +01:00
assert(type(table) == "table")
local known = {}
local function _is_circular(value)
if type(value) ~= "table" then
return false
end
if known[value] then
return true
end
known[value] = true
for key, value in pairs(value) do
if _is_circular(key) or _is_circular(value) then
return true
end
end
end
return _is_circular(table)
2020-12-25 17:54:36 +01:00
end
2021-01-02 17:02:34 +01:00
--+ Simple table equality check. Stack overflow if tables are too deep or circular.
--+ Use `is_circular(table)` to check whether a table is circular.
--> Equality of noncircular tables if `table` and `other_table` are tables
--> `table == other_table` else
function equals_noncircular(table, other_table)
2021-03-27 20:10:49 +01:00
local is_equal = table == other_table
if is_equal or type(table) ~= "table" or type(other_table) ~= "table" then
return is_equal
end
if #table ~= #other_table then
return false
end
local table_keys = {}
for key, value in pairs(table) do
local value_2 = other_table[key]
if not equals_noncircular(value, value_2) then
if type(key) == "table" then
table_keys[key] = value
else
return false
end
end
end
for other_key, other_value in pairs(other_table) do
if type(other_key) == "table" then
local found
for table, value in pairs(table_keys) do
if equals_noncircular(other_key, table) and equals_noncircular(other_value, value) then
table_keys[table] = nil
found = true
break
end
end
if not found then
return false
end
else
if table[other_key] == nil then
return false
end
end
end
return true
2020-02-09 01:39:54 +01:00
end
2021-01-02 17:02:34 +01:00
equals = equals_noncircular
--+ Table equality check properly handling circular tables - tables are equal as long as they provide equal key/value-pairs
--> Table content equality if `table` and `other_table` are tables
--> `table == other_table` else
function equals_content(table, other_table)
2021-03-27 20:10:49 +01:00
local equal_tables = {}
local function _equals(table, other_equal_table)
local function set_equal_tables(value)
equal_tables[table] = equal_tables[table] or {}
equal_tables[table][other_equal_table] = value
return value
end
local is_equal = table == other_equal_table
if is_equal or type(table) ~= "table" or type(other_equal_table) ~= "table" then
return is_equal
end
if #table ~= #other_equal_table then
return set_equal_tables(false)
end
local lookup_equal = (equal_tables[table] or {})[other_equal_table]
if lookup_equal ~= nil then
return lookup_equal
end
-- Premise
set_equal_tables(true)
local table_keys = {}
for key, value in pairs(table) do
local other_value = other_equal_table[key]
if not _equals(value, other_value) then
if type(key) == "table" then
table_keys[key] = value
else
return set_equal_tables(false)
end
end
end
for other_key, other_value in pairs(other_equal_table) do
if type(other_key) == "table" then
local found = false
for table_key, value in pairs(table_keys) do
if _equals(table_key, other_key) and _equals(value, other_value) then
table_keys[table_key] = nil
found = true
-- Breaking is fine as per transitivity
break
end
end
if not found then
return set_equal_tables(false)
end
else
if table[other_key] == nil then
return set_equal_tables(false)
end
end
end
return true
end
return _equals(table, other_table)
2021-01-02 17:02:34 +01:00
end
--+ Table equality check: content has to be equal, relations between tables as well
--+ The only difference may be in the memory addresses ("identities") of the (sub)tables
--+ Performance may suffer if the tables contain table keys
--+ equals(table, copy(table)) is true
--> equality (same tables after table reference substitution) of circular tables if `table` and `other_table` are tables
--> `table == other_table` else
function equals_references(table, other_table)
2021-03-27 20:10:49 +01:00
local function _equals(table, other_table, equal_refs)
if equal_refs[table] then
return equal_refs[table] == other_table
end
local is_equal = table == other_table
-- this check could be omitted if table key equality is being checked
if type(table) ~= "table" or type(other_table) ~= "table" then
return is_equal
end
if is_equal then
equal_refs[table] = other_table
return true
end
-- Premise: table = other table
equal_refs[table] = other_table
local table_keys = {}
for key, value in pairs(table) do
if type(key) == "table" then
table_keys[key] = value
else
local other_value = other_table[key]
if not _equals(value, other_value, equal_refs) then
return false
end
end
end
local other_table_keys = {}
for other_key, other_value in pairs(other_table) do
if type(other_key) == "table" then
other_table_keys[other_key] = other_value
elseif table[other_key] == nil then
return false
end
end
local function _next(current_key, equal_refs, available_keys)
local key, value = next(table_keys, current_key)
if key == nil then
return true
end
for other_key, other_value in pairs(other_table_keys) do
local copy_equal_refs = shallowcopy(equal_refs)
if _equals(key, other_key, copy_equal_refs) and _equals(value, other_value, copy_equal_refs) then
local copy_available_keys = shallowcopy(available_keys)
copy_available_keys[other_key] = nil
if _next(key, copy_equal_refs, copy_available_keys) then
return true
end
end
end
return false
end
return _next(nil, equal_refs, other_table_keys)
end
return _equals(table, other_table, {})
2020-02-09 01:39:54 +01:00
end
2020-12-01 19:29:18 +01:00
function shallowcopy(table)
2021-03-27 20:10:49 +01:00
local copy = {}
for key, value in pairs(table) do
copy[key] = value
end
return copy
2020-12-01 19:29:18 +01:00
end
function deepcopy_noncircular(table)
2021-03-27 20:10:49 +01:00
local function _copy(value)
if type(value) == "table" then
return deepcopy_noncircular(value)
end
return value
end
local copy = {}
for key, value in pairs(table) do
copy[_copy(key)] = _copy(value)
end
return copy
end
function deepcopy(table)
2021-03-27 20:10:49 +01:00
local copies = {}
local function _deepcopy(table)
if copies[table] then
return copies[table]
end
local copy = {}
copies[table] = copy
local function _copy(value)
if type(value) == "table" then
if copies[value] then
return copies[value]
end
return _deepcopy(value)
end
return value
end
for key, value in pairs(table) do
copy[_copy(key)] = _copy(value)
end
return copy
end
return _deepcopy(table)
end
2020-12-12 16:28:52 +01:00
tablecopy = deepcopy
copy = deepcopy
2020-02-09 01:39:54 +01:00
function count(table)
2021-03-27 20:10:49 +01:00
local count = 0
for _ in pairs(table) do
count = count + 1
end
return count
2020-02-09 01:39:54 +01:00
end
function is_empty(table)
2021-03-27 20:10:49 +01:00
return next(table) == nil
2020-02-09 01:39:54 +01:00
end
function foreach(table, func)
2021-03-27 20:10:49 +01:00
for k, v in pairs(table) do
func(k, v)
end
2020-03-23 20:20:43 +01:00
end
2021-07-03 12:19:44 +02:00
function deep_foreach_any(table, func)
local seen = {}
local function visit(value)
func(value)
if type(value) == "table" then
if seen[value] then return end
seen[value] = true
for k, v in pairs(value) do
visit(k)
visit(v)
end
end
end
visit(table)
end
-- Recursively counts occurences of objects (non-primitives including strings) in a table.
function count_objects(value)
2021-07-14 11:51:47 +02:00
local counts = {}
if value == nil then
-- Early return for nil
return counts
end
2021-07-14 11:51:47 +02:00
local function count_values(value)
local type_ = type(value)
if type_ == "boolean" or type_ == "number" then return end
2021-07-14 11:51:47 +02:00
local count = counts[value]
counts[value] = (count or 0) + 1
if not count and type_ == "table" then
for k, v in pairs(value) do
count_values(k)
count_values(v)
end
end
end
count_values(value)
return counts
2021-07-06 10:31:42 +02:00
end
function foreach_value(table, func)
2021-03-27 20:10:49 +01:00
for _, v in pairs(table) do
func(v)
end
2020-03-23 20:20:43 +01:00
end
function call(table, ...)
2021-03-27 20:10:49 +01:00
for _, func in pairs(table) do
func(...)
end
end
function icall(table, ...)
2021-03-27 20:10:49 +01:00
for _, func in ipairs(table) do
func(...)
end
end
function foreach_key(table, func)
2021-03-27 20:10:49 +01:00
for key, _ in pairs(table) do
func(key)
end
2020-03-23 20:20:43 +01:00
end
function map(table, func)
2021-03-27 20:10:49 +01:00
for key, value in pairs(table) do
table[key] = func(value)
end
return table
2020-02-09 01:39:54 +01:00
end
2021-11-13 16:29:00 +01:00
map_values = map
function map_keys(table, func)
2021-03-27 20:10:49 +01:00
local new_tab = {}
for key, value in pairs(table) do
new_tab[func(key)] = value
end
return new_tab
end
2020-12-19 11:40:52 +01:00
function process(tab, func)
2021-03-27 20:10:49 +01:00
local results = {}
for key, value in pairs(tab) do
table.insert(results, func(key,value))
end
return results
2020-02-09 01:39:54 +01:00
end
function call(funcs, ...)
2021-03-27 20:10:49 +01:00
for _, func in ipairs(funcs) do
func(...)
end
2020-02-09 01:39:54 +01:00
end
2020-03-23 20:20:43 +01:00
function find(list, value)
2021-03-27 20:10:49 +01:00
for index, other_value in pairs(list) do
if value == other_value then
return index
end
end
2020-02-09 01:39:54 +01:00
end
2020-03-23 20:20:43 +01:00
contains = find
function to_add(table, after_additions)
2021-03-27 20:10:49 +01:00
local additions = {}
for key, value in pairs(after_additions) do
if table[key] ~= value then
additions[key] = value
end
end
return additions
2020-02-09 01:39:54 +01:00
end
difference = to_add
2021-01-02 17:06:26 +01:00
function deep_to_add(table, after_additions)
2021-03-27 20:10:49 +01:00
local additions = {}
for key, value in pairs(after_additions) do
if type(table[key]) == "table" and type(value) == "table" then
additions[key] = deep_to_add(table[key], value)
elseif table[key] ~= value then
additions[key] = value
end
end
return additions
2021-01-02 17:06:26 +01:00
end
function add_all(table, additions)
2021-03-27 20:10:49 +01:00
for key, value in pairs(additions) do
table[key] = value
end
return table
2020-02-09 01:39:54 +01:00
end
2020-12-20 15:21:03 +01:00
function deep_add_all(table, additions)
2021-03-27 20:10:49 +01:00
for key, value in pairs(additions) do
if type(table[key]) == "table" and type(value) == "table" then
deep_add_all(table[key], value)
else
table[key] = value
end
end
return table
2020-12-20 15:21:03 +01:00
end
function complete(table, completions)
2021-03-27 20:10:49 +01:00
for key, value in pairs(completions) do
if table[key] == nil then
table[key] = value
end
end
return table
end
function deepcomplete(table, completions)
2021-03-27 20:10:49 +01:00
for key, value in pairs(completions) do
if table[key] == nil then
table[key] = value
elseif type(table[key]) == "table" and type(value) == "table" then
deepcomplete(table[key], value)
end
end
return table
2020-03-23 20:20:43 +01:00
end
function merge_tables(table, other_table)
2021-03-27 20:10:49 +01:00
return add_all(copy(table), other_table)
2020-03-23 20:20:43 +01:00
end
union = merge_tables
function intersection(table, other_table)
2021-03-27 20:10:49 +01:00
local result = {}
for key, value in pairs(table) do
if other_table[key] then
result[key] = value
end
end
return result
2020-03-23 20:20:43 +01:00
end
function append(table, other_table)
2021-03-27 20:10:49 +01:00
local length = #table
for index, value in ipairs(other_table) do
table[length + index] = value
end
return table
2020-02-09 01:39:54 +01:00
end
function keys(table)
2021-03-27 20:10:49 +01:00
local keys = {}
for key, _ in pairs(table) do
keys[#keys + 1] = key
end
return keys
2020-02-09 01:39:54 +01:00
end
function values(table)
2021-03-27 20:10:49 +01:00
local values = {}
for _, value in pairs(table) do
values[#values + 1] = value
end
return values
2020-02-09 01:39:54 +01:00
end
function flip(table)
2021-03-27 20:10:49 +01:00
local flipped = {}
for key, value in pairs(table) do
flipped[value] = key
end
return flipped
2020-02-09 01:39:54 +01:00
end
function set(table)
2021-03-27 20:10:49 +01:00
local flipped = {}
for _, value in pairs(table) do
flipped[value] = true
end
return flipped
2020-02-09 01:39:54 +01:00
end
function unique(table)
2021-03-27 20:10:49 +01:00
return keys(set(table))
2020-02-09 01:39:54 +01:00
end
2021-11-13 16:34:49 +01:00
function ivalues(table)
local index = 0
return function()
index = index + 1
return table[index]
end
end
function rpairs(table)
2021-03-27 20:10:49 +01:00
local index = #table
return function()
if index >= 1 then
local value = table[index]
index = index - 1
if value ~= nil then
return index + 1, value
end
end
end
2020-02-09 01:39:54 +01:00
end
function best_value(table, is_better_func)
2021-11-13 13:15:58 +01:00
local best_key = next(table)
if best_key == nil then
2021-03-27 20:10:49 +01:00
return
end
2021-11-13 13:15:58 +01:00
local candidate_key = best_key
2021-03-27 20:10:49 +01:00
while true do
2021-11-13 13:15:58 +01:00
candidate_key = next(table, candidate_key)
if candidate_key == nil then
return best_key
2021-03-27 20:10:49 +01:00
end
2021-11-13 13:15:58 +01:00
if is_better_func(candidate_key, best_key) then
best_key = candidate_key
2021-03-27 20:10:49 +01:00
end
end
2020-02-09 01:39:54 +01:00
end
function min(table)
2021-03-27 20:10:49 +01:00
return best_value(table, function(value, other_value) return value < other_value end)
2020-02-09 01:39:54 +01:00
end
function max(table)
2021-03-27 20:10:49 +01:00
return best_value(table, function(value, other_value) return value > other_value end)
2020-02-09 01:39:54 +01:00
end
function default_comparator(value, other_value)
2021-03-27 20:10:49 +01:00
if value == other_value then
return 0
end
if value > other_value then
return 1
end
return -1
2020-03-23 20:20:43 +01:00
end
--> index if element found
--> -index for insertion if not found
2020-03-23 20:20:43 +01:00
function binary_search_comparator(comparator)
2021-03-27 20:10:49 +01:00
return function(list, value)
local min, max = 1, #list
while min <= max do
local pivot = min + math.floor((max - min) / 2)
local element = list[pivot]
local compared = comparator(value, element)
if compared == 0 then
return pivot
elseif compared > 0 then
min = pivot + 1
else
max = pivot - 1
end
end
return -min
end
2020-03-23 20:20:43 +01:00
end
binary_search = binary_search_comparator(default_comparator)
2021-11-13 16:29:00 +01:00
--> whether the list is sorted in ascending order
function is_sorted(list, comparator)
for index = 2, #list do
if comparator(list[index - 1], list[index]) >= 0 then
return false
end
end
return true
end
function reverse(table)
2021-11-13 13:15:58 +01:00
local len = #table
for index = 1, math.floor(len / 2) do
local index_from_end = len + 1 - index
table[index_from_end], table[index] = table[index], table[index_from_end]
2021-03-27 20:10:49 +01:00
end
return table
end
function repetition(value, count)
2021-03-27 20:10:49 +01:00
local table = {}
for index = 1, count do
table[index] = value
end
return table
2021-06-17 19:45:08 +02:00
end
-- Export environment
return _ENV