前置知识: Lua

Lua迭代器

00:00
2 min Intermediate 2026/6/14

自定义迭代器

概述

Lua 的泛型 for 循环通过迭代器函数遍历集合。理解迭代器工作原理和自定义方式,是编写优雅 Lua 代码的关键。Lua 提供了无状态迭代器和有状态迭代器两种模式

基础概念

泛型 for 的工作机制

-- 泛型 for 的完整语法
for var_1, ..., var_n in explist do block end

-- 等价于以下代码:
local f, s, var = explist
while true do
  local var_1, ..., var_n = f(s, var)
  if var_1 == nil then break end
  var = var_1
  block
end

-- f: 迭代器函数
-- s: 恒定状态(不变量)
-- var: 控制变量

内置迭代器

迭代器用途
pairs遍历的所有键值对
ipairs遍历数组连续整数键
next返回下一个键值对

快速上手

简单迭代器

-- 最简单的迭代器:闭包实现
function range(n)
  local i = 0
  return function()
    i = i + 1
    if i <= n then return i end
  end
end

for i in range(5) do
  print(i) -- 输出 1, 2, 3, 4, 5
end

-- 带步长的迭代器
function range(start, stop, step)
  step = step or 1
  local current = start - step
  return function()
    current = current + step
    if current <= stop then return current end
  end
end

for i in range(1, 10, 2) do
  print(i) -- 输出 1, 3, 5, 7, 9
end

pairs 和 ipairs 的区别

local t = {10, 20, 30, key = "value"}

-- ipairs:只遍历连续整数键(1, 2, 3...)
for i, v in ipairs(t) do
  print(i, v) -- 1 10, 2 20, 3 30
end

-- pairs:遍历所有键值对
for k, v in pairs(t) do
  print(k, v) -- 包括 key = "value"
end

-- 注意:pairs 的遍历顺序不确定
-- 如果需要有序遍历,需要先排序键
local keys = {}
for k in pairs(t) do table.insert(keys, k) end
table.sort(keys, function(a, b) return tostring(a) < tostring(b) end)
for _, k in ipairs(keys) do
  print(k, t[k])
end

详细用法

无状态迭代器

-- 无状态迭代器:不依赖闭包,所有状态通过参数传递
-- 更高效,因为不需要创建闭包

-- 实现自定义的 ipairs
local function iter(t, i)
  i = i + 1
  local v = t[i]
  if v ~= nil then return i, v end
end

function myIpairs(t)
  return iter, t, 0
end

-- 使用
for i, v in myIpairs({"a", "b", "c"}) do
  print(i, v)
end

-- 反向遍历迭代器
local function revIter(t, i)
  i = i - 1
  if i > 0 then return i, t[i] end
end

function revIpairs(t)
  return revIter, t, #t + 1
end

for i, v in revIpairs({"a", "b", "c"}) do
  print(i, v) -- 3 c, 2 b, 1 a
end

有状态迭代器

-- 有状态迭代器:使用闭包保存状态
-- 适合复杂遍历逻辑

-- 遍历树形结构
function treeIter(root)
  local stack = {root}
  return function()
    if #stack == 0 then return nil end
    local node = table.remove(stack)
    -- 子节点入栈(右子树先入栈,左子树后入栈)
    if node.right then table.insert(stack, node.right) end
    if node.left then table.insert(stack, node.left) end
    return node
  end
end

-- 过滤迭代器
function filterIter(iterFn, predicate)
  return function()
    for value in iterFn do
      if predicate(value) then return value end
    end
  end
end

-- 使用
local items = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
local evens = filterIter(range(10), function(x) return x % 2 == 0 end)
for v in evens do print(v) end -- 2, 4, 6, 8, 10

文件行迭代器

-- io.lines 返回文件行迭代器
for line in io.lines("data.txt") do
  process(line)
end

-- 自定义 CSV 行迭代器
function csvLines(filename)
  local file = io.open(filename, "r")
  if not file then return nil end
  return function()
    local line = file:read()
    if not line then
      file:close()
      return nil
    end
    -- 解析 CSV 行
    local fields = {}
    for field in line:gmatch("([^,]*)") do
      table.insert(fields, field)
    end
    return unpack(fields)
  end
end

-- 使用
for name, age, city in csvLines("users.csv") do
  print(name, age, city)
end

常见场景

字符串迭代器

-- 逐字符迭代
function chars(s)
  local i = 0
  return function()
    i = i + 1
    if i <= #s then return s:sub(i, i) end
  end
end

for c in chars("Hello") do
  print(c) -- H, e, l, l, o
end

-- 单词迭代器
function words(s)
  return s:gmatch("%S+")
end

for w in words("hello world from lua") do
  print(w)
end

组合迭代器

-- map 迭代器:对每个元素应用函数
function mapIter(iterFn, transform)
  return function()
    local value = iterFn()
    if value ~= nil then return transform(value) end
  end
end

-- 使用
local doubled = mapIter(range(5), function(x) return x * 2 end)
for v in doubled do print(v) end -- 2, 4, 6, 8, 10

-- take 迭代器:只取前 n 个
function takeIter(iterFn, n)
  local count = 0
  return function()
    if count >= n then return nil end
    count = count + 1
    return iterFn()
  end
end

for v in takeIter(range(100), 5) do
  print(v) -- 1, 2, 3, 4, 5
end

注意事项

  • 状态迭代器比有状态迭代器更效,优先使用
  • ipairs 遇到 nil 会停止遍历,即使后还有元素
  • pairs 的遍历顺序不确定,不要依赖遍历顺序
  • 闭包迭代器会捕获变量,注意中的变量引用
  • 迭代器函数返回 nilfor 循环结束
  • 自定义迭代器应保持语义清晰,避免副作用

进阶用法

协程迭代器

-- 使用协程实现复杂迭代器
function traverseTree(root)
  return coroutine.wrap(function()
    if root then
      for v in traverseTree(root.left) do coroutine.yield(v) end
      coroutine.yield(root.value)
      for v in traverseTree(root.right) do coroutine.yield(v) end
    end
  end)
end

-- 中序遍历二叉树
for value in traverseTree(root) do
  print(value)
end

-- 生成排列
function permutations(arr)
  return coroutine.wrap(function()
    local n = #arr
    local function generate(k)
      if k == 1 then
        coroutine.yield(table.concat(arr, ","))
      else
        for i = 1, k do
          arr[i], arr[k] = arr[k], arr[i]
          generate(k - 1)
          arr[i], arr[k] = arr[k], arr[i]
        end
      end
    end
    generate(n)
  end)
end

for p in permutations({1, 2, 3}) do
  print(p)
end

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式