Lua中的迭代器提供的是一种遍历表中所有元素的机制,它通常是函数的形式,每次调用函数,返回“下一个”值。Lua中的迭代器有两种实现方式,基于闭包(closure)或者基于协程(coroutine)。以下会通过一个实例来分别讨论两种迭代器的实现。

为更好演示迭代器的功能并逐步实现一个迭代器函数,本文结合一个需求的场景,从一个字符串中拆出一个一个的UTF8字符,即每次调用迭代器函数会返回下一个UTF8字符,直到迭代完整个字符串。

首先需要编写一个这样的函数,输入一个字符串和一个位置pos,得到从这个pos起的第一个UTF8字符,返回该UTF8字符占用的字节数(即Lua中用#取到的字符串长度)以及由对应的字节组成的字符串(其实就是其对应的UTF8字符)。函数的一种实现方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
local getutf8char = function(str,pos)
local byte = str:byte(pos);
local len,uchar = 1,"";
if byte >= 0x00 and byte <= 0x7F then
len = 1
elseif byte >= 0xC2 and byte <= 0xDF then
len = 2
elseif byte >= 0xE0 and byte <= 0xEF then
len = 3
elseif byte >= 0xF0 and byte <= 0xF4 then
len = 4
else
return len,uchar;
end
uchar = str:sub(pos,pos + len - 1);
return len,uchar;
end

local s = "你好hello繁體字123こんにちは안녕하세요<>~!@#《》"
local pos = 1;
while pos < #s do
local len,char = getutf8char(s,pos);
print("pos",pos,"len",len,"char",char);
pos = pos + len;
end

接下来借助此函数的功能来编写完整的迭代器函数。

基于闭包的迭代器

在Lua中实现迭代器,一种比较简单的方法是使用闭包(closure)。

原理

与常规的函数不同,迭代器函数需要在每次成功调用之后保存一些状态(如当前遍历到的位置等),以便于在下次调用时根据这些状态返回“下一个”值。

借助闭包来实现迭代器时,需要提供一个闭包函数和一个用于创建闭包的工厂函数,在工厂函数中会使用非局部变量(non-local,或者称为upvalue)的形式来保存迭代器的状态,并在闭包函数中引用这些值。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
local uchars_1 = function(str)
local pos = 1
local nextchar = function()
if pos > #str then return nil end
local len,char = getutf8char(str,pos)
pos=pos + len
return char
end
return nextchar,str,pos
end

for c in uchars_1 (s) do
print(c)
end

基于协程的迭代器

另一种实现迭代器的方法是借助协程(coroutine)。

原理

这里也会用到两个函数,内部的函数可以遍历所有的情况(所有结果),但是在返回结果时将协程挂起(yield),外部的是工厂函数,构建一个协程并将内部的函数放在协程中执行。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
local innerfunc = function(s)
local pos = 1
while pos <= #s do
local len,char = getutf8char(s,pos)
pos=pos + len
coroutine.yield(char)
end
end

local uchars_2 = function(s)
local co = coroutine.wrap(function() innerfunc(s) end)
return co
end

for c in uchars_2(s) do
print (c)
end

REFERENCE

Lua程序设计(第二版)