953 lines
24 KiB
Lua
953 lines
24 KiB
Lua
local orig_insert = table.insert
|
|
local orig_remove = table.remove
|
|
local orig_concat = table.concat
|
|
local orig_sort = table.sort
|
|
|
|
function table.insert(t, pos, value)
|
|
if type(t) ~= "table" then error("table.insert: first argument must be a table", 2) end
|
|
|
|
if value == nil then
|
|
-- table.insert(t, value) form
|
|
orig_insert(t, pos)
|
|
else
|
|
-- table.insert(t, pos, value) form
|
|
if type(pos) ~= "number" or pos ~= math.floor(pos) then
|
|
error("table.insert: position must be an integer", 2)
|
|
end
|
|
orig_insert(t, pos, value)
|
|
end
|
|
end
|
|
|
|
function table.remove(t, pos)
|
|
if type(t) ~= "table" then error("table.remove: first argument must be a table", 2) end
|
|
if pos ~= nil and (type(pos) ~= "number" or pos ~= math.floor(pos)) then
|
|
error("table.remove: position must be an integer", 2)
|
|
end
|
|
return orig_remove(t, pos)
|
|
end
|
|
|
|
function table.concat(t, sep, start_idx, end_idx)
|
|
if type(t) ~= "table" then error("table.concat: first argument must be a table", 2) end
|
|
if sep ~= nil and type(sep) ~= "string" then error("table.concat: separator must be a string", 2) end
|
|
if start_idx ~= nil and (type(start_idx) ~= "number" or start_idx ~= math.floor(start_idx)) then
|
|
error("table.concat: start index must be an integer", 2)
|
|
end
|
|
if end_idx ~= nil and (type(end_idx) ~= "number" or end_idx ~= math.floor(end_idx)) then
|
|
error("table.concat: end index must be an integer", 2)
|
|
end
|
|
return orig_concat(t, sep, start_idx, end_idx)
|
|
end
|
|
|
|
function table.sort(t, comp)
|
|
if type(t) ~= "table" then error("table.sort: first argument must be a table", 2) end
|
|
if comp ~= nil and type(comp) ~= "function" then error("table.sort: comparator must be a function", 2) end
|
|
orig_sort(t, comp)
|
|
end
|
|
|
|
function table.length(t)
|
|
if type(t) ~= "table" then error("table.length: argument must be a table", 2) end
|
|
return #t
|
|
end
|
|
|
|
function table.size(t)
|
|
if type(t) ~= "table" then error("table.size: argument must be a table", 2) end
|
|
local count = 0
|
|
for _ in pairs(t) do
|
|
count = count + 1
|
|
end
|
|
return count
|
|
end
|
|
|
|
function table.is_empty(t)
|
|
if type(t) ~= "table" then error("table.is_empty: argument must be a table", 2) end
|
|
return next(t) == nil
|
|
end
|
|
|
|
function table.is_array(t)
|
|
if type(t) ~= "table" then error("table.is_array: argument must be a table", 2) end
|
|
if table.is_empty(t) then return true end
|
|
|
|
local max_index = 0
|
|
local count = 0
|
|
for k, v in pairs(t) do
|
|
if type(k) ~= "number" or k ~= math.floor(k) or k <= 0 then
|
|
return false
|
|
end
|
|
max_index = math.max(max_index, k)
|
|
count = count + 1
|
|
end
|
|
return max_index == count
|
|
end
|
|
|
|
function table.clear(t)
|
|
if type(t) ~= "table" then error("table.clear: argument must be a table", 2) end
|
|
for k in pairs(t) do
|
|
t[k] = nil
|
|
end
|
|
end
|
|
|
|
function table.clone(t)
|
|
if type(t) ~= "table" then error("table.clone: argument must be a table", 2) end
|
|
local result = {}
|
|
for k, v in pairs(t) do
|
|
result[k] = v
|
|
end
|
|
return result
|
|
end
|
|
|
|
function table.deep_copy(t)
|
|
if type(t) ~= "table" then error("table.deep_copy: argument must be a table", 2) end
|
|
|
|
local function copy_recursive(obj, seen)
|
|
if type(obj) ~= "table" then return obj end
|
|
if seen[obj] then return seen[obj] end
|
|
|
|
local result = {}
|
|
seen[obj] = result
|
|
|
|
for k, v in pairs(obj) do
|
|
result[copy_recursive(k, seen)] = copy_recursive(v, seen)
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
return copy_recursive(t, {})
|
|
end
|
|
|
|
function table.contains(t, value)
|
|
if type(t) ~= "table" then error("table.contains: first argument must be a table", 2) end
|
|
for _, v in pairs(t) do
|
|
if v == value then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function table.index_of(t, value)
|
|
if type(t) ~= "table" then error("table.index_of: first argument must be a table", 2) end
|
|
for k, v in pairs(t) do
|
|
if v == value then return k end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function table.find(t, predicate)
|
|
if type(t) ~= "table" then error("table.find: first argument must be a table", 2) end
|
|
if type(predicate) ~= "function" then error("table.find: second argument must be a function", 2) end
|
|
|
|
for k, v in pairs(t) do
|
|
if predicate(v, k, t) then return v, k end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function table.find_index(t, predicate)
|
|
if type(t) ~= "table" then error("table.find_index: first argument must be a table", 2) end
|
|
if type(predicate) ~= "function" then error("table.find_index: second argument must be a function", 2) end
|
|
|
|
for k, v in pairs(t) do
|
|
if predicate(v, k, t) then return k end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function table.count(t, value_or_predicate)
|
|
if type(t) ~= "table" then error("table.count: first argument must be a table", 2) end
|
|
|
|
local count = 0
|
|
if type(value_or_predicate) == "function" then
|
|
for k, v in pairs(t) do
|
|
if value_or_predicate(v, k, t) then count = count + 1 end
|
|
end
|
|
else
|
|
for _, v in pairs(t) do
|
|
if v == value_or_predicate then count = count + 1 end
|
|
end
|
|
end
|
|
return count
|
|
end
|
|
|
|
function table.filter(t, predicate)
|
|
if type(t) ~= "table" then error("table.filter: first argument must be a table", 2) end
|
|
if type(predicate) ~= "function" then error("table.filter: second argument must be a function", 2) end
|
|
|
|
local result = {}
|
|
if table.is_array(t) then
|
|
local max_index = 0
|
|
for k in pairs(t) do
|
|
if type(k) == "number" and k > max_index then
|
|
max_index = k
|
|
end
|
|
end
|
|
for i = 1, max_index do
|
|
local v = t[i]
|
|
if v ~= nil and predicate(v, i, t) then
|
|
orig_insert(result, v)
|
|
end
|
|
end
|
|
else
|
|
for k, v in pairs(t) do
|
|
if predicate(v, k, t) then
|
|
result[k] = v
|
|
end
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
|
|
function table.reject(t, predicate)
|
|
if type(t) ~= "table" then error("table.reject: first argument must be a table", 2) end
|
|
if type(predicate) ~= "function" then error("table.reject: second argument must be a function", 2) end
|
|
|
|
return table.filter(t, function(v, k, tbl) return not predicate(v, k, tbl) end)
|
|
end
|
|
|
|
function table.map(t, transformer)
|
|
if type(t) ~= "table" then error("table.map: first argument must be a table", 2) end
|
|
if type(transformer) ~= "function" then error("table.map: second argument must be a function", 2) end
|
|
|
|
local result = {}
|
|
for k, v in pairs(t) do
|
|
result[k] = transformer(v, k, t)
|
|
end
|
|
return result
|
|
end
|
|
|
|
function table.map_values(t, transformer)
|
|
if type(t) ~= "table" then error("table.map_values: first argument must be a table", 2) end
|
|
if type(transformer) ~= "function" then error("table.map_values: second argument must be a function", 2) end
|
|
|
|
local result = {}
|
|
for k, v in pairs(t) do
|
|
result[k] = transformer(v, k, t)
|
|
end
|
|
return result
|
|
end
|
|
|
|
function table.map_keys(t, transformer)
|
|
if type(t) ~= "table" then error("table.map_keys: first argument must be a table", 2) end
|
|
if type(transformer) ~= "function" then error("table.map_keys: second argument must be a function", 2) end
|
|
|
|
local result = {}
|
|
for k, v in pairs(t) do
|
|
local new_key = transformer(k, v, t)
|
|
result[new_key] = v
|
|
end
|
|
return result
|
|
end
|
|
|
|
function table.reduce(t, reducer, initial)
|
|
if type(t) ~= "table" then error("table.reduce: first argument must be a table", 2) end
|
|
if type(reducer) ~= "function" then error("table.reduce: second argument must be a function", 2) end
|
|
|
|
local accumulator = initial
|
|
local started = initial ~= nil
|
|
|
|
for k, v in pairs(t) do
|
|
if not started then
|
|
accumulator = v
|
|
started = true
|
|
else
|
|
accumulator = reducer(accumulator, v, k, t)
|
|
end
|
|
end
|
|
|
|
if not started then
|
|
error("table.reduce: empty table with no initial value", 2)
|
|
end
|
|
|
|
return accumulator
|
|
end
|
|
|
|
function table.sum(t)
|
|
if type(t) ~= "table" then error("table.sum: argument must be a table", 2) end
|
|
local total = 0
|
|
for _, v in pairs(t) do
|
|
if type(v) ~= "number" then error("table.sum: all values must be numbers", 2) end
|
|
total = total + v
|
|
end
|
|
return total
|
|
end
|
|
|
|
function table.product(t)
|
|
if type(t) ~= "table" then error("table.product: argument must be a table", 2) end
|
|
local result = 1
|
|
for _, v in pairs(t) do
|
|
if type(v) ~= "number" then error("table.product: all values must be numbers", 2) end
|
|
result = result * v
|
|
end
|
|
return result
|
|
end
|
|
|
|
function table.min(t)
|
|
if type(t) ~= "table" then error("table.min: argument must be a table", 2) end
|
|
if table.is_empty(t) then error("table.min: table is empty", 2) end
|
|
|
|
local min_val = nil
|
|
for _, v in pairs(t) do
|
|
if type(v) ~= "number" then error("table.min: all values must be numbers", 2) end
|
|
if min_val == nil or v < min_val then
|
|
min_val = v
|
|
end
|
|
end
|
|
return min_val
|
|
end
|
|
|
|
function table.max(t)
|
|
if type(t) ~= "table" then error("table.max: argument must be a table", 2) end
|
|
if table.is_empty(t) then error("table.max: table is empty", 2) end
|
|
|
|
local max_val = nil
|
|
for _, v in pairs(t) do
|
|
if type(v) ~= "number" then error("table.max: all values must be numbers", 2) end
|
|
if max_val == nil or v > max_val then
|
|
max_val = v
|
|
end
|
|
end
|
|
return max_val
|
|
end
|
|
|
|
function table.average(t)
|
|
if type(t) ~= "table" then error("table.average: argument must be a table", 2) end
|
|
if table.is_empty(t) then error("table.average: table is empty", 2) end
|
|
return table.sum(t) / table.size(t)
|
|
end
|
|
|
|
function table.all(t, predicate)
|
|
if type(t) ~= "table" then error("table.all: first argument must be a table", 2) end
|
|
|
|
if predicate then
|
|
if type(predicate) ~= "function" then error("table.all: second argument must be a function", 2) end
|
|
for k, v in pairs(t) do
|
|
if not predicate(v, k, t) then return false end
|
|
end
|
|
else
|
|
for _, v in pairs(t) do
|
|
if not v then return false end
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function table.any(t, predicate)
|
|
if type(t) ~= "table" then error("table.any: first argument must be a table", 2) end
|
|
|
|
if predicate then
|
|
if type(predicate) ~= "function" then error("table.any: second argument must be a function", 2) end
|
|
for k, v in pairs(t) do
|
|
if predicate(v, k, t) then return true end
|
|
end
|
|
else
|
|
for _, v in pairs(t) do
|
|
if v then return true end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function table.none(t, predicate)
|
|
if type(t) ~= "table" then error("table.none: first argument must be a table", 2) end
|
|
return not table.any(t, predicate)
|
|
end
|
|
|
|
function table.unique(t)
|
|
if type(t) ~= "table" then error("table.unique: argument must be a table", 2) end
|
|
|
|
local seen = {}
|
|
local result = {}
|
|
|
|
if table.is_array(t) then
|
|
for _, v in ipairs(t) do
|
|
if not seen[v] then
|
|
seen[v] = true
|
|
orig_insert(result, v)
|
|
end
|
|
end
|
|
else
|
|
for k, v in pairs(t) do
|
|
if not seen[v] then
|
|
seen[v] = true
|
|
result[k] = v
|
|
end
|
|
end
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
function table.intersection(t1, t2)
|
|
if type(t1) ~= "table" then error("table.intersection: first argument must be a table", 2) end
|
|
if type(t2) ~= "table" then error("table.intersection: second argument must be a table", 2) end
|
|
|
|
local set2 = {}
|
|
for _, v in pairs(t2) do
|
|
set2[v] = true
|
|
end
|
|
|
|
local result = {}
|
|
if table.is_array(t1) then
|
|
for _, v in ipairs(t1) do
|
|
if set2[v] then
|
|
orig_insert(result, v)
|
|
end
|
|
end
|
|
else
|
|
for k, v in pairs(t1) do
|
|
if set2[v] then
|
|
result[k] = v
|
|
end
|
|
end
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
function table.union(t1, t2)
|
|
if type(t1) ~= "table" then error("table.union: first argument must be a table", 2) end
|
|
if type(t2) ~= "table" then error("table.union: second argument must be a table", 2) end
|
|
|
|
local result = table.clone(t1)
|
|
|
|
if table.is_array(t1) and table.is_array(t2) then
|
|
for _, v in ipairs(t2) do
|
|
if not table.contains(result, v) then
|
|
orig_insert(result, v)
|
|
end
|
|
end
|
|
else
|
|
for k, v in pairs(t2) do
|
|
if result[k] == nil then
|
|
result[k] = v
|
|
end
|
|
end
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
function table.difference(t1, t2)
|
|
if type(t1) ~= "table" then error("table.difference: first argument must be a table", 2) end
|
|
if type(t2) ~= "table" then error("table.difference: second argument must be a table", 2) end
|
|
|
|
local set2 = {}
|
|
for _, v in pairs(t2) do
|
|
set2[v] = true
|
|
end
|
|
|
|
return table.filter(t1, function(v) return not set2[v] end)
|
|
end
|
|
|
|
function table.reverse(t)
|
|
if type(t) ~= "table" then error("table.reverse: argument must be a table", 2) end
|
|
if not table.is_array(t) then error("table.reverse: argument must be an array", 2) end
|
|
|
|
local result = {}
|
|
local len = #t
|
|
for i = 1, len do
|
|
result[i] = t[len - i + 1]
|
|
end
|
|
return result
|
|
end
|
|
|
|
function table.shuffle(t)
|
|
if type(t) ~= "table" then error("table.shuffle: argument must be a table", 2) end
|
|
if not table.is_array(t) then error("table.shuffle: argument must be an array", 2) end
|
|
|
|
local result = table.clone(t)
|
|
local len = #result
|
|
|
|
math.randomseed(os.time() + os.clock() * 1000000)
|
|
|
|
for i = len, 2, -1 do
|
|
local j = math.random(1, i)
|
|
result[i], result[j] = result[j], result[i]
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
function table.rotate(t, positions)
|
|
if type(t) ~= "table" then error("table.rotate: first argument must be a table", 2) end
|
|
if not table.is_array(t) then error("table.rotate: first argument must be an array", 2) end
|
|
if type(positions) ~= "number" or positions ~= math.floor(positions) then
|
|
error("table.rotate: second argument must be an integer", 2)
|
|
end
|
|
|
|
local len = #t
|
|
if len == 0 then return {} end
|
|
|
|
positions = positions % len
|
|
if positions == 0 then return table.clone(t) end
|
|
|
|
local result = {}
|
|
for i = 1, len do
|
|
local new_pos = ((i - 1 + positions) % len) + 1
|
|
result[new_pos] = t[i]
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
function table.slice(t, start_idx, end_idx)
|
|
if type(t) ~= "table" then error("table.slice: first argument must be a table", 2) end
|
|
if not table.is_array(t) then error("table.slice: first argument must be an array", 2) end
|
|
if type(start_idx) ~= "number" or start_idx ~= math.floor(start_idx) then
|
|
error("table.slice: start index must be an integer", 2)
|
|
end
|
|
if end_idx ~= nil and (type(end_idx) ~= "number" or end_idx ~= math.floor(end_idx)) then
|
|
error("table.slice: end index must be an integer", 2)
|
|
end
|
|
|
|
local len = #t
|
|
if start_idx < 0 then start_idx = len + start_idx + 1 end
|
|
if end_idx and end_idx < 0 then end_idx = len + end_idx + 1 end
|
|
|
|
start_idx = math.max(1, math.min(start_idx, len))
|
|
end_idx = end_idx and math.max(1, math.min(end_idx, len)) or len
|
|
|
|
local result = {}
|
|
for i = start_idx, end_idx do
|
|
orig_insert(result, t[i])
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
function table.splice(t, start_idx, delete_count, ...)
|
|
if type(t) ~= "table" then error("table.splice: first argument must be a table", 2) end
|
|
if not table.is_array(t) then error("table.splice: first argument must be an array", 2) end
|
|
if type(start_idx) ~= "number" or start_idx ~= math.floor(start_idx) then
|
|
error("table.splice: start index must be an integer", 2)
|
|
end
|
|
if delete_count ~= nil and (type(delete_count) ~= "number" or delete_count ~= math.floor(delete_count) or delete_count < 0) then
|
|
error("table.splice: delete count must be a non-negative integer", 2)
|
|
end
|
|
|
|
local len = #t
|
|
if start_idx < 0 then start_idx = len + start_idx + 1 end
|
|
start_idx = math.max(1, math.min(start_idx, len + 1))
|
|
|
|
delete_count = delete_count or (len - start_idx + 1)
|
|
delete_count = math.max(0, math.min(delete_count, len - start_idx + 1))
|
|
|
|
local deleted = {}
|
|
for i = 1, delete_count do
|
|
deleted[i] = t[start_idx + i - 1]
|
|
end
|
|
|
|
local insert_items = {...}
|
|
local insert_count = #insert_items
|
|
local move_count = len - start_idx - delete_count + 1
|
|
|
|
-- Shift elements
|
|
if insert_count > delete_count then
|
|
-- Moving right
|
|
for i = len, start_idx + delete_count, -1 do
|
|
t[i + insert_count - delete_count] = t[i]
|
|
end
|
|
elseif insert_count < delete_count then
|
|
-- Moving left
|
|
for i = start_idx + delete_count, len do
|
|
t[i + insert_count - delete_count] = t[i]
|
|
end
|
|
-- Clear the end
|
|
for i = len + insert_count - delete_count + 1, len do
|
|
t[i] = nil
|
|
end
|
|
end
|
|
|
|
-- Insert new items
|
|
for i = 1, insert_count do
|
|
t[start_idx + i - 1] = insert_items[i]
|
|
end
|
|
|
|
return deleted
|
|
end
|
|
|
|
function table.sort_by(t, key_func)
|
|
if type(t) ~= "table" then error("table.sort_by: first argument must be a table", 2) end
|
|
if not table.is_array(t) then error("table.sort_by: first argument must be an array", 2) end
|
|
if type(key_func) ~= "function" then error("table.sort_by: second argument must be a function", 2) end
|
|
|
|
local result = table.clone(t)
|
|
orig_sort(result, function(a, b)
|
|
return key_func(a) < key_func(b)
|
|
end)
|
|
return result
|
|
end
|
|
|
|
function table.is_sorted(t, comp)
|
|
if type(t) ~= "table" then error("table.is_sorted: first argument must be a table", 2) end
|
|
if not table.is_array(t) then error("table.is_sorted: first argument must be an array", 2) end
|
|
if comp ~= nil and type(comp) ~= "function" then error("table.is_sorted: comparator must be a function", 2) end
|
|
|
|
comp = comp or function(a, b) return a < b end
|
|
|
|
for i = 2, #t do
|
|
if comp(t[i], t[i-1]) then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function table.keys(t)
|
|
if type(t) ~= "table" then error("table.keys: argument must be a table", 2) end
|
|
|
|
local result = {}
|
|
for k, _ in pairs(t) do
|
|
orig_insert(result, k)
|
|
end
|
|
return result
|
|
end
|
|
|
|
function table.values(t)
|
|
if type(t) ~= "table" then error("table.values: argument must be a table", 2) end
|
|
|
|
local result = {}
|
|
for _, v in pairs(t) do
|
|
orig_insert(result, v)
|
|
end
|
|
return result
|
|
end
|
|
|
|
function table.pairs(t)
|
|
if type(t) ~= "table" then error("table.pairs: argument must be a table", 2) end
|
|
|
|
local result = {}
|
|
for k, v in pairs(t) do
|
|
orig_insert(result, {k, v})
|
|
end
|
|
return result
|
|
end
|
|
|
|
function table.merge(...)
|
|
local tables = {...}
|
|
if #tables == 0 then return {} end
|
|
|
|
for i, t in ipairs(tables) do
|
|
if type(t) ~= "table" then
|
|
error("table.merge: argument " .. i .. " must be a table", 2)
|
|
end
|
|
end
|
|
|
|
local result = {}
|
|
for _, t in ipairs(tables) do
|
|
for k, v in pairs(t) do
|
|
result[k] = v
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
|
|
function table.extend(t1, ...)
|
|
if type(t1) ~= "table" then error("table.extend: first argument must be a table", 2) end
|
|
|
|
local tables = {...}
|
|
for i, t in ipairs(tables) do
|
|
if type(t) ~= "table" then
|
|
error("table.extend: argument " .. (i + 1) .. " must be a table", 2)
|
|
end
|
|
end
|
|
|
|
for _, t in ipairs(tables) do
|
|
for k, v in pairs(t) do
|
|
t1[k] = v
|
|
end
|
|
end
|
|
return t1
|
|
end
|
|
|
|
function table.invert(t)
|
|
if type(t) ~= "table" then error("table.invert: argument must be a table", 2) end
|
|
|
|
local result = {}
|
|
for k, v in pairs(t) do
|
|
result[v] = k
|
|
end
|
|
return result
|
|
end
|
|
|
|
function table.pick(t, ...)
|
|
if type(t) ~= "table" then error("table.pick: first argument must be a table", 2) end
|
|
|
|
local keys = {...}
|
|
local result = {}
|
|
|
|
for _, key in ipairs(keys) do
|
|
if t[key] ~= nil then
|
|
result[key] = t[key]
|
|
end
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
function table.omit(t, ...)
|
|
if type(t) ~= "table" then error("table.omit: first argument must be a table", 2) end
|
|
|
|
local omit_keys = {}
|
|
for _, key in ipairs({...}) do
|
|
omit_keys[key] = true
|
|
end
|
|
|
|
local result = {}
|
|
for k, v in pairs(t) do
|
|
if not omit_keys[k] then
|
|
result[k] = v
|
|
end
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
function table.deep_equals(t1, t2)
|
|
if type(t1) ~= "table" then error("table.deep_equals: first argument must be a table", 2) end
|
|
if type(t2) ~= "table" then error("table.deep_equals: second argument must be a table", 2) end
|
|
|
|
local function equals_recursive(a, b, seen)
|
|
if a == b then return true end
|
|
if type(a) ~= type(b) then return false end
|
|
if type(a) ~= "table" then return false end
|
|
|
|
local key_pair = tostring(a) .. ":" .. tostring(b)
|
|
if seen[key_pair] then return true end
|
|
seen[key_pair] = true
|
|
|
|
-- Check if they have the same keys
|
|
local keys_a, keys_b = {}, {}
|
|
for k in pairs(a) do keys_a[k] = true end
|
|
for k in pairs(b) do keys_b[k] = true end
|
|
|
|
for k in pairs(keys_a) do
|
|
if not keys_b[k] then return false end
|
|
end
|
|
for k in pairs(keys_b) do
|
|
if not keys_a[k] then return false end
|
|
end
|
|
|
|
-- Check if all values are equal
|
|
for k in pairs(keys_a) do
|
|
if not equals_recursive(a[k], b[k], seen) then
|
|
return false
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
return equals_recursive(t1, t2, {})
|
|
end
|
|
|
|
function table.flatten(t, depth)
|
|
if type(t) ~= "table" then error("table.flatten: first argument must be a table", 2) end
|
|
if not table.is_array(t) then error("table.flatten: first argument must be an array", 2) end
|
|
if depth ~= nil and (type(depth) ~= "number" or depth ~= math.floor(depth) or depth < 1) then
|
|
error("table.flatten: depth must be a positive integer", 2)
|
|
end
|
|
|
|
depth = depth or 1
|
|
|
|
local function flatten_recursive(arr, current_depth)
|
|
local result = {}
|
|
for _, v in ipairs(arr) do
|
|
if type(v) == "table" and table.is_array(v) and current_depth > 0 then
|
|
local flattened = flatten_recursive(v, current_depth - 1)
|
|
for _, item in ipairs(flattened) do
|
|
orig_insert(result, item)
|
|
end
|
|
else
|
|
orig_insert(result, v)
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
|
|
return flatten_recursive(t, depth)
|
|
end
|
|
|
|
function table.deep_merge(...)
|
|
local tables = {...}
|
|
if #tables == 0 then return {} end
|
|
|
|
for i, t in ipairs(tables) do
|
|
if type(t) ~= "table" then
|
|
error("table.deep_merge: argument " .. i .. " must be a table", 2)
|
|
end
|
|
end
|
|
|
|
local function merge_recursive(target, source)
|
|
for k, v in pairs(source) do
|
|
if type(v) == "table" and type(target[k]) == "table" then
|
|
target[k] = merge_recursive(target[k], v)
|
|
else
|
|
target[k] = type(v) == "table" and table.deep_copy(v) or v
|
|
end
|
|
end
|
|
return target
|
|
end
|
|
|
|
local result = table.deep_copy(tables[1])
|
|
for i = 2, #tables do
|
|
result = merge_recursive(result, tables[i])
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
function table.chunk(t, size)
|
|
if type(t) ~= "table" then error("table.chunk: first argument must be a table", 2) end
|
|
if not table.is_array(t) then error("table.chunk: first argument must be an array", 2) end
|
|
if type(size) ~= "number" or size ~= math.floor(size) or size <= 0 then
|
|
error("table.chunk: size must be a positive integer", 2)
|
|
end
|
|
|
|
local result = {}
|
|
local len = #t
|
|
|
|
for i = 1, len, size do
|
|
local chunk = {}
|
|
for j = i, math.min(i + size - 1, len) do
|
|
orig_insert(chunk, t[j])
|
|
end
|
|
orig_insert(result, chunk)
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
function table.partition(t, predicate)
|
|
if type(t) ~= "table" then error("table.partition: first argument must be a table", 2) end
|
|
if type(predicate) ~= "function" then error("table.partition: second argument must be a function", 2) end
|
|
|
|
local truthy, falsy = {}, {}
|
|
|
|
if table.is_array(t) then
|
|
for i, v in ipairs(t) do
|
|
if predicate(v, i, t) then
|
|
orig_insert(truthy, v)
|
|
else
|
|
orig_insert(falsy, v)
|
|
end
|
|
end
|
|
else
|
|
for k, v in pairs(t) do
|
|
if predicate(v, k, t) then
|
|
truthy[k] = v
|
|
else
|
|
falsy[k] = v
|
|
end
|
|
end
|
|
end
|
|
|
|
return truthy, falsy
|
|
end
|
|
|
|
function table.group_by(t, key_func)
|
|
if type(t) ~= "table" then error("table.group_by: first argument must be a table", 2) end
|
|
if type(key_func) ~= "function" then error("table.group_by: second argument must be a function", 2) end
|
|
|
|
local result = {}
|
|
|
|
for k, v in pairs(t) do
|
|
local group_key = key_func(v, k, t)
|
|
if result[group_key] == nil then
|
|
result[group_key] = {}
|
|
end
|
|
|
|
if table.is_array(t) then
|
|
orig_insert(result[group_key], v)
|
|
else
|
|
result[group_key][k] = v
|
|
end
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
function table.zip(...)
|
|
local arrays = {...}
|
|
if #arrays == 0 then error("table.zip: at least one argument required", 2) end
|
|
|
|
for i, arr in ipairs(arrays) do
|
|
if type(arr) ~= "table" then
|
|
error("table.zip: argument " .. i .. " must be a table", 2)
|
|
end
|
|
if not table.is_array(arr) then
|
|
error("table.zip: argument " .. i .. " must be an array", 2)
|
|
end
|
|
end
|
|
|
|
local min_length = #arrays[1]
|
|
for i = 2, #arrays do
|
|
min_length = math.min(min_length, #arrays[i])
|
|
end
|
|
|
|
local result = {}
|
|
for i = 1, min_length do
|
|
local tuple = {}
|
|
for j = 1, #arrays do
|
|
orig_insert(tuple, arrays[j][i])
|
|
end
|
|
orig_insert(result, tuple)
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
function table.compact(t)
|
|
if type(t) ~= "table" then error("table.compact: argument must be a table", 2) end
|
|
|
|
-- Check if table has only integer keys (array-like)
|
|
local has_only_int_keys = true
|
|
local max_key = 0
|
|
for k in pairs(t) do
|
|
if type(k) ~= "number" or k ~= math.floor(k) or k <= 0 then
|
|
has_only_int_keys = false
|
|
break
|
|
end
|
|
max_key = math.max(max_key, k)
|
|
end
|
|
|
|
if has_only_int_keys then
|
|
-- Treat as array-like and return clean array
|
|
local result = {}
|
|
for i = 1, max_key do
|
|
local v = t[i]
|
|
if v ~= nil and v ~= false then
|
|
orig_insert(result, v)
|
|
end
|
|
end
|
|
return result
|
|
else
|
|
-- Regular table filtering
|
|
return table.filter(t, function(v) return v ~= nil and v ~= false end)
|
|
end
|
|
end
|
|
|
|
function table.sample(t, n)
|
|
if type(t) ~= "table" then error("table.sample: first argument must be a table", 2) end
|
|
if not table.is_array(t) then error("table.sample: first argument must be an array", 2) end
|
|
if n ~= nil and (type(n) ~= "number" or n ~= math.floor(n) or n < 0) then
|
|
error("table.sample: sample size must be a non-negative integer", 2)
|
|
end
|
|
|
|
n = n or 1
|
|
local len = #t
|
|
if n >= len then return table.clone(t) end
|
|
|
|
local shuffled = table.shuffle(t)
|
|
return table.slice(shuffled, 1, n)
|
|
end
|
|
|
|
function table.fold(t, folder, initial)
|
|
if type(t) ~= "table" then error("table.fold: first argument must be a table", 2) end
|
|
if type(folder) ~= "function" then error("table.fold: second argument must be a function", 2) end
|
|
|
|
local accumulator = initial
|
|
for k, v in pairs(t) do
|
|
accumulator = folder(accumulator, v, k, t)
|
|
end
|
|
return accumulator
|
|
end
|