- [!] 版本说明: 本文描述的问题已得到解决。从 5.0 版本开始,n 值不再用于表格列表组件的大小计算。
问题
表格包含成员 "n" 作为表格插入的优化。例如,从手册中:-
- getn (table)
- 返回表格作为列表时的大小。如果表格具有数值型的
n
字段,则此值即为表格的大小。否则,大小为表格中具有非空值的最大的数值索引。此函数可以在 Lua 中定义
function getn (t)
if type(t.n) == "number" then return t.n end
local max = 0
for i, _ in t do
if type(i) == "number" and i>max then max=i end
end
return max
end
由于表格的双重性质,即它们可以同时是列表和字典,"n" 可能会与表格中的用户数据冲突。以下部分列出了解决此问题的几种方法。请随时在解决方案旁边发表评论或提出您自己的解决方案。请留下您的姓名或首字母,以便统计 "投票"。
这可以更好地定义。n 字段不是 "表格大小",也不用于 "表格插入"。(当然,Lua API 函数名称和文档混淆了这个问题。)表格是 Lua 中的纯数据类型,而列表则不是。在表格数据类型之上实现列表有几种方法。由于列表是一种重要的数据类型,即使在 VM 本身中也需要(用于可变参数),标准库提供了一种实现,包括使用 n 字段表示列表大小以及用于确定任意表格的 "列表组件" 的算法(这就是 getn
在没有 n 字段时所做的)。
替代解决方案
这不是问题,保留它。
您的编程风格可能不需要在同一个表格中混合列表和字典,因此这可能不是问题。
- 这不是我的编程风格不需要在同一个表格中混合向量和字典;而是我认为在同一个表格中混合不受控制的键和受控制的键不是一个好主意。迟早它会咬你一口,无论受控制的键被称为 n 还是 __n__ 还是 ____NUMBER_OF_NUMERIC_KEYS____。我不喜欢像 __n__ 这样的名称,既从美学角度,也从 "通过不可能避免" 只是创造更难找到的错误的角度来看。混合受控制的键(例如槽名称)是另一个问题,但我没有问题不将我的槽称为 "n"。此外,开始进行会造成向后不兼容的更改并非易事。-- RiciLake
- 所以,基本上你不想破坏向后兼容性,你发现因为表的大小被命名为“n”,任何冲突的 bug 都很容易找到,而且你也不介意你的表大小始终被称为“n”。为什么不消除这个问题,正如你所说,“迟早会咬你”,而且你可以灵活地命名?我不记得看到过任何使用 n 设置表大小的代码(所以没有向后兼容问题?)。--NDT
- 我认为这总结了它 :-)。如果我使用一个表作为向量,我不会把它用作字典,尽管我可能会在里面放我自己的键。在这种情况下,我只是不使用键
n
或任何数字键。这与将键存储在任何以表实现的对象中没有什么不同;你必须避免使用对象的定义键,这些键应该是有文档记录的。碰巧的是,我确实有使用 n
设置表大小的代码——除非你已经看过所有 Lua 代码,否则我认为你不能轻率地声称没有向后兼容问题。无论如何,我认为使用 vec.n
而不是 getn(vec)
来检索表大小是很常见的,因为如果你能确保键存在(甚至 vec.n or getn(vec)
),这会快得多。-- RiciLake
- “希望有文档记录” :-) 正是这种混乱可以避免。表是多用途的,可以作为列表或字典使用,这意味着不应该应用这种类型的限制。我个人宁愿忍受修复可能被此破坏的代码。我认为 Lua 作为一种语言还处于起步阶段,它的根源在于方便的嵌入和配置。它仍然需要解决一些小问题,比如这个,才能被认为是一种严肃的脚本语言。我不确定成为“Python 杀手”是否是它的设计目标,但我相信作者热衷于看到语言及其用户群的发展。随着 Lua 的改进,这种情况将继续发生。:-) --NDT
- 如果你不喜欢 getn,定义你自己的 getn、tinsert 和 tremove 版本。就这样。只有一个地方使用了 'n':在 vararg 函数的 arg 表中;只需说 arg 不是列表而是表 ;-) 但请参见下面的另一个想法 (len[t])。-- ET
- 公平地说,还有其他几个地方,其中一个是
call
。但是,编写替换函数肯定很容易,而且没有人强迫你使用 tinsert 和 tremove -- RiciLake
- 我的观点更像是“这确实是一个问题,就这样吧”。我还没有看到一个好的解决方案。
setn
的想法是在一个 hack 之上的 hack。--JohnBelmonte
它应该被重命名
“n”变量应该重命名为不太可能冲突的名称,例如“__n__”。
- 任何直接的命名空间入侵都是丑陋的,并且在一定程度上缺乏通用性,这可能会损害现实世界的功能。例如,如果有人想使用表的预映射域作为操作系统 shell 的环境变量,那么您将不得不以某种方式限制名称以排除用于表大小的任何名称。在内部,没有理由让这个名称成为合法的 Lua 变量名。因此,实际上,NULL 指针或其他哨兵将是更好的选择,这样它就不会入侵 Lua 变量命名空间。这可能会导致代码中出现一些异常,但是由于它们与非法的可能值重叠,因此这些异常应该在今天得到检查。--Paul Hsieh
- 使用 table[0]。无论选择什么任意的非数字名称,都有可能侵入表的命名空间,以用于非列表用途。但是,由于表已经包含一个列表,它已经拒绝了所有正整数作为索引(table[1]、table[2] 等),因此在其中添加零 (0) 不会造成太多额外入侵;远不如 'n'。-- PeterHill
setn()
setn()
将是一个更好的解决方案,可以补充 getn()
。
- 我认为这是最好的选择,它消除了任何冲突的可能性,并允许在实际的底层实现中进行一些变化。--Tom Wrensch
- 这是我最喜欢的 - 将其设置为一个无法通过正常索引访问的值... 这可以在内部通过弱表实现 - 也许甚至可以公开该表,尽管我认为 setn() 函数在可读性方面会更好。--PeterPrade
- 这似乎是一个更好的方法。我不喜欢将任意东西强加于我的想法,无论它可能有多容易编码。--Terence Martin
- 可能是最好的方法。--LavergneThomas?
- 我也喜欢这种方法。我厌倦了在我的 foreach() 循环中绕过 'n'。补充 getn() 是有道理的。下面的方法非常聪明,我当然可以接受它,但我认为它可能对新用户来说不太直观。
- 我认为这是最好的解决方案,因为我真的认为添加“内部”字段非常危险。例如,想象一下有一个表包含文件中找到的所有空格分隔字符串。这种“隐藏”字段非常危险,即使以“不太可能”的方式命名也是如此。-- Vincent Penquerc'h
len[t]
- 好的,这将适用于 Lua 4.1,其中您有弱表(来自 Luiz Carlos Silveira 的基本想法):将列表(或任何其他对象)的长度存储在一个全局表中,例如名为
len
。要访问对象 x
的长度,只需编写 len[x]
而不是 x.n
或 getn(x)
/setn(x,n)
。您甚至可以将索引标记方法设置为 len
,以便在长度不存在时计算长度。而且,如果您担心可能会输入 () 而不是 [],请也设置调用方法 ;-) -- ET
- 我也喜欢这个想法的巧妙之处。它有许多优点:它不会在列表的内部或可见实现中留下任何杂乱,因此您可以遍历纯列表,而无需担心忽略
n
、__n__
或 whatever
键。表查找比过程调用更快,因此它可能比当前实现更快。此外,它向后兼容没有 index
标记方法的表
settagmethod(tag({}), "index",
function(v, k) if k == "n" then return len[v] end end)
- 所以,我愿意改变我的投票 --RiciLake
- 这也与
getn
和 setn
兼容;如果您想要这样做,只需包含
function getn(v) return len[v] end
function setn(v, n) len[v] = n end
- 我想将我的投票改为这个。一个弱键长度表将从表中分离
n
并使表“纯净”。正如 John 指出的,列表是表的用法,而此实现将列表用法与表分离。getn()
/setn()
仍然可以使用。也许 len[x]
应该更改为 tlen[x]
以与 tinsert()
/tremove()
保持一致。-- Nick Trout
- len[] 是否将全局名称映射到整数?或者它是否将对象域映射到整数?我是 Lua 新手,所以如果我理解错了,请随意编辑此评论或我的其他评论,尽情发挥。--Paul Hsieh Paul:len[] 是一个映射,它将映射对象(作为弱键)映射到它们的“列表长度”(如果它们以前被视为列表,即 getn() 或 tinsert() 等已使用此表对象作为参数调用)-- PeterPrade
- 为这个添加全局变量的事实本身让我觉得这与在表中添加隐藏字段是一样的,尽管侵入性较小。当然,添加 setn 也会引入一个全局变量,但我认为它不是一个 hack,因为它是一个 API 入口,而不是一个用来摆脱你想放在某个地方的尴尬事物的场所。-- Vincent Penquerc'h
请添加任何其他解决方案...
实现一个不可覆盖的 getnEx() 函数
- 我不理解想要将表格大小设置为与实际大小不同的值的愿望。我建议改为在实现中跟踪每个表格的未公开的真实最大整数域成员,该成员由一个名为 getnEx() 的函数跟踪。然后为了向后兼容,可以将 getn() 实现为
function getn (t)
if type(t.n) == "number" then return t.n end
return getnEx(t) -- internal function
end
如果 getn() 函数导致问题,人们可以忽略它,直接使用 getnEx() 函数,该函数不需要变通方法。getnEx() 本质上等同于
function getnEx (t)
local max = 0
for i, _ in t do
if type(i) == "number" and i>max then max=i end
end
return max
end
--Paul Hsieh
投票结果
请更新下面的列表。如果您希望匿名投票,只需在下面添加投票(但听到您的意见会很好 :-)。
- 保持原样 : 1 票。
- 更改“n” : 1 票
- 实现一个不可覆盖的 getnEx() 函数 : 1 票
- 添加
setn()
: 12 票
- 添加
len[v]
: 5 票
最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2019 年 3 月 8 日下午 10:27 GMT (差异)