我怎么知道一个表是否是一个数组?

时间:2011-09-23 08:27:53

标签: lua

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

问题是如何检测表是否被用作数组?

  • 当然,一个解决方案是遍历所有对,看看它们是否只有数字连续键,但是不够快。
  • 另一个解决方案是在表格中放置一个标志,表示它是一个数组而不是一个对象。

任何更简单/更智能的解决方案?

9 个答案:

答案 0 :(得分:8)

如果你想要快速,简单,非侵入性的解决方案,它将在大多数的时间内工作,那么我只想检查索引1 - 如果它存在,那么表就是一个数组。当然,没有保证,但根据我的经验,表很少有数字键和其他键。你是否可以将某些对象误认为数组,以及你是否希望这种情况发生,这通常取决于你的使用场景 - 我想这对于一般的JSON库来说并不好。

编辑:对于科学,我去看看Lua CJSON是如何做事的。它遍历所有对并检查所有键是否为整数,同时保持最大键(相关函数为lua_array_length)。然后它决定是否将表序列化为数组或对象,具体取决于表的稀疏程度(用户控制的比率),即索引为1,2,5,10的表可能会被序列化为数组而指数1,2,1000000将作为对象。我想这实际上是非常好的解决方案。

答案 1 :(得分:4)

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

已经存在JSON库,可能已经这样做了(例如。Lua CJSON

其他选项

  • 由用户指定参数的类型,或者他希望对哪种类型进行处理。
  • 通过排列__newindex显式声明数组,以便只允许使用新的数字和后续索引。

答案 2 :(得分:3)

@AlexStack

  

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对象和{{对于空数组,1}}将为零,因此可能需要在函数开头进行额外检查,例如:count

作为旁注,我正在粘贴另一个实现,发现于lua-cjson(由Mark Pulford撰写):

if not next(t) then return true

答案 3 :(得分:3)

区分数组/非数组的最简单算法是:

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的特殊情况,我所做的是用metatable中的字段标记数组。

-- use this when deserializing
local function mark_as_array(t)
  setmetatable(t, {__isarray = true})
end

-- use this when serializing
local function is_array(t)
  local mt = getmetatable(t)
  return mt.__isarray
end

答案 4 :(得分:2)

这是基于Lua特定的#len函数机制的最简单的检查。

function is_array(table)
  if type(table) ~= 'table' then
    return false
  end

  -- objects always return empty size
  if #table > 0 then
    return true
  end

  -- only object can have empty length with elements inside
  for k, v in pairs(table) do
    return false
  end

  -- if no elements it can be array and not at same time
  return true
end

local a = {} -- true
local b = { 1, 2, 3 } -- true
local c = { a = 1, b = 1, c = 1 } -- false

答案 5 :(得分:1)

感谢。我开发了以下代码,它可以工作:

---Checks if a table is used as an array. That is: the keys start with one and are sequential numbers
-- @param t table
-- @return nil,error string if t is not a table
-- @return true/false if t is an array/isn't an array
-- NOTE: it returns true for an empty table
function isArray(t)
    if type(t)~="table" then return nil,"Argument is not a table! It is: "..type(t) end
    --check if all the table keys are numerical and count their number
    local count=0
    for k,v in pairs(t) do
        if type(k)~="number" then return false else count=count+1 end
    end
    --all keys are numerical. now let's see if they are sequential and start with 1
    for i=1,count do
        --Hint: the VALUE might be "nil", in that case "not t[i]" isn't enough, that's why we check the type
        if not t[i] and type(t[i])~="nil" then return false end
    end
    return true
end

答案 6 :(得分:1)

您可以简单地测试一下(假设 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 does some logic 决定何时使用表的这个“数组部分”和“哈希部分”。这就是为什么在最后一个示例中提供的表被检测为数组的原因:尽管中间有 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))。

答案 7 :(得分:0)

我为漂亮的打印lua表编写了这个函数,并且必须解决同样的问题。这里没有任何解决方案可以解决边缘情况,例如某些键是数字而其他键则不是。这将测试每个索引,看它是否与数组兼容。

function pp(thing)
    if type(thing) == "table" then
        local strTable = {}
        local iTable = {}
        local iterable = true
        for k, v in pairs(thing) do
            --if the key is a string, we don't need to do "[key]"
            local key = (((not (type(k) == "string")) and "["..pp(k).."]") or k)
            --this tests if the index is compatible with being an array
            if (not (type(k) == "number")) or (k > #thing) or(k < 1) or not (math.floor(k) == k) then
                iterable = false
            end
            local val = pp(v)
            if iterable then iTable[k] = val end
            table.insert(strTable, (key.."="..val))
        end
        if iterable then strTable = iTable end
        return string.format("{%s}", table.concat(strTable,","))
    elseif type(thing) == "string" then
        return '"'..thing..'"'
    else
        return tostring(thing)
    end
end

答案 8 :(得分:0)

这并不漂亮,而且取决于表的大小和巧妙的欺骗性,它可能会很慢,但在我的测试中,它适用于每种情况:

  • 空表

  • 数字数组

  • 重复数字的数组

  • 带有数字值的字母键

  • 混合数组/非数组

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

  • 双打表

  • 双打作为键的表

    function isarray(tableT)   
    
        --has to be a table in the first place of course
        if type(tableT) ~= "table" then return false end
    
        --not sure exactly what this does but piFace wrote it and it catches most cases all by itself
        local piFaceTest = #tableT > 0 and next(tableT, #tableT) == nil
        if piFaceTest == false then return false end
    
        --must have a value for 1 to be an array
        if tableT[1] == nil then return false end
    
         --all keys must be integers from 1 to #tableT for this to be an array
         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
    
         --every numerical key except the last must have a key one greater
         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
    
         --otherwise we probably got ourselves an array
         return true
     end
    

非常感谢 PiFace 和 Houshalter,他们的代码大部分都是基于他们的。