Chapter 7: Iterators and the Generic for
本章我们将讨论如何为泛型for 编写简单且高效的迭代器。
7.1 Iterators and Closures
Lua用函数来实现迭代器,每调用一次函数,它就返回集合的下一个元素。
所有迭代器在连续操作中需要保存一些状态,这些状态表明这是哪里以及如何从这里继续处理。闭包(Closures) 为此任务提供了一个很赞的机制。闭包是一个函数,这个函数从它的闭合环境中访问一个或多个局部变量。这些变量在对闭包连续调用中保留它们的值。当然,创建一个新闭包我们必须创建一个新的非局部变量。因此,一个闭包构造包含两个函数:闭包本身和一个创建闭包的工厂。
一个列表的简单迭代器
function values (t)
local i = 0
return function () i = i + 1; return t[i] end
end
和ipairs 不同,这个迭代器只返回值,不返回索引。
这个例子中,values 是一个工厂。我们对工厂的每次调用都会创建一个新闭包。这个闭包将自身的状态保存在外部的t 和i。每调用一次迭代器就从列表t 中返回下一个元素,在最后一个元素后迭代器会返回nil ,这是结束迭代的信号。
将上面的迭代器用在while 循环中
function values (t)
local i = 0
return function () i = i + 1; return t[i] end
end
t = {1,2,3}
iter = values(t) -- 创建一个迭代器(注意,这很特别)
while true do
local element = iter() -- 调用迭代器
if element == nil then break end
print(element)
end
将自已编写的迭代器用在泛型for 中
function values (t)
local i = 0
return function () i = i + 1; return t[i] end
end
t = {1,2,3}
iter = values(t)
for element in values(t) do
print(element)
end
范型for 会在迭代器返回nil 时终止。
打印文件中的所有单词串
function allwords (f)
local line = f:read("*line") -- current line
local pos = 1 -- current position in the line
return function ()
while line do
local s, e = string.find(line, "%w+", pos)
if s then -- found a word?
pos = e + 1
return string.sub(line, s, e)
else
line = f:read("*line")
pos = 1
end
end
return nil
end
end
f = io.open("c://input.txt","r") -- open input file
assert(f)
for word in allwords(f) do
print(word)
end
f:close()
7.2 The Semantics of the Generic for
泛型for 的语义学
前面的迭代器有一个缺点,每次新循环都需要创建一个新闭包。多数情况下,这不成问题。例如前面的allwords 迭代器,这个迭代器创建了一个闭包,其代价与读整个文件相比微不足道。但是,在某种状况下,这种开销是不能接受的。这时,我们可以用泛型for 自身来保存迭代状态。
for k, v in pairs(t) do print(k, v) end
for line in io.lines() do
io.write(line, "/n")
end
k, v, line 叫做控制变量,其值在循环过程中永运不为nil,一旦为nil 就循环就退出了。
泛型for 展开后的样子
for var_1, ..., var_n in <explist> do <block> end
do
local _f, _s, _var = <explist>
while true do
local var_1, ... , var_n = _f(_s, _var)
_var = var_1
if _var == nil then break end
<block>
end
end
型泛for 的本质
function allwords (f)
local line = f:read("*line") -- current line
local pos = 1 -- current position in the line
return function ()
while line do
local s, e = string.find(line, "%w+", pos)
if s then -- found a word?
pos = e + 1
return string.sub(line, s, e)
else
line = f:read("*line")
pos = 1
end
end
return nil
end
end
f = io.open("c://input.txt","r") -- open input file
assert(f)
-- 已自写的“泛型for”
do
local _f, _s, _var = allwords(f)
while true do
local var_1= _f(_s, _var)
_var = var_1
if _var == nil then break end
print(_var)
end
end
for word in allwords(f) do
print(word)
end
f:close()
其中_f 是由工厂制造出来迭代函数(闭包),_s 是invariant state,_var 控制变量
7.3 Stateless Iterators
无状态迭代器
像名字所暗示的,这种迭代器自身不保存任何状态。因为我们在不同的循环中使用相同的无状态迭迭器;这样,创建新闭包的开销可以忽略。
迭代器之所以不需要保存当前位置这个状态,是因为泛型for 代劳了。前面例子中的allwords 迭代器就需要自已保存当前行及当前行中的位置这两个状态,它是一个有状态迭代器的例子。
ipairs 函数的实现
t = {1, 2, 3}
local function iter (a, i)
i = i + 1
local v = a[i]
if v then
return i, v
end
end
function lpairs (a) -- 我们的lpairs 不是ipairs
return iter, a, 0
end
for i, v in lpairs(t) do
print(i, v)
end
另一个有趣的无状态迭代器是链表迭代器
链表迭代器
local function getnext (list, node)
return not node and list or node.next
end
function traverse (list) return getnext, list, nil end
list = nil
for i=1, 5 do
list = {next=list, value=i}
end
for v in traverse (list) do
print(v.value)
end
可以返回string Key 的Next 函数
t = {monday = 1, tuesday = 2, wednesday = 3}
k,v = next(t, "tuesday")
print(k,v)
t = {1,2,3}
k,v = next(t)
print(k,v)
k,v = next(t,1)
print(k,v)
k,v = next(t,2)
print(k,v)
pairs 函数的实现
t = {monday = 1, tuesday = 2, wednesday = 3}
function mypairs (t)
return next, t, nil
end
for k, v in mypairs(t) do
print(k,v)
end
7.4 Iterators with Complex State
用表而不是闭包来保存状态的迭代器
local iterator -- to be defined later
function allwords (f)
local state = {line = f:read("*line"), pos = 1}
return iterator, state
end
function iterator (state)
while state.line do
local s, e = string.find(state.line, "%w+", state.pos)
if s then -- found a word?
state.pos = e + 1
return string.sub(state.line, s, e)
else
state.line = f:read("*line")
state.pos = 1
end
end
return nil
end
f = io.open("c://input.txt","r") -- open input file
assert(f)
for word in allwords(f) do
print(word)
end
f:close()
如果可能,尽量写无状态迭代器,如果它不能满足需求时就用闭包,最后才考虑用表来保存状态的迭代器。后面,我们还将看到更强大的迭代器,但其消耗也更高。
7.5 True Iterators
前面的“迭代器”并不是真正的迭代器,因为迭代都是发生在for 循环。也许叫生成器更合适。
这里我们要创建真迭代器,在其内部实现迭代。当使用这种迭代器我们不需要写循环;只需传一参数描述每次迭代都需执行的一个操作。
未验证代码,怎样将标准输入io.lines() 改成文件输入??
function allwords (f)
for line in io.lines() do
for word in string.gmatch(line, "%w+") do
f(word) -- call the function
end
end
end
allwords(print)
local count = 0
allwords(function (w)
if w == "hello" then count = count + 1 end
end)
print(count)
local count = 0
for w in allwords() do
if w == "hello" then count = count + 1 end
end
print(count)
一方面,真迭代器容易写,另一方面生成器风格迭代器更灵活。首先,它允许两个或更多并行迭代(例如:考虑逐单词比较两个文件)。其次,可以在迭代器内部使用break 和return。
真迭代器中,return 是从匿名函数中返回,不是从进行迭代的那个函数返回。综上,我更喜欢生成器风格迭代器。