Programming in Lua, 2nd edition - Chapter 7: Iterators and the Generic for

本文深入探讨Lua中的迭代器实现方式,包括闭包迭代器、无状态迭代器以及使用表保存状态的迭代器,并对比了不同迭代器的特点和适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

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 是从匿名函数中返回,不是从进行迭代的那个函数返回。综上,我更喜欢生成器风格迭代器

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值