【问题标题】:How do I know if a table is an array?我怎么知道一个表是否是一个数组?
【发布时间】:2011-11-23 11:35:45
【问题描述】:

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

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

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

有更简单/更智能的解决方案吗?

【问题讨论】:

标签: lua


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

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

    -- 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
    

    【讨论】:

      【解决方案3】:

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

      现有的 JSON 库可能已经这样做了(例如,Lua CJSON

      其他选项是

      • 由用户自行指定参数是什么类型,或者他希望将其处理为什么类型。
      • 通过安排__newindex 明确声明数组,以便只允许使用新的数字和后续索引。

      【讨论】:

      • 我喜欢 __newindex 解决方案。
      【解决方案4】:

      @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 与对象,count 将为零与空数组,因此它可能需要在函数开头进行额外检查,例如:if not next(t) then return true

      作为旁注,我正在粘贴另一个实现,在 lua-cjson 中找到(作者 Mark Pulford):

      -- Determine with a Lua table can be treated as an array.
      -- Explicitly returns "not an array" for very sparse arrays.
      -- Returns:
      -- -1   Not an array
      -- 0    Empty table
      -- >0   Highest index in the array
      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 
      

      【讨论】:

        【解决方案5】:

        这里是基于 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
        

        【讨论】:

        • 有用。你可以缩短一点:function is_array(tbl) return type(tbl) == 'table' and (#tbl > 0 or next(tbl) == nil) end.
        • 提醒一下,解决方案并非详尽无遗,并且对于除非数组组件之外包含数组组件的任何表(例如{[1] = 1, a = 1},错误的表示下面是一个数组。简单地使用function is_array(tbl) return type(tbl) == 'table' and tbl[1] ~= nil end 会更快、更准确(即不是很准确)
        • 请注意,BenjaminDobell 评论中的函数对于空表返回 false,这与此答案不同。
        【解决方案6】:

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

        ---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
        

        【讨论】:

        • 如果您跟踪元表中的条目,这可能会更快一些,但它不会是通用的。但是,对于大型表,它会快得多。
        • 那它就不是数组了。
        【解决方案7】:

        您可以简单地测试一下(假设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))。

        【讨论】:

        • 在我的测试中,除了最后一个之外,这一切都有效。它在不应该的时候返回 true。
        • function isarray(tableT) for k, v in pairs(tableT) do if tonumber(k) ~= nil and k ~= #tableT then if tableT[k+1] ~= k+1 then return false end end end return #tableT > 0 and next(tableT, #tableT) == nil end 似乎有效
        • 嗯,你是对的。我仅在 LuaJIT 中对此进行了测试。在最后一种情况下,LuaJIT 说 #({1, 2, 3, nil, 5})3,而不是 5。在标准 Lua 中情况并非如此。
        • 但是您提供的这个功能似乎也有问题。 tableT[k+1] ~= k+1 几乎总是正确的
        • 哦,对不起。是的,那是个误会。
        【解决方案8】:

        我为漂亮的打印 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
        

        【讨论】:

          【解决方案9】:

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

          • 空表

          • 数字数组

          • 重复数字的数组

          • 带有数字值的字母键

          • 混合数组/非数组

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

          • 双打表

          • 以双精度为键的表格

            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,我主要基于他们的代码。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2010-10-16
            • 2010-12-06
            • 2022-10-06
            • 1970-01-01
            • 1970-01-01
            • 2011-11-28
            • 2012-09-27
            相关资源
            最近更新 更多