How to remove a lua table entry by its key?

I have a lua table that I use as a hashmap, ie with string keys :

local map = { foo = 1, bar = 2 }

I would like to "pop" an element of this table identified by its key. There is a table.remove() method, but it only takes the index of the element to remove (ie a number) and not a generic key. I would like to be able to do table.remove(map, 'foo') and here is how I implemented it :

function table.removekey(table, key)
local element = table[key]
table[key] = nil
return element
end

Is there a better way to do that ?

114216 次浏览

不,将键的值设置为 nil是在表的散列表部分中删除项的公认方法。你的所作所为很正常。但是,我建议不要重写 table.remove()-对于表的数组部分,默认的 table.move ()功能包括重新编号索引,而重写则不会这样做。如果你想把你的函数添加到 table函数集,那么我可能会把它命名为类似于 table.removekey()之类的东西。

TLDR

(because you're only damn trying to remove a thing from a map, how hard can that be)

将键设置为 nil(例如 t[k] = nil)不仅是可接受的和标准的,而且是从表的“内容”中删除条目的 只有方法。这就说得通了。此外,数组部分和散列表部分是 实施细节,不应该在这个问答中提到

理解 Lua 的表

(以及为什么你不能从无限中移除)

Lua 表没有“从表中删除条目”的概念,也没有“向表中插入条目”的概念。这与来自不同编程语言的许多其他数据结构不同。

In Lua, tables are modelling a perfect associative structure of infinite size.

Lua 中的表本质上是可变的,构造新表的基本方法只有一种: 使用 {}表达式。构建一个包含初始内容(如 t = {10, 20, ["foo"] = 123, 30}或类似内容)的表实际上等同于 语法糖首先构建一个新表(如 t = {}} ,然后逐个设置“初始”条目(如 t[1] = 10t[2] = 20t["foo"] = 123t[3] = 30)。除糖过程的细节无助于理解所讨论的问题,因此在这个答案中我将避免使用表格结构糖。

在 Lua 中,一个新构建的表最初将所有可能的值与 nil关联起来。这意味着对于表 t = {}t[2]将评估为 nilt[100]将评估为 nilt["foo"]将评估为 nilt[{}]将评估为 nil,等等。

构造完成后,您可以通过 设定在某个键处变换表的值。然后该键现在将与该值关联。例如,在设置 t["foo"] = "bar"之后,键 "foo"现在将与值 "bar"关联。因此,t["foo"]现在将评估为 "bar"

nil设置为某个键将使该键与 nil相关联。例如,在设置 t["foo"] = nil之后,"foo"将(再次)与 nil关联。因此,t["foo"]将(再次)评估为 nil

虽然任何键都可以关联到 nil(最初所有可能的键都是) ,但是这样的条目(键/值对)不被认为是 a part of the table(即不被认为是表 内容的一部分)。

函数 pairsipairs(以及其他多个函数)对表的内容进行操作,即值不是 nil的关联的操作。这种关联的数量总是有限的。

记住上面所说的一切,当你说“从表中删除一个条目”时,将键与 nil关联可能会完成你所期望的所有事情,因为 t[k]将计算为 nil(就像它在构建表之后所做的那样) ,而像 pairsipairs这样的函数将忽略这些条目,因为值为 nil的条目(关联)不被认为是“表的一部分”。

序列

(如果桌子还不够棘手的话)

在这个答案中,我讨论的是表 一般来说,即对它们的键没有任何假设。在 Lua 中,具有特定键集的表可以称为 序列,并且可以使用 table.remove从这种表中删除整数键。但是,首先,这个函数对于非序列(即一般的表)是有效的未定义的,其次,没有理由假设它不仅仅是一个 util,也就是可以在 Lua 中使用原语操作直接实现的东西。

哪些表是序列,哪些表不是序列,这是另一个棘手的话题,我在这里就不详述了。

引用参考文献

(我真的不是瞎编的)

上面所说的一切都是基于官方的 语言参考手册语言参考手册。有趣的部分主要是 2.1 – Values and Types章..。

表可以是异构的; 也就是说,它们可以包含所有类型的值(除了 nil)。任何值为 nil 的键都不被认为是表的一部分。相反,不属于表的任何键都有一个相关联的值 nil。

这部分用词不够完美。首先,我发现“表可以是异构的”这个短语令人困惑。这是这个术语在引用中的唯一用法,而“ can be”部分使得它不那么明显,无论是“ being different”是一个表的 有可能属性,还是表的 定义属性。第二句使第一种解释更加合理,因为如果“任何值为 nil 的键不被认为是表的一部分”,那么它就意味着“值为 nil 的键”不是矛盾。此外,rawset函数的规范(间接地)为 t[k] = v语法提供了语义,在 6.1-基本功能章中说..。

将 table [ index ]的实际值设置为 value,而不调用任何元方法。Table 必须是一个表,索引任何不同于 nil 和 NaN 的值,并为任何 Lua 值设置值。

由于这里的 nil值不是特殊大小写的,这意味着您需要从 可以 准备好了 t[k]nil。要理解这一点,唯一的方法就是接受从现在开始,那个键将是“一个值为零的键”,因此“将不被认为是表的一部分”(pairs将忽略它,等等) ,并且作为“任何不是表的一部分的键有一个相关的值为零”,t[k]将计算为 nil

整个引用也没有提到从任何其他地方的表中“移除”键或条目。

表格的另一个视角

(如果你讨厌无穷)

虽然我个人认为这个参考模型的透视图很优雅,但我也理解它与其他流行模型不同的事实可能会使推理变得更加困难。

I believe that the following perspective is effectively equivalent to the previous one.

你可以认为..。

  • {}返回一个 空荡荡的表。
  • 如果 t 包含k,则 t[k]计算为 v,否则为 nil
  • 如果表中不包含键 k,则将 t[k] = v 插入物设置为一个新的条目(kv) ,如果 t已经包含键 k,则将 k0设置为这样的条目,最后,作为 k1,如果 vnil,则将 k的条目设置为 k2
  • The content of the table (i.e. what's considered "a part of the table") is the set of all entries from the table

在这个模型中,表不能“包含”nil值。

这就是 没有语言参考如何定义事物,但就我的理解而言,这样的模型明显是等价的。

不要谈论执行细节

(除非你确定你是这个意思)

表中所谓的“散列表部分”(补充了表中所谓的“数组部分”)是 实施细节,除非我们讨论性能或解释特定的未定义或实现定义的行为,否则在我看来,在最好的情况下是令人困惑的,而在最坏的情况下则是完全错误的。

例如,在像这样构造的表中... ... t = {}t[1] = 10t[2] = 20t[3] = 30,数组部分将(可能!)设置为 [10, 20, 30]并设置为 t[2] = nil将“删除”条目(220)“从数组部分”,也可能调整它的大小或将 3 -> 30移动到散列表部分。我不太确定。我这么说只是为了证明我们不想在这里讨论实现细节。