LUA学习(五) 迭代器

在Lua中迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。

迭代器需要保存上一次的调用状态和下一次的调用参数。用闭合函数可以很好地实现这些要求,因为每个闭合函数可以很好地维护自身可以访问的外部变量。每个闭合函数必须由可供其访问的外部变量,而这个外部变量需要由其他函数提供,我们可以暂且把这些函数成为工厂函数。所以,一个典型的迭代器,主要由工厂函数和闭合函数组成。如:

function list_iter (t)  
	local i = 0  
	local n = #t  
	return function() i = i + 1  
			if i <= n then 
				return t[i]   
			end  
		end  
end

t = {10, 20,30}
iter =list_iter(t)      -- createsthe iterator  
while true do  
	local element = iter()   -- calls the iterator   
	if element == nil then 
		break
	end   
	print(element) 
end
list_iter()函数就是工厂函数,尾调用中的函数与i、n两个变量一起组成了闭合函数。它们两个合起来定义了一个迭代器list_iter。我们将list_iter函数赋值给function类型变量iter,每次调用iter函数,都会获得数组t 的元素。闭合函数内部维护变量i和n的值,所以每次调用闭合函数,都能依次获得数组t 的元素。这就是一个典型的迭代器。lua语言提供许多功能强大的内置迭代器。
注意:计算数组的长度,在5.1版本以前的lua中可以使用table.getn()函数,但是在最新的5.3版本中的lua中,已经去掉这个函数了。对于纯数组table,可以使用#标识来计算长度。



泛型for迭代器:

迭代器的实现,总需要提供一个闭合函数。虽然这不是什么大问题,但是如果无限制的创建闭合函数,也是不推荐的。

闭合函数主要的作用是由自身维护外部的局部变量,如果可以使用其他方法可以维护这些局部变量,就可以不使用闭合函数了。

在lua中,泛型for循环可以很好地达到这个目的。泛型for循环主要维护了三个变量:迭代函数、状态常量、控制变量。前边介绍过泛型for循环,它的格式为:

for  in  do  
     
end  
是一个或多个以逗号分割的变量名列表,是一个或多个以逗号分 割的表达式列表,通常情况下 exp-list 只有一个值:迭代工厂函数的调用。

在具体的应用中,主要为以下格式:

for k, v in pairs(t) do
    print(k, v)
end
k,v为变量列表,pairs()为表达式列表,即工厂函数。


lua的解释器会对泛型for语句做处理,在for语句被执行时,会有以下五个步骤:


首先,初始化,计算 in 后面表达式的值,表达式应该返回范性 for 需要的三个值: 迭代函数,状态常量和控制变量;与多值赋值一样,如果表达式返回的结果个数不足三 个会自动用 nil补足,多出部分会被忽略。


第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于 for结构来说, 状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。


第三,将迭代函数返回的值赋给变量列表。


第四,如果返回的第一个值为 nil循环结束,否则执行循环体。

第五,回到第二步再次调用迭代函数。 

更精确的说,对于泛型for循环:

for var_1, ..., var_n in explist do block end  
等价于:

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
_f为工厂函数,_s为状态变量,_var为控制变量。在执行循环时,每次都执行工厂函数_f,参数为_s 和_var,然后将函数的第一个返回值赋值给_var。如果_var为空,则停止循环,否则,执行block代码块,继续循环。


无状态的迭代器:

无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。
这种无状态迭代器的典型的简单的例子是ipairs,他遍历数组的每一个元素。如:

a = {"one", "two", "three"}  
for i, v in ipairs(a) do  
  print(i, v)  
end 
执行结果为:

1	one
2	two
3	three
迭代的状态包括被遍历的表(循环过程中不会改变的状态常量)和当前的索引下标(控制变量),ipairs和迭代函数都很简单,我们在Lua中可以这样实现:

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 
当 Lua 调用 ipairs(a)开始循环时,他获取三个值:迭代函数 iter,状态常量 a 和控制变 量初始值 0;然后 Lua调用 iter(a,0)返回 1,a[1]C除非 a[1]=nil);第二次迭代调用 iter(a,1) 返回 2,a[2]……直到第一个非 nil 元素。

注意:通过以上这个例子,也可以看出,泛型for'循环的工厂函数总是会返回三个返回值,不足会根据情况自动以0或是nil补全。


多状态的迭代器:

很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最 简单的方法是使用闭包,还有一种方法就是将所有的状态信息封装到 table 内,将 table作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在 table 内,所以迭代函 数通常不需要第二个参数。下面我们重写 allwords (line,pos)的 table。迭代器,这一次我们不是使用闭包而是使用带有两个域。

创建一个多状态的迭代器:

array = {"Lua", "Tutorial"}

function elementIterator (collection)
	local index = 0
	local count = #collection
	-- 闭包函数
	return function ()
		index = index + 1
		if index <= count then
			--  返回迭代器的当前元素
			return collection[index]
		end
	end
end

for element in elementIterator(array) do
   print(element)
end 
打印为:

Lua
Tutorial
在这个函数中,迭代函数elementIterator()作为工厂函数,创建了一个闭合函数。这种多状态的迭代器,需要借助外部的table才可以实现。尽量不要使用多状态的迭代器,创建table的消耗和效率都比较差。


真正的迭代器:

lua中的迭代器,所谓的迭代函数只是一个取值函数,从本质上说并没有进行迭代,而是通过for循环来进行迭代。

有一种方式创建一个在内部完成迭代的迭代器。这样当我们使用迭代器的时候就不需要使用循环了;我们仅仅使用每一次迭代需要处理的任务作为参数调用迭代器即可, 具体地说,迭代器接受一个函数作为参数,并且这个函数在迭代器内部被调用。如:

array = {"Lua", "Tutorial"}

function elementIterator (f)
	local count = #array
	for i = 1, count do
		f(array[i])
	end
end

elementIterator(print)
这样的迭代器,在内部通过for循环完成了迭代。

你可能感兴趣的:(Lua)