local orig_insert = table.insert local orig_remove = table.remove local orig_concat = table.concat local orig_sort = table.sort -- Enhanced table.insert with validation 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 -- Enhanced table.remove with validation 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 -- Enhanced table.concat with validation 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 -- Enhanced table.sort with validation 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 --- Returns the length of an array-like table -- @param t table to measure -- @return number of array elements function table.length(t) if type(t) ~= "table" then error("table.length: argument must be a table", 2) end return #t end --- Returns the total number of key-value pairs in a table -- @param t table to count -- @return total number of elements 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 --- Checks if a table is empty -- @param t table to check -- @return true if table has no elements 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 --- Checks if a table is an array (contiguous integer keys starting from 1) -- @param t table to check -- @return true if table is an array 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 --- Removes all elements from a table -- @param t table to clear 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 --- Creates a shallow copy of a table -- @param t table to clone -- @return new table with same key-value pairs 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 --- Creates a deep copy of a table, handling circular references -- @param t table to deep copy -- @return new table with recursively copied values 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 --- Checks if a table contains a specific value -- @param t table to search -- @param value value to find -- @return true if value is found 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 --- Returns the first key that maps to the given value -- @param t table to search -- @param value value to find -- @return key that maps to value, or nil if not found 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 --- Finds the first element that satisfies a predicate -- @param t table to search -- @param predicate function(value, key, table) -> boolean -- @return value, key of first matching element, or nil 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 --- Finds the key of the first element that satisfies a predicate -- @param t table to search -- @param predicate function(value, key, table) -> boolean -- @return key of first matching element, or nil 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 --- Counts elements matching a value or predicate -- @param t table to search -- @param value_or_predicate value to match or function(value, key, table) -> boolean -- @return number of matching elements 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 --- Filters elements that satisfy a predicate -- @param t table to filter -- @param predicate function(value, key, table) -> boolean -- @return new table with elements that pass the test 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 --- Filters elements that don't satisfy a predicate -- @param t table to filter -- @param predicate function(value, key, table) -> boolean -- @return new table with elements that fail the test 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 --- Transforms each element using a function -- @param t table to transform -- @param transformer function(value, key, table) -> new_value -- @return new table with transformed elements 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 --- Transforms values while preserving keys -- @param t table to transform -- @param transformer function(value, key, table) -> new_value -- @return new table with transformed values 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 --- Transforms keys while preserving values -- @param t table to transform -- @param transformer function(key, value, table) -> new_key -- @return new table with transformed keys 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 --- Reduces table to a single value using an accumulator function -- @param t table to reduce -- @param reducer function(accumulator, value, key, table) -> new_accumulator -- @param initial optional initial accumulator value -- @return final accumulator value 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 --- Sums all numeric values in a table -- @param t table containing numbers -- @return sum of all values 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 --- Multiplies all numeric values in a table -- @param t table containing numbers -- @return product of all values 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 --- Finds the minimum numeric value in a table -- @param t table containing numbers -- @return minimum value 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 --- Finds the maximum numeric value in a table -- @param t table containing numbers -- @return maximum value 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 --- Calculates the average of all numeric values -- @param t table containing numbers -- @return average value 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 --- Tests if all elements satisfy a predicate or are truthy -- @param t table to test -- @param predicate optional function(value, key, table) -> boolean -- @return true if all elements pass the test 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 --- Tests if any element satisfies a predicate or is truthy -- @param t table to test -- @param predicate optional function(value, key, table) -> boolean -- @return true if at least one element passes the test 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 --- Tests if no element satisfies a predicate or is truthy -- @param t table to test -- @param predicate optional function(value, key, table) -> boolean -- @return true if no elements pass the test 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 --- Removes duplicate values from a table -- @param t table to deduplicate -- @return new table with unique values 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 --- Returns elements common to both tables -- @param t1 first table -- @param t2 second table -- @return new table with intersecting values 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 --- Combines two tables, keeping all unique values -- @param t1 first table -- @param t2 second table -- @return new table with all unique values from both tables 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 --- Returns elements in first table but not in second -- @param t1 first table -- @param t2 second table -- @return new table with values from t1 that are not in t2 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 --- Reverses the order of elements in an array -- @param t array to reverse -- @return new array with elements in reverse order 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 --- Randomly shuffles elements in an array -- @param t array to shuffle -- @return new array with elements in random order 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 --- Rotates array elements by a number of positions -- @param t array to rotate -- @param positions number of positions to rotate (positive = right, negative = left) -- @return new array with rotated elements 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 --- Extracts a section of an array -- @param t array to slice -- @param start_idx starting index (inclusive) -- @param end_idx ending index (inclusive, optional) -- @return new array containing the slice 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 --- Modifies an array by removing elements and/or adding new ones -- @param t array to modify -- @param start_idx starting index for modification -- @param delete_count number of elements to remove -- @param ... elements to insert at start_idx -- @return array of removed elements 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 --- Sorts an array by a key function -- @param t array to sort -- @param key_func function to extract sort key from each element -- @return new sorted array 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 --- Checks if an array is sorted according to a comparator -- @param t array to check -- @param comp optional comparator function -- @return true if array is sorted 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 --- Returns an array of all keys in a table -- @param t table to extract keys from -- @return array of keys 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 --- Returns an array of all values in a table -- @param t table to extract values from -- @return array of values 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 --- Returns an array of [key, value] pairs -- @param t table to convert to pairs -- @return array of [key, value] arrays 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 --- Merges multiple tables into a new table -- @param ... tables to merge -- @return new table with all key-value pairs 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 --- Extends the first table with key-value pairs from other tables -- @param t1 table to extend -- @param ... tables to merge into t1 -- @return t1 (modified) 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 --- Creates a table with keys and values swapped -- @param t table to invert -- @return new table with values as keys and keys as values 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 --- Creates a new table with only specified keys -- @param t table to pick from -- @param ... keys to include -- @return new table with only specified keys 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 --- Creates a new table excluding specified keys -- @param t table to omit from -- @param ... keys to exclude -- @return new table without specified keys 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 --- Deep equality comparison between two tables -- @param t1 first table -- @param t2 second table -- @return true if tables are deeply equal 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 --- Flattens nested arrays to specified depth -- @param t array to flatten -- @param depth maximum depth to flatten (default 1) -- @return new flattened array 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 --- Deep merges multiple tables, combining nested tables -- @param ... tables to deep merge -- @return new table with deeply merged contents 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 --- Splits an array into chunks of specified size -- @param t array to chunk -- @param size size of each chunk -- @return array of chunks 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 --- Splits a table into two based on a predicate -- @param t table to partition -- @param predicate function(value, key, table) -> boolean -- @return two tables: elements that pass and elements that fail 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 --- Groups table elements by a key function -- @param t table to group -- @param key_func function(value, key, table) -> group_key -- @return table where keys are group keys and values are grouped elements 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 --- Combines multiple arrays element-wise into tuples -- @param ... arrays to zip together -- @return array of tuples 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 --- Removes falsy values (nil and false) from a table -- @param t table to compact -- @return new table with only truthy values 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 --- Returns a random sample of elements from an array -- @param t array to sample from -- @param n number of elements to sample (default 1) -- @return array with sampled elements 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 --- Folds a table using an accumulator function -- @param t table to fold -- @param folder function(accumulator, value, key, table) -> new_accumulator -- @param initial initial accumulator value -- @return final accumulator value 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