Iterators
Prerequisites: Closures
A generic for loop works with any iterator — a function that
returns the next value each time it's called, and nil when done.
Closure-based iterators
local function range(from, to, step)
step = step or 1
local current = from - step
return function()
current = current + step
if current <= to then return current end
end
end
for n in range(1, 10, 3) do io.write(n, " ") end
print()
local function words(s)
return s:gmatch("%a+")
end
for w in words("iterators are neat") do print(w) end
1 4 7 10
iterators
are
neat
The iterator keeps its position in a closure variable (current).
Iterating over a table in sorted order
local function sortedPairs(t)
local keys = {}
for k in pairs(t) do keys[#keys + 1] = k end
table.sort(keys)
local i = 0
return function()
i = i + 1
local k = keys[i]
if k ~= nil then return k, t[k] end
end
end
local stock = {pears = 0, apples = 10, plums = 4}
for fruit, count in sortedPairs(stock) do
print(fruit, count)
end
apples 10
pears 0
plums 4
A reusable fix for pairs' unpredictable order.
How the generic for works
for vars in explist do evaluates explist to three values — an
iterator function, a state, and an initial control value — then
calls iterator(state, control) until it returns nil. ipairs uses
this "stateless" form:
local function evens(t, i)
i = i + 2
if t[i] ~= nil then return i, t[i] end
end
local function everyOther(t) return evens, t, -1 end
for i, v in everyOther({"a", "b", "c", "d", "e"}) do
print(i, v)
end
1 a
3 c
5 e
The control value starts at -1, so the first call returns index 1, then
3, then 5. Stateless iterators avoid creating a closure per loop, which
matters in hot code.