Lua Iterators
Lua - Iterators
Section titled “Lua - Iterators”An iterator is a mechanism that allows you to traverse through all elements of a collection (often a table in Lua) one by one. Lua’s generic for loop is the primary way to use iterators.
Generic for Loop and Iterators
Section titled “Generic for Loop and Iterators”The generic for loop uses an iterator function to step through a collection. The syntax is:
for var_1, var_2, ..., var_n in iterator_function, state, initial_value do -- loop bodyendLua provides two commonly used built-in iterator functions for tables:
ipairs(t): Iterates over the integer keys of tabletfrom 1 up to the first missing integer key (i.e., for array-like sequences). It returns the index (key) and value at each step.pairs(t): Iterates over all key-value pairs in tablet, including non-integer keys and non-sequential integer keys. The order of iteration is not guaranteed forpairs.
Example with ipairs (for sequences):
local days = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"}
for index, dayName in ipairs(days) do print(index, dayName)end-- Output:-- 1 Monday-- 2 Tuesday-- 3 Wednesday-- 4 Thursday-- 5 FridayExample with pairs (for general tables):
local person = { name = "Alice", age = 30, city = "New York" }person[1] = "Extra Info" -- Add a numeric key
for key, value in pairs(person) do print(key, value)end-- Possible Output (order might vary):-- name Alice-- city New York-- 1 Extra Info-- age 30Custom Iterators
Section titled “Custom Iterators”In Lua, iterators are implemented using functions. The generic for loop repeatedly calls this iterator function. There are two main types of custom iterators based on how they maintain state:
- Stateless Iterators
- Stateful Iterators
Stateless Iterators
Section titled “Stateless Iterators”A stateless iterator is a function that, for each iteration, receives the current state and control variable from the for loop itself. It doesn’t need to keep any state internally across calls; the for loop manages the state.
Let’s create a stateless iterator that generates squares of numbers up to a limit.
-- Iterator function: square_iter(limit, current_num)-- limit: the invariant state (max count)-- current_num: the control variablefunction square_iter(limit, current_num) current_num = current_num + 1 if current_num <= limit then return current_num, current_num * current_num -- returns new control_var, value(s) end -- No more values, return nil to stop iterationend
-- Using the stateless iterator directly with the for loop-- for <loop_vars> in <iterator_func>, <invariant_state>, <initial_control_var> doprint("Squares using direct iterator function:")for i, n_squared in square_iter, 3, 0 do print(i, n_squared)end-- Output:-- 1 1-- 2 4-- 3 9Often, you’ll wrap the iterator function in another function (a factory) that sets up the initial state for the for loop, similar to how ipairs and pairs work.
-- Factory function for the squares iteratorfunction squares_factory(limit) -- Returns: iterator_function, invariant_state, initial_control_value return square_iter, limit, 0end
print("\nSquares using factory function:")for i, n_squared in squares_factory(4) do print(i, n_squared)end-- Output:-- 1 1-- 2 4-- 3 9-- 4 16Stateful Iterators
Section titled “Stateful Iterators”A stateful iterator maintains its state across calls using closures. A closure is a function that captures (closes over) its surrounding local variables (upvalues). To create a stateful iterator, you typically define a factory function that creates and returns the closure.
Let’s create an iterator for the elements of an array using a closure.
local fruits = {"Apple", "Banana", "Cherry"}
-- Factory function that returns the iterator (a closure)function element_iterator_factory(collection) local index = 0 -- Upvalue, state for the iterator local count = #collection -- Upvalue, another piece of state
-- This is the closure, the actual iterator function return function() index = index + 1 if index <= count then return collection[index] -- Return the next element else return nil -- No more elements, stop iteration end endend
print("\nIterating fruits with stateful iterator:")-- The for loop calls the closure returned by element_iterator_factoryfor fruit in element_iterator_factory(fruits) do print(fruit)end-- Output:-- Apple-- Banana-- CherryIn this example, element_iterator_factory is called once. It sets up index and count. The inner anonymous function (the closure) is returned and becomes the iterator. Each time the for loop calls this closure, it can access and modify the index upvalue from its enclosing scope, thus maintaining its state.
Practical Application: Iterators are used everywhere for processing collections. Custom iterators are powerful for abstracting complex data structures or sequences, like reading lines from a file (io.lines), traversing a tree, or generating values on demand.