第07章 迭代器和泛型 for

本章讲述了如何创建和使用适用于泛型 for 的迭代器。

迭代器与 closure

迭代器:可以遍历某集合所有元素的机制。

在 Lua 中,通常将迭代器表示为函数。每调用一次,即返回集合中的“下一个”元素。

values = function (t)
     local i = 0
     return function () i = i+1
                    return t[i]
               end
end
t = {1, 2, 30}
iter = values(t) -- 创建迭代器
while true do
     local element = iter() -- 调用迭代器
     if element == nil then break end
     print(element)
end

以下使用泛型 for,用法更简单,因泛型 for 在其内部保留了迭代器,不用再显式地写出来。

for element in values(t) do
     print(element)
end

另外一个比较复杂的例子。遍历一篇文本文件的所有单词

allwords = function()
     local line = io.read()
     local pos = 1
     return function()
          while line do
               local s, e = string.find(line, "%w+", pos)
               if s then
                    pos = e + 1
                    return string.sub(line, s, e)
               else -- 一行读完
                    line = io.read()
                    pos = 1
               end
          end
          return nil -- 读完了,返回 nil
     end
end
for word in allwords() do
     print(word)
end

由上面也可以看到迭代器好用,但不好写。一般都倾向于使用系统自带的迭代器。

泛型 for 的语义(原理)

泛型 for 在循环过程内部保存了迭代器函数。实际上是3个值:

  • 迭代器函数
  • 恒定状态(invariant state)
  • 控制变量(control variable, 下面示例代码中<var-list>的第一个元素)

语法:

for <var-list>  in <exp-list> do
     <body>
end

<var-list> 是多个变量的列表,<exp-list>是多个表达式的列表。在实际循环中,当控制变量(<var-list>的第一个元素)的值为 nil 时,循环就结束了。

for 的第一件事,就是对 in 后的表达式求值。它返回三个值供 for 保存:迭代器函数,恒定状态和控制变量的初值。如果 in 后的表达式返回多于三个值,那么只保留前三个值,如果返回值不足三个,将以 nil 补足。

t = {1, 3, 33, 333}
for k, v in pairs(t) do
     print(k, v)
end

上面的循环将输出:

1    1
2    3
3    33
4    333

【原理】更一般地,

for var_1, ..., var_n in <explist> do <block> end

等价于

do
     local _f, _s, _var = <explist> -- iterator function, invariant state, init of control variable
     while true do
          local var_1, ..., var_n = _f(_s, _var)
          _var = var_1
          if _var == nil then
               break
          end
          <block>
     end
end

假如迭代器函数为 f, 恒定状态为 s, 控制变量的初值为 a0,则:

a1 = f(s, a0)
a2 = f(s, a1)
...

无状态的迭代器

自身不保存任何状态的迭代器

pairs, ipairs 即无状态的迭代器。前面的几章中也有例子,这里只简单写一个。

a = {1, 2, "one", "two", 3.1415}
for i, v in ipairs(a) do
     print(i, v)
end

书中的例子是上面这个,不过我写成下面这种,输出结果也是一样的。

for k, v in pairs(a) do
     print(k, v)
end

其余内容讲到了 ipairs 和 pair 在 lua 中的具体实现,以及它们之间的区别。TODO:择日再读,了然于心后再添加到这里来。

具有复杂状态的迭代器

状态打包成 table 等,这样就能塞入很多状态了。TODO:择日再读,了然于心后再添加到这里来。

真正的迭代器

这是一个明确含义的说法。前面的“伪迭代器”只是提供了每次迭代的一个返回值,真正做迭代的是 for 循环。

所谓真正的迭代器,就是在迭代器中做真正的迭代操作,那样就不需要什么for循环了。迭代器接受一个函数作为参数,并在内部循环中调用这个参数。

老版本的 Lua 中没有 for,这种方式,当时是比较流行的。这部分内容暂时就不展开笔记了。

后记:本章内容比较深入,日后还需再回顾的(TODO)。