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

Lua 迭代器与泛型for

2017-06-18 17:38 281 查看

note 目录

迭代器与闭包(closure)

泛型for的语义

无状态迭代器

具有复杂状态的迭代器

真正的迭代器

1:迭代器与闭包(closure)

迭代器就是一种可以遍历一种集合中所有元素的机制。迭代器通常为函数。

在Lua中,通常将迭代器表示为函数。每一次调用函数,即返回集合中的”下一个元素”。每个迭代器都需要在每次成功调用之间保存一些状态,这样才能知道它所在的位置,以及下一次遍历的位置。Closure对于这类任务提供了极佳的支持。

闭包就是一个内部函数,可以访问一个或多个外部函数的局部变量。每次闭包的成功调用,这些外部的局部变量都会保存他们的值(状态)。所以如果要创建一个闭包,必须要创建其外部的局部变量。闭包必须包含2个函数,闭包的结构为:

闭包 =  闭包函数 + 工厂(创建闭包函数的工厂,有局部变量)


function values(t)                 --闭包工厂
local i = 0
return funciton()              --闭包 (closure)
i = i + 1
return t[i]
end
end


可以在一个
while
循环中使用这个迭代器:

t = {10,20,30}
iter = values(t)                   --创建迭代器
while true do
local element = iter()         --调用迭代器
if element == nil then
break
end
print(element)
end

output:
10
20
30


然而使用泛型
for in
则更为简单:

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


在泛型
for in
中,Lua内部帮忙我们保存了迭代器函数,因此不再需要iter变量,它在每次新迭代调用迭代器,并在迭代器返回
nil
时结束循环。

2:泛型for的语义

上面的不管是
while
for
的example中,都有一个明显的缺点:

在每次循环时都要创建新的闭包
closure
(在每次调用迭代函数
iter()
),在循环很大的时候开销会比较大。

因此,在这类情况下,希望可以通过泛型for自身来保存迭代器状态。

泛型
for
在循环过程内部保存了迭代器函数。实际上它保存了3个值:

一个迭代器函数,一个恒定状态(invariant state)【一般是数组或tab】,一个控制变量(control variable)【一般是数组的索引或tab的key】

泛型
for
语法

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


[var-list]

一个或多个变量名的列表,以逗号分隔开。

[exp-list]

一个或多个表达式的列表,同样是以逗号分隔,通常表达式列表只有一个元素,即只对迭代器工厂的调用。

example:

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


泛型
for
的执行过程:

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

- 2:将状态常量(数组或表tab),控制变量(一般是数组的索引或tab的key)作为参数调用迭代函数。

- 3:将迭代函数返回的值赋给变量列表(var-list)。

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

- 5:回到第二步,再次调用迭代函数。

将泛型
for
展开为
while


do
local _f,_s,_val = <explist>
while true do
local var_1,...var_n = _f(_s,_var)
_var = var_1
if _var == nil then
break
end
end
end


假设迭代函数为
f
,恒定状态为
s
,控制变量初值为
a0
。那么在循环控制中控制变量的值依次为:

a1 = f(s,a0)

a2 = f(s,a1)

依次类推,直至ai为
nil
值结束循环。如果for还有其他的变量,那么它们也会在每次调用
f
后获得额外的值。

3:无状态迭代器

所谓的“无状态迭代器”,就是一种自身不保存任何状态的迭代器,因此,我们可以在多个循环体中使用同一个无状态的迭代器,避免创建新的
closure
开销。

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

--【ipairs】

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

output:

1 one
2 two
3 three


【ipairs的Lua实现】

在这里,迭代的状态就是需要遍历
table
(一个恒定状态,他不会在循环中改变),及当前的索引值(控制变量)。ipairs(工厂)在Lua中的实现。

local 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

local a = {"one","two","three"}

for i , v in ipairs(a) do
print(i,v)
end

output:

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元素。

【pairs】

函数
paris
ipairs
类似,也是遍历一个
table
中的所有元素。不同的是,它的迭代器函数是Lua中的一个基本函数
next


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


在调用
next(t,k)
时,
k
table
t的一个
key
,此调用会以
table
中的任意次序返回一组值:

table
的下一个’key’,及这个
key
对应的值。而调用
next(t,nil)
时,返回
table
的第一组值。

若没有下一组值时,
next
返回
nil


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

for k, v in next, c do
print(k,v)
end


4:具有复杂状态的迭代器

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

尽可能地尝试编写无状态的迭代器,那些迭代器将所有的状态保存在for变量中,不需要在开始一个循环时创建任何新的对象。

如果迭代器无法套用这个模型,那么应该尝试使用
closure
closure
显得跟家优雅些,通常一个基于
closure
实现的迭代器会比创建一个
table
更廉价,其次访问“非局部变量”也比访问
table
字段更快。

协同程序(coroutine)可以用来别写迭代器。

5:真正的迭代器

迭代器的名字有一些误导,因为它并没有迭代,完成迭代功能的是for语句,也许更好的叫法应该是生成器(generator)

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

funciton allwords(f)
for line in io.lines() do
for word in string.gmatch(line,"%w+") do
f(word)      --call the function
end
end
end


如果我们只想打印单词

allwrods(print)


更一般的做法是使用匿名函数作为参数。

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


真正的迭代器风格的写法在Lua老版本中很流行,那时还没有for循环。

两种风格的写法相差不大,但也有区别:一方面,第二种风格更容易书写和理解;另一方面,for结构更灵活,可以使用break和continue语句;在真正的迭代器风格写法中return语句只是从匿名函数中返回而不是退出循环。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息