Lua_第 7 章 迭代器与泛型 for

第 7 章 迭代器与泛型 for

       在这一章我们讨论为范性 for 写迭代器,我们从一个简单的迭代器开始,然后我们 学习如何通过利用范性 for 的强大之处写出更高效的迭代器。

7.1 迭代器与闭包

       迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。在 Lua 中我们 常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。

       迭代器需要保留上一次成功调用的状态和下一次成功调用的状态,也就是他知道来自于哪里和将要前往哪里。闭包提供的机制可以很容易实现这个任务。记住:闭包是一 个内部函数,它可以访问一个或者多个外部函数的外部局部变量。每次闭包的成功调用后这些外部局部变量都保存他们的值(状态)。当然如果要创建一个闭包必须要创建其外 部局部变量。所以一个典型的闭包的结构包含两个函数:一个是闭包自己;另一个是工 厂(创建闭包的函数)。

举一个简单的例子,我们为一个 list 写一个简单的迭代器,与 ipairs()不同的是我们 实现的这个迭代器返回元素的值而不是索引下标: 

function list_iter (t)
    local i = 0
    local n = table.getn(t)
    return function() i = i + 1
 if i <= n then return t[i] 
       end
    end
end

这个例子中 list_iter  是一个工厂,每次调用他都会创建一个新的闭包(迭代器本身)。闭包保存内部局部变量(t,i,n),因此每次调用他返回 list 中的下一个元素值,当 list 中没 有值时,返回 nil.我们可以在 while 语句中使用这个迭代器: 

t = {10, 20,30}
iter =list_iter(t)      -- createsthe iterator
     while true do
     local element = iter()   -- calls the iterator 
     if element == nil then breakend 
   print(element) end

我们设计的这个迭代器也很容易用于范性 for 语句

 

t = {10, 20,30}
for element in list_iter(t) do
print(element)
end

 

范性 for 为迭代循环处理所有的薄记(bookkeeping):首先调用迭代工厂;内部保留 迭代函数,因此我们不需要 iter 变量;然后在每一个新的迭代处调用迭代器函数;当迭 代器返回 nil 时循环结束(后面我们将看到范性 for 能胜任更多的任务)。

下面看一个稍微高级一点的例子:我们写一个迭代器遍历一个文件内的所有匹配的 单词。为了实现目的,我们需要保留两个值:当前行和在当前行的偏移量,我们使用两 个外部局部变量 line、pos 保存这两个值。

 

function allwords()
    local line = io.read()   -- current line
    local pos = 1            -- current positionin the line
    return function ()       -- iterator function
        while line do        -- repeat while thereare lines
             local s, e =string.find(line, "%w+", pos)
                 if s then         -- found aword?
                 pos = e + 1   -- next positionis after this word
                 return string.sub(line, s, e) -- returnthe word
           else
                   line = io.read()-- word not found;try next line
                    pos = 1       --restart from firstposition
              end
       end
return nil    -- no morelines: end oftraversal
end
end

       迭代函数的主体部分调用了 string.find 函数,string.find在当前行从当前位置开始查找匹配的单词,例子中匹配的单词使用模式'%w+'描述的;如果查找到一个单词,迭代函 数更新当前位置 pos为单词后的第一个位置,并且返回这个单词(string.sub 函数从 line 中提取两个位置参数之间的子串)。否则迭代函数读取新的一行并重新搜索。如果没有 line可读返回 nil 结束。

尽管迭代函数有些复杂,但使用起来是很直观的:

for word in allwords() do
print(word)
end

通常情况下,迭代函数都难写易用。这不是一个大问题:一般 Lua 编程不需要自己 定义迭代函数,而是使用语言提供的,除非确实需要自己定义。

7.2 范性 for 的口义 

       前面我们看到的迭代器有一个缺点:每次调用都需要创建一个闭包,大多数情况下 这种做法都没什么问题,例如在 allwords 迭代器中创建一个闭包的代价比起读整个文件来说微不足道,然而在有些情况下创建闭包的代价是不能忍受的。在这些情况下我们可 以使用范性for本身来保存迭代的状态。

       前面我们看到在循环过程中范性 for 在自己内部保存迭代函数,实际上它保存三个 值:迭代函数,状态常量和控制变量.下面详细说明。范性 for 的文法如下:

 

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

<var-list>是一个或多个以逗号分割的变量名列表,<exp-list>是一个或多个以逗号分 割的表达式列表,通常情况下 exp-list 只有一个值:迭代工厂的调用。 

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

变量列表 k,v;表达式列表 pair(t),在很多情况下变量列表也只有一个变量,比如:

for line in io.lines() do
    io.write(line, '\n')
end

我们称变量列表中第一个变量为控制变量,其值为 nil 时循环结束。 下面我们看看范性 for 的执行过程:

首先,初始化,计算 in 后面表达式的值,表达式应该返回范性 for 需要的三个值: 迭代函数,状态常量和控制变量;与多值赋值一样,如果表达式返回的结果个数不足三 个会自动用 nil补足,多出部分会被忽略。

第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于 for结构来说, 状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。

第三,将迭代函数返回的值赋给变量列表。

第四,如果返回的第一个值为 nil循环结束,否则执行循环体。

第五,回到第二步再次调用迭代函数。 更精确的来说:

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 breakend
       block
       end

end

如果我们的迭代函数是 f,状态常量是 s,控制变量的初始值是 a0,那么控制变量将 循环:a1=f(s,a0)、a2=f(s,a1)、……,直到 ai=nil。

 

7.3 元状态的迭代器

 

无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。

每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调 用,一个无状态的迭代器只利用这两个值可以获取下一个元素。这种无状态迭代器的典 型的简单的例子是 ipairs,他遍历数组的每一个元素。

 

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

迭代的状态包括被遍历的表(循环过程中不会改变的状态常量)和当前的索引下标 (控制变量),ipairs 和迭代函数都很简单,我们在 Lua 中可以这样实现:

 

function iter (a, i)
   i = i + 1
  local v = a[i]
  if v then
      return i, v
 end
end
 
function ipairs (a)
return iter, a, 0
end

 

当 Lua 调用 ipairs(a)开始循环时,他获取三个值:迭代函数 iter,状态常量 a 和控制变 量初始值 0;然后 Lua调用 iter(a,0)返回 1,a[1]C除非 a[1]=nil);第二次迭代调用 iter(a,1) 返回 2,a[2]……直到第一个非 nil 元素。

Lua库中实现的 pairs是一个用 next 实现的原始方法:

 

function pairs (t)
   return next, t, nil
end

还可以不使用 ipairs 直接使用 next

for k, v in next, t do
...
end

 记住:exp-list  返回结果会被调整为三个,所以 Lua 获取 next、t、nil;确切地说当 他调用pairs 时获取。

 

 

7.4 多状态的迭代器

 

很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最 简单的方法是使用闭包,还有一种方法就是将所有的状态信息封装到 table 内,将 table作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在 table 内,所以迭代函 数通常不需要第二个参数。下面我们重写 allwords (line,pos)的 table。迭代器,这一次我们不是使用闭包而是使用带有两个域

开始迭代的函数是很简单的,他必须返回迭代函数和初始状态:

 

local iterator       -- to bedefined later
function allwords()
     local state = {line= io.read(), pos = 1}
     return iterator, state
end

真正的处理工作是在迭代函数内完成:

function iterator (state)
 
while state.line do      -- repeat whilethere are lines
-- search fornext word
       local s, e = string.find(state.line, "%w+", state.pos)
       if s then     -- found aword?
-- update nextposition (after thisword)
          state.pos = e + 1
          return string.sub(state.line, s, e)
      else   -- word notfound
             state.line =io.read()   -- trynext line...
             state.pos = 1     --... from firstposition 
                 end
     end

 return nil        -- no morelines: end loop
 
end

我们应该尽可能的写无状态的迭代器,因为这样循环的时候由 for 来保存状态,不 需要创建对象花费的代价小;如果不能用无状态的迭代器实现,应尽可能使用闭包;尽 可能不要使用 table 这种方式,因为创建闭包的代价要比创建 table 小,另外 Lua 处理闭 包要比处理 table速度快些。后面我们还将看到另一种使用协同来创建迭代器的方式,这 种方式功能更强但更复杂。

 7.5 真正的迭代器

 迭代器的名字有一些误导,因为它并没有迭代,完成迭代功能的是 for 语句,也许 更好的叫法应该是'生成器';但是在其他语言比如 java、C++迭代器的说法己经很普遍了, 我们也将沿用这种术语。

有一种方式创建一个在内部完成迭代的迭代器。这样当我们使用迭代器的时候就不需要使用循环了;我们仅仅使用每一次迭代需要处理的任务作为参数调用迭代器即可, 具体地说,迭代器接受一个函数作为参数,并且这个函数在迭代器内部被调用。

作为一个具体的例子,我们使用上述方式重写 allwords 迭代器:

 

function allwords (f)
     -- repeat foreach line in the file
    for l in io.lines() do
     -- repeat foreach word in the line
    for w in string.gfind(l, "%w+") do
-- call thefunction
f(w)
              end
        end

end


如果我们想要打印出单词,只需要

 

allwords(print)

 

更一般的做法是我们使用匿名函数作为作为参数,下面的例子打印出单词'hello'出现 的次数:

 

local count = 0 allwords(function (w)
if w == "hello" then count = count+ 1 end end)
print(count)

用for结构完成同样的任务:

local count = 0
   for w in allwords() do
       if w == "hello" then count = count + 1 end
   end
print(count)


真正的迭代器风格的写法在 Lua 老版本中很流行,那时还没有 for 循环。 两种风格的写法相差不大,但也有区别:一方面,第二种风格更容易书写和理解;

另一方面,for 结构更灵活,可以使用 break 和 continue 语句;在真正的迭代器风格写法

中 return 语句只是从匿名函数中返回而不是退出循环。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值