【问题标题】:Check if a Lua table member exists at any level检查 Lua 表成员是否存在于任何级别
【发布时间】:2017-04-03 01:23:25
【问题描述】:

我需要检查一个成员是否存在于不在下一级但沿着成员路径的表中。

foo = {}
if foo.bar.joe then
  print(foo.bar.joe)
end

这将转换为 attempt to index field 'bar' (a nil value),因为 bar 未定义。

我通常的解决方案是逐个测试链条。

foo = {}
if foo.bar and foo.bar.joe then
  print(foo.bar.joe)
end

但是当有许多嵌套表时,这可能非常乏味。有没有比逐个测试更好的方法?

【问题讨论】:

    标签: lua lua-table


    【解决方案1】:

    我不明白您所说的“沿成员路径”是什么意思。从示例中,我假设您正在尝试在“子表”中查找值?

    local function search(master, target) --target is a string
        for k,v in next, master do
            if type(v)=="table" and v[target] then return true end
        end
    end
    

    一个简单的例子。如果使用这样的函数,可以通过foo表和joe字符串来查看foo.*.joe是否存在。希望这会有所帮助。

    【讨论】:

      【解决方案2】:
      debug.setmetatable(nil, {__index = {}})
      
      foo = {}
      print(foo.bar.baz.quux)
      print(({}).prd.krt.skrz.drn.zprv.zhlt.hrst.zrn)  -- sorry ))
      

      【讨论】:

        【解决方案3】:

        要搜索位于表格任何级别的元素,我会使用如下方法:

        function exists(tab, element)
            local v
            for _, v in pairs(tab) do
                if v == element then
                    return true
                elseif type(v) == "table" then
                    return exists(v, element)
                end
            end
            return false
        end
        
        testTable = {{"Carrot", {"Mushroom", "Lettuce"}, "Mayonnaise"}, "Cinnamon"}
        print(exists(testTable, "Mushroom")) -- true
        print(exists(testTable, "Apple")) -- false
        print(exists(testTable, "Cinnamon")) -- true
        

        【讨论】:

          【解决方案4】:

          我认为您正在寻找以下方面的内容:

          local function get(Obj, Field, ...)
              if Obj == nil or Field == nil then
                  return Obj
              else
                  return get(Obj[Field], ...)
              end
          end
          
          local foo = {x = {y = 7}}
          assert(get() == nil)
          assert(get(foo) == foo)
          assert(get(foo, "x") == foo.x)
          assert(get(foo, "x", "y") == 7)
          assert(get(foo, "x", "z") == nil)
          assert(get(foo, "bar", "joe") == nil)
          assert(get(foo, "x", "y") or 41 == 7)
          assert(get(foo, "bar", "joe") or 41 == 41)
          local Path = {foo, "x", "y"}
          assert(get(table.unpack(Path)) == 7)
          

          get 简单地遍历给定的路径,直到遇到 nil。似乎完成了这项工作。不过,请随意想一个比“get”更好的名字。

          像往常一样,与or结合时要小心。

          Egor 的聪明回答给我留下了深刻的印象,但总的来说,我认为我们不应该依赖这样的技巧。

          另请参阅

          【讨论】:

          • 我很惊讶这个简单而优雅的答案没有得到任何支持。
          【解决方案5】:

          如果我正确理解了您的问题,这是一种可能性:

          function isField(s)
            local t
            for key in s:gmatch('[^.]+') do
              if t == nil then
                if _ENV[ key ] == nil then return false end
                t = _ENV[ key ]
              else
                if t[ key ] == nil then return false end
                t = t[ key ]
              end
              --print(key) --for DEBUGGING
            end
            return true
          end
          
          -- To test
          
          t = {}
          t.a = {}
          t.a.b = {}
          t.a.b.c = 'Found me'
          
          if isField('t.a.b.c') then print(t.a.b.c) else print 'NOT FOUND' end
          if isField('t.a.b.c.d') then print(t.a.b.c.d) else print 'NOT FOUND' end
          

          更新:根据考特里特的建议,这里有一个版本也适用于当地人,但必须接受两个参数:(

          function isField(t,s)
            if t == nil then return false end
            local t = t
            for key in s:gmatch('[^.]+') do
              if t[ key ] == nil then return false end
              t = t[ key ]
            end
            return true
          end
          
          -- To test
          
          local
          t = {}
          t.a = {}
          t.a.b = {}
          t.a.b.c = 'Found me'
          
          if isField(t,'a.b.c') then print(t.a.b.c) else print 'NOT FOUND' end
          if isField(t,'a.b.c.d') then print(t.a.b.c.d) else print 'NOT FOUND' end
          

          【讨论】:

          • 我建议将其实现为isField(t, s) — 尝试从字符串中的第一个组件获取表仅适用于全局变量。例如,local u = {v = 1}; assert(isField('u.v')) 失败。
          • 相应更新。谢谢。
          【解决方案6】:

          foo = {}

          foo.boo = {}

          foo.boo.jeo = {}

          foo.boo.joe 是 foo['boo']['joe'] 等等

          我做下一个函数

          function exist(t)
          
              local words = {}
              local command
          
              for i,v in string.gmatch(t, '%w+') do words[#words+1] = i end
          
              command = string.format('a = %s', words[1])
          
              loadstring(command)()
          
              if a == nil then return false end
          
              for count=2, #words do
                  a = a[words[count]]
                  if a == nil then return false end
              end
          
              a = nil
              return true
          end
          
          foo = {}
          foo.boo = {}
          foo.boo.joe = {}
          
          print(exist('foo.boo.joe.b.a'))
          

          使用 loadstring 来制作临时变量。我的 lua 版本是 5.1

          在 5.2 5.3 中删除 loadstring,而不是使用 load

          【讨论】:

          • foo.bar.joe 等价于 _G['foo'].bar.joe(如果范围内没有局部变量 foo _G['foo.bar.joe']!您不能使用正常的变量语法访问后者,因为它包含对变量名无效的字符。
          • 现在它引发了一个错误(就像在问题中一样,出于同样的原因)。
          猜你喜欢
          • 1970-01-01
          • 2010-11-23
          • 2010-09-17
          • 1970-01-01
          • 2013-01-14
          • 2017-03-26
          • 1970-01-01
          • 2012-11-27
          相关资源
          最近更新 更多