Lua,自定义迭代器 - 定义的正确方法?

问题描述 投票:0回答:3

我正在处理大量用 Lua 编写的数据文件。大部分都是这样写的,以“电话本”为例:

data = {
    -- First Level - country
    USA = {
        -- Second level - city
        Denver = {
            -- Third level - actual entries
            {name = 'John', number = '12345'},
            -- more entries
        },
        Washington = {
            {name = 'Ann', number = '54321'},
            -- more entries
        },
        -- more cities with entries
    },
    -- more countries with cities and entries
}

因此,第一级是“国家/地区”,第二级是“城市”的事实是隐含的,但它使数据更加紧凑。

现在,当实际搜索某些数据时,我想将此数据作为条目进行迭代包括这种分级的隐式信息

-- Coroutine yielding entries including level data
function corIter(data)
    for country,l1 in pairs(data) do
        for city,l2 in pairs(l1) do
            for _,entry in pairs(l2) do
                -- Copy the entry
                local out = {}
                for k,v in pairs(entry) do
                    out[k] = v
                end
                -- Add level properties
                out.country = country
                out.city = city
                coroutine.yield(out)
            end
        end
    end
end

-- Iterate over the entries
local cor = coroutine.create(corIter)
local _, entry = coroutine.resume(cor, data)
while entry do
    -- Handle the entry, has 'name', 'number', 'country' and 'city' keys
    table.print(entry) -- (custom print function I use)

    -- Get the next one
    _, entry = coroutine.resume(cor)
end  

但我认为这种方法可能很糟糕,因为它使整个线程保持活动状态,只是为了以特定的方式迭代该死的表。

还有其他“明显”的解决方案吗?关键是性能和易用性。我并不完全需要一个通用的解决方案(对于数据表内任意数量的“级别”),但这一切都像黑客一样。

lua iterator
3个回答
1
投票
local function phones(d)
   local cn, c, tn, t, i
   return
      function()
         local a
         repeat
            if tn then
               a, i = t[i], i+1
               if not a then
                  i, tn, t = 1, next(c, tn)
               end
            else
               cn, c = next(d, cn)
               i, tn, t = 1, next(c or {})
            end
         until a or not cn
         return cn, tn, a
      end
end

for country, town, abonent in phones(data) do
   print(country, town, abonent.name, abonent.number)
end

1
投票

您可以在 Lua 中创建自己的自定义迭代器,无需使用协程。迭代器是调用时返回结构中的下一个元素的函数(您可以使用任何您想要的结构)。

您的示例的迭代器将是这样的:

function corIter(data)
    local itOut = {}

    for country,l1 in pairs(data) do
        for city,l2 in pairs(l1) do
            for _,entry in pairs(l2) do
                -- Copy the entry
                local out = {}
                for k,v in pairs(entry) do
                    out[k] = v
                end

                out.country = country
                out.city = city

                table.insert(itOut,out)
            end
        end
    end

    local i = 0
    return function()
        i = i + 1
        return itOut[i]
    end
end

“corIter”返回的匿名函数将返回数据中的下一个元素。请注意,当我们使用“对”将条目复制到另一个表以迭代它们时,没有任何东西可以保证条目的顺序将保持为原始顺序。

现在您可以使用此代码来打印条目:

for entry in corIter(data) do
    print(entry) -- this is a table
    for k,v in pairs(entry) do
        print(k,v) -- these are the actual values
    end
end

0
投票

@Egor 的答案非常好,因为它定义了 a lua iterator 而不是将数据收集到表中。迭代器有几个好处:

  • 中止迭代,无需支付额外费用。当找到第一个匹配项时,您并不总是遍历整个数据集。
  • 对数据变化更具弹性。如果迭代更改了另一个客户的电话号码,我们将不会返回过时的数据。
  • 不需要创建很多临时表。气相色谱压力较小。

但是,我发现它太简洁了,无法理解。我对其进行了重新设计,以使用更具描述性的名称和更少的复合语句。

local function phones(phonebook)
    local country_name, country_t, city_name, city_t, i
    return function()
        local client_t
        repeat
            if city_name then
                client_t = city_t[i]
                i = i+1
                if not client_t then
                    i = 1
                    city_name, city_t = next(country_t, city_name)
                end
            else
                country_name, country_t = next(phonebook, country_name)
                i = 1
                city_name, city_t = next(country_t or {})
            end
        until client_t or not country_name
        return country_name, city_name, client_t
    end
end

local data = {
    USA = {
        Denver = {
            {name = 'John', number = '12345'},
            {name = 'Johaan', number = '13336'},
        },
        Washington = {
            {name = 'Ann', number = '54321'},
            {name = 'Ana', number = '53132'},
        },
    },
}
for country, town, client in phones(data) do
    print(country, town, client.name, client.number)
end

输出:

USA Washington  Ann 54321
USA Washington  Ana 53132
USA Denver  John    12345
USA Denver  Johaan  13336
© www.soinside.com 2019 - 2024. All rights reserved.