如何在 Lua 中的二维表上创建迭代器?
Posted
技术标签:
【中文标题】如何在 Lua 中的二维表上创建迭代器?【英文标题】:How do I make an iterator over a 2-dimensional table in Lua? 【发布时间】:2012-10-29 08:04:46 【问题描述】:我有一个由表组成的 lua 表,所以它是二维的:根 -> 子 -> 孙子。
此层次结构中的任何级别都不能保证是“类数组”。第一级具有“零间隙”的整数,第二级甚至不按整数索引(而是按表)。
有问题的表是库中的私有结构。我想为图书馆用户提供一种解析其孙辈的方法。我不太关心它们被解析的顺序,只要它们都是。
我首先想到的是使用一个接受回调的函数:
-- this scope has access to root
function eachGrandChild(callback)
for _,child in pairs(root) do
for index,grandChild in pairs(child)
callback(index, grandChild)
end
end
end
用法:
-- no access to root, only to eachGrandChild
eachGrandChild(function(index, grandChild) print(index, grandChild) end)
这么多就明白了。
我的问题是:我可以使用iterator 来提供类似的功能吗?
我说的是可以让我这样做的东西:
for index,grandChild in iterator() do
print(index, grandChild)
end
我一直在考虑这个问题,但我无法破解它。我见过的所有示例都使用数字在每次迭代中轻松“管理迭代器的状态”。由于我没有数字,所以我有点卡住了。
【问题讨论】:
【参考方案1】:Coroutines 使编写这种迭代器变得容易。协程是一个可以暂停和恢复执行的函数,在概念上类似于线程。协程可以包含深度嵌套的循环,从最内层循环产生一个值,然后在恢复时从中断处继续。当它 yield 时,恢复它的调用者可以接收到 Yield 值。
在您的情况下,将 eachGrandChild
转换为 yields 孙子的生成器函数。
function eachGrandChild(root)
for _,child in pairs(root) do
for index,grandChild in pairs(child) do
coroutine.yield(index, grandChild)
end
end
end
然后使用coroutine.wrap
创建一个函数,该函数将为您的生成器创建一个协程并在每次调用该函数时恢复它。
function grandChildren(t)
return coroutine.wrap(function() eachGrandChild(t) end)
end
现在你有了你的迭代器:
for key, val in grandChildren(root) do
print(key, val)
end
There's a chapter on this in Programming in Lua.
【讨论】:
很棒的答案。我说的“管理函数状态”部分实际上是在大喊“协程!”对我来说,但我没有把这些点联系起来。我有点担心执行速度(这个解析循环每帧会执行很多次,所以它必须相当快)但我会做一些测试。 @kikito:您的直接回调方法肯定会更有效,部分原因是函数调用开销(resume、yield)减少了。但它并没有你想象的那么糟糕(Lua 的协程是very well written)。例如,如果您对协程实现与 prapin 的实现进行基准测试,我怀疑您会发现协程更快。 你做对了。即使每次创建一个新的匿名函数,回调代码也是最快的。协程大约需要 200% 的时间来完成相同的工作,而基于 next 的解决方案大约需要 220%。所以我想我会坚持回调。总之这是一次很好的学习体验! :) 请记住,premature optimization is the root of all evil。是的,200% 可能听起来很糟糕,但我们谈论的是几微秒的差异。您是否每帧调用数千次? “过早的邪恶是所有优化的根源”:D 我知道这句话。是的,这将被每帧调用数千次——它将成为碰撞库的核心(root
实际上是一个空间索引)【参考方案2】:
我同意 Mud 的观点,即协程是解决问题的最佳方法。
作为记录,为了比较,我写了一个没有协程的迭代器。
为每个元素调用第一个函数eachGrandChild
。它使用state
变量,其中包含两个索引(***和二级)。
function eachGrandChild(state)
while state.childIndex ~= nil do
local child = root[state.childIndex]
state.grandChildIndex = next(child, state.grandChildIndex)
if state.grandChildIndex == nil then
state.childIndex = next(root, state.childIndex)
else
return state.grandChildIndex, child[state.grandChildIndex]
end
end
end
使用辅助函数初始化迭代器:
function grandChildren(root)
return eachGrandChild, childIndex = next(root)
end
现在迭代器可以正常使用了:
for key, val in grandChildren(root) do
print(key, val)
end
与基于协程的版本相比,eachGrandChild
的代码行数更多,更难阅读。
【讨论】:
感谢您的回答。尽管如此,我还是会尝试这两种方法 - 如果稍微长一点,你的方法可能会更有效。如果可能的话,我会宣布这两个答案都是正确的。你绝对值得 +1 我冒昧地重构了迭代器代码 - 名称而不是幻数,一段时间而不是重复直到,以及简化的“孙子用完”条件。我还从状态中删除了root
,因为它被假定在范围内可用。我希望你不介意我编辑你的答案。
@kikito 感谢您的代码编辑。这现在更具可读性。以上是关于如何在 Lua 中的二维表上创建迭代器?的主要内容,如果未能解决你的问题,请参考以下文章