如何在表中表示 nil

8
假设我想存储一个元素列表,其中包括一些空值。值的位置很重要,并且我需要表示在给定位置上列表中没有值的情况。
这里有一个问题:
a = {1,2,3,nil,4}

for k,v in ipairs(a) do
  print(k,v)
end

print(a[4]) 
print(a[5])

这个for循环只会打印元素1、2和3,因为它在nil处停止。第一个打印语句打印出nil,但我不确定它是否实际存储在表中。(谁知道呢?)第二个打印语句按预期打印出4。

那么问题来了:如何在表中表示一组元素,并有效地迭代它们?考虑到上述条件,例如位置的重要性以及某些位置是“空的”。换句话说:没有值,但该位置上缺少该值具有一定意义。

5个回答

8

这是模块“null.lua”

local function null(...)
   local t, n = {...}, select('#', ...)
   for k = 1, n do
      local v = t[k]
      if     v == null then t[k] = nil
      elseif v == nil  then t[k] = null
      end
   end
   return (table.unpack or unpack)(t, 1, n)
end
_G.null = null

使用 null() 作为编码器和解码器

require("null")

a = {null(1,2,3,nil,4)}
-- the same could be done element-by-element
-- a = {null(1),null(2),null(3),null(nil),null(4)}

for k,v in ipairs(a) do
   v = null(v)
   print(k,v)
end

print(null(a[4]))
print(null(a[5]))

我接受这个答案是因为它使用了本地表和本地的 ipairs,非常直观易用,并且能很好地处理真值测试。 - nagylzs

5
Lua表可以用来创建任何抽象数据结构,在您的情况下,您表示想要一个“列表”。Lua表是一种数据结构,将数字索引访问与键值访问相结合。
根据你的例子,你正在使用表的数字索引功能,这使你能够通过ipairs()迭代这些值。您将无法将nil放入表中,因为数字索引在第一个nil条目处停止。表中剩余的值被存储为键值对。
有几个解决方法,但这取决于为什么需要在列表中使用nil。最简单的方法是使用字符串“nil”而不是本机数据类型nil。
a = {1, 2, 3, "nil", 4}

for k,v in ipairs(a) do
  print(k,v)
end

这段代码的结果是:
1   1 
2   2
3   3
4   nil
5   4

由于Lua实现字符串的方式,与比较原生类型nil相比,与字符串" nil "进行比较不会导致性能损失。
在《Lua编程》第5章表中讨论了“空洞”(由nil引起)的问题。Roberto Ierusalimschy建议跟踪数组的大小以避免出现问题。
以下代码展示了一种面向对象的方法来跟踪列表的大小。这个主题有很多可能的变化。
function makeList(...)
  local list = table.pack(...)

  list.length = 
    function(self) return self.n 
    end

  list.append = 
    function(self, value)
      self.n = self.n + 1
      self[self.n] = value
    end

  list.print = 
    function(self)
      for i = 1, self.n do print(i, self[i]) end
    end

  return list
end

a = makeList(1, 2, 3, nil, 4)
a:append(5)

a:print()

print(a:length())

结果如下:
1   1
2   2
3   3
4   nil
5   4
6   5
6

请注意,函数table.pack会创建一个包含正确项数的字段'n',即使'nil'存在也是如此。请参阅《PIL》第6.2章“可变参数函数”进行完整解释。

3
通常的说法是定义null={}一次,然后使用null代替nil - lhf
好的,这是一个解决方法。我知道我可以使用null或“nil”代替nil。但我认为这是Lua的缺点。例如,如果我还需要测试元素的真值怎么办?如果元素是由函数调用返回的呢?那么我就必须重构代码并添加样板代码。这不是一种有效的方式。 - nagylzs
1
不要误会,但我认为这种方法很糟糕。使用“nil”(一个字符串)会使调试变得非常非常糟糕。 - pschulz
第二段几乎完全不正确或是特定于实现的。正如文档所述,是ipairs停止迭代。表构造器为所有非nil值创建整数键,也正如文档所述。 - Tom Blodget
是否有一种方法可以不使用括号添加 null? - Meng
显示剩余2条评论

3

嗯,如果在表格中存储nil,会出现问题。

最简单的解决方案是引入您自己的唯一值。

local mynil = {} -- every new table is unique!

a = {1,2,3,mynil,4}

for k,v in ipairs(a) do
  if (v == mynil) then
    v = nil
  end
  print(k,v)
end

现在不会再出现存储在表中的 "nil" 字符串的问题了,小问题是多了一个比较。ipairs 或任何其他迭代器将显示带有 mynil 值的键存在。这意味着您可以将 mynil 键的存在与缺失键 =nil 分开。

附:如果要移动列表中的元素位置,可以考虑使用 table.remove(list, key) 函数。


2

不要只是随便拼凑,为此编写自己的数据结构。如果您通过编写适当的迭代器“重载”ipairs,则可以将其用作表:

function create(...)
    local t = table.pack(...)
    local self = {
        num = t.n,
        elements = { ... }
    }
    return self
end

function elements(t)
    local f = function(s, i)
        i = i + 1
        if i <= s.num then
            return i, s.elements[i]
        end
    end
    return f, t, 0
end

local seq = create(1, 2, nil, 3)

print(seq.num)
for i, e in elements(seq) do
    print(i, e)
end
-- results:
-- 4
-- 1    1
-- 2    2
-- 3    nil
-- 4    3

您可以为这个结构定义一个元表,并使用自己的ipairs,这样您甚至不需要更改名称。


1
这个问题的答案相当简单,而且建议的“workaround a”绝对是过度解决。每当表格中的项目数量发生变化时(注意:不要使用 #,您必须手动跟踪以处理 nil 值),并使用数字 for 循环来迭代它。

1
我不认为这个问题需要“杀鸡焉用牛刀”的聪明解决方案。我总是更喜欢写大约20行代码,而不是每次使用时都要考虑使用某种结构。 - pschulz

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