如何在 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 中的二维表上创建迭代器?的主要内容,如果未能解决你的问题,请参考以下文章

通过Lua迭代器自定义实现对c#集合的遍历

lua自定义迭代器

lua 10 迭代器

lua 迭代器 iterator

双向链表上的 Java 迭代器

雷林鹏分享:Lua 迭代器