Lua学习笔记(七):迭代器与泛型for

  1、迭代器与闭包

  迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。在Lua中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。

  迭代器需要保留上一次成功调用的状态和下一次成功调用的状态,也就是他知道来自于哪里和将要前往哪里。闭包提供的机制可以很容易实现这个任务。记住:闭包是一个内部函数,它可以访问一个或者多个外部函数的外部局部变量。每次闭包的成功调用后这些外部局部变量都保存他们的值(状态)。当然如果要创建一个闭包必须要创建其外部局部变量。所以一个典型的闭包的结构包含两个函数:一个是闭包自己,另一个是工厂(创建闭包的函数)。

  举一个简单的例子,我们为一个list写一个简单的迭代器,与ipairs()不同的是我们实现的这个迭代器返回元素的值而不是索引下标:

1 function list_iter (t)
2     local i = 0
3     local n = table.getn(t)
4     return function ()
5         i = i + 1
6         if i <= n then return t[i] end
7     end
8 end

  这个例子中list_iter是一个工厂,每次调用他都会创建一个新的闭包(迭代器本身)。闭包保存内部局部变量(t,i,n),因此每次调用他返回list中的下一个元素值,当list中没有值时,返回nil,我们可以在while语句中使用这个迭代器:

1 t = {10, 20, 30}
2 iter = list_iter(t)        -- creates the iterator
3 while true do
4     local element = iter()    -- calls the iterator
5     if element == nil then break end
6     print(element)
7 end

  我们设计的这个迭代器也很容易用于范型for语句

1 t = {10, 20, 30}
2 for element in list_iter(t) do
3     print(element)
4 end

  范型for为迭代循环处理所有的簿记(bookkeeping):首先调用迭代工厂;内部保留迭代函数,因此我们不需要iter变量;然后在每一个新的迭代处调用迭代器函数,当迭代器返回nil时循环结束。

  下面我们看一个稍微复杂一点的例子:我们写一个迭代器遍历一个文件内的所有匹配的单词。为了实现目的,我们需要保留两个值:当前行和当前行的偏移量,我们使用两个外部局部变量line、pos保存这两个值。

 1 function allwords()
 2     local line = io.read()    -- current line
 3     local pos = 1                -- current position in the line
 4     return function ()        -- iterator function
 5         while line do            -- repeat while there are lines
 6         local s, e = string.find(line, "%w+", pos)
 7             if s then            -- found a word?
 8                 pos = e + 1    -- next position is after this word
 9                 return string.sub(line, s, e) -- return the word
10             else
11                 line = io.read()  -- word not found; try next line
12                 pos = 1        -- restart from first position
13             end
14         end
15     return nil        -- no more lines: end of traversal
16     end
17 end

  迭代函数的主体部分调用了string.find函数,string.find在当前行从当前位置开始查找匹配的单词,例子中匹配的单词使用模式"%w+"描述的,如果查找到一个单词,迭代函数数更新当前位置pos为单词后的第一个位置,并且返回这个单词(string.sub函数从line中提取两个位置参数之间的子串)。否则迭代函数读取新的一行并重新搜索。如果没有line可读返回nil结束。

  尽管迭代函数有些复杂,但使用起来是很直观的:

1 for word in allwords() do
2     print(word)
3 end

  通常情况下,迭代函数大都难写易用。这不是大问题,一般Lua编程不需要自己写迭代函数,语言本身提供了许多。当然,必要时自己动手构造一二亦可。

  2、范型for的语义

  前面我们看到的迭代器有一个缺点:每次调用都需要创建一个闭包,大多数情况下这种做法都没有什么问题,例如在allwords迭代器中创建一个闭包的代价比起读整个文件来说微不足道,然而在有些情况下创建闭包的代价是不能忍受的。在这些情况下我们可以使用范型for本身来保存迭代的状态。

  前面我们看到在循环过程中范型for在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。下面详细说明。

  范型for的文法如下:

1 for <var-list> in <exp-list> do
2     <body>
3 end

  <var-list>是以一个或多个逗号分隔的变量名列表,<exp-list>是以一个或多个逗号分隔的表达式列表,通常情况下exp-list只有一个值:迭代工厂的调用。

1 for k, v in pairs(t) do
2     print(k, v)
3 end

  上面代码中,k,v为变量列表;pair(t)为表达式列表。

  在很多情况下变量列表也只有一个变量,比如:

1 for line in io.lines() do
2     io.write(line, ‘\n‘)
3 end

  我们称变量列表中第一个变量为控制变量,其值为nil时循环结束。

  下面我们看看范型for的执行过程:

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

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

  3).将迭代函数返回的值赋给变量列表。

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

  5).回到第二步再次调用迭代函数。

  更具体地说:

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

-----等价于
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、无状态的迭代器

  无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价、

  每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。这种无状态迭代器的典型的简单的例子是ipairs,他遍历数组的每一个元素。

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

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

 1 function iter (a, i)
 2     i = i + 1
 3     local v = a[i]
 4     if v then
 5         return i, v
 6     end
 7 end
 8
 9 function ipairs (a)
10     return iter, a, 0
11 end

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

  Lua库中实现的pairs是一个用next实现的原始方法:

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

--还可以不使用ipairs直接使用next
for k, v in next, t do
    ...
end

  记住:exp-list返回结果会被调整为三个,所以Lua获取next、t、nil;确切地说当他调用pairs时获取。

  4、多状态的迭代器

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

  下面我们重写allwords迭代器,这一次我们不是使用闭包而是使用带有两个域(line、pos)的table。

--开始迭代的函数是很简单的,他必须返回迭代函数和初始状态
local iterator        -- to be defined later

function allwords()
    local state = {line = io.read(), pos = 1}
    return iterator, state
end

--真正的处理工作是在迭代函数内完成
function iterator (state)
    while state.line do        -- repeat while there are lines
        -- search for next word
        local s, e = string.find(state.line, "%w+", state.pos)
        if s then        -- found a word?
            -- update next position (after this word)
            state.pos = e + 1
            return string.sub(state.line, s, e)
        else    -- word not found
            state.line = io.read()    -- try next line...
            state.pos = 1        -- ... from first position
        end
    end
    return nil            -- no more lines: end loop
end

  我们应该尽可能的写无状态的迭代器,因为这样循环的时候由for来保存状态,不需要创建对象花费的代价小,如果不能用无状态的迭代器实现,应尽可能使用闭包,尽可能不要使用table这种方式,因为创建闭包的代价要比创建table小,另外Lua处理闭包要比处理table速度快些。

  5、真正的迭代器

  迭代器的名字有一些误导,因为它并没有迭代,完成迭代功能的是for语句,也许更好的叫法应该是生成器;但是在其他语言比如java、c++迭代器的说法已经很普遍了,我们也就沿用这个术语。

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

--作为一个具体的例子,我们使用上述方式重写allwords迭代器
function allwords (f)
    -- repeat for each line in the file
    for l in io.lines() do
        -- repeat for each word in the line
        for w in string.gfind(l, "%w+") do
            -- call the function
            f(w)
        end
    end
end

--如果我们想要打印出单词,只需要
allwords(print)

--更一般的做法是我们使用匿名函数作为参数,下面的例子打印出单词"hello"出现的次数:
local count = 0
allwords(function (w)
    if w == "hello" then count = count + 1 end
end)
print(count)

--用for结构完成同样的任务
local count = 0
for w in allwords() do
    if w == "hello" then count = count + 1 end
end
print(count)

  真正的迭代器风格的写法在Lua老版本很流行,那时还没有for循环。

  两种风格的写法相差不大,但也有区别:一方面,第二种风格更容易书写和理解,另一方面,for结构更灵活,可以使用break和continue语句。在真正的迭代器风格写法中return语句只是从匿名函数中返回而不是退出循环。

Lua学习笔记(七):迭代器与泛型for,布布扣,bubuko.com

时间: 2024-08-01 10:41:40

Lua学习笔记(七):迭代器与泛型for的相关文章

Lua学习笔记之迭代器与范型for

1.  迭代器与闭包 迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素.迭代器需要保留上一次成功调用的状态和下一次成功调用的状态,也就是它知道来自于哪里和将要前往哪里.闭包提供的机制可以很容易实现这个任务.记住:闭包是以恶搞内部函数,它可以访问一个或者多个外部函数的外部局部变量.每一次闭包的成功调用后这些局部变量都保存他们的值. 2.  范性for的语义 范性for的文法如下: for <var-list>in <exp-list> do <body> end

第十七篇:博采众长--初探WDDM驱动学习笔记(七)

基于WDDM驱动的DirectX视频加速重定向框架设计与实现 现在的研究生的论文, 真正质量高的, 少之又少, 开题开得特别大, 动不动就要搞个大课题, 从绪论开始到真正自己所做的内容之间, 是东拼西凑地抄概念, 抄公式, 达到字数篇幅的要求, 而自己正真做了什么, 有哪些实际感受, 做出的内容, 相比前面的东拼西凑就几点内容, 之后就草草结束, 步入感谢的段落. 原因不光只有学生自己, 所谓的读研, 如果没有一个环境, 学生有再大的愿望, 再强的毅力, 到头来也只是空无奈. 有些导师要写书,

马哥学习笔记七——LAMP编译安装之MYSQL

1.准备数据存放的文件系统 新建一个逻辑卷,并将其挂载至特定目录即可.这里不再给出过程. 这里假设其逻辑卷的挂载目录为/mydata,而后需要创建/mydata/data目录做为mysql数据的存放目录. 2.新建用户以安全方式运行进程: # groupadd -r mysql # useradd -g mysql -r -s /sbin/nologin -M -d /mydata/data mysql # chown -R mysql:mysql /mydata/data 3.安装并初始化my

C++学习笔记之迭代器

模板是的算法独立于存储的数据类型,而迭代器使算法独立于使用的容器类型.理解迭代器是理解STL的关键. 迭代器应该具备的特征: (1)应该能够对迭代器进行解除引用的操作,以便能够访问它引用的值.即如果P是一个迭代器,则应该对*P进行定义 (2)应该能够将一个迭代器赋给另一个迭代器.如果P和Q都是迭代器,则应对P=Q定义. (3)应该能够对迭代器进行比较,看它们是否相等.即如果P和Q都是迭代器,则应对P==Q和P!=Q进行定义. (4)应该能够使用迭代器遍历容器中的所有元素,这可以通过迭代器定义的+

python学习笔记七:条件&循环语句

1.print/import更多信息 print打印多个表达式,使用逗号隔开 >>> print 'Age:',42 Age: 42   #注意个结果之间有一个空格符 import:从模块导入函数 import 模块 from 模块 import 函数 from 模块 import * 如果两个模块都有open函数的时候, 1)使用下面方法使用: module1.open()... module2.open()... 2)语句末尾增加as子句 >>> import ma

Lua学习笔记9:多文件

一 终端中执行多个文件:-l 加入在文件一中定义了一个变量,在另一文件中输出这个变量,代码如下: --file1.lua num = 100 --file2.lua print(num) 终端输入(注意:不是lua命令行): lua -lfile1 -lfile2 注意:不要加上文件后缀名.lua 二 命令行中加载文件 --lib.lua function norm(x, y) local n2 = x^2 + y^2 return math.sqrt(n2) end function twic

lua学习笔记10:lua简单命令行

前面多次用了命令行,这次就好好学下命令行: 一 格式 lua [options][script][args] 二 具体命令 -e 直接将命令传个lua -l 加载一个文件 -i 进入交互模式 例如,终端输入: lua -e "print(math.sin(12))" lua学习笔记10:lua简单命令行,布布扣,bubuko.com

lua学习笔记11:lua中的小技巧

lua中的小技巧,即基础lua语言本身的特种,进行一个些简化的操作 一 巧用or x = x or v 等价于: if not x then x = v end 如果x为nil或false,就给他赋值为 二 三元运算符实现 a and b or c 类似C语言: a ? b : c and 的运算由优先级高于or lua学习笔记11:lua中的小技巧,布布扣,bubuko.com

swift学习笔记(七)自动引用计数

与Object-c一样,swift使用自动引用计数来跟踪并管理应用使用的内存.当实例不再被使用时,及retainCount=0时,会自动释放是理所占用的内存空间. 注:引用计数仅适用于类的实例,因为struct和enumeration属于值类型,也就不牵涉引用,所以其存储和管理方式并不是引用计数. 当一个实例被初始化时,系统会自动分配一定的内存空间,用于管理属性和方法.当实例对象不再被使用时,其内存空间被收回. swift中的引用类型分为三种,即Strong强引用,weak弱引用和无主引用unw