您的位置:首页 > 编程语言 > Lua

Lua学习笔记07:迭代器与closure(迭代器与泛型for-01)

2020-06-05 05:18 281 查看

一、Lua迭代器:所谓“迭代器”就是一种可以遍历(iterate over)一种集合中所有元素的机制。在Lua中,通常将迭代器表示为函数。每调用一次函数,即返回集合中的“下一个”元素。
每个迭代器都需要在每次成功调用之间保持一种状态,这样才能知道它所在的位置及如何进到下一个位置。closure对于这类任务提供了极佳的支持,一个closure就是一种可以访问外部嵌套环境中的局部变量的函数。对于closure而言,这些变量就可用于在成功调用之间保持状态值,从而使closure可以记住它在一次遍历中所在的位置。当然,为了创建一个新的closure,还必须创建它的这些“非局部的变量(non-local variable)”。因此一个closure结构通常涉及到两个函数:closure本身和一个用于创建该closure的工厂(factory)函数。 
例:
function values (t)
    local i = 0
    return function () 
                  i = i + 1
                  return t[i] 
               end
end
在本例中,values就是一个工厂。每当调用这个工厂时,它就创建一个新的closure(即迭代器本身)。这个closure将它的状态保存在其外部变量t和i中。每当调用这个迭代器时,它就从列表t中返回下一个值。直到最后一个元素返回后,迭代器就会返回nil,表示迭代的结束。
-----------------------------------------------------
可以在一个while循环中使用这个迭代器:
t = {10, 20, 30}
iter = values(t)                -- 创建迭代器
while true do
    local element = iter()      -- 调用迭代器
    if element == nil then break end
    print(element)
end
然而使用泛型for则更为简单。接下来会发现,它正是为这种迭代而设计的:
t2 = {40, 50, 60}
for element in values(t2) do
    print(element)
end
泛型for为一次迭代循环做了所有的记录工作。它在内部保存了迭代器函数,因此不再需要iter变量。它在每次新迭代时调用迭代器,并在迭代器返回nil时结束循环。
-----------------------------------------------------
[迭代器示例——遍历文件中所有单词]
下面的示例中展示了一个可以遍历当前输入文件中所有单词的迭代器——allwords。为了完成这样的遍历,需要保持两个值:当前行的内容(变量line)及在该行中所处的位置(变量pos)。
这里用到一个string.find函数,返回当前查找单词位置。
尽管迭代器本身具有复杂性,但allwords的使用还是很简明易懂的:
for word in allwords() do
    print(word)
end
对于迭代器而言,一种常见的情况就是:编写迭代器本身或许不太容易,但使用它们却是很容易的。这也不会称为一个大问题,因为通常使用Lua编程的最终用户不会去定义迭代器,而只是使用那些程序提供的迭代器。
function allwords ()
    local line = io.read()      -- 当前行
    local pos = 1               -- 一行中的当前位置
    return function()           -- 迭代器函数
        while line do           -- 若为有效的行内容就进入循环
            local s, e = string.find(line, "%w+", pos)   --"%w+"会以空格为区分寻找单词或数字。该行代码用于在字符串line中的第pos个元素位置寻找单词 s为单词的起始位置变量,e为单词结束位置的变量
            if s then           -- 是否找到一个单词
                pos = e + 1     -- 该单词的下一个位置
                return string.sub(line, s, e)   -- 返回该单词
            else
                line = io.read()    -- 没有找到单词,返回下一行
                pos = 1             -- 在第一个位置重新开始
            end
        end
        return nil              -- 没有其余行了,遍历结束
    end
end
----------------------------------------------
以上迭代器都有一个缺点:都要为每个新的循环创建一个新的closure,对有些情况而言,创建一个或多个closure的代价相对比读取文件的代价较大,因此再使用该方法时则比较不好;因此可以试着让泛型for自身来保存迭代器的状态
----------------------------------------------
二、泛型for保存状态的机制:泛型for在循环过程的内部保存了迭代器函数
泛型for的语法如下:
-------------------------------------------
for <var-list> in <exp-list> do
  <body>
end
-------------------------------------------
它的执行顺序为:
1、对in后面的表达式<exp-list>求值,得到三个值给for保存:迭代器函数、恒定状态和控制变量的值(控制变量为<var-list>中的第一个变量,<var-list>可以为多个变量)
2、以恒定状态和控制变量调用迭代器函数,然后将迭代器函数的返回值赋予变量列表<var-list>中的变量
3、for做的第一件事就是对in后面的表达式求值,这些表达式应该返回3个值供for保存:迭代器函数、恒定状态和控制变量的初值。这里和多重赋值是一样的,只有最后一个表达式才会产生多个结果,并且只会保留前3个值,多余的值会被丢弃;而不够的话,就以nil补足。
4、如果第一个返回值为nil,那么终止循环;否则重复2

来看一个例子:
--迭代器函数
local function iter(a, i)
    i = i + 1
    local v = a[i]
    if v then return i,v end
end
--表达式列表<exp-list>
function ipairs(a)
    return iter, a, 0---------->iter(a,0)
end
 
a = {"one", "two", "three"}
for i,v in ipairs(a) do
    print(i, v)
end
------------------------
for var_1,.....,var_n in <exp-list> do
      <block>
end
等价于以下代码:
do
    local  _f,_s,_var=<exp-list>
    while true  do
        local  var_1,...,var_n=_f(_s,_var)---f为迭代器函数,控制状态为_s,控制变量初始值a0,a1=f(s,a0)、a2=f(s,a1)、......、an=f(s,a(n-1))
        _var=var_1
        if  _var==nil  then
             break
        end
        <block>
        end
end
---------------------------
1、对表达式求值,得到iter(迭代器函数),a(恒定状态),0(控制变量初值)
2、执行iter(a,0),将其返回值赋值给<var-list>,那么for中的i等于1,v等于“one”。
3、因为第一个返回值不为nil,用恒定状态(a)和控制变量(1)调用迭代器函数(iter),即iter(a,1),得到i=2,v="two",如此循环,直至i=nil
4、在ipairs的代码中,只是返回了迭代器函数,并没有返回恒定状态和控制变量。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: