现在的位置: 首页 > 综合 > 正文

Lua迭代器与泛型for

2019年01月09日 ⁄ 综合 ⁄ 共 3203字 ⁄ 字号 评论关闭

迭代器与闭包

迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。在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

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

范性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
我们称<strong>变量列表中第一个变量为控制变量,其值为nil时循环结束</strong>。

范性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 break end
       block
    end
end
如果我们的迭代函数是f,状态常量是s,控制变量的初始值是a0,那么控制变量将循环:a1=f(s,a0)、a2=f(s,a1)、……,直到ai=nil。

无状态的迭代器

无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。这种无状态迭代器的典型的简单的例子是ipairs,他遍历数组的每一个元素。

a = {"one", "two", "three"}
for i, v in ipairs(a) do
    print(i,"-->",v)
end
--输出
--1   --> one
--2   --> two
--3   --> three

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

b = {"one", "two", "three"}
function iter (a, i)
    i = i + 1
    local v = a[i]
    if v then
       return i, v
    end
end 
function myipairs (a)
    return iter, a, 0
end
for i, v in myipairs(b) do
    print(i,"==>",v)
end
--输出
--1   ==> one
--2   ==> two
--3   ==> three

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

c = {"one", "two", "three"}
function mypairs (t)
    return next, t, nil
end
for k,v in mypairs(c) do
    print(k,"--",v)
end
--输出
--1   --  one
--2   --  two
--3   --  three

--还可以不使用ipairs直接使用next
for k, v in next, c do
    print(k,"-->",v)
end
--输出
--1   -->  one
--2   -->  two
--3   -->  three

多状态的迭代器 

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

真正的迭代器 

迭代器的名字有一些误导,因为它并没有迭代,完成迭代功能的是for语句,也许更好的叫法应该是生成器(generator);但是在其他语言比如java、C++迭代器的说法已经很普遍了,我们也就沿用这个术语。
有一种方式创建一个在内部完成迭代的迭代器。这样当我们使用迭代器的时候就不需要使用循环了;我们仅仅使用每一次迭代需要处理的任务作为参数调用迭代器即可,具体地说,迭代器接受一个函数作为参数,并且这个函数在迭代器内部被调用。

function allwords (f)
    -- repeat for each line in the file
    for l in io.lines() do
       -- repeat for each word in the line
       for w in string.gfind(l, "%w+") do
           -- call the function
           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语句只是从匿名函数中返回而不是退出循环。

抱歉!评论已关闭.