Skip to content
Iterators and the Generic for
step 1/5

Reading — step 1 of 5

Learn

~3 min readTables — The One Data Structure

Iterators and the Generic for

You have used the counting for already:

for i = 1, 3 do io.write(i, " ") end

--> 1 2 3

That form needs to know the bounds up front. The other form — the generic for — asks something else for the next value until it runs out, and it is how you walk a table whose size and keys you do not control.

ipairs: the array part, in order

local t = {"a", "b", "c"}
for i, v in ipairs(t) do io.write(i, "=", v, " ") end

--> 1=a 2=b 3=c

ipairs starts at 1 and stops the moment it finds a missing index. That word stops is load-bearing:

local holes = {"a", nil, "c"}
for i, v in ipairs(holes) do io.write(i, "=", v, " ") end
print("| # is " .. #holes)

--> 1=a | # is 3

ipairs gave up after the first element, while #holes claims 3. A table with a hole in its array part has no well-defined length in Lua, and these two operators disagree about it in exactly the way that ruins an afternoon. Keep array parts contiguous.

pairs: every key, in no particular order

local d = {x = 1, y = 2}
local keys = {}
for k, v in pairs(d) do keys[#keys + 1] = k .. "=" .. v end
table.sort(keys)
print(table.concat(keys, " "))

--> x=1 y=2

Note the table.sort. pairs order is unspecified — the manual promises nothing, and it can differ between runs. Any output that must be stable has to sort the keys itself. That is not a wart to work around; it follows from a hash table having no natural order.

pairs also sees the array part, so a table with both gives you everything:

local mixed = {"one", name = "two"}
local n = 0
for _ in pairs(mixed) do n = n + 1 end
print(n)

--> 2

Writing your own

The generic for has no idea what ipairs is. It just calls a function repeatedly until that function returns nil. A function that remembers a variable from around it — a closure — therefore already is an iterator, and the Closures lesson in the next chapter takes the idea further:

local function upto(limit)
    local i = 0
    return function()
        i = i + 1
        if i <= limit then return i, i * i end
    end
end

for i, sq in upto(4) do io.write(i, ":", sq, " ") end

--> 1:1 2:4 3:9 4:16

The closure keeps i between calls; returning nothing ends the loop. That is the entire contract.

Common mistakes

  • Using ipairs on a table with holes — it stops at the first gap, silently returning a prefix of your data.
  • Trusting pairs order — sort the keys when the output must be stable.
  • Using # on a non-contiguous table — it is only meaningful when indices run 1..n with no gaps.
  • Forgetting the loop ends on nil — an iterator that returns a falsy-but-present value like false still continues; one that returns nothing stops.
  • pairs when you meant ipairspairs on a list also yields the string keys somebody added later.

Your exercise

Every Nth asks you to write the iterator yourself, not to call one.

The first line holds two numbers, n and k. The next n lines are values. Write stepper(t, k), returning a closure that yields k, t[k], then 2k, t[2k], and so on while the index is within the table — and returns nothing once it is past the end. The driver walks it with a generic for and prints index:value for each.

With 5 values and k of 2 you print positions 2 and 4 only. The closure has to remember where it was between calls; that is the whole job.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…

Iterators and the Generic for — Lua Fundamentals