Claude-skill-registry-data Lua Tables Patterns
Use when lua tables as the universal data structure including arrays, dictionaries, objects, metatables, object-oriented patterns, data structures, and advanced table manipulation for building flexible, efficient Lua applications.
git clone https://github.com/majiayu000/claude-skill-registry-data
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry-data "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/lua-tables-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-data-lua-tables-patterns && rm -rf "$T"
data/lua-tables-patterns/SKILL.mdLua Tables Patterns
Introduction
Tables are Lua's sole compound data structure, serving as arrays, dictionaries, objects, modules, and more. This versatility makes tables fundamental to Lua programming, enabling implementations of virtually any data structure through creative use of table features and metatables.
Tables use associative arrays that can be indexed with any Lua value except nil and NaN. Array-like tables use consecutive integer indices starting from 1, while dictionary-like tables use arbitrary keys. Metatables enable operator overloading and advanced behaviors.
This skill covers table fundamentals, array and dictionary patterns, metatables, object-oriented programming, common data structures, performance optimization, and idiomatic table manipulation techniques.
Table Fundamentals
Tables combine arrays and hash maps into a single versatile data structure with 1-based indexing.
-- Empty table creation local t1 = {} local t2 = table.create(100) -- Pre-allocate (LuaJIT) -- Array-style tables (consecutive integers from 1) local array = {10, 20, 30, 40, 50} print(array[1]) -- 10 print(array[5]) -- 50 print(#array) -- 5 (length operator) -- Dictionary-style tables (any key type) local dict = { name = "Alice", age = 30, email = "alice@example.com" } print(dict.name) -- Dot notation print(dict["age"]) -- Bracket notation -- Mixed array and dictionary local mixed = { 10, 20, 30, -- array part: [1]=10, [2]=20, [3]=30 name = "Bob", age = 25 } print(mixed[2]) -- 20 print(mixed.name) -- "Bob" -- Constructor syntax variations local point = {x = 10, y = 20} local point2 = {["x"] = 10, ["y"] = 20} -- Equivalent -- Nested tables local nested = { user = { name = "Charlie", address = { city = "Seattle", zip = "98101" } } } print(nested.user.address.city) -- "Seattle" -- Table insertion and removal local list = {} table.insert(list, "first") table.insert(list, "second") table.insert(list, 2, "middle") -- Insert at position 2 print(list[1]) -- "first" print(list[2]) -- "middle" print(list[3]) -- "second" local removed = table.remove(list, 2) -- Remove at position 2 print(removed) -- "middle" -- Table iteration -- Pairs: All keys (unordered) for key, value in pairs(dict) do print(key, value) end -- Ipairs: Integer keys in order (1, 2, 3, ...) for index, value in ipairs(array) do print(index, value) end -- Manual iteration for i = 1, #array do print(i, array[i]) end -- Table copying (shallow) local original = {1, 2, 3, x = 10} local copy = {} for k, v in pairs(original) do copy[k] = v end -- Table concatenation local fruits = {"apple", "banana", "cherry"} local str = table.concat(fruits, ", ") print(str) -- "apple, banana, cherry" -- Sorting local numbers = {5, 2, 8, 1, 9} table.sort(numbers) -- numbers is now {1, 2, 5, 8, 9} -- Custom sort local people = { {name = "Alice", age = 30}, {name = "Bob", age = 25}, {name = "Charlie", age = 35} } table.sort(people, function(a, b) return a.age < b.age end)
Tables efficiently combine arrays and hash tables, with the implementation automatically optimizing storage based on usage patterns.
Array Patterns
Lua arrays use 1-based indexing and provide efficient sequential access through the array part of tables.
-- Creating arrays local empty = {} local numbers = {10, 20, 30, 40, 50} local strings = {"hello", "world", "lua"} -- Array length print(#numbers) -- 5 -- Appending elements numbers[#numbers + 1] = 60 table.insert(numbers, 70) -- Prepending (expensive) table.insert(numbers, 1, 0) -- Removing elements local last = table.remove(numbers) -- Remove and return last local first = table.remove(numbers, 1) -- Remove and return first -- Array operations function map(array, fn) local result = {} for i = 1, #array do result[i] = fn(array[i]) end return result end local doubled = map(numbers, function(x) return x * 2 end) function filter(array, predicate) local result = {} for i = 1, #array do if predicate(array[i]) then table.insert(result, array[i]) end end return result end local evens = filter(numbers, function(x) return x % 2 == 0 end) function reduce(array, fn, initial) local accumulator = initial for i = 1, #array do accumulator = fn(accumulator, array[i]) end return accumulator end local sum = reduce(numbers, function(acc, x) return acc + x end, 0) -- Array slicing function slice(array, start_idx, end_idx) local result = {} end_idx = end_idx or #array for i = start_idx, end_idx do table.insert(result, array[i]) end return result end local subset = slice(numbers, 2, 4) -- Array reversal function reverse(array) local result = {} for i = #array, 1, -1 do table.insert(result, array[i]) end return result end -- In-place reversal function reverse_in_place(array) local n = #array for i = 1, math.floor(n / 2) do array[i], array[n - i + 1] = array[n - i + 1], array[i] end end -- Array flattening function flatten(array) local result = {} for i = 1, #array do if type(array[i]) == "table" then local nested = flatten(array[i]) for j = 1, #nested do table.insert(result, nested[j]) end else table.insert(result, array[i]) end end return result end local nested = {1, {2, 3}, {4, {5, 6}}} local flat = flatten(nested) -- {1, 2, 3, 4, 5, 6} -- Array chunking function chunk(array, size) local result = {} for i = 1, #array, size do local chunk = {} for j = i, math.min(i + size - 1, #array) do table.insert(chunk, array[j]) end table.insert(result, chunk) end return result end local chunked = chunk({1, 2, 3, 4, 5, 6, 7}, 3) -- {{1, 2, 3}, {4, 5, 6}, {7}} -- Unique values function unique(array) local seen = {} local result = {} for i = 1, #array do if not seen[array[i]] then seen[array[i]] = true table.insert(result, array[i]) end end return result end -- Array intersection function intersection(a, b) local set = {} for i = 1, #a do set[a[i]] = true end local result = {} for i = 1, #b do if set[b[i]] then table.insert(result, b[i]) end end return result end
Use arrays for sequential data with numeric indices, leveraging Lua's optimizations for consecutive integer keys.
Dictionary and Set Patterns
Dictionary tables use arbitrary keys for fast lookups, while sets use keys with true values.
-- Dictionary creation local user = { id = 1, name = "Alice", email = "alice@example.com", age = 30 } -- Dynamic key access local key = "name" print(user[key]) -- "Alice" -- Adding and modifying user.city = "Seattle" user.age = 31 -- Removing keys user.age = nil -- Checking key existence if user.name then print("Name exists") end -- Counting keys function table_length(t) local count = 0 for _ in pairs(t) do count = count + 1 end return count end print(table_length(user)) -- Merging dictionaries function merge(t1, t2) local result = {} for k, v in pairs(t1) do result[k] = v end for k, v in pairs(t2) do result[k] = v end return result end -- Deep copy function deep_copy(obj) if type(obj) ~= 'table' then return obj end local copy = {} for k, v in pairs(obj) do copy[deep_copy(k)] = deep_copy(v) end return setmetatable(copy, getmetatable(obj)) end -- Set implementation local Set = {} function Set.new(list) local set = {} for i = 1, #list do set[list[i]] = true end return set end function Set.add(set, value) set[value] = true end function Set.remove(set, value) set[value] = nil end function Set.contains(set, value) return set[value] == true end function Set.union(a, b) local result = {} for k in pairs(a) do result[k] = true end for k in pairs(b) do result[k] = true end return result end function Set.intersection(a, b) local result = {} for k in pairs(a) do if b[k] then result[k] = true end end return result end function Set.difference(a, b) local result = {} for k in pairs(a) do if not b[k] then result[k] = true end end return result end function Set.to_list(set) local list = {} for k in pairs(set) do table.insert(list, k) end return list end -- Usage local set1 = Set.new({1, 2, 3, 4, 5}) local set2 = Set.new({4, 5, 6, 7, 8}) Set.add(set1, 10) print(Set.contains(set1, 3)) -- true local union = Set.union(set1, set2) local inter = Set.intersection(set1, set2) -- OrderedDict pattern local OrderedDict = {} function OrderedDict.new() return {_keys = {}, _values = {}} end function OrderedDict.set(dict, key, value) if not dict._values[key] then table.insert(dict._keys, key) end dict._values[key] = value end function OrderedDict.get(dict, key) return dict._values[key] end function OrderedDict.pairs(dict) local i = 0 return function() i = i + 1 local key = dict._keys[i] if key then return key, dict._values[key] end end end
Dictionaries provide O(1) average-case lookups and enable flexible key-value storage patterns.
Metatables and Metamethods
Metatables enable operator overloading, property access control, and custom behaviors through metamethods.
-- Basic metatable local t = {} local mt = { __index = function(table, key) return "default value" end } setmetatable(t, mt) print(t.anything) -- "default value" -- Operator overloading local Vector = {} Vector.__index = Vector function Vector.new(x, y) return setmetatable({x = x, y = y}, Vector) end function Vector.__add(a, b) return Vector.new(a.x + b.x, a.y + b.y) end function Vector.__sub(a, b) return Vector.new(a.x - b.x, a.y - b.y) end function Vector.__mul(a, scalar) if type(scalar) == "number" then return Vector.new(a.x * scalar, a.y * scalar) end end function Vector.__tostring(v) return string.format("Vector(%d, %d)", v.x, v.y) end function Vector.__eq(a, b) return a.x == b.x and a.y == b.y end local v1 = Vector.new(1, 2) local v2 = Vector.new(3, 4) local v3 = v1 + v2 print(v3) -- Vector(4, 6) -- Read-only tables function read_only(table) local proxy = {} local mt = { __index = table, __newindex = function(t, k, v) error("Attempt to modify read-only table") end } return setmetatable(proxy, mt) end local config = read_only({ api_key = "secret", timeout = 5000 }) -- config.timeout = 10000 -- Error! -- Lazy initialization function lazy_table(constructor) local cache = {} local mt = { __index = function(t, key) local value = constructor(key) rawset(t, key, value) return value end } return setmetatable({}, mt) end local fibonacci = lazy_table(function(n) if n <= 1 then return n end return fibonacci[n - 1] + fibonacci[n - 2] end) print(fibonacci[10]) -- 55 -- Default values function table_with_default(default) local mt = { __index = function() return default end } return setmetatable({}, mt) end local counts = table_with_default(0) counts.apple = counts.apple + 1 counts.banana = counts.banana + 1 -- Method call syntax local Object = {} Object.__index = Object function Object.new() return setmetatable({}, Object) end function Object:method() -- self refers to the instance print("Called on", self) end local obj = Object.new() obj:method() -- Syntactic sugar for obj.method(obj) -- __call metamethod local Factory = {} function Factory.new() local instance = {count = 0} local mt = { __call = function(self, ...) self.count = self.count + 1 return ... end } return setmetatable(instance, mt) end local f = Factory.new() f("hello") -- Callable like a function print(f.count) -- 1 -- Property tracking function tracked_table() local data = {} local mt = { __index = data, __newindex = function(t, k, v) print(string.format("Set %s = %s", k, v)) data[k] = v end } return setmetatable({}, mt) end
Metatables enable powerful metaprogramming patterns and domain-specific languages in Lua.
Object-Oriented Patterns
Lua supports multiple OOP approaches through tables and metatables, from simple prototypes to class-based systems.
-- Prototype-based OOP local Animal = {species = "Unknown"} function Animal:new(o) o = o or {} setmetatable(o, self) self.__index = self return o end function Animal:speak() print("Some sound") end local Dog = Animal:new{species = "Canine"} function Dog:speak() print("Woof!") end function Dog:fetch() print("Fetching...") end local dog = Dog:new{name = "Buddy"} dog:speak() -- "Woof!" print(dog.species) -- "Canine" -- Class-based OOP local Class = {} function Class:new(...) local instance = setmetatable({}, self) self.__index = self if instance.init then instance:init(...) end return instance end -- Define a class local Person = Class:new() function Person:init(name, age) self.name = name self.age = age end function Person:greet() return string.format("Hello, I'm %s", self.name) end function Person:birthday() self.age = self.age + 1 end local alice = Person:new("Alice", 30) print(alice:greet()) alice:birthday() print(alice.age) -- 31 -- Inheritance local Employee = Class:new() setmetatable(Employee, {__index = Person}) function Employee:init(name, age, company) Person.init(self, name, age) self.company = company end function Employee:work() return string.format("%s works at %s", self.name, self.company) end local bob = Employee:new("Bob", 25, "TechCorp") print(bob:greet()) -- Inherited from Person print(bob:work()) -- Encapsulation with closures function BankAccount(initial_balance) local balance = initial_balance or 0 return { deposit = function(amount) if amount > 0 then balance = balance + amount return true end return false end, withdraw = function(amount) if amount > 0 and amount <= balance then balance = balance - amount return true end return false end, get_balance = function() return balance end } end local account = BankAccount(100) account.deposit(50) account.withdraw(30) print(account.get_balance()) -- 120 -- Mixins local Flyable = {} function Flyable:fly() print(self.name .. " is flying") end function Flyable:land() print(self.name .. " has landed") end function mixin(target, source) for k, v in pairs(source) do target[k] = v end end local Bird = Person:new() mixin(Bird, Flyable) local bird = Bird:new("Eagle", 5) bird:fly() -- Multiple dispatch pattern local Shape = Class:new() function Shape:init(type) self.type = type end local Circle = Shape:new() function Circle:init(radius) Shape.init(self, "circle") self.radius = radius end local Rectangle = Shape:new() function Rectangle:init(width, height) Shape.init(self, "rectangle") self.width = width self.height = height end -- Visitor pattern local AreaCalculator = {} function AreaCalculator.visit_circle(circle) return math.pi * circle.radius * circle.radius end function AreaCalculator.visit_rectangle(rect) return rect.width * rect.height end function calculate_area(shape) local method_name = "visit_" .. shape.type return AreaCalculator[method_name](shape) end
Choose OOP patterns based on needs: prototypes for simple hierarchies, classes for structured systems, closures for encapsulation.
Common Data Structures
Implement classic data structures using tables for specific algorithmic needs.
-- Stack local Stack = {} function Stack.new() return {items = {}} end function Stack.push(stack, item) table.insert(stack.items, item) end function Stack.pop(stack) return table.remove(stack.items) end function Stack.peek(stack) return stack.items[#stack.items] end function Stack.is_empty(stack) return #stack.items == 0 end -- Queue local Queue = {} function Queue.new() return {items = {}, head = 1, tail = 0} end function Queue.enqueue(queue, item) queue.tail = queue.tail + 1 queue.items[queue.tail] = item end function Queue.dequeue(queue) if queue.head > queue.tail then return nil end local item = queue.items[queue.head] queue.items[queue.head] = nil queue.head = queue.head + 1 return item end -- Linked List local LinkedList = {} function LinkedList.new() return {head = nil, tail = nil, size = 0} end function LinkedList.append(list, value) local node = {value = value, next = nil} if list.tail then list.tail.next = node list.tail = node else list.head = node list.tail = node end list.size = list.size + 1 end function LinkedList.prepend(list, value) local node = {value = value, next = list.head} list.head = node if not list.tail then list.tail = node end list.size = list.size + 1 end function LinkedList.to_array(list) local array = {} local current = list.head while current do table.insert(array, current.value) current = current.next end return array end -- Binary Search Tree local BST = {} function BST.new() return {root = nil} end function BST.insert(tree, value) local function insert_node(node, value) if not node then return {value = value, left = nil, right = nil} end if value < node.value then node.left = insert_node(node.left, value) elseif value > node.value then node.right = insert_node(node.right, value) end return node end tree.root = insert_node(tree.root, value) end function BST.search(tree, value) local function search_node(node, value) if not node then return false end if value == node.value then return true end if value < node.value then return search_node(node.left, value) else return search_node(node.right, value) end end return search_node(tree.root, value) end function BST.inorder(tree) local result = {} local function traverse(node) if not node then return end traverse(node.left) table.insert(result, node.value) traverse(node.right) end traverse(tree.root) return result end -- Priority Queue (Binary Heap) local PriorityQueue = {} function PriorityQueue.new(comparator) return { heap = {}, compare = comparator or function(a, b) return a < b end } end function PriorityQueue.push(pq, item) table.insert(pq.heap, item) local i = #pq.heap while i > 1 do local parent = math.floor(i / 2) if pq.compare(pq.heap[i], pq.heap[parent]) then pq.heap[i], pq.heap[parent] = pq.heap[parent], pq.heap[i] i = parent else break end end end function PriorityQueue.pop(pq) if #pq.heap == 0 then return nil end local result = pq.heap[1] pq.heap[1] = pq.heap[#pq.heap] table.remove(pq.heap) local i = 1 while true do local left = i * 2 local right = i * 2 + 1 local smallest = i if left <= #pq.heap and pq.compare(pq.heap[left], pq.heap[smallest]) then smallest = left end if right <= #pq.heap and pq.compare(pq.heap[right], pq.heap[smallest]) then smallest = right end if smallest == i then break end pq.heap[i], pq.heap[smallest] = pq.heap[smallest], pq.heap[i] i = smallest end return result end -- Graph (Adjacency List) local Graph = {} function Graph.new() return {vertices = {}} end function Graph.add_vertex(graph, vertex) if not graph.vertices[vertex] then graph.vertices[vertex] = {} end end function Graph.add_edge(graph, from, to, weight) Graph.add_vertex(graph, from) Graph.add_vertex(graph, to) table.insert(graph.vertices[from], {to = to, weight = weight or 1}) end function Graph.neighbors(graph, vertex) return graph.vertices[vertex] or {} end
Implement data structures as needed for specific algorithmic requirements rather than using them universally.
Best Practices
-
Use local variables for tables to improve performance and avoid global namespace pollution
-
Pre-allocate tables when size is known to reduce reallocation overhead
-
Prefer ipairs for arrays and pairs for dictionaries to match iteration semantics
-
Use metatables sparingly as they add overhead; reserve for when needed
-
Avoid holes in arrays (nil values) as they break length operator and iteration
-
Cache table.insert and table.remove in tight loops for better performance
-
Use rawget and rawset to bypass metamethods when building metatables
-
Prefer weak tables for caches to allow garbage collection of unused entries
-
Document table structure in comments for complex nested tables
-
Use consistent key types to avoid confusion between string and numeric keys
Common Pitfalls
-
Forgetting 1-based indexing causes off-by-one errors when translating from other languages
-
Using # on dictionaries returns incorrect length as it only counts array part
-
Creating holes in arrays with nil values breaks length operator and ipairs
-
Not checking for nil keys in dictionaries leads to missed values
-
Mutating tables during iteration causes unpredictable behavior and missing elements
-
Overusing metatables adds performance overhead without clear benefits
-
Confusing . and : syntax for method calls causes incorrect self parameter
-
Not handling empty tables in recursive functions causes infinite loops
-
Using tables as boolean since empty tables are truthy in Lua
-
Shallow copying nested tables leaves references to nested structures
When to Use This Skill
Apply table patterns throughout Lua development as tables are the primary data structure.
Use arrays for ordered collections with numeric indices and sequential access patterns.
Leverage dictionaries for key-value storage, lookups, and mapping between values.
Apply metatables when implementing operator overloading, property access control, or DSLs.
Implement OOP patterns when building complex systems requiring encapsulation and inheritance.
Use custom data structures when specific algorithmic properties are needed beyond basic tables.