我如何知道一个表是不是数组?
我正在开发一个简单的优化 JSON 函数。Lua 使用表来表示数组,但在 JSON 中我需要识别它们之间的区别。使用以下代码:
t={
a="hi",
b=100
}
function table2json(t,formatted)
if type(t)~="table" then return nil,"Parameter is not a table. It is: "..type(t) end
local ret=""--return value
local lvl=0 --indentation level
local INDENT=" " --OPTION: the characters put in front of every line for indentation
function addToRet(str) if formatted then ret=ret..string.rep(INDENT,lvl)..str.."\n" else ret=ret..str end end
addToRet("{")
lvl=1
for k,v in pairs(t) do
local typeof=type(v)
if typeof=="string" then
addToRet(k..":\""..v.."\"")
elseif typeof=="number" then
addToRet(k..":"..v)
end
end
lvl=0
addToRet("}")
return ret
end
print(table2json(t,true))
正如你在 JSON 参考中看到的,object
是 Lua 中所谓的 table
,它与 array
不同。
问题是我如何检测表是否作为数组使用?
- 当然,一个解决方案是遍历所有键值对,并查看它们是否仅具有数字连续键,但这不够快。
- 另一个解决方案是在表中放置一个标志,说明它是一个数组而不是对象。
是否有更简单/更智能的解决方案?
原文链接 https://stackoverflow.com/questions/7526223
如果你想要一个快速、简单、不会干扰的解决方案,并且大多数情况下都有效,那么我建议只需检查索引1 - 如果存在,则该表是一个数组。当然,并没有保证,但根据我的经验,表很少同时有数字和其他键。你能否容忍将一些对象误判为数组,并且你是否预计这种情况经常发生,这取决于你的使用场景 - 我想这对于通用的 JSON 库来说不是一个好的解决方案。
编辑:为了研究,我去看了Lua CJSON的实现方式。它遍历所有键值对,并检查所有键是否为整数,同时保留最大的键(相关函数为lua_array_length
)。然后根据表的稀疏程度(比率为用户可控)决定将表序列化为数组还是对象,即1、2、5、10索引的表可能会被序列化为数组,而1、2、1000000索引的表则会被序列化为对象。我想这实际上是一个相当好的解决方案。
---检查一个表是否被用作数组。即:键从1开始,且是顺序数字
-- @param t table
-- @return 如果t不是table,则返回nil,error字符串
-- @return 如果t是一个数组,则返回true;如果不是数组,则返回false
-- 注意:返回一个空表,会返回true
function isArray(t)
if type(t)~="table" then return nil,"参数不是table类型!它是:"..type(t) end
--检查所有表键是否是数值型并计算数量
local count=0
for k,v in pairs(t) do
if type(k)~="number" then return false else count=count+1 end
end
--所有键都是数值型。现在检查它们是否是连续的且从1开始的
for i=1,count do
--提示:值可能是“nil”,在这种情况下“not t[i]”不够,所以我们要检查类型
if not t[i] and type(t[i])~="nil" then return false end
end
return true
end
if not t[i] and type(t[i])~="nil" then return false end
这段代码有问题,如果其中一个元素为 false
,则会失败。
> return isArray({"one", "two"})
true
> return isArray({false, true})
false
我认为整个表达式可以改为 type(t[i]) == nil
,但在某些情况下仍会失败,因为它不支持 nil
值。
我认为一个很好的方法是尝试使用 ipairs
或检查 #t
是否等于 count
,但是 #t
对象返回 0,空数组将返回一个零的 count
,因此可能需要在函数开头进行额外检查,类似于:if not next(t) then return true
。
作为旁注,我贴出了另一个实现,它在 lua-cjson(由 Mark Pulford 开发)中找到:
-- 确定 Lua 表是否可以作为数组处理。
-- 对于非常稀疏的数组显式返回“不是数组”。
-- 返回:
-- -1 不是数组
-- 0 空表
-- >0 数组中的最高索引
local function is_array(table)
local max = 0
local count = 0
for k, v in pairs(table) do
if type(k) == "number" then
if k > max then max = k end
count = count + 1
else
return -1
end
end
if max > count * 2 then
return -1
end
return max
end
如果 thing 的类型是 table:
创建一个空的字符串数组 strTable 和一个空的整型数组 iTable
假设所有的索引都符合数组索引的规定
遍历 thing 中的每个键值对:
如果键是字符串,就不需要加上 "[]"
否则,加上 "[]" 并且递归打印键
判断索引是否符合数组索引的规定,如果不符合,就说明这个 table 不是数组
记录下键对应的值
如果这个 table 是数组,则将 strTable 替换成 iTable
用逗号将 strTable 中所有字符串拼接起来,添加上大括号,返回最终的字符串表达式
如果 thing 的类型是 string:
在两边加上双引号,返回最终的字符串表达式
否则:
返回 tostring(thing)
下面是一个区分数组和非数组的最简算法:
local function is_array(t)
local i = 0
for _ in pairs(t) do
i = i + 1
if t[i] == nil then return false end
end
return true
end
解释在这里:https://web.archive.org/web/20140227143701/http://ericjmritz.name/2014/02/26/lua-is_array/
尽管如此,空表仍然可能会让你产生问题——它们到底是“数组”还是“哈希表”?
对于序列化 json 的特殊情况,我会在元表中为数组类型添加一个字段。
-- 反序列化时使用
local function mark_as_array(t)
setmetatable(t, {__isarray = true})
end
-- 序列化时使用
local function is_array(t)
local mt = getmetatable(t)
return mt.__isarray
end
以下是基于 Lua 特定的 #len 函数机制的更简单的检查方法。
function is_array(table)
if type(table) ~= 'table' then
return false
end
-- 对象总是返回空的大小
if #table > 0 then
return true
end
-- 只有对象可以有空长度和内部元素
for k, v in pairs(table) do
return false
end
-- 如果没有元素,则它可以是数组,并且不是同时
return true
end
local a = {} -- true
local b = { 1, 2, 3 } -- true
local c = { a = 1, b = 1, c = 1 } -- false
你可以简单地测试这个函数 (假设 t
是一个表):
function isarray(t)
return #t > 0 and next(t, #t) == nil
end
print(isarray{}) --> false
print(isarray{1, 2, 3}) --> true
print(isarray{a = 1, b = 2, c = 3}) --> false
print(isarray{1, 2, 3, a = 1, b = 2, c = 3}) --> false
print(isarray{1, 2, 3, nil, 5}) --> true
它测试表的「数组部分」是否有任何值,然后通过使用 next
和最后一个连续数字索引检查该部分后是否有任何值。
请注意,Lua 为决定何时使用表的「数组部分」和「散列部分」进行了一些逻辑处理。这就是为什么在最后一个例子中,提供的表会被检测为数组:它足够稠密,即使中间有 nil
,也被视为数组,换句话说,它不足够稀疏。正如另一个答案所提到的,这在数据序列化的情况下非常有用,你不必为自己编写程序,可以使用 Lua 底层逻辑。如果你对最后一个例子进行序列化,你可以使用 for i = 1, #t do ... end
而不是使用 ipairs
。
根据我对 Lua 和 LuaJIT 实现的观察,函数 next
总是首先查找表的数组部分,因此任何非数组索引都会在整个数组部分之后找到,即使在此之后没有任何特定的顺序。然而,我不确定这是否是在不同的 Lua 版本中保持一致的行为。
此外,你可以决定是否将空表也视为数组。在这个实现中,它们不被视为数组。你可以将其更改为 return next(t) == nil or (#t > 0 and next(t, #t) == nil)
来进行相反的操作。
无论如何,我认为这是你可以在代码行数和复杂度方面得到的最短的解决方案,因为它受到 next
的下限约束 (我相信它是 O(1) 或 O(logn) )。
这不太好看,根据表格的大小和巧妙的欺骗程度,它可能会变得很慢,但在我的测试中,它可以处理以下每种情况:
空表格
数组数字
重复数字的数组
数字值的字母键
混合数组/非数组
稀疏数组(索引序列中的间隙)
双重表
有双重键的表
function isarray(tableT) --首先,必须是表格 if type(tableT) ~= "table" then return false end --我不确定这样做的确切作用,但piFace编写了它并且它可以自己处理多数情况 local piFaceTest = #tableT > 0 and next(tableT, #tableT) == nil if piFaceTest == false then return false end --必须有1的值才能是数组 if tableT[1] == nil then return false end --所有键都必须是从1到#tableT的整数才能成为数组 for k, v in pairs(tableT) do if type(k) ~= "number" or (k > #tableT) or(k < 1) or math.floor(k) ~= k then return false end end --除了最后一个之外的每个数字键必须有一个比它大的键 for k,v in ipairs(tableT) do if tonumber(k) ~= nil and k ~= #tableT then if tableT[k+1] == nil then return false end end end --否则我们可能已经得到了一个数组 return true end
非常感谢PiFace和Houshalter,他们的代码大部分都是我基于它编写的。
在至少 luajit 2.1.0-beta3 版本中(在这个版本下我测试过),对于数值索引,使用 pairs() 进行迭代会是有序的,因此这也能够正常工作并且可能比 https://stackoverflow.com/a/25709704/7787852 执行得更快。
即使 Lua 参考手册明确声明 pairs() 的迭代顺序不可靠。
local function is_array(t)
local prev = 0
for k in pairs(t) do
if k ~= prev + 1 then
return false
end
prev = prev + 1
end
return true
end
- 如何在roblox studio中1:1导入真实世界的地形?
- 求解,lua_resume的第二次调用继续执行协程问题。
- 【上海普陀区】内向猫网络招募【Skynet游戏框架Lua后端程序员】
- SF爱好求教:如何用lua实现游戏内调用数据库函数实现账号密码注册?
- Lua实现网站后台开发
- LUA错误显式返回,社区常见的规约是怎么样的
- lua5.3下载库失败
- 请问如何实现文本框内容和某个网页搜索框内容连接,并把网页输出来的结果反馈到另外一个文本框上
- lua lanes多线程使用
- 一个kv数据库
- openresty 有没有比较轻量的 docker 镜像
- 想问一下,有大佬用过luacurl吗
- 在Lua执行过程中使用Load函数出现问题
- 为什么 neovim 里没有显示一些特殊字符?
- Lua比较两个表的值(不考虑键的顺序)
- 有个lua简单的项目,外包,有意者加微信 liuheng600456详谈,最好在成都
- 如何在 Visual Studio 2022 中运行 Lua 代码?
- addEventListener 返回 nil Lua
- Lua中获取用户配置主目录的跨平台方法
- 如何编写 Lua 模式将字符串(嵌套数组)转换为真正的数组?
没有内置的方式可以区分,因为在 Lua 中没有区别。
已经存在一些 JSON 库,可能已经实现了这一点(例如 Lua CJSON)。
其他选项包括:
__newindex
进行显式声明使数组只能使用新数字和紧随其后的索引。