北极洲 2019-06-13
当我在工作中使用lua进行开发时,发现在lua中有4种方式遍历一个table,当然,从本质上来说其实都一样,只是形式不同,这四种方式分别是:
代码如下:
for key, value in pairs(tbtest) do XXX end for key, value in ipairs(tbtest) do XXX end for i=1, #(tbtest) do XXX end for i=1, table.maxn(tbtest) do XXX end
这四种方式各有特点,由于在工作中我几乎每天都会使用遍历table的方法,一开始也非常困惑这些方式的不同,一段时间后才渐渐明白,这里我也是把自己的一点经验告诉大家,对跟我一样的lua初学者也许有些帮助(至少当初我在写的时候在网上就找了很久,不知道是因为大牛们都认为这些很简单,不需要说,还是因为我笨,连这都要问)。
首先要明确一点,就是lua中table并非像是C/C++中的数组一样是顺序存储的,准确来说lua中的table更加像是C++中的map,通过Key对应存储Value,但是并非顺序来保存key-value对,而是使用了hash的方式,这样能够更加快速的访问key对应的value,我们也知道hash表的遍历需要使用所谓的迭代器来进行,同样,lua也有自己的迭代器,就是上面4种遍历方式中的pairs和ipairs遍历。但是lua同时提供了按照key来遍历的方式(另外两种,实质上是一种),正式因为它提供了这种按key的遍历,才造成了我一开始的困惑,我一度认为lua中关于table的遍历是按照我table定义key的顺序来的。
下面依次来讲讲四种遍历方式,首先来看for k,v in pairs(tbtest) do这种方式:
先看效果:
代码如下:
tbtest = { [1] = 1, [2] = 2, [3] = 3, [4] = 4, } for key, value in pairs(tbtest) do print(value) end
也就是说for k,v in pairs(tbtest) do 这样的遍历顺序并非是tbtest中table的排列顺序,而是根据tbtest中key的hash值排列的顺序来遍历的。
当然,同时lua也提供了按照key的大小顺序来遍历的,注意,是大小顺序,仍然不是key定义的顺序,这种遍历方式就是for k,v in ipairs(tbtest) do。
for k,v in ipairs(tbtest) do 这样的循环必须要求tbtest中的key为顺序的,而且必须是从1开始,ipairs只会从1开始按连续的key顺序遍历到key不连续为止。
代码如下:
tbtest = { [1] = 1, [2] = 2, [3] = 3, [5] = 5, } for k,v in ipairs(tbtest) do print(v) end
代码如下:
local tbtest = { [2] = 2, [3] = 3, [5] = 5, } for k,v in ipairs(tbtest) do print(v) end
代码如下:
tbtest = { [1] = 1, [2] = 2, [3] = 3, } print(#(tbtest))
代码如下:
tbtest = { [1] = 1, [2] = 2, [6] = 6, } print(#(tbtest))
代码如下:
tbtest = { ["a"] = 1, [2] = 2, [3] = 3, } print(#(tbtest))
代码如下:
tbtest = { [“a”] = 1, [“b”] = 2, [“c”] = 3, } print(#(tbtest))
table.maxn获取的只针对整数的key,字符串的key是没办法获取到的,比如:
代码如下:
tbtest = { [1] = 1, [2] = 2, [3] = 3, } print(table.maxn(tbtest)) tbtest = { [6] = 6, [1] = 1, [2] = 2, } print(table.maxn(tbtest))
代码如下:
tbtest = { ["a"] = 1, [2] = 2, [3] = 3, } print(table.maxn(tbtest))
代码如下:
tbtest = { [“a”] = 1, [“b”] = 2, [“c”] = 3, } print(table.maxn(tbtest)) print(#(tbtest))
换句话说,事实上因为lua中table的构造表达式非常灵活,在同一个table中,你可以随意定义各种你想要的内容,比如:
代码如下:
tbtest = { [1] = 1, [2] = 2, [3] = 3, ["a"] = 4, ["b"] = 5, }
代码如下:
tbtest = { tbaaa = { [1] = 1, [2] = 2, [3] = 3, }, ["a"] = 4, ["b"] = 5, }
代码如下:
for k, v in pairs(tbtest) do print(k, v) end
代码如下:
for k, v in ipairs(tbtest) do print(k,v) end
既然这里谈到了遍历,就说一下目前看到的几种针对table的遍历方式:
for i=1, #tbtest do --这种方式无法遍历所有的元素,因为'#'只会获取tbtest中从key为1开始的key连续的那几个元素,如果没有key为1,那么这个循环将无法进入
for i=1, table.maxn(tbtest) do --这种方式同样无法遍历所有的元素,因为table.maxn只会获取key为整数中最大的那个数,遍历的元素其实是查找tbtest[1]~tbtest[整数key中最大值],所以,对于string做key的元素不会去查找,而且这么查找的效率低下,因为如果你整数key中定义的最大的key是10000,然而10000以下的key没有几个,那么这么遍历会浪费很多时间,因为会从1开始直到10000每一个元素都会查找一遍,实际上大多数元素都是不存在的,比如:
代码如下:
tbtest = { [1] = 1, [10000] = 2, } local count = 0 for i=1, table.maxn(tbtest) do count = count + 1 print(tbtest[i]) end print(count)
你会看到打印结果是多么的坑爹,只有1和10000是有意义的,其他的全是nil,而且count是10000。耗时非常久。一般我不这么遍历。但是有一种情况下又必须这么遍历,这个在我的工作中还真的遇到了,这是后话,等讲完了再谈。
代码如下:
for k, v in pairs(tbtest) do
代码如下:
for k, v in ipairs(tbtest) do
好,再来谈谈为什么我需要使用table.maxn这种非常浪费的方式来遍历,在工作中, 我遇到一个问题,就是需要把当前的周序,转换成对应的奖励,简单来说,就是从一个活动开始算起,每周的奖励都不是固定的,比如1~4周给一种奖励,5~8周给另一种奖励,或者是一种排名奖励,1~8名给一种奖励,9~16名给另一种奖励,这种情况下,我根据长久的C语言的习惯,会把table定义成这个样子:
代码如下:
tbtestAward = { [8] = 1, [16] = 3, }
代码如下:
function GetAward(nSeq) for 遍历整个奖励表 do if 满足key的条件 then return 返回对应奖励的key end end return nil end
代码如下:
for i=1, table.maxn(tbtestAward) do if tbtestAward[i] ~= nil then if nSeq <= i then return i end end end
这么写效率确实低下,因为实际上还是遍历了从key为1开始直到key为table.maxn中间的每一个值,不过能够满足我上面的要求。当时我是这么实现的,因为这个奖励表会不断的发生变化,这样我每次修改只需要修改这个奖励表就能够满足要求了,后来我想了想,觉得其实我如果自己再定义一个序数转换成对应的奖励数种类的表就可以避免这种坑爹的操作了,不过如果奖励发生修改,我需要统一排查的地方就不止这个奖励表了,权衡再三,我还是没有改,就这么写了。没办法,不断变化的需求已经把我磨练的忘记了程序的最高理想。我甚至愿意牺牲算法的效率而去追求改动的稳定性。在此哀悼程序员的无奈。我这种时间换空间的做法确实不知道好不好。
后来我在《Programming In Lua》中看到了一个神奇的迭代器,使用它就可以达到我想要的这种遍历方式,而且不需要去遍历那些不存在的key。它的方法是把你所需要遍历的table里的key按照遍历顺序放到另一个临时的table中去,这样只需要遍历这个临时的table按顺序取出原table中的key就可以了。如下:
首先定义一个迭代器:
代码如下:
function pairsByKeys(t) local a = {} for n in pairs(t) do a[#a+1] = n end table.sort(a) local i = 0 return function() i = i + 1 return a[i], t[a[i]] end end
代码如下:
for key, value in pairsByKeys(tbtestAward) do if nSeq <= key then return key end end
代码如下:
tbtest = { [1] = 1, [2] = 2, [3] = 3, [5] = 5, } for i=1, #(tbtest) do print(tbtest[i]) end
代码如下:
tbtest = { [1] = 1, [2] = 2, [4] = 4, [5] = 5, } for i=1, #(tbtest) do print(tbtest[i]) end
表格的现在还是较为常用的一种标签,但不是用来布局,常见处理、显示表格式数据。在HTML网页中,要想创建表格,就需要使用表格相关的标签。<table> <tr> <td>单元格内的文字</td> ...