结合泛型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. 远程连接mysql数据库,1130问题

    远程或使用非127.0.0.1和localhost地址连接时,出现代号为1130问题, ERROR 1130: Host 192.168.2.159 is not allowed to connect ...

  2. JDK各版本新增的主要特性

    JDK1.5新特性: 1.自动装箱与拆箱: 2.枚举 3.静态导入,如:import staticjava.lang.System.out 4.可变参数(Varargs) 5.内省(Introspec ...

  3. excel 常用函数

    1.统计整列唯一值的数量 =sumproduct(1/countif(offset(A1,,,COUNTA(A:A)),OFFSET(A1,,,COUNTA(A:A))))

  4. [kuangbin带你飞]专题六 最小生成树 POJ 1287 Networking

    最小生成树模板题 跑一次kruskal就可以了 /* *********************************************** Author :Sun Yuefeng Creat ...

  5. tomcat7或8如何修改浏览器标题上的ICON小图标

    替换tomcat根目录/webapps/ROOT下的favicon.ico文件,之后重启了服务器,可是还是显示那只猫的图标. 如果不能显示,请清除浏览器的缓存

  6. [编码解码] Base64 编码换行和+号遍空格的处理

    Android自身带有Base64加密与解密的包,可以方便地加密密码方便传输. String base64Token = Base64.encodeToString(token.trim().getB ...

  7. C++友元

    通过friend关键字,我们可以将不属于当前类的一个函数在当前类中加以声明,该函数便可以成为当前类的友元函数. 例1: #include<iostream>using namespace ...

  8. ubuntu14.04 下安装mysql5.6

    1.sudo apt-get install mysql-server-5.6 2.测试是否安装成功 ps aux |grep mysql mysql -u root -p 3.允许远程访问设置 su ...

  9. windows线程池四种情形(win核心读书笔记)

    windows线程池四种情形(win核心读书笔记) Mircosoft从Windows2000引入线程池API,并在Vista后对线程池重新构架,引入新的线程池API.以下所有线程池函数均适用于Vis ...

  10. JavaScript动态加载资源【js|css】示例代码

    在开发过程中会用到各种第三方的插件,或者自己写在单独文件中的js方法库或者css样式,在html头部总是需要写一大堆的script和link标签,如果想要自己实现动态的引入资源文件,可以使用开源的re ...