《lua程序设计》读书笔记 第七章:迭代器与泛型for

7.1 迭代器与closure

“迭代器”就是一种遍历一种集合中所有元素的机制,在Lua中,通常将迭代器表示为函数,没调用一次函数,便返回集合中的“下一个”元素。closure为迭代器提供了绝佳的支持,一个closure结构通常涉及到两个函数:closure本身和一个用于创建closure的工厂函数。
我们编写一个简单的迭代器,返回table中每个元素的值:

    function value(t)
        local i = 0
        return function() i = i + 1; return t[i] end
    end

在本例中,value是一个工厂,它将返回一个闭包closure。使用该迭代器遍历一个table如下:

t = {10, 20, 30}
iter = value(t)
while true do
    local element = iter()
    if element == nil then break end
    print(element)
end

然而使用泛型for则更为简单,它正是为迭代器而设计的:

t = {10, 20, 30}
for val in value(t) do
    print(val)
end

再看一个稍微复杂点的示例:

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

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

7.2 泛型for的语义

前面提到的迭代器都有一个缺点,就是需要为每个新的循环都创建一个closure。
泛型for在循环过程内部保存了迭代器函数,实际上它保存了3个值:一个迭代器函数、一个恒定状态和一个控制变量。泛型for语法如下:

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

< var-list > 是一个或多个变量名的列表,以逗号分隔;< exp-list > 是一个或多个表达式的列表,同样以逗号分隔。通常表达式列表只有一条语句,即对迭代器工厂的调用。变量列表的第一元素为“控制变量”。在循环过程中该值绝不会为nil,因为当它为nil时循环就结束了。
for做的第一件事情是对in后面的表达式求值。这些表达式应该返回3个值供for保存:迭代器函数、恒定状态、控制变量的初值。这里有点类似多重赋值,即只有最后的表达式才会产生多个结果,且最多保留3个值。
明确的说,以下语句:for var_1, …, val_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

当我们使用一个简单的迭代器时,工厂只返回一个迭代器函数,因此恒定状态和控制变量就是nil了。

7.3 无状态的迭代器

所谓无状态的迭代器,就是自身不保留任何状态的迭代器,在每次迭代时,for循环会用恒定状态和控制变量来调用迭代器函数来生成下一个元素。典型例子就是ipairs。
自己实现ipairs如下:

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

7.4 具有复杂状态的迭代器

同行迭代器需要保存很多状态,可是for循环只提供了一个恒定状态和一个控制变量。一个简单的解决方法就是使用closure,或者还可以将迭代器所需的所有状态打包为一个table,保存在恒定状态中。

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

function iterator(state)
    while state.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

尽可能编写无状态的迭代器,那样所有状态保存在for变量中,不需要在开始一个循环时创建新的对象。否则,应该尝试使用closure,因为通常一个closure的实现的迭代器比一个使用table的迭代器更加高效,这是因为一个closure比一个table更廉价,其次访问“非局部的变量”比访问table字段更快。

7.5 真正的迭代器

实际上上述所说的迭代器并没有做实际的迭代,真正做迭代的是for循环。而迭代器只是为每次迭代提供一些成功后的返回值,其更像是一个“生成器”。还有一种创建迭代器的方式是,在迭代器中做实际的迭代操作:

function allwords(f)
    for line in io.lines() do
        for word in string.gmatch(line, "%W+") do
            f(word)
        end
    end
end

两种迭代器有大致相同的开销,但是生成器风格的迭代器更加灵活。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值