Lua学习笔记 第七章 迭代器与泛型for

7.1 迭代器与closure

所谓迭代器就是一种可以遍历一种集合中所有元素的机制;在Lua中,通常将迭代器表示为函数;

每调用一次函数即返回集合中的下一个元素;

closure(闭合函数)对于迭代器的实现提供了极佳的支持;一个closure结构通常涉及到两个函数

:closure本身和一个用于创建该closure的工厂(factory)函数;

简单的迭代器示例  ——

function values(t)

    local i = 0

    returnfunction() i = i + 1; return t[i] end

end

在本例中values就是一个工厂,每当调用这个工厂是,它就创建一个新的closure(迭代器本身)

这个closure将它的状态保存在其外部变量ti中。

每当调用这个迭代器时,它就从列表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则更为简单:

for element in values(t) do

    print(element)

end

泛型for在内部保存了迭代器函数,因此不再需要iter变量;它在每次新迭代时调用迭代器,

并在迭代器返回nil时结束循环;

高级示例allwords——遍历当前输入文件中所有单词的迭代器。为了完成这种迭代,需要保持

两个值:当前行的内容(变量line)及在该行中所处的位置(变量pos)

function allwords()

    local line =io.read()

    local pos = 1

    return  function()

      while line do

        local s, e = string.find(line,"%w+",pos)  --查找任意单词

        if s then

            pos = e + 1

           return string.sub(line, s, e)             --返回该单词

        else

    line =io.read()                       --没有找到单词,尝试下一行

            pos = 1

        end

    end

    return nil

  end

end

 

7.2 泛型for的语义

上述迭代器都有一个缺点,就是需要为每个新的循环创建一个新的closure。在一些

情况下这样的开销可能不容易接受。可以通过泛型for来解决。

泛型for在循环过程中保存着3个值: 一个迭代器函数,一个恒定状态(invariant state)

和一个控制变量(control varible)。泛型for的语法如下:

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

    <body>

end

<var-list> 是包含一个或多个变量的变量列表,以逗号分隔;变量列表的第一个元素成为控制变量,

在循环过程中如果它为nil时循环就结束。

<exp-list> 是一个或多个表达式列表,通常表达式列表只有一个元素,即一句对迭代器工厂的调用。

for做的第一件事情就是对in后面的表达式求值。表达式应该返回3个值供for保存:

迭代器函数、恒定状态和控制变量的初值。

之后for会以恒定状态和控制变量来调用迭代器函数。然后for将迭代器函数的返回值赋予变量列表中变量。

如果第一个返回值为nil(即赋予控制变量的那个值),循环终止。否则,for执行它的循环体,随后再次调用

迭代器函数,并重复这个过程。更明确地说以下语句:

for var1, ..., varn in <explist> do <body>end

等价于以下代码:

do

    local f, s,var = <explist>

    while true do

        localvar1,..., varn = f(s, var)

        var =var1

        if var ==nil then break end

        <body>

    end

end

 

7.3无状态的迭代器

所谓的无状态的迭代器就是一种自身不保存任何状态的迭代器。我们可以在多个循环中使用同一个

无状态的迭代器,这样可以避免创建新的closure开销。

在每次迭代中,for循环都会用恒定状态和控制变量来调用迭代器函数。

一个无状态的迭代器可以根据这两个值为下次迭代生成下一个元素。

这类迭代器典型的例子就是ipairs.如代码:

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

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

for i, v in ipairs(a) do

    print(i, v)

end

Lua调用for循环中的ipairs(a)时,它会获得3个值:迭代器函数iter、恒定状态a和控制变量的初值。

然后Lua调用iter(a,0)得到1,a[1]。在第二次迭代中,继续调用iter(a,1)得到2,a[2]。依次类推,

直至得到的第一个元素为nil时终止循环。

函数pairsipairs类似;不同的是它的迭代器函数是Lua中的一个基本函数next.

function pairs(t)

return next, t, nil

end

调用next(t,nil)时返回table的第一组值;调用next(t,k)时,ktable(t)中的一个key,此调用会以table

的任意次序返回一组值:table的下一个key,及这个key所对应的值。若没有下一组值时,next返回nil

关于无状态迭代器的另外一个例子是一种可以遍历链表的迭代器.

local function getnext(list,node)

    if not node then

        returnlist

    else

        return node.next

    end

end

function traverse(list)

    returngetnext, list, nil

end

这里将链表的头结点作为恒定状态(traver的第二个返回值),将当前结点作为控制变量。当第一次调用迭代器函数getnext时,nodenil,因此返回list作为第一个结点。在后续调用中node就不在为nil了,所以返回node.next

使用:

list = nil

for line in io.lines() do

    list = {val =line, next = list}

end

for node in traverse(list) do

    print(node.val)

end

 

7.4具有复杂状态的迭代器

通常迭代器需要保存许多状态,可是泛型for却只提供了一个恒定状态和一个控制变量用于状态的保存。

解决方法:一是使用closure;二是将迭代器所需的所有状态打包为一个table保存在恒定状态中。

在循环过程中恒定状态总是同一个table,但这个table的内容是可以改变的。

示例 —— 重写allwords迭代器,这个迭代器遍历当前输入文件中的所有单词,这次将它的状态保存到

一个table中,这个table具有两个字段:line pos

function iterator(state)

    whilestate.line do

        local s,e = string.find(state.line, "%w+", state.pos)

        if s then 

    state.pos =e + 1

            return string.sub(state.line, s, e)

        else

            state.line= io.read()

            state.pos= 1

        end

    end

    return nil

end

 

function allwords()

    local state ={line = io.read(),pos = 1}

    returniterator, state

end

建议:

尽可能地编写无状态的迭代器,那些迭代器将所有的状态保存在for变量中,不需要在开始

一个循环时创建任何新的对象。

如果迭代器无法套用这个模型,那么就应该使用closureclosure显得更加优雅一点,通常基于

一个closure实现的迭代器会比一个使用table的迭代器更为高效。因为创建一个closure比创建

一个table更廉价,其次访问"非局部的变量"也比访问table字段更快。

 

7.5真正的迭代器

迭代器并没有做实际的迭代,真正做迭代的是for循环。而迭代器只是为每次迭代提供一些成功

后的返回值。准确地说应该称其为"生成器".

一种创建迭代器的方式就是,迭代器接受一个函数作为参数,并在其内部的循环中调用这个函数 

—— 示例,重写allwords:

function allwords(f)

    for line in io.lines()do

        for wordin string.gmatch(line, "%w+") do

            f(word) -- 调用函数参数

        end

    end

end

应用: allwords(print)

通常还可以使用一个匿名函数作为循环体,以下代码计算了单词"hello"在输入文件中出现的次数:

local count = 0

allwords(function(w)

    if w =="hello" then count = count + 1 end

    end)

print(count)

“真正的迭代器”在老版本的Lua中非常流行,与“生成器风格的迭代器”相比,这两种风格都有相同

的开销,即每次迭代都有一次函数调用。

不过“生成器风格的迭代器”更加灵活,这种灵活性体现在两方面,首先它允许两个或多个

并行的迭代过程,其次它允许在迭代体中使用breakreturn语句。对于正在的迭代器来说,

return语句只能从匿名函数中返回,并不能从做迭代的函数中返回。


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值