我如何知道一个表是不是数组?

我正在开发一个简单的优化 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

点赞
stackoverflow用户513763
stackoverflow用户513763

没有内置的方式可以区分,因为在 Lua 中没有区别。

已经存在一些 JSON 库,可能已经实现了这一点(例如 Lua CJSON)。

其他选项包括:

  • 让用户指定参数的类型,或指定其应该如何处理。
  • 通过对 __newindex 进行显式声明使数组只能使用新数字和紧随其后的索引。
2011-09-23 09:11:59
stackoverflow用户169828
stackoverflow用户169828

如果你想要一个快速、简单、不会干扰的解决方案,并且大多数情况下都有效,那么我建议只需检查索引1 - 如果存在,则该表是一个数组。当然,并没有保证,但根据我的经验,表很少同时有数字和其他键。你能否容忍将一些对象误判为数组,并且你是否预计这种情况经常发生,这取决于你的使用场景 - 我想这对于通用的 JSON 库来说不是一个好的解决方案。

编辑:为了研究,我去看了Lua CJSON的实现方式。它遍历所有键值对,并检查所有键是否为整数,同时保留最大的键(相关函数为lua_array_length)。然后根据表的稀疏程度(比率为用户可控)决定将表序列化为数组还是对象,即1、2、5、10索引的表可能会被序列化为数组,而1、2、1000000索引的表则会被序列化为对象。我想这实际上是一个相当好的解决方案。

2011-09-23 09:39:17
stackoverflow用户796584
stackoverflow用户796584
---检查一个表是否被用作数组。即:键从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
2011-09-23 11:31:12
stackoverflow用户1351465
stackoverflow用户1351465

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
2014-01-06 20:41:02
stackoverflow用户2698948
stackoverflow用户2698948
如果 thing 的类型是 table:
    创建一个空的字符串数组 strTable 和一个空的整型数组 iTable
    假设所有的索引都符合数组索引的规定
    遍历 thing 中的每个键值对:
        如果键是字符串,就不需要加上 "[]"
        否则,加上 "[]" 并且递归打印键
        判断索引是否符合数组索引的规定,如果不符合,就说明这个 table 不是数组
        记录下键对应的值
    如果这个 table 是数组,则将 strTable 替换成 iTable
    用逗号将 strTable 中所有字符串拼接起来,添加上大括号,返回最终的字符串表达式
如果 thing 的类型是 string:
    在两边加上双引号,返回最终的字符串表达式
否则:
    返回 tostring(thing)
2014-09-05 21:20:54
stackoverflow用户312586
stackoverflow用户312586

下面是一个区分数组和非数组的最简算法:

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
2014-09-07 11:31:02
stackoverflow用户3426294
stackoverflow用户3426294

以下是基于 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
2018-10-08 07:30:30
stackoverflow用户9781480
stackoverflow用户9781480

你可以简单地测试这个函数 (假设 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

根据我对 LuaLuaJIT 实现的观察,函数 next 总是首先查找表的数组部分,因此任何非数组索引都会在整个数组部分之后找到,即使在此之后没有任何特定的顺序。然而,我不确定这是否是在不同的 Lua 版本中保持一致的行为。

此外,你可以决定是否将空表也视为数组。在这个实现中,它们不被视为数组。你可以将其更改为 return next(t) == nil or (#t > 0 and next(t, #t) == nil) 来进行相反的操作。

无论如何,我认为这是你可以在代码行数和复杂度方面得到的最短的解决方案,因为它受到 next 的下限约束 (我相信它是 O(1) 或 O(logn) )。

2021-02-25 14:06:27
stackoverflow用户960691
stackoverflow用户960691

这不太好看,根据表格的大小和巧妙的欺骗程度,它可能会变得很慢,但在我的测试中,它可以处理以下每种情况:

  • 空表格

  • 数组数字

  • 重复数字的数组

  • 数字值的字母键

  • 混合数组/非数组

  • 稀疏数组(索引序列中的间隙)

  • 双重表

  • 有双重键的表

    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,他们的代码大部分都是我基于它编写的。

2021-03-25 05:08:13
stackoverflow用户7787852
stackoverflow用户7787852

在至少 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
2023-02-07 12:41:55