Lua中遍历及删除table.docx
- 文档编号:28217043
- 上传时间:2023-07-09
- 格式:DOCX
- 页数:17
- 大小:21.90KB
Lua中遍历及删除table.docx
《Lua中遍历及删除table.docx》由会员分享,可在线阅读,更多相关《Lua中遍历及删除table.docx(17页珍藏版)》请在冰豆网上搜索。
Lua中遍历及删除table
当我在工作中利用lua进行开发时,发此刻lua中有4种方式遍历一个table,固然,从本质上来讲其实都一样,只是形式不同,这四种方式别离是:
1forkey,valueinpairs(tbtest)do
2XXX
3end
4
5forkey,valueinipairs(tbtest)do
6XXX
7end
8
9fori=1,#(tbtest)do
10XXX
11end
12
13fori=1,table.maxn(tbtest)do
14XXX
15end
前两种是泛型遍历,后两种是数值型遍历。
固然你还会说lua的table遍历还有很多种方式啊,没错,只是最多见的这些遍历确实有必要弄清楚。
这四种方式各有特点,由于在工作中我几乎天天都会利用遍历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的顺序来的。
下面依次来讲讲四种遍历方式,第一来看fork,vinpairs(tbtest)do这种方式:
先看成效:
16tbtest={
17[1]=1,
18[2]=2,
19[3]=3,
20[4]=4,
21}
22
23forkey,valueinpairs(tbtest)do
24print(value)
25end
我以为输出应该是1,2,3,4,事实上的输出是1,2,4,3。
我因为那个造成了一个bug,这是后话。
也确实是说fork,vinpairs(tbtest)do如此的遍历顺序并非是tbtest中table的排列顺序,而是依照tbtest中key的hash值排列的顺序来遍历的。
固然,同时lua也提供了依照key的大小顺序来遍历的,注意,是大小顺序,仍然不是key概念的顺序,这种遍历方式确实是fork,vinipairs(tbtest)do。
fork,vinipairs(tbtest)do如此的循环必需要求tbtest中的key为顺序的,而且必需是从1开始,ipairs只会从1开始按持续的key顺序遍历到key不持续为止。
26tbtest={
27[1]=1,
28[2]=2,
29[3]=3,
30[5]=5,
31}
32
33fork,vinipairs(tbtest)do
34print(v)
35end
只会打印1,2,3。
而5那么可不能显示。
36localtbtest={
37[2]=2,
38[3]=3,
39[5]=5,
40}
41
42fork,vinipairs(tbtest)do
43print(v)
44end
如此就一个都可不能打印。
第三种遍历方式有一种神奇的符号'#',那个符号的作用是是获取table的长度,比如:
45tbtest={
46[1]=1,
47[2]=2,
48[3]=3,
49}
50print(#(tbtest))
打印的确实是3
51tbtest={
52[1]=1,
53[2]=2,
54[6]=6,
55}
56print(#(tbtest))
如此打印的确实是2,而且和table内的概念顺序没有关系,不管你是不是先概念的key为6的值,‘#’都会查找key为1的值开始。
若是table的概念是如此的:
57tbtest={
58["a"]=1,
59[2]=2,
60[3]=3,
61}
62
63print(#(tbtest))
那么打印的确实是0了。
因为‘#’没有找到key为1的值。
一样:
64tbtest={
65[“a”]=1,
66[“b”]=2,
67[“c”]=3,
68}
69print(#(tbtest))
打印的也是0
因此,fori=1,#(tbtest)do这种遍历,只能遍历当tbtest中存在key为1的value时才会显现结果,而且是依照key从1开始依次递增1的顺序来遍历,找到一个递增不是1的时候就终止再也不遍历,不管后面是不是仍然是顺序的key,比如:
table.maxn获取的只针对整数的key,字符串的key是没方法获取到的,比如:
70tbtest={
71[1]=1,
72[2]=2,
73[3]=3,
74}
75print(table.maxn(tbtest))
76
77
78tbtest={
79[6]=6,
80[1]=1,
81[2]=2,
82}
83print(table.maxn(tbtest))
如此打印的确实是3和6,而且和table内的概念顺序没有关系,不管你是不是先概念的key为6的值,table.maxn都会获取整数型key中的最大值。
若是table的概念是如此的:
84tbtest={
85["a"]=1,
86[2]=2,
87[3]=3,
88}
89print(table.maxn(tbtest))
那么打印的确实是3了。
若是table是:
90tbtest={
91[“a”]=1,
92[“b”]=2,
93[“c”]=3,
94}
95print(table.maxn(tbtest))
96print(#(tbtest))
那么打印的就全数是0了。
换句话说,事实上因为lua中table的构造表达式超级灵活,在同一个table中,你能够随意概念各类你想要的内容,比如:
97tbtest={
98[1]=1,
99[2]=2,
100[3]=3,
101["a"]=4,
102["b"]=5,
103}
同时由于那个灵活性,你也没有方法获取整个table的长度,其实在coding的进程中,你会发觉,你真正想要获取整个table长度的地址几乎没有,你总能采取一种超级巧妙的概念方式,把这种需要获取整个table长度的操作幸免掉,比如:
104tbtest={
105tbaaa={
106[1]=1,
107[2]=2,
108[3]=3,
109},
110["a"]=4,
111["b"]=5,
112}
你可能会惊讶,上面这种table该如何遍历呢?
113fork,vinpairs(tbtest)do
114print(k,v)
115end
输出是:
a4b5tbaaatable:
XXXXX。
由此你能够看到,其实在table中概念一个table,那个table的名字确实是key,对应的内容实际上是table的地址。
固然,若是你用
116fork,vinipairs(tbtest)do
117print(k,v)
118end
来遍历的话,就什么都可不能打印,因为没有key为1的值。
但当你增加一个key为1的值时,ipairs只会打印那一个值,此刻你明白ipairs是如何工作的吧。
既然那个地址谈到了遍历,就说一下目前看到的几种针对table的遍历方式:
fori=1,#tbtestdo--这种方式无法遍历所有的元素,因为'#'只会获取tbtest中从key为1开始的key持续的那几个元素,若是没有key为1,那么那个循环将无法进入
fori=1,table.maxn(tbtest)do--这种方式一样无法遍历所有的元素,因为table.maxn只会获取key为整数中最大的那个数,遍历的元素实际上是查找tbtest[1]~tbtest[整数key中最大值],因此,关于string做key的元素可不能去查找,而且这么查找的效率低下,因为若是你整数key中概念的最大的key是10000,但是10000以下的key没有几个,那么这么遍历会浪费很多时刻,因为会从1开始直到10000每一个元素都会查找一遍,事实上大多数元素都是不存在的,比如:
119tbtest={
120[1]=1,
121[10000]=2,
122}
123localcount=0
124fori=1,table.maxn(tbtest)do
125count=count+1
126print(tbtest[i])
127end
128print(count)
你会看到打印结果是何等的坑爹,只有1和10000是成心义的,其他的满是nil,而且count是10000。
耗时超级久。
一样我不这么遍历。
可是有一种情形下又必需这么遍历,那个在我的工作中还真的碰到了,这是后话,等讲完了再谈。
129fork,vinpairs(tbtest)do
那个是唯一一种能够保证遍历tbtest中每一个元素的方式,别快乐的太早,这种遍历也有它自身的缺点,确实是遍历的顺序不是依照tbtest概念的顺序来遍历的,那个前面讲到过,固然,关于不需要顺序遍历的用法,那个是唯一靠得住的遍历方式。
130fork,vinipairs(tbtest)do
那个只会遍历tbtest中key为整数,而且必需从1开始的那些持续元素,若是没有1开始的key,那么那个遍历是无效的,我个人以为这种遍历方式完全能够被改造table和fori=1,#(tbtest)do的方式来代替,因为ipairs的成效和'#'的成效,在遍历的时候是类似的,都是依照key的递增1顺序来遍历。
好,再来谈谈什么缘故我需要利用table.maxn这种超级浪费的方式来遍历,在工作中,我碰到一个问题,确实是需要把当前的周序,转换成对应的奖励,简单来讲,确实是从一个活动开始算起,每周的奖励都不是固定的,比如1~4周给一种奖励,5~8周给另一种奖励,或是一种排名奖励,1~8名给一种奖励,9~16名给另一种奖励,这种情形下,我依照长久的C语言的适应,会把table概念成那个样子:
131tbtestAward={
132[8]=1,
133[16]=3,
134}
那个代表,1~8给奖励1,9~16给奖励3。
如此概念的益处是奖励我只需要写一次(那个地址的奖励用数字做了简化,事实上奖励也是一个大的table,里面还有超级复杂的结构)。
然后我就碰到一个问题,即我需要依照周序数,或是排名序数来确信给哪一种奖励,比如当前周序数是5,那么我应该给我概念好的key为8的那一档奖励,或当前周序数是15,那么我应该给奖励3。
由此读者看出,其实我概念的key是一个分界,小于那个key而大于上一个key,那么就给那个key的奖励,这确实是我判定的条件。
逻辑上没有问题,可是lua的遍历方式却把我狠狠地坑了一把。
读者能够自己想一想我上面介绍的4种遍历方式,该用哪一种来实现我的这种需求呢?
那个函数的大致框架如下:
135functionGetAward(nSeq)
136for遍历整个奖励表do
137if知足key的条件then
138return返回对应奖励的key
139end
140end
141returnnil
142end
我也不卖关子了,别离来讲一说吧,第一因为我的key不是持续的,而且没有key为1的值,因此ipairs和'#'遍历是没用的。
这种情形下理想的遍历貌似是pairs,因为它会遍历我的每一个元素,可是读者不要忘记了,pairs遍历并非是依照我概念的顺序来遍历,若是我真的利用的条件是:
序数nSeq小于那个key而大于上一个key,那么就返回那个key。
那么我无法保证程序执行的正确性,因为key的顺序有可能是乱的,也确实是有可能先遍历到的是key为16的值,然后才是key为8的值。
这么看来我只剩下table.maxn这么一种方式了,于是我写下了这种代码:
143fori=1,table.maxn(tbtestAward)do
144iftbtestAward[i]~=nilthen
145ifnSeq<=ithen
146returni
147end
148end
149end
这么写效率确实低下,因为事实上仍是遍历了从key为1开始直到key为table.maxn中间的每一个值,只是能够知足我上面的要求。
那时我是这么实现的,因为那个奖励表会不断的发生转变,如此我每次修改只需要修改那个奖励表就能够够知足要求了,后来我想了想,感觉其实我若是自己再概念一个序数转换成对应的奖励数种类的表就能够够幸免这种坑爹的操作了,只是若是奖励发生修改,我需要统一排查的地址就不止那个奖励表了,衡量再三,我仍是没有改,就这么写了。
没方法,不断转变的需求已经把我考验的忘记了程序的最高理想。
我乃至情愿捐躯算法的效率而去追求改动的稳固性。
在此悼念程序员的无奈。
我这种时刻换空间的做法确实不明白好不行。
后来我在《ProgrammingInLua》中看到了一个神奇的迭代器,利用它就能够够达到我想要的这种遍历方式,而且不需要去遍历那些不存在的key。
它的方式是把你所需要遍历的table里的key依照遍历顺序放到另一个临时的table中去,如此只需要遍历那个临时的table按顺序掏出原table中的key就能够够了。
如下:
第一概念一个迭代器:
150functionpairsByKeys(t)
151locala={}
152forninpairs(t)do
153a[#a+1]=n
154end
155table.sort(a)
156locali=0
157returnfunction()
158i=i+1
159returna[i],t[a[i]]
160end
161end
然后在遍历的时候利用那个迭代器就能够够了,table同上,遍历如下:
162forkey,valueinpairsByKeys(tbtestAward)do
163ifnSeq<=keythen
164returnkey
165end
166end
而且后来我发觉有了那个迭代器,我全然不需要先做一步获取是哪一档次的奖励的操作,直接利用那个迭代器进行发奖就能够够了。
大师确实是大师,我怎么就没想到呢!
还有些话我尚未说,比如上面数值型遍历也并非是像看起来那样进行遍历的,比如下面的遍历:
167tbtest={
168[1]=1,
169[2]=2,
170[3]=3,
171[5]=5,
172}
173
174fori=1,#(tbtest)do
175print(tbtest[i])
176end
打印的顺序是:
1,2,3。
可不能打印5,因为5已经不在table的数组数据块中了,我估量是被放到了hash数据块中,可是当我修改其中的一些key时,比如:
177tbtest={
178[1]=1,
179[2]=2,
180[4]=4,
181[5]=5,
182}
183
184fori=1,#(tbtest)do
185print(tbtest[i])
186end
打印的内容却是:
1,2,nil,4,5。
那个地址又遍历到了中间没有的key值,而且还能继续遍历下去。
我最近正在看lua源码中table的实现部份,已经明白了是怎么回事,只是我想等我能够加倍清楚的论述lua中table的实现进程了再向大伙儿介绍。
用我师傅的话说确实是不要利用一些未概念的行为方式,幸免在工作中犯错,只是工作外,我仍是希望能明白未概念的行为中那些必然性,o(︶︿︶)o唉!
因果论的小孩伤不起。
等我下一篇博文分析lua源码中table的实现就能够够加倍清楚的说明这些了。
---------------------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------------------------------
原文
在Lua中,table如何平安的移除元素这点挺重要,因为若是不警惕,会没有正确的移除,造成内存泄漏。
引子
比如有些朋友常常这么做,大伙儿看有啥问题
将test表中的偶数移除掉
localtest={2,3,4,8,9,100,20,13,15,7,11}
fori,vinipairs(test)do
ifv%2==0then
table.remove(test,i)
end
end
fori,vinipairs(test)do
print(i.."===="..v)
end
打印结果:
1====3
2====8
3====9
4====20
5====13
6====15
7====7
8====11
[Finishedin0.0s]
有问题吧,20怎么还在?
这确实是在遍历中删除致使的。
如何做呢?
Let'sgetstarted!
localtest={'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p'}
localremove={a=true,b=true,c=true,e=true,f=true,p=true}
localfunctiondump(table)
fork,vinpairs(table)do
print(k)
print(v)
print("*********")
end
end
说明:
一样咱们不在循环中删除,在循环中删除会造成一些错误。
这是能够成立一个remove表用来标记将要删除的,如上面例子,把将要删除的标记为true
方式1从后往前删除
fori=#test,1,-1do
ifremove[test[i]]then
table.remove(test,i)
end
end
dump(test)
什么缘故不之前去后,朋友们能够测试,table.remove操作后,后面的元素会往前移位,这时后续的删除索引对应的元素已经不是之前的索引对应的元素了。
方式2while删除
locali=1
whilei<=#testdo
ifremove[test[i]]then
table.remove(test,i)
else
i=i+1
end
end
dump(test)
方式3quick中提供的removeItem
functiontable.removeItem(list,item,removeAll)
localrmCount=0
fori=1,#listdo
iflist[i-rmCount]==itemthen
table.remove(list,i-rmCount)
ifremoveAllthen
rmCount=rmCount+1
else
break
end
end
end
end
fork,vinpairs(remove)do
table.removeItem(test,k)
end
下面附带一个自己些的遍历删除的例子
localbulletDel={}--寄存删除子弹元素的table
localenemyDel={}--寄存删除敌机的table
fork_bullet,v_bulletinipairs(bullet)do
localbulletPoint=cc.p(v_bullet:
getPositionX(),v_bullet:
getPositionY())
fork_enemy,v_enemyinipairs(enemy)do
localenemyRect=v_enemy:
getBoundingBox()
ifcc.rectContainsPoint(enemyRect,bulletPoint)then
table.insert(bulletDel,k_bullet)--那个地址有个小技术,把要删除的数据的表的索引放到删除表缓存中,如此在删除就很方便了
table.insert(enemyDel,k_enemy)
end
end
end
forkey,valueinipairs(bulletDel)
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Lua 遍历 删除 table