确定Lua表是否为空(不包含任何条目)的最有效方法是什么?

146

最有效的方法是确定一个表格是否为空(即,当前既不包含数组样式值,也不包含字典样式值)?

目前,我正在使用 next()

if not next(myTable) then
    -- Table is empty
end

有没有更有效的方法?

注意:在此处,# 运算符不足以满足要求,因为它仅对表中的数组样式值进行操作 - 因此 #{test=2}#{} 是无法区分的,因为两者都返回 0。还要注意,检查表变量是否为 nil 不足以满足要求,因为我不是在寻找 nil 值,而是寻找具有 0 条目的表(即 {})。

8个回答

215

你的代码很高效,但是是错误的。(考虑 {[false]=0}。)正确的代码是

if next(myTable) == nil then
   -- myTable is empty
end

为了达到最大的效率,您需要将next绑定到一个本地变量,例如:

...
local next = next 
...
... if next(...) ...

next为局部变量时,代码通过在一个"upvalues"数组中进行常数时间的索引操作来找到原始函数next。当next被设置为全局变量时,查找next需要在"环境"哈希表中进行索引,该哈希表包含全局变量的值。尽管该索引操作仍然是常数时间的,但它比局部变量的数组查找慢得多。


2
关于技术正确性的观点很好;在我使用原始代码的特定情况下,“false”不会是一个预期的键,因此“if not”运行良好,但是我将来可能会习惯于与“nil”进行比较,作为一种好习惯。是的,我已经绑定了常用的实用函数到本地变量以提高速度。不过还是感谢你的建议。 - Amber
8
为什么我们通过使用local next来增加速度? - Moberg
6
这是由LUA处理其命名空间的方式引起的。简单来说,它首先会向上爬取本地表,因此如果当前块中有一个local next,它将使用该变量,然后向上爬到下一个块并重复这个过程。一旦超出了本地范围,它才会使用全局命名空间。这是其中一个简化版本,但最终肯定会对程序速度产生影响。 - ATaco
6
在运行时,全局变量需要进行哈希表查找,而“局部”变量只需要进行数组查找。 - Norman Ramsey
3
@HerlySQR,它不是更快,而是更正确。if not会漏掉一个情况,即当表不为空时,如答案中所述,对于表{[false]=0}next()返回键,在该示例中为false,并且if not false会让您误以为表为空。== null方法即使在这种情况下也可以正确工作。 - Irfy
显示剩余4条评论

1

一种可能的方法是通过使用元表“newindex”键来计算元素数量。当分配非nil值时,增加计数器(计数器也可以存在于元表中),当分配nil时,减少计数器。

测试空表的方法是检查计数器是否为0。

这里有一个指向元表文档的指针。

我确实喜欢你的解决方案,而且我真的不能假设我的解决方案总体上更快。


6
原问题并不仅仅涉及计算“数组”条目的数量。 - lhf
3
0x6的建议不仅适用于数组式元素(newindex可用于数字和非数字索引)。然而,主要问题是检测何时赋值为nil,因为如果键已经存在于表中,__newindex将不会触发。 - Amber
3
为了让这个技巧生效,metatable 必须同时实现 __index__newindex,将实际数据存储在一个影子表中,并保持真实表为空,以便 __index 能够被调用。我想出声地思考,我怀疑每次查找的成本增加可能不值得。 - RBerteig

1
最好避免重载后的 __eq 方法进行评估。
if rawequal(next(myTable), nil) then
   -- myTable is empty
end

或者

if type(next(myTable)) == "nil" then
   -- myTable is empty
end

3
我是一个Lua新手,试图理解为什么这个答案被点踩了。我猜测这是因为在Lua中,“如果两个对象具有不同的元方法,则相等性操作的结果为false,甚至不调用任何元方法”。(引用在lua.org上的Programming in Lua的这一页的底部)。那么这是否消除了避免对nil进行__eq重载的需要? - SansWit
SansWit:是的,它可以。 - Luatic

0

这可能是你想要的:

function table.empty (self)
    for _, _ in pairs(self) do
        return false
    end
    return true
end

a = { }
print(table.empty(a))
a["hi"] = 2
print(table.empty(a))
a["hi"] = nil
print(table.empty(a))

输出:

true
false
true

11
使用 next() 比循环 pairs() 更高效(也更简洁)。 - Amber
8
实际上,使用pairs()循环遍历本质上就是使用next()技巧,只不过需要更多的开销。 - dubiousjim
7
不建议向标准的“table”库写入内容。 - Ti Strga

-4

尝试使用Serpent,对我有用

serpent = require 'serpent'

function vtext(value)
  return serpent.block(value, {comment=false})
end

myTable = {}

if type(myTable) == 'table' and vtext(myTable) == '{}' then
   -- myTable is empty
end

-5

我知道这是老旧的,而且我可能会误解你的意思,但是如果你只想让表格为空,也就是说,除非你只是检查它是否为空,而实际上并不想要或需要它为空,否则你可以通过简单地重新创建它来清空它,除非我弄错了。可以使用以下语法完成此操作。

yourtablename = {} -- this seems to work for me when I need to clear a table.

6
那不是问题。 - Yu Hao

-6
这个怎么样?
if endmyTable[1] == nil then
  -- myTable is empty
end

2
这个在索引为字符串的表上不起作用。 - SamHoque
或者一个表在索引1处没有值。考虑使用{[2] = true} - Lewis R

-10

尝试使用#。它返回表中的所有实例。如果表中没有实例,则返回0

if #myTable==0 then
print('There is no instance in this table')
end

1
提问者表示 # 在这里不够用,并给出了原因;您能解释一下为什么这样可以避开那些原因吗? - ameed
1
嗯...我不知道。我是新手,所以我唯一知道的方法就是使用 #。 - arthurgps2
# 仅适用于从索引 1 开始的连续数组。以下表格不适用: {a=true}, {[2]=true} - Lewis R

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接