diff --git a/modules/table+/table.lua b/modules/table+/table.lua index e98592c..e5f5d53 100644 --- a/modules/table+/table.lua +++ b/modules/table+/table.lua @@ -3,6 +3,7 @@ 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 @@ -18,6 +19,7 @@ function table.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 @@ -26,6 +28,7 @@ function table.remove(t, pos) 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 @@ -38,17 +41,24 @@ function table.concat(t, sep, start_idx, end_idx) 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 @@ -58,11 +68,17 @@ function table.size(t) 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 @@ -79,6 +95,8 @@ function table.is_array(t) 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 @@ -86,6 +104,9 @@ function table.clear(t) 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 = {} @@ -95,6 +116,9 @@ function table.clone(t) 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 @@ -115,6 +139,10 @@ function table.deep_copy(t) 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 @@ -123,6 +151,10 @@ function table.contains(t, value) 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 @@ -131,6 +163,10 @@ function table.index_of(t, value) 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 @@ -141,6 +177,10 @@ function table.find(t, predicate) 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 @@ -151,6 +191,10 @@ function table.find_index(t, predicate) 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 @@ -167,6 +211,10 @@ function table.count(t, value_or_predicate) 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 @@ -195,6 +243,10 @@ function table.filter(t, predicate) 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 @@ -202,6 +254,10 @@ function table.reject(t, predicate) 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 @@ -213,6 +269,10 @@ function table.map(t, transformer) 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 @@ -224,6 +284,10 @@ function table.map_values(t, transformer) 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 @@ -236,6 +300,11 @@ function table.map_keys(t, transformer) 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 @@ -259,6 +328,9 @@ function table.reduce(t, reducer, initial) 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 @@ -269,6 +341,9 @@ function table.sum(t) 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 @@ -279,6 +354,9 @@ function table.product(t) 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 @@ -293,6 +371,9 @@ function table.min(t) 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 @@ -307,12 +388,19 @@ function table.max(t) 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 @@ -329,6 +417,10 @@ function table.all(t, predicate) 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 @@ -345,11 +437,18 @@ function table.any(t, predicate) 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 @@ -375,6 +474,10 @@ function table.unique(t) 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 @@ -402,6 +505,10 @@ function table.intersection(t1, t2) 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 @@ -425,6 +532,10 @@ function table.union(t1, t2) 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 @@ -437,6 +548,9 @@ function table.difference(t1, t2) 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 @@ -449,6 +563,9 @@ function table.reverse(t) 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 @@ -466,6 +583,10 @@ function table.shuffle(t) 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 @@ -488,6 +609,11 @@ function table.rotate(t, positions) 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 @@ -513,6 +639,12 @@ function table.slice(t, start_idx, end_idx) 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 @@ -564,6 +696,10 @@ function table.splice(t, start_idx, delete_count, ...) 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 @@ -576,6 +712,10 @@ function table.sort_by(t, key_func) 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 @@ -591,6 +731,9 @@ function table.is_sorted(t, comp) 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 @@ -601,6 +744,9 @@ function table.keys(t) 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 @@ -611,6 +757,9 @@ function table.values(t) 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 @@ -621,6 +770,9 @@ function table.pairs(t) 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 @@ -640,6 +792,10 @@ function table.merge(...) 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 @@ -658,6 +814,9 @@ function table.extend(t1, ...) 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 @@ -668,6 +827,10 @@ function table.invert(t) 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 @@ -683,6 +846,10 @@ function table.pick(t, ...) 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 @@ -701,6 +868,10 @@ function table.omit(t, ...) 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 @@ -739,6 +910,10 @@ function table.deep_equals(t1, t2) 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 @@ -766,6 +941,9 @@ function table.flatten(t, depth) 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 @@ -795,6 +973,10 @@ function table.deep_merge(...) 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 @@ -816,6 +998,10 @@ function table.chunk(t, size) 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 @@ -843,6 +1029,10 @@ function table.partition(t, predicate) 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 @@ -865,6 +1055,9 @@ function table.group_by(t, key_func) 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 @@ -895,6 +1088,9 @@ function table.zip(...) 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 @@ -925,6 +1121,10 @@ function table.compact(t) 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 @@ -940,6 +1140,11 @@ function table.sample(t, n) 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 diff --git a/tests/table.lua b/tests/table.lua index 1f2e9ad..627d9fa 100644 --- a/tests/table.lua +++ b/tests/table.lua @@ -883,5 +883,29 @@ test("Statistical Analysis", function() assert_close(83.4, class_average, 0.1) end) +test("Table Metatable Method Chaining", function() + local t = {1, 2, 3, 4, 5} + + -- Check if methods are available directly on table instances + assert_equal("function", type(t.filter), "table should have filter method via metatable") + assert_equal("function", type(t.map), "table should have map method via metatable") + assert_equal("function", type(t.length), "table should have length method via metatable") + + -- Test actual method chaining + local result = t:filter(function(v) return v > 2 end) + :map(function(v) return v * 2 end) + + assert_table_equal({6, 8, 10}, result) + + -- Test chaining with method calls + local nums = {1, 2, 3, 4, 5} + local sum = nums:sum() + assert_equal(15, sum) + + local sizes = {a = 1, b = 2} + local size = sizes:size() + assert_equal(2, size) +end) + summary() test_exit() diff --git a/todo_sessions.json b/todo_sessions.json deleted file mode 100644 index 61a1bb3..0000000 --- a/todo_sessions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "session:x5joQraQyEkfzzRMrcP4o8yK0xjgwtCW": "{\"todos\":[{\"text\":\"asdasd\",\"completed\":true,\"id\":\"1753414744_8147\",\"created_at\":1753414744},{\"text\":\"fsdf\",\"completed\":true,\"id\":\"1753414748_8147\",\"created_at\":1753414748},{\"text\":\"asdasd\",\"completed\":false,\"id\":\"1753415063_8147\",\"created_at\":1753415063},{\"id\":\"1753415066_8147\",\"completed\":false,\"text\":\"asdkjfhaslkjdhflkasjhdf\",\"created_at\":1753415066},{\"text\":\"alsdhnfpuihawepiufhbpioweHBFIOEWBSF\",\"completed\":false,\"id\":\"1753415069_8147\",\"created_at\":1753415069}]}" -}