【读书笔记】语言特性-迭代器和泛型for(一)

目录

一. 迭代器的介绍

        1. 创建一个返回表中每个元素的迭代器 (相当于 for key, value, in ipairs(t) 中的 value值)

        2. 遍历来自标准输出的所有单词

二. 泛型for的语法

        2.1 泛型for的语法:

                2.1.1 泛型for的执行流程

三. 无状态迭代器

        3.1 Lua语言编写无状态迭代器 ipairs

        3.2 无状态迭代器的构成原理

        3.3 Lua语言编写无状态迭代器 pairs

                3.3.1 next函数:

                3.3.2 另一种方式调用 pairs

        3.4 创建一个访问链表的迭代器 (链表详情见:编程实操-数据结构)

四. 按顺序遍历表

        4.1 需求1:构造一个表保存每个函数的名称及其声明所在行数,然后按照字母的顺序输出这些函数名

        4.2 实现一个按照键的顺序遍历表的迭代器

五. 迭代器的真实含义


一. 迭代器的介绍

        迭代器 是一种可以让我们遍历一个集合中所有元素的代码结构。

        下面通过之前学的闭包的概念来创建几个迭代器: 闭包详情编程实操-闭包

        1. 创建一个返回表中每个元素的迭代器 (相当于 for key, value, in ipairs(t) 中的 value值)

function values(t)
    local i = 0
    return function() i = i + 1; return t[i] end
end

-- 创建了value的闭包工厂,每次调用value时都会创建一个新的闭包
t = {10,20,30}
iter = values(t)	-- 创建迭代器
while true do
    local element = iter()	-- 调用迭代器
    if element == nil then break end
    print(element)
end
-- 上面的 values迭代器,可以用泛型for来实现
t = {10, 20, 30}
for element in values(t) do
    print(element)
end

        2. 遍历来自标准输出的所有单词

function allwords()
    local line = io.read()	--当前行
    local pos = 1		-- 当前行所在位置
    return function()
    	while line do
    	  local w,e = string.match(line, "(%w+)()", pos)
             if w then -- 发现一个单词
             	pos = e
              	return w
             else
             	line = io.read() -- 没找到读取下一行
              	pos = 1		-- 将pos重置为1
             end
      	end
       return nil	-- 没有了,迭代结束
    end
end

-- 调用迭代器
for word in allwords() do
    print(word)
end

二. 泛型for的语法

        泛型for在循环过程中在其内部保存了迭代函数

        实际上,泛型for保存了三个值:一个迭代函数一个不可变状态一个控制变量

        2.1 泛型for的语法:

        

for var-list in exp-list do
    body
end

        其中,var-list是由一个或多个变量名组成的列表,用逗号分隔;exp-list是一个或多个表达式组成的列表,用逗号分隔。通常,表达式列表只有一个元素,即一句对迭代工厂的调用。

        例如,在如下代码中,变量列表是k,v,表达式列表只有一个元素pairs(t)

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

        我们把变量列表中的第一个(或唯一的)变量称为控制变量,也就是上面的 K,这个值在循环过程中永远不为nil, 因为当控制变量为nil 时,循环就结束了

                2.1.1 泛型for的执行流程

                第一步:表达式返回三个值给for保存

                for 执行的第一件事是对in 后面的表达式求值。这些表达式会返回三个值供for保存迭代函数、不可变状态、控制变量的初始值

for k, v in pairs(t) do print(k,v) end
-- pairs(t)表达式 会 返回 t(迭代函数)、不可变状态、控制变量的初始值
-- local f, s, k = pairs(t) 这三值给for保存

                 注意:类似多重赋值,只有最后一个表达式能返回超过一个值,所以 exp-list 若有多个,那只有其中最后一个表达式会返回多值

                表达式最多返回三个结果(迭代函数、不可变状态、控制变量的初始值),多余的值会被丢弃,不足三个用nil补齐

                第二步:用 表达式返回的不可变状态,和控制变量作为参数来调用迭代函数,若 控制变量不为nil,for继续执行循环体,直到 控制变量为nil,则终止循环

                其实,对于for来说,不可变状态没有意义,for循环只是把初始化步骤得到状态值传递给所有迭代函数,然后,for将迭代函数返回值赋给变量列表中声明的变量(例 Key, Value),如果 第一个返回值为nil,那么循环终止,否则,则继续

                简单来讲,泛型for就等价于

for var_1,...,var_n in explist do block end
-- 等价于
do
    local _f, _s, _var =exlist -- 表达式返回 迭代器,不可变状态,控制变量初始值
    while true do
      local var_1,..,var_n = _f(_s,_var) -- 把不可变状态、控制变量初始值作为迭代器的参数,迭代器返回n个值
      _var = var_1
      if _var == nil then break end      -- 迭代器返回的第一个值为nil时,结束循环
    end
end

                因此,假设迭代函数为 f,不可变状态为 s,控制变量的初始值为a0,那么在循环中控制变量的值依次为

                a1 = f(s, a0), a2 = f(s, a1 ),依此类推 ,直至ai为 nil, 如果 for 还有其他变量,那么这些变量 只是简单地在每次调用 f 后得到额外的返回值

三. 无状态迭代器

        顾名思义,无状态迭代器就是一种自身不保存任何状态的迭代器,可以在多个循环中使用同一个无状态迭代器,避免创建新闭包的开销(简单说,它不同于 闭包创建迭代器的方式,不需要每次循环创建新闭包

        这类迭代器的典型例子是 ipairs, 它可以迭代一个序列的所有元素:

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

        3.1 Lua语言编写无状态迭代器 ipairs

-- 创建迭代函数
local function iter(t,i)
    i = i + 1
    local v = t[i]
    if v then
      return i, v
    end
end

-- ipairs 迭代器
function ipairs(t)
    return iter, t, 0
end

-- for 循环
for k, v in ipairs({1,2,3}) do
    print(k,v) -- 1,1  2,2 3,3
end

上面的示例中,定义的ipairs 函数中会 返回 三个值,其中 分别是 迭代函数 iter不可变状态表t控制变量的初始值0,在调用for 循环 ipairs(t) 返回的值符合泛型for执行需要的值,成功完成迭代器的循环。详情见:2.1.1

        3.2 无状态迭代器的构成原理

                无状态迭代器,通过返回三个值(迭代函数、不可变状态、控制变量),再通过for循环 迭代器,从而完成循环。

        与 非无状态迭代器相比, 无状态迭代器(例 ipairs)每次for循环时,都是调用同一迭代函数 iter(),因为没有创建闭包,所以开销小

        3.3 Lua语言编写无状态迭代器 pairs

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

                和 ipairs类似,只不过 迭代函数 是Lua语言的基本函数之一:next

                3.3.1 next函数:

                        调用next(t, k)时,该函数会以 随机次序返回表中的下一个键及k对应的值(value)。调用next(t, nil)时,返回表中的第一个键值对。当所有元素被遍历完,函数next返回nil

                3.3.2 另一种方式调用 pairs

for k, v in next, t do
    -- loop body
end

                注意:for循环会把表达式列表结果调整为三个值(因为 for 循环中的 exp-list 最多存在三个,不足会用nil补齐),和上例中 pairs(t) 返回的值是一样的

        3.4 创建一个访问链表的迭代器 (链表详情见:编程实操-数据结构)

local function getnext(node)
    return node.next	
end
function traverse(list)
    return getnext, nil, list
end

                但这样会跳过第一个节点,调整下

local function getnext(list, node)
    if not node then
    	return list
    else
    	return node.next
    end
end

function traverse(list)
    return getnetx, list, nil
end

这里的技巧是,除了将当前节点作为控制变量,还要将头节点作为不可变状态( traverse回的第二个值) 第一次调用迭代函数 getnext 时, node 且,因 函数返回 list 作为第一个节点 在后续 调用中, node 不再是 nil ,所以迭代函数会像我们所期望的那样返回 node.next

四. 按顺序遍历表

        有一个很常见很常见的问题,就是 如何 对表中的元素排序?,要知道表中的元素是没有顺序的。解决方法是:先把键值对拷贝到一个数组中,然后再对数组进行排序

        4.1 需求1:构造一个表保存每个函数的名称及其声明所在行数,然后按照字母的顺序输出这些函数名

lines = {
    ["luaH_set"] = 10,
    ["luaH_get"] = 24,
    ["luaH_present"] = 48,
}

a = {} -- 构建一个空数组
for n in pairs(lines) do 
    a[#a + 1] = n -- n 为 Key值
end
-- {"luaH_set","luaH_get","luaH_present"}
table.sort(a) -- 排序
for _, n in ipairs(a) do
    print(n)
end

        上面的例子中,用数组保存函数名,实现了有序,再通过 table.sort()排序,再通过 ipairs 有序输出

        4.2 实现一个按照键的顺序遍历表的迭代器

function pairsByKeys(t, f)
    local a = {}
    for n in pairs(t) do
    	a[#a + 1] = n
    end
    table.sort(a, f)
    local i = 0
    return function() -- 迭代函数
    	i = i + 1
     	return a[i], t[a[i]] -- 返回键和值
    end
end
for name, line in pairsByKeys(lines) do
    print(name, line)
end

五. 迭代器的真实含义

        "迭代器"这个名称多少有点误导性,因为迭代器并没有真正的迭代:真正的迭代是for循环完成的,迭代器只不过为每次迭代提供连续的值,不过,这个名字已经在Java等语言广泛使用了,那还是叫“迭代器”吧

        其实,我们可以编写 "真正的迭代器": 迭代器接收一个函数作为参数,这个函数在循环的内部被调用

function allwords(f)
    for line in io.lines() do
      for word in string.gmatch(line, "%w+") do
          f(word)
      end    	
    end
end
allwords(print)

        通常,我们可以使用一个匿名函数作为循环体。(用函数做参,可以匿名函数的形式传入)

例如:需要计算单词“hello” 在输入文件中出现的次数(计算某个单词出现的次数的通用函数)

local count = 0
allwords(function (w)
    if w == "hello" then count = count + 1 end
end)
print(count)

        也可以用之前的迭代器风格来实现

local count = 0
for w in allwords() do
    if w == "hello" then count = count + 1 end
end
print(count)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值