【lua學習】lua疊代器和泛型for淺析
1. 疊代器與Closure:
在Lua中,疊代器通常為函數,每調用一次函數,即傳回集合中的“下一個”元素。每個疊代器都需要在每次成功調用之間保持一些狀态,這樣才能知道它所在的位置和下一次周遊時的位置。從這一點看,Lua中closure機制為此問題提供了語言上的保障,見如下示例:
function values(t)
local i =
return function()
i = i +
return t[i]
end
end
t = {, , }
it = values(t)
print([[======================]])
while true do
local element = it()
if element == nil then
break
end
print(element)
end
--另外一種基于foreach的調用方式(泛型for)
t2 = {, , }
print([[======================]])
for element in values(t2) do
print(element)
end
運作結果:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CXykEROJTSU9ENjRVTxh2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DMwYTO0kjM3EDNyYDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
從上面的應用示例來看,相比于while方式,泛型for的方式提供了更清晰的實作邏輯。因為Lua在其内部替我們儲存了疊代器函數,并在每次疊代時調用該隐式的内部疊代器,直到疊代器傳回nil時結束循環。
2. 泛型for的語義:
上面示例中的疊代器有一個明顯的缺點,即每次循環時都需要建立一個新的closure變量,否則第一次疊代成功後,再将該closure用于新的for循環時将會直接退出。
這裡我們還是先詳細的講解一下Lua中泛型(for)的機制,之後再給出一個無狀态疊代器的例子,以便于我們的了解。如果我們的疊代器實作為無狀态疊代器,那麼就不必為每一次的泛型(for)都重新聲明一個新的疊代器變量了。
泛型(for)的文法如下:
for <var-list> in <exp-list> do
<body>
end
為了便于了解,由于我們在實際應用中通常隻是包含一個表達式(expr),是以簡單起見,這裡的說明将隻是包含一個表達式,而不是表達式清單。現在我們先給出表達式的原型和執行個體,如:
function ipairs2(a)
return iter,a,
end
該函數傳回3個值,第一個為實際的疊代器函數變量,第二個是一個恒定對象,這裡我們可以了解為待周遊的容器,第三個變量是在調用iter()函數時為其傳入的初始值。
下面我們再看一下iter()函數的實作,如:
local function iter(a, i)
i = i +
local v = a[i]
if v then
return i, v
else
return nil, nil
end
end
在疊代器函數iter()中傳回了兩個值,分别對應于table的key和value,其中key(傳回的i)如果為nil,泛型(for)将會認為本次疊代已經結束。下面我們先看一下實際用例,如:
local function iter(a, i)
i = i +
local v = a[i]
if v then
return i, v
else
return nil, nil
end
end
function ipairs2(a)
return iter,a,
end
a = {"one","two","three"}
for k,v in ipairs2(a) do
print(k, v)
end
運作結果如下:
這個例子中的泛型(for)寫法可以展開為下面的基于while循環的方式,如:
local function iter(a, i)
i = i +
local v = a[i]
if v then
return i, v
else
return nil, nil
end
end
function ipairs2(a)
return iter,a,
end
a = {"one","two","three"}
do
local _it,_s,_var = ipairs2(a)
while true do
local var_1,var_2 = _it(_s,_var)
_var = var_1
if _var == nil then --注意,這裡隻判斷疊代器函數傳回的第一個是否為nil。
break
end
print(var_1,var_2)
end
end
運作結果同上,對比可知for語句執行的過程:for做的第一件事就是對in後面的表達式求值,這些表達式應該傳回3個值供for儲存:疊代器函數、恒定狀态和控制變量的初值。這裡和多重指派是一樣的,隻有最後一個表達式才會産生多個結果,并且隻會保留前3個值,多餘的值會被丢棄;而不夠的話,就以
nil
補足。
在初始化完成以後,for會以恒定狀态和控制變量來調用疊代器函數。然後for将疊代器函數的傳回值賦予變量清單中的變量。如果第一個傳回值為nil,那麼循環就終止,否則,for執行它的循環體,随後再次調用疊代器函數,并重複這個過程。
3. 無狀态疊代器的例子:
local function getnext(list, node) --疊代器函數。
if not node then
return list
else
return node.next
end
end
function traverse(list) --泛型(for)的expression
return getnext,list,nil
end
--Lua 實作連結清單
node={}
list=node
--初始化,建構一個空表
function init()
list.data=""
list.next=nil
end
--向連結清單的尾部添加資料
function addRear(d)
node.next={} --建立一個節點,相當于malloc一個節點
node=node.next
node.next=nil
node.data=d
end
--清理連結清單,操作完成後,連結清單還在,隻不過為空,相當剛開始的初始化狀态
function clear()
if not list then
print('連結清單不存在')
end
while true do
firstNode=list.next
if not firstNode then --表示連結清單已為空表了
break
end
t=firstNode.next
list.next=nil
list.next=t
end
list.data=""
print('-- clear ok --')
end
--銷毀連結清單
function destory()
clear() --先清除連結清單
list=nil
end
init()
--初始化連結清單中的資料。
for line in io.lines() do
if(line == 'q')
then
break
end
addRear(line)
end
--以泛型(for)的形式周遊連結清單。
for node in traverse(list) do
print(node.data)
end
destory()
運作結果如下:
這裡使用的技巧是将連結清單的頭結點作為恒定狀态(traverse傳回的第二個值),而将目前節點作為控制變量。第一次調用疊代器函數getnext()時,node為nil,是以函數傳回list作為第一個結點。在後續調用中node不再為nil了,是以疊代器傳回node.next,直到傳回連結清單尾部的nil結點,此時泛型(for)将判斷出疊代器的周遊已經結束。
最後需要說明的是,traverse()函數和list變量可以反複的調用而無需再建立新的closure變量了。這主要是因為疊代器函數(getnext)實作為無狀态疊代器。
4. 具有複雜狀态的疊代器:
在上面介紹的疊代器實作中,疊代器需要儲存許多狀态,可是泛型(for)卻隻提供了恒定狀态和控制變量用于狀态的儲存。一個最簡單的辦法是使用closure。當然我們還以将所有的資訊封裝到一個table中,并作為恒定狀态對象傳遞給疊代器。雖說恒定狀态變量本身是恒定的,即在疊代過程中不會換成其它對象,但是該對象所包含的資料是否變化則完全取決于疊代器的實作。就目前而言,由于table類型的恒定對象已經包含了所有疊代器依賴的資訊,那麼疊代器就完全可以忽略泛型(for)提供的第二個參數。下面我們就給出一個這樣的執行個體,見如下代碼:
local iterator
function allwords()
local state = { line = io.read(), pos = }
return iterator, state
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
--[[
for word in allwords() do
print(word)
end
]]--