Lua 避免在用作数组的表中出现间隙
示例
定义我们的条款
通过阵列这里我们指的是作为一个序列的Lua表。例如:
-- Create a table to store the types of pets we like. local pets = {"dogs", "cats", "birds"}
我们将此表用作序列:一组由整数键组成的项目。许多语言将其称为数组,我们也将如此。但是严格来说,Lua中没有数组这样的东西。只有表格,其中一些像数组,有些像哈希(或字典,如果您愿意),有些是混合的。
关于pets数组的重要一点是没有差距。第一项pets[1]是字符串“dogs”,第二项pets[2]是字符串“cats”,最后一项pets[3]是“birds”。Lua的标准库和为Lua编写的大多数模块都将1作为序列的第一个索引。因此,无间隙数组具有项,1..n而不会丢失序列中的任何数字。(在极限情况下,,n=1并且数组中只有一项。)
Lua提供了内置功能ipairs来迭代此类表。
-- Iterate over our pet types. for idx, pet in ipairs(pets) do print("位置项目 " .. idx .. " is " .. pet .. ".") end
这将打印“位置1的项是狗。”,“位置2的项是猫”,“位置3的项是鸟”。
但是,如果我们执行以下操作会怎样?
local pets = {"dogs", "cats", "birds"} pets[12] = "goldfish" for idx, pet in ipairs(pets) do print("位置项目 " .. idx .. " is " .. pet .. ".") end
诸如第二个示例这样的数组是稀疏数组。序列中有缺口。该数组如下所示:
{"dogs", "cats", "birds", nil, nil, nil, nil, nil, nil, nil, nil, "goldfish"} -- 1 2 3 4 5 6 7 8 9 10 11 12
nil值不占用任何其他内存;内部卢阿只保存值[1]="dogs",[2]="cats",[3]="birtds"和[12]="goldfish"
为了回答眼前的问题,ipairs将在鸟鸣之后停止;pets[12]除非我们调整代码,否则永远不会达到“金鱼”。这是因为,ipairs从迭代1..n-1这里n是第一的位置nil发现的。Lua定义table[length-of-table+1]为nil。因此,按照适当的顺序,当Lua尝试获取三项数组中的第四项时,迭代停止。
什么时候?
稀疏数组出现问题的两个最常见的地方是(i)尝试确定数组的长度时和(ii)尝试遍历数组时。特别是:
使用#长度运算符时,由于长度运算符会从nil发现的第一个停止计数。
ipairs()由于如上所述,在使用该函数时,它将在nil找到的第一个位置停止迭代。
使用该函数时,因为此方法在首次发现时便停止解包。table.unpack()nil
当使用(直接或间接)访问上述任何功能的其他功能时。
为避免此问题,编写代码很重要,这样,如果您希望表是数组,就不会引入间隙。差距可以通过几种方式引入:
如果将某些内容添加到错误位置的数组中。
如果将nil值插入数组。
如果从数组中删除值。
您可能会想,“但是我永远不会做任何事情。”好吧,不是故意的,但这是一个具体的例子,说明事情可能会出错。想象一下,您想为Lua编写一个Rubyselect和Perl的过滤方法grep。该方法将接受一个测试函数和一个数组。遍历数组,依次对每个项目调用test方法。如果该项通过,则将该项添加到结果数组,该数组在方法末尾返回。以下是错误的实现:
local filter = function (fun, t) local res = {} for idx, item in ipairs(t) do if fun(item) then res[idx] = item end end return res end
问题在于,当函数返回时false,我们将跳过序列中的数字。想象一下调用filter(isodd,{1,2,3,4,5,6,7,8,9,10}):每次传递给的数组中有偶数时,返回的表中都会有间隙filter。
这是一个固定的实现:
local filter = function (fun, t) local res = {} for _, item in ipairs(t) do if fun(item) then res[#res + 1] = item end end return res end
提示
使用标准函数:table.insert(<table>,<value>)始终追加到数组的末尾。 table[#table+1]=value是这个的捷径。table.remove(<table>,<index>)会将所有后续值移回以填补空白(这也会使其变慢)。
插入之前检查nil值,避免使用诸如之类的东西,它们可能会将值潜入我们的表中。table.pack(function_call())nil
插入后检查nil值,必要时通过移动所有连续值来填补空白。
如果可能,请使用占位符值。例如,更改nil为0或其他一些占位符值。
如果不可避免要留有空隙,则应妥善记录(注释)。
编写一个__len()元方法并使用#运算符。
示例6.:
tab = {"john", "sansa", "daenerys", [10] = "the imp"} print(#tab) --> prints 3 setmetatable(tab, {__len = function() return 10 end}) -- __len needs to be a function, otherwise it could just be 10 print(#tab) --> prints 10 for i=1, #tab do print(i, tab[i]) end --> prints: -- 1 john -- 2 sansa -- 3 daenerys -- 4 nil -- ... -- 10 the imp for key, value in ipairs(tab) do print(key, value) end --> this only prints '1 john \n 2 sansa \n 3 daenerys'
另一种选择是使用该pairs()函数并滤除非整数索引:
for key in pairs(tab) do if type(key) == "number" then print(key, tab[key] end end -- note: this does not remove float indices -- does not iterate in order