1-7、Lua迭代器与泛型for

2023-11-10

1-7、Lua迭代器与泛型for(理解为主,必选先理解闭包的概念和用法)

1、迭代器与闭包

迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。在Lua中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。
迭代器需要保留上一次成功调用的状态和下一次成功调用的状态,也就是他知道来自于哪里和将要前往哪里。闭包提供的机制可以很容易实现这个任务。记住:闭包是一个内部函数,它可以访问一个或者多个外部函数的外部局部变量。每次闭包的成功调用后这些外部局部变量都保存他们的值(状态)。当然如果要创建一个闭包必须要创建其外部局部变量。所以一个典型的闭包的结构包含两个函数:一个是闭包自己;另一个是工厂(创建闭包的函数)。
举一个简单的例子,我们为一个list写一个简单的迭代器,与ipairs()不同的是我们实现的这个迭代器返回元素的值而不是索引下标:

function list_iter (t)
	local i = 0
	local n = table.getn(t)
	--简单的说,闭包是一个函数以及它的upvalues,下面的匿名函数和i的组合就是闭包,
	--i已经是一个新的变量,和外部local i = 0时的变量i已经不是同一个了,它既不是全局变量也不是局部变量,
	--lua称其为upvalues
	return function ()
		i = i + 1
		if i <= n then return t[i] end
	end
end

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

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

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

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

范性for为迭代循环处理所有的薄记(bookkeeping):首先调用迭代工厂;内部保留迭代函数,因此我们不需要iter变量;然后在每一个新的迭代处调用迭代器函数;当迭代器返回nil时循环结束(后面我们将看到范性for能胜任更多的任务)。
下面看一个稍微复杂一点的例子:我们写一个迭代器遍历一个文件内的所有匹配的单词。为了实现目的,我们需要保留两个值:当前行和在当前行的偏移量,我们使用两个外部局部变量line、pos保存这两个值。

function allwords()
	local line = io.read()	-- current line
	local pos = 1				-- current position in the line
	return function ()		-- iterator function
		while line do			-- repeat while there are lines
		local s, e = string.find(line, "%w+", pos)
			if s then			-- found a word?
				pos = e + 1	-- next position is after this word
				return string.sub(line, s, e) -- return the word
			else
				line = io.read()  -- word not found; try next line
				pos = 1		-- restart from first position
			end
		end
	return nil		-- no more lines: end of traversal
	end
end

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

for word in allwords() do
	print(word)
end

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

2、范性for的语义

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

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

for <var-list> in <exp-list> do
	<body>
end

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

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

上面代码中,k, v为变量列表;pair(t)为表达式列表。
在很多情况下变量列表也只有一个变量,比如:

for line in io.lines() do
	io.write(line, '\n')
end

我们称变量列表中第一个变量为控制变量,其值为nil时循环结束。
下面我们看看范性for的执行过程:

首先,初始化,计算in后面表达式的值,表达式应该返回范性for需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil补足,多出部分会被忽略。
第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于for结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
第三,将迭代函数返回的值赋给变量列表。
第四,如果返回的第一个值为nil循环结束,否则执行循环体。
第五,回到第二步再次调用迭代函数。

更具体地说:

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,他遍历数组的每一个元素。

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

迭代的状态包括被遍历的表(循环过程中不会改变的状态常量)和当前的索引下标(控制变量),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](除非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语句,也许更好的叫法应该是生成器(generator);但是在其他语言比如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语句只是从匿名函数中返回而不是退出循环。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

1-7、Lua迭代器与泛型for 的相关文章

  • 如何更新lua中的时间以反映执行过程中系统时区的变化?

    Problem 我想修改awful widget textclockAwesome wm 中的小部件可以立即反映系统时区的更改 这个小部件和所有的 Awesome wm 配置都是用 lua 编写的 目前 如果系统时区发生更改 小部件将继续根
  • 如何访问废弃的函数参数?

    在 Lua 中 调用带有多余参数的函数将简单地丢弃这些参数 有没有可能与debug库来访问这些被丢弃的参数 我不是在寻找可变参数函数 function test local info debug getinfo 1 u print info
  • Lua 如何创建可用于变量的自定义函数?

    对于像 io close 这样的方法 你可以像这样使用它 file close 有没有办法创建一个像这样工作的自定义函数 您可以在变量上调用它 对我来说 我尝试使用它通过使用 string find 查找空格来将参数与文本文件分开 所以在文
  • 如何在多个Lua State(多线程)之间传递数据?

    我在中启动Redis连接池redis lua 通过从 C 调用 我得到了redis lua state 此 Lua 状态全局启动一次 仅在其他线程中启动get从中 当有一个 HTTP 请求 工作线程 时 我需要从redis lua stat
  • 什么更快?循环或多个 if 条件

    我想知道什么更快 是只用一条指令 即 1 1 执行 9 次 for 循环还是执行 9 个 if 条件时 我认为 if 更快 因为您不需要检查循环中的指令 它应该几乎相同 因为for循环本质上是检查if条件为真并运行一段代码 非常类似于if声
  • Lua 中的内联条件(a == b ? "yes" : "no")?

    无论如何 Lua 中可以使用内联条件吗 Such as print blah a true blah nahblah Sure print blah a and blah or nahblah
  • 在 Corona sdk 上保存高分?

    我想保存游戏中创建的高分 并且当玩家点击高分按钮时可以在主菜单中看到 有人可以帮助我吗 您可以使用SQLITE https docs coronalabs com api library sqlite3 index html将高分保存到数据
  • 用于嵌入式服务器的 Web 技术

    我最近开始了一个针对嵌入式设备的新 Web 开发项目 并希望征求一些有关使用技术的建议 该设备将提供 HTML 页面 其中包括用于从 JSON 服务器检索数据的 AJAX 代码 我们暂时使用 Cherokee 作为 Web 服务器 但我们并
  • corona sdk中拖动物理对象

    我尝试在场景中拖动重力 0 0 的动态主体 我有一个主体类型为动态的正方形 以及一个主体类型为静态的图像 但是当将方形拖动到图像上时 它会产生一点力 但是可以超出图像并传递到另一边 如图所示 这是我拖动正方形的代码 local functi
  • Lua-迭代嵌套表

    我已经学习 Lua 几个星期了 这一次又一次成为我的症结所在 我尝试阅读有关该主题的帖子和书籍 我使用 Lua 查询软件监控系统 Nimsoft 我的数据以表格形式返回给我 我不会发布整个输出 但这里有一个我认为可以描述结构的片段 表参考是
  • 确定已编译Lua的编译器版本

    我有一些已编译的 LuaQ 我需要确定用于编译它的确切版本 有什么可能的方法吗 编译的脚本在文件开头有一个标头 4 bytes signature x1bLua 1 byte version 0x51 1 byte format 1 byt
  • 使用 FastCGI 运行 Lua 脚本

    我目前正在尝试找出使用 FastCGI 与 lighttpd 或 Nginx 一起运行 Lua 脚本的方法 我唯一能挖到的是WSAPI http keplerproject github com wsapi 开普勒计划的一部分 但我想知道是
  • 在Lua中获取前一天的日期

    谁能告诉我如何使用 Lua 获取 YYYY MM DD 格式的前一天日期 即 一个片段 它将返回运行当天的前一天的日期 Try print os date Y m d os time 24 60 60 严格来说 这只能保证在 POSIX 系
  • Lua 访问表的键和值

    我想在关卡编辑器中读取 Lua 文件 这样我就可以以可视化格式显示其数据供用户编辑 如果我有一个像这样的 Lua 表 properties Speed 10 TurnSpeed 5 Speed显然是关键并且10价值 我知道如果我知道像这样的
  • 如何通过 C API 在自己的环境中执行不受信任的 Lua 文件

    我想通过调用在其自己的环境中执行不受信任的 lua 文件lua setfenv http pgl yoyo org luai i lua setfenv这样它就不会影响我的任何代码 该函数的文档仅解释了如何调用函数 而不解释如何执行文件 目
  • 在Luasocket中,在什么条件下,即使在select告诉它可以安全读取之后,accept调用也可以阻塞?

    卢阿索基特select http w3 impa br diego software luasocket socket html select函数应该告诉何时可以在不阻塞的情况下读取套接字 它显然也可以用来告诉服务器套接字何时准备好接受新连
  • 在Windows上使用gcc 5.3.0编译Lua 5.2.4模块

    我需要用 gcc 5 3 0 编译 Lua 5 2 4 模块 在 Windows 上 在此之前 我按照以下步骤构建 Lua 5 2 4 gcc c DLUA BUILD AS DLL c ren lua o lua obj ren luac
  • Lua 中的贪婪/非贪婪模式匹配和可选后缀

    在 Lua 中 我正在尝试模式匹配和捕获 384 Critical Strike Reforged from Parry Chance as 384 Critical Strike 后缀在哪里 Reforged from s 是可选的 长版
  • 如何从 Lua 字符串中删除所有特殊字符、标点符号和空格?

    在Lua中 我只能找到其他语言的示例 如何从字符串中删除所有标点符号 特殊字符和空格 所以 举例来说 s t r i p p e d会成为stripped In Lua 模式 https www lua org manual 5 3 man
  • Lua(命令行)执行后保持打开状态

    我已经广泛寻找这个但我似乎找不到它 有什么方法可以执行Lua通过双击脚本 在中执行它 Lua Command Line 并在执行后保持打开状态 例如 print Hello World 该代码可以编译并运行 但是如果我双击hello lua

随机推荐

  • 按位非‘~’符号的使用

    所谓的按位非就是在数字前加上 的符号 最简单的记忆方法是 a a 1 let a 1 let b a b 2 let a 2 let b a b 3 let a 1 let b a b 0 使用场景 在if判断的时候 if在0的情况下的转换
  • C3-Squid-access.log

    C3 Squid access log 拓扑 DNS 10 0 100 71 Haproxy 10 0 100 82 Squid 10 0 100 72 73 Nginx 10 0 100 75 76 NFS 10 0 100 70 DNS
  • ASP.NET 中得到网站绝对路径的几种方法

    在编写 ASP NET 应用程序的时候 有时为了更好地进行控制静态文件的路径 如在模板页或者用户控件中设置js或者css文件的路径等 采用绝对路径是难免的 下面就是几种获取绝对路径的几种方法 C 代码 VirtualPathUtility
  • SVN学习笔记 .

    转载自 http blog csdn net tengbaichuan article details 10632349 参考文档 官方文档 http www subversion org cn svnbook 包括可下载的PDF 和一页H
  • 浅谈npm、yarn、cnpm、pnpm(内附网址链接)

    1 npm 1 1 npm简介 npm由三个独立的部分组成 网站 网站是开发者查找包 package 设置参数以及管理npm使用体验的主要途径 注册表 registry 注册表是一个巨大的数据库 保存了每个包 package 的信息 命令行
  • Ubuntu 16.04 取消 conda 自动启动工作空间 base

    问题描述 Ubuntu 16 04 安装conda 之后它会自动启动工作空间base base的默认 Python 版本是 3 7 而Ubuntu 16 04 的默认 Python 版本是2 7 解决方案 如果不想默认启动 base工作空间
  • gcc g++ 学习

    一 编译的时候 此时main cpp头文件是 include Person h g main cpp Person Person cpp o main I Person 解析 Person Person cpp 链接main cpp的上一层
  • 工厂模式--Factory Method with Go

    Factory Method 工厂设计模式允许创建对象 而无需指定将要创建的对象的确切类型 Implementation 举个栗子 用个例子展示怎么把数据存储在不同的后端 比如 内存 磁盘 Types type 一个Store interf
  • PAT C入门题目-竖着输出字符串(Z:c语言求数组长度 sizeof()&strlen())

    7 2 I Love GPLT 5 分 这道超级简单的题目没有任何输入 你只需要把这句很重要的话 I Love GPLT 竖着输出就可以了 即每个字符占一行 包括空格 即每行只能有1个字符和回车 include
  • sketch基础教程大全,对象、图层、画板常见技巧

    sketch对象 图层 画板的使用技巧 1 通过快捷键调整图形的形状 选择图形 按住Command按键 然后通过上 下 左 右方向键按1像素调整图形形状 同时按住按钮 CommandShift方向键 可调整方向键 2 复制元素 选择一个元素
  • Python爬虫从入门到精通:(24)scrapy框架01_scrapy框架的认识、安装_Python涛哥

    scrapy框架的认识 安装 框架简介 什么是框架 所谓的框架其实就是一个被集成了很多功能且具有很强通用性的一个项目模板 怎么学习 学习的是框架中集成好的各种功能的特性是作用 进阶学习 逐步的探索框架的底层 安装scrapy 是一个专门用于
  • 数据结构和算法(二)

    ArrayList 和LinkedList原理 代码实现 性能区别 1 ArrayList 为什么查询快 数组和集合区别 动态大小 数组的长度是固定的 ArrayList 数组集合 内部使用数组实现的 自定义ArrayList 如下 pub
  • 如何理解受控和非受控件组件?

    受控组件 受控组件是React中的一种组件 其特点是输入框的值 value 由React状态 state 控制 也就是说 React中的状态变化会直接影响输入框的值 受控组件通过form的输入元素 input select等 的value属
  • 孕期做什么副业好?在家兼职挣钱的孕妇不仅能很好地抚养孩子,还能继续她们的职业生涯

    孕期做什么副业好 在家兼职挣钱的孕妇不仅能很好地抚养孩子 还能继续她们的职业生涯 许多孕妇在得知怀孕后会在家休息分娩 一旦你在家休假分娩 你会感到特别无聊 无聊 甚至沮丧 这对胎儿的发育会非常不利 所以在这个时候 那些希望在家分娩的孕妇可以
  • composer改回原来的源

    以前为了图安装快 把composer的源改成了国内的 composer config g repo packagist composer https packagist phpcomposer com 今天使用时发现还要用户名和密码登录 怕
  • CentOS7.3编译安装go1.10.1

    直接使用编译好的包 go1 10 1 linux amd64 tar gz 下载go wget https studygolang com dl golang go1 10 1 linux amd64 tar gz tar xvf go1
  • vscode中如何快速生成vue3模板-非常实用的小技巧

    vscode中如何快速生成vue3模板 在vue项目开发过程中 我们会发现我们每次新建一个vue组件文件的时候 都需要写一些重复的代码 比如下面代码
  • 运算放大器---虚短和虚断

    运算放大器 虚短和虚断 前言 运算放大器两板斧 虚短 虚断 虚短 在分析运算放大器处于线性状态时 可把两输入端视为等电位 这一特性称为虚假短路 简称虚短 当某一端接地的时候 V V 0 虚断 在分析运放处于线性状态时 可以把两输入端视为等效
  • Conda——问题解决:CondaValueError: Malformed version string ‘~’ : invalid character(s)

    报错截图 解决方法 更新conda即可 conda upgrade n base c defaults override channels conda 成功截图
  • 1-7、Lua迭代器与泛型for

    1 7 Lua迭代器与泛型for 理解为主 必选先理解闭包的概念和用法 文章目录 1 7 Lua迭代器与泛型for 理解为主 必选先理解闭包的概念和用法 1 迭代器与闭包 2 范性for的语义 3 无状态的迭代器 4 多状态的迭代器 5 真