迭代器和闭包

简介: 迭代器和闭包

迭代器iterator )是一种可以让我们遍历一个集合中所有元素的代码结构。在 Lua 语言中,通常使用函数表示迭代器:每一次调用函数时,函数会返回集合中的“下一个”元素。一个典型的例子就是 io.read ,每次调用该函数时它都会返回标准输入中的下一行,在没有可以读取的行时返回 nil


所有的迭代器都需要在连续的调用之间保存一些状态,这样才能知道当前迭代所处的位置及如何从当前位置步进到下一位置。对于函数 io.read 而言,C语言会将状态保存在流的结构体中。对于我们自己的迭代器而言,闭包则为保存状态提供了一种良好的机制。请注意:一个闭包就是一个可以访问其自身的环境中一个或多个局部变量的函数。这些变量将连续调用过程中的值并将其保存在闭包中,从而使得闭包能够记住迭代所处的位置。当然,要创建一个新的闭包,我们还必须创建非局部变量。因此,一个闭包结构通常涉及两个函数:闭包本身和一个用于创建该闭包及其封装变量的工厂factory )。


作为示例,让我们来为列表编写一个简单的迭代器。与 ipairs 不同的是,该迭代器并不是返回每个元素的索引值而是返回元素的值:

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


在这个例子中, values 就是工厂。每当调用这个工厂时,它就会创建一个新的闭包(即迭代器本身)。这个闭包将它的状态保存在其外部的变量 ti 中,这两个变量也是由 values 创建的。每次调用这个迭代器时,它就从列表 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正是为了这种迭代而设计的:

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


泛型for为一次迭代循环做了所有的记录工作:它在内部保存了迭代函数,因此不需要变量 iter ;它在每次做新的迭代时都会再次调用迭代器,并在迭代器返回 nil 时结束循环。


来看一个更高级的示例,这个示例展示了一个迭代器,它可以遍历来自标准输入的所有单词:

function allwords()
  local line = io.read()                                    -- 当前行
  local pos = 1                                             -- 当前行的当前位置
  return function()                                         -- 迭代函数
    while line do                                           -- 当还有行时循环
      local w, e = string.match(line, "(%w+)()", pos)       
      if w then                                             -- 发现一个单词?
        pos = e                                             -- 下一个位置位于该单词后
        return w                                            -- 返回该单词
      else
        line = io.read()                                    -- 没找到单词,尝试下一行
        pos = 1                                             -- 从第一个位置重新开始
      end
    end
    return nil                                              -- 没有行了,迭代结束
  end
end


为了完成这样的遍历,我们需要保存两个值:当前行的内容(遍历 line )及当前行的当前位置(遍历 pos )。有了这些数据,我们就可以不断产生下一个单词。这个迭代函数的主要部分是调用函数 string.match ,以当前位置作为起始在当前行中搜索一个单词。函数 string.match 使用模式 '%w+' 来匹配一个单词,也就是匹配一个或多个字母/数字字符。如果函数 string.match 找到了一个单词,它就捕获并返回这个单词及该单词之后的第一个字符的位置(一个空匹配),迭代函数则更新当前位置并返回该单词,否则,迭代函数读取新的一行,然后重复上述搜索过程。在所有的行都被读取完后,迭代函数返回 nil 以表示迭代结束。


尽管迭代器本身有点复杂,但allwords的使用还是很简明易懂的:

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


对于迭代器而言,一种常见的情况就是:编写迭代器可能不太容易,但使用迭代器却十分简单。这也不是一个大问题,因为使用 Lua 语言编程的最终用户一般不会取定义迭代器,而只会使用那些宿主应用已经提供的迭代器。

目录
相关文章
|
5月前
|
前端开发 JavaScript
对象解构与迭代器的猫腻?
这篇文章介绍了变量的解构赋值技巧在前端开发中的常用性,包括对象解构和数组解构。文章中给出了一些代码示例,解释了可迭代对象的概念以及如何使用迭代协议来实现数组解构。此外,文章还介绍了生成器的概念并提供了代码示例。作者建议对这些概念不熟悉的读者可以查阅ES6的文档来更好地理解。
对象解构与迭代器的猫腻?
|
索引
在循环内使用闭包(Closures)
在循环内使用闭包(Closures)
73 0
|
算法 区块链 C++
迭代器与仿函数
迭代器与仿函数
|
JavaScript 前端开发
迭代器和生成器
在JavaScript中,迭代器(`Iterator`)是一个对象,用于在可迭代的数据结构中遍历和访问每个元素,而不必暴露该数据结构的内部结构。
|
Python
生成器和迭代器
生成器和迭代器
50 0
|
JavaScript 前端开发 中间件
一文彻底搞懂迭代器与生成器函数
参考mdn上解释,迭代器是一个对象,每次调用next方法返回一个{done: false, value: ''},每次调用next返回当前值,直至最后一次调用时返回 {value:undefined,done: true}时结束,无论后面调用next方法都只会返回{value: undefined,done:true}
154 0
一文彻底搞懂迭代器与生成器函数
|
JavaScript 前端开发
迭代器、生成器
JS查漏补缺系列是我在学习JS高级语法时做的笔记,通过实践费曼学习法进一步加深自己对其的理解,也希望别人能通过我的笔记能学习到相关的知识点。这一次我们来了解JavaScript中的迭代器、生成器
91 0
闭包和对象
尊敬的大师 Qc Na 和他的学生 Anton 一起散步。Anton 希望能和大师展开一场讨论,于是说道:“老师,我听说对象是个非常好的东西,真的吗?“ 大师不屑地看了他的学生一眼,答道:“愚蠢的孩子,对象不过是穷人的闭包。”
110 0
闭包和对象