结合泛型for的所有功能,写出更加简单,高效的迭代器。

1、迭代器和closure

  迭代器是一种可以遍历集合中所有元素的机制。在Lua中用函数去表示它。

每调用一次,就返回下一个元素。

  迭代器在两次成功调用期间,都需要保持一些状态,这样才能知道它所在的位置及如何步进到下一个位置。

closure为此提供了一个很好的机制,一个closure就是一种可以访问upvalue的函数。

这些变量就可用于在成功调用之间保持状态值,从而使closure记住它在一次遍历中所在的位置。

因此,一个典型的closure结构包括两个函数:closure本身、创建该closure的工厂函数。

下面写一个迭代器函数,与ipairs不同的是它不返回每个元素的索引,仅返回元素的值:

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

该例中,values是一个工厂(闭合函数)。每次调用它就创建一个closure(迭代器本身)。

这个closure把它的状态保存在外部变量t和 i 中。每次调用这个迭代器,它就从 t 中返回下一个值。

直到最后一个元素,返回nil,迭代结束。

如果用while来实现:

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

用while实现,可以清晰看到迭代器的工作流程,是不是 浅显易懂 ^_^。

如果用泛型for就更便捷了。你会发现,它就是为这种迭代而生设计的:

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

泛型for为一次迭代循环做了所有的薄记工作,它在内部保存了迭代器函数,因此不需要iter变量。

它在每次新迭代时调用迭代器,并在迭代器返回nil时结束循环。

一个更高级的迭代器:遍历当前输入文件中所有的单词-----allwords

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

为了完成这个功能,需要保持两个值:当前行的内容(line)和该行中所处的位置(pos)。

迭代器的主要部分是string.find函数,用"%w+"去匹配一个或多个数字/字符。

如果找到,迭代器就会更新当前pos为该单词之后的第一个字符,并返回该单词。

否则就读取新的一行反复这个搜索过程。若没有剩余的行,就返回nil,表示结束:

function allwords()
local line = io.read() --初始化第一行
local pos = --起始位置,初始化为1
return function() --迭代器函数
while line do --line为真,重复循环遍历
local s,e = string.find(line,"%w+",pos)
if s then --找到了一个word
pos = e + --word后的下一个位置
return string.sub(line,s,e) --返回这个word
else
line = io.read() --开始找新的一行
pos = --同时初始化pos为1
end
end
return nil --所有行都遍历完
end
end

可以看到,编写迭代器不太容易,但是使用起来很方便。通常Lua用户不会自己定义迭代器。

后面会用有状态的closure重写allwords,把line和pos保存到一个table里,看上去就更加简洁了^_^。

2、迭泛型for语义

  泛型for提供保存状态的机制可以简化上面的while循环(每次创建一个新的closure)。

泛型for在循环过程内部保存了迭代器函数。实际上它保存着3个值:一个迭代器函数,一个恒定状态,一个控制变量。

泛型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

--or

for line in io.lines() do
io.write(line,"\n")
end

var-list中的第一个变量叫作控制变量。在循环过程中,它的值不能为nil。为nil则循环终止。

for语句第一件做的事是计算exp的值————返回三个值(1 iterator ; 2 invariant state ; 3 initial value)供for语句使用。

第三个值用来初始化for的控制变量。这有点类似于多重赋值。

在初始化步奏后,for会以恒定状态和控制变量来调用迭代器函数。然后for将迭代器函数的返回值赋给变量列表中的变量。

一个更明朗的例子:

for var_1,..., var_n in <explist> do <block> end

-- equal below

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,循环结束。

3、无状态的迭代器

一种自身不保存任何状态的迭代器。

因此,可以在多个循环中使用同一个无状态的迭代器,避免创建新的closure开销。

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

一个无状态的迭代器可以根据这两个值来为下一次迭代生成下一个元素。比如ipairs,迭代一个数组的所有元素:

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

ipairs工厂函数和迭代器可以用lua编写出来:

local function iter (a,i)
i = i +
local v = a[i]
if v then
return i,v
end
end function ipairs(a)
return iter,a,
end

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

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

在调用next(t,k)时,此调用会将table中的任意次序的一组值返回:此table的下一个key,和对应的值。

而调用next(t,nil)时返回table的第一组值。若没有下一组值时,next返回nil。

for k,v in next,t do
<loop body>
end

ext-list会调整为三个返回值,next,t和nil 。结果和pairs(t)一样。

无状态迭代器遍历链表(Lua中很少用,但有时会用到链表):

local function getnext(list,node)
if not node then
return list --返回list头
else
return node.next --返回下一个list
end
end function traverse(list)
return getnext , list , nil --控制变量为nil
end

这里将链表的头节点作为恒定状态(traverse返回的第二个值),将当前节点作为控制变量。

getnext第一次被调用时,node为nil。之后调用则返回node.next.

list = nil
for line in io.lines() do
list = {val = line, next = list }
end --for循环结束后,list是包含最后一行的值,在链表里可以当成node head,下面的迭代器就用它开始遍历整个链表 for node in traverse(list) do --这里的list是链表节点头,也是一个table
print(node.val)
end

这里的链表在遍历的时候,是从最后一行开始往前遍历。与c语言中的链表遍历刚好相反。

4、复杂状态的迭代器

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

一个最简单的办法是用closure,或者可以将迭代器所需要的所有状态打包为一个table,保存在恒定状态中。

closre可以通过这个table保存任意多的数据,还可以在循环过程中改变这些数据。

由于这种迭代器可以在恒定状态中保存所有数据,所以它们通常可以忽略泛型for提供的第二个参数(控制变量)。

改写allwords例子,把line和pos保存到table里:

local iterator   --在后面定义
function allwords()
local state = {line=io.read(),pos = }
return iterator,state --这里省略了第三个返回值,控制变量,也就是泛型for为迭代器提供的第二个参数
end

iterator函数:

function iterator(state)
while state.line do -- 如果为有效的行内容才进入循环
local s,e = string.find(state.line,"%w+",state.pos)
if s then --找到一个单词
state.pos = e +
return string.sub(state.line,s,e)
else
state.line = io.read() --读新的一行
state.pos = --从第一个位置开始
end
end
return nil --所有行已经读完
end

通常一个基于closure实现的迭代器会比一个使用table的迭代器更为高效。

因为创建一个closure比table廉价,并且访问“upvalue”比访问table字段更快。

以后还可以使用协同程序(coroutine)编写迭代器。功能最强,但是开销大。

5、真正的迭代器

  其实真正做迭代的是for循环,迭代器只是为每次迭代提供一些成功后的返回值。

把迭代器称作“生成器(generator)”更准确。

有一种创建迭代器的方式:在迭代器中做实际的迭代操作。

当使用这种迭代器时,就不需要写一个循环了。但是,需要一个描述在每次迭代时需要做什么的参数,并用此参数来调用迭代器。

确切地说,迭代器接收一个函数作为参数,并在其内部的循环中调用这个函数。

接下来再重写allwords:

function allwords(f)
for line in io.lines() do
for word in string.gmatch(line,"%w+") do
f(word) --调用函数
end
end
end

如果只想打印word:

allwords(print)

还可以使用一个匿名函数作为循环体。

下面的代码计算了"hello"在文件中出现的次数:

local count =
allwords(function (w)
if w == "hello" then count = count + end
end)
print (count) --如果用之前的迭代器,就是下面的风格 local count =
for w in allwords() do
if w == "hello" then count = count + end
end
print(count)

生成器风格的迭代器更加灵活,它允许两个或多个并行(比如两个文件逐个单词对比)的迭代过程,允许在迭代中使用break和return。

对于真正的迭代器来说,return语句只能从匿名函数中返回,而不能从做迭代的函数中返回。

Chapter7 迭代器的更多相关文章

  1. chapter11_2 Lua链表与队列

    链表 由于table是动态的实体,所以在Lua中实现链表是很方便的.每个节点以一个table来表示,一个“链表”只是节点table中的一个字段. 该字段包含了对其他table的引用.例如,要实现一个基 ...

  2. ###《Effective STL》--Chapter7

    点击查看Evernote原文. #@author: gr #@date: 2014-08-31 #@email: forgerui@gmail.com Chapter7 在程序中使用STL Topic ...

  3. 匹夫细说C#:庖丁解牛迭代器,那些藏在幕后的秘密

    0x00 前言 在匹夫的上一篇文章<匹夫细说C#:不是“栈类型”的值类型,从生命周期聊存储位置>的最后,匹夫以总结和后记的方式涉及到一部分迭代器的知识.但是觉得还是不够过瘾,很多需要说清楚 ...

  4. 轻量级“集合”迭代器-Generator

    Generator是PHP 5.5加入的新语言特性.但是,它似乎并没有被很多PHP开发者广泛采用.因此,在我们了解PHP 7对Generator的改进之前,我们先通过一个简单却显而易见的例子来了解下G ...

  5. C#设计模式-迭代器模式

    一. 迭代器(Iterator)模式 迭代器是针对集合对象而生的,对于集合对象而言,必然涉及到集合元素的添加删除操作,同时也肯定支持遍历集合元素的操作,我们此时可以把遍历操作也放在集合对象中,但这样的 ...

  6. 设计模式(十):从电影院中认识"迭代器模式"(Iterator Pattern)

    上篇博客我们从醋溜土豆丝与清炒苦瓜中认识了“模板方法模式”,那么在今天这篇博客中我们要从电影院中来认识"迭代器模式"(Iterator Pattern).“迭代器模式”顾名思义就是 ...

  7. Python(四)装饰器、迭代器&生成器、re正则表达式、字符串格式化

    本章内容: 装饰器 迭代器 & 生成器 re 正则表达式 字符串格式化 装饰器 装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等.装饰器是解 ...

  8. 用struts2标签如何从数据库获取数据并在查询页面显示。最近做一个小项目,需要用到struts2标签从数据库查询数据,并且用迭代器iterator标签在查询页面显示,可是一开始,怎么也获取不到数据,想了许久,最后发现,是自己少定义了一个变量,也就是var变量。

    最近做一个小项目,需要用到struts2标签从数据库查询数据,并且用迭代器iterator标签在查询页面显示,可是一开始,怎么也获取不到数据,想了许久,最后发现,是自己少定义了一个变量,也就是var变 ...

  9. Java迭代器

    迭代器在其实就是指针,读取集合或者数组中的一个值,读完以后又指向下一条数据. iterator() 迭代器只读,不能改效率要比for循环高 迭代器的一些方法: HasNext() 如果仍有元素可以迭代 ...

随机推荐

  1. android studio 将library导出为jar 亲测成功

    本人使用的是helloChart这个开源项目,其主要用于图表,来自git 地址为:https://github.com/lecho/hellocharts-android 下载命令为 git clon ...

  2. Beyond Compare V3.2.3 Beta 中文版

    软件名称: Beyond Compare V3.2.3 Beta 中文版 软件语言: 简体中文 授权方式: 免费软件 运行环境: Win7 / Vista / Win2003 / WinXP 软件大小 ...

  3. OllyDBG V1.10聆风听雨汉化版

    软件名称:OllyDBG V1.10聆风听雨汉化版 软件语言: 简体中文 授权方式: 免费软件 运行环境: Win 32位/64位 软件大小: 3.84MB 图片预览: 软件简介: Ollydbg2. ...

  4. Unity 解决 An asset is marked with HideFlags.DontSave but is included in the build 问题。

    问题是:不能使用Unity使用的默认的字体.想要显示中文直接去下载一个字体.没必要使用默认的字体

  5. 关于Python2字符编码的体会

    对于Python的字符编码问题也懵了很久,最近做爬虫多次遇到网页转码的问题,干脆彻底解决掉!Just Do it! 1.两种类型str与unicode str和unicode都是basestring的 ...

  6. ibaits的一个简单的完整的例子

    ibaits的简单介绍: iBatis 是apache 的一个开源项目,一个O/R Mapping(对象/关系映射) 解决方案,iBatis 最大的特点就是小巧,上手很快.如果不需要太多复杂的功能,i ...

  7. 【Python之路】第九篇--Python基础之线程、进程和协程

    进程与线程之间的关系 线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除.线程可与属于同一进程的其它线程共享进程所拥有的全 ...

  8. NIC Bonding: 2 nic port as 1 interface

    The following is concluded from here. Consider we have 2 interfaces: eth0 & eth1 bond the two in ...

  9. Android:关于背景选择器Selector的item顺序

    在使用背景选择器的时候,如果item的顺序不对,会导致不起作用. 1.首先背景选择器的normal选项一定要放在最后. 2.pressed的选择器应该在seclet的前面.我在使用的时候找了半天问题, ...

  10. 总结一下js的原型和原型链

    最近学习了js的面向对象编程,原型和原型链这块是个难点,理解的不是很透彻,这里搜集了一些这方面的资料,以备复习所用 一. 原型与构造函数 Js所有的函数都有一个prototype属性,这个属性引用了一 ...