结合泛型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. Shell 脚本计算时间差

    在shell脚本中统计程序执行完毕所需要的时间不像在java中使用System.currentTimeMillis()方便 稍微记录一下,以供备用,免得又去花时间想(统计程序执行消耗多少s): sta ...

  2. linux shell脚本学习xargs命令使用详解

    作用是将参数列表转换成小块分段传递给其他命令,以避免参数列表过长的问题 xargs是给命令传递参数的一个过滤器,也是组合多个命令的一个工具.它把一个数据流分割为一些足够小的块,以方便过滤器和命令进行处 ...

  3. swift3 控件创建

    //MARK:- UIScrollView let scrollView = UIScrollView() scrollView.delegate = target scrollView.backgr ...

  4. JavaScript忍者秘籍——原型

    概要:本篇博客主要介绍JavaScript的原型 1.对象实例化 - 初始化的优先级 初始化操作的优先级如下: ● 通过原型给对象实例添加的属性 ● 在构造器函数内给对象实例添加的属性 在构造器内的绑 ...

  5. 递归——CPS(三)

    JScript不是天然支持CPS,但是可以写一个分发引擎使得能工作在CPS风格下.一般只有一个活动的continuation,所以可以定义规则:JScript CPS 函数允许有返回,但是它们做的最后 ...

  6. Redis 集群解决方案比较

    调研比较了三个Redis集群的解决方案: 系统 贡献者 是否官方Redis实现 编程语言 Twemproxy Twitter 是 C Redis Cluster Redis官方 是 C Codis 豌 ...

  7. 在Linux下访问Windows共享目录的配置方法

    在Linux下访问Windows共享目录的配置方法 1.在Windows上设置一个共享目录 如:将d:\RedHat_disk设置为共享目录 2.在Windows上创建一个用户,如tommy,密码11 ...

  8. B/S和C/S【转载Jane的博客 http://blog.sina.com.cn/liaojane】

    什么是C/S和B/S结构?         C/S又称Client/Server或客户/服务器模式.服务器通常采用高性能的PC.工作站或小型机,并采用大型数据库系统,如Oracle.Sybase.In ...

  9. mysql备份和还原

    MySQLl提供了一个mysqldump命令,我们可以用它进行数据备份. 按提示输入密码,这就把abc数据库所有的表结构和数据备份到abc_20161108.sql了, # mysqldump -u ...

  10. C# DataTable转实体通用方法

    public static class DataTableHelper { public static T GetEntity<T>(DataTable table) where T : ...