检查Lua表的成员是否存在在任何级别

4

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

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

这会导致一个错误:尝试索引字段'bar'(一个空值),因为bar没有被定义。

我的常规解决方案是逐个测试链条的每个部分。

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

但是,当有很多嵌套的表格时,这种方式可能非常繁琐。有没有比逐个部分测试更好的方法?

6个回答

2

如需查找位于表格任何级别的元素,我会使用以下方法:

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

2

我不理解你所说的“沿着成员路径”的含义。从示例中,我猜测你是在尝试在“子表”中查找一个值?

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

2
我认为您正在寻找类似以下的内容:

我认为您可能需要这样的内容:

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为止。看起来做得很好。不过,如果你能想出一个更好的名字,请随意更改。

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

我对Egor聪明的回答印象深刻,但总体上,我认为我们不应该依赖这种技巧。

另请参阅


我很惊讶这个简单而优雅的答案没有得到任何赞同。 - Wei Zhong

1

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']!你无法使用普通的变量语法访问后者,因为它包含对于变量名无效的字符。 - siffiejoe
现在它正在引发一个错误(就像问题中一样,原因也相同)。 - siffiejoe

1
如果我正确理解了您的问题,这里有一个可能的解决方案:
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

更新:根据cauterite的建议,这是一个也适用于本地变量但需要传入两个参数的版本 :(

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接