Lua:如何在表中查找键(或对象)

时间:2012-02-08 21:20:12

标签: lua hashtable lua-table

我想存储一个lua表,其中键是其他lua表。我知道这是可能的但我希望能够使用这些表的副本在表中查找。具体来说,我希望能够做到:

t = {}
key = { a = "a" }
t[key] = 4
key2 = { a = "a" }

然后我希望能够查找:

t[key2]

并获得4。

我知道我可以将key转换为字符串并将其放入表格t。我还考虑过编写自定义哈希函数或者通过嵌套表来实现这一点。有没有最好的方法让我获得这种功能?我还有其他选择吗?

6 个答案:

答案 0 :(得分:6)

在Lua中,单独创建的两个表被视为“不同”。但是如果你创建一个表,你可以将它分配给你想要的任何变量,当你比较它们时,Lua会告诉你它们是平等的。换句话说:

t = {}
key = { a = "a" }
t[key] = 4
key2 = key
...
t[key2] -- returns 4

所以,这就是做你想做的简单,干净的方式。将key存储在某处,以便您可以使用它来检索4。这也很快。

如果你真的不想这样做......那么,有一种方法。但它效率低下且难看。

第一部分是制作一个比较两个独立表格的函数。如果两个表是“等价的”,它应该返回true,否则返回false。我们称之为等价物。它应该像这样工作:

equivalent({a=1},{a=1})          -- true
equivalent({a=1,b=2}, {a=1})     -- false
equivalent({a={b=1}}, {a={b=2}}) -- false

该函数必须是递归的,以处理包含表本身的表。如果其中一个表“包含”另一个表但具有更多元素,也不能被愚弄。我出来了这个实现;可能那里有更好的。

local function equivalent(a,b)
  if type(a) ~= 'table' then return a == b end

  local counta, countb = 0, 0

  for k,va in pairs(a) do
    if not equivalent(va, b[k]) then return false end
    counta = counta + 1
  end

  for _,_ in pairs(b) do countb = countb + 1 end

  return counta == countb
end

我不打算在这里解释这个功能。我希望它足够清楚它的作用。

该难题的另一部分包括在比较键时使t使用equivalent函数。这可以通过仔细的元表操作和额外的“存储”表来完成。

我们基本上将t转换为冒名顶替者。当我们的代码告诉它在键下存储一个值时,它不会将它保存在自身中;相反,它将它提供给额外的表(我们称之为store)。当代码向t询问某个值时,它会在store中搜索该值,但会使用equivalent函数来获取该值。

这是代码:

local function equivalent(a,b)
... -- same code as before
end

local store = {} -- this is the table that stores the values

t = setmetatable({}, {
  __newindex = store,
  __index = function(tbl, key)
    for k,v in pairs(store) do
      if equivalent(k,key) then return v end
    end
  end
})

用法示例:

t[{a = 1}] = 4

print(t[{a = 1}]) -- 4
print(t[{a = 1, b = 2}]) -- nil

答案 1 :(得分:2)

这在Lua中是不可能的。如果使用表作为键,则键是表的特定“实例”。即使您使用相同的内容创建不同的表,实例也是不同的,因此它是一个不同的键。

如果你想做这样的事情,你可以创建一种哈希函数,它遍历表作为一个键(如果需要甚至可以递归),并构造表内容的字符串表示。它不需要是人类可读的,只要它对于不同的内容是不同的并且对于具有相同内容的表是相同的。除了使用pairs()遍历表之外,您还需要将键插入表中并使用table.sort()对其进行排序,因为pairs()以任意顺序返回它们,并且您需要“等于”表格的相同字符串。

构建完此类字符串后,您可以将其用作键:

function hash(t) ... end
t = {}
key1 = { a = "a", b = "b" }
t[hash(key1)] = 4
key2 = { a = "a", b = "b" }
print(t[hash(key2)]) -- should print "4" if the hash function works correctly

在我看来,对于索引的简单任务来说,这一切都太复杂了,您可能想要重新考虑使用表副本进行索引的愿望。你为什么想要这样的功能?

<强>更新

如果你只需要使用短语,我认为连接它们比创建这样的通用散列函数更容易。如果您需要短语序列,您实际上不需要遍历表并对键进行排序,只需从每个短语中收集主要信息。您仍然需要使用辅助函数,它可以为您创建合适的密钥:

function pkey(...)
    local n, args = select('#', ...), { ... }
    for i=1,n do args[i] = args[i].value end -- extract your info here
    return table.concat(args, ' ') -- space or other separator, such as ':'          
end
tab[pkey(phrase1, phrase2, phrase3)] = "value"

答案 2 :(得分:1)

kikito的答案很好,但有一些缺陷:

  • 如果执行t[{a=1}] = true两次,store将包含两个表(在哈希表的生命周期内泄漏内存)
  • 一旦您已经存储了值,修改该值并不起作用,也无法将其删除。试图更改它将导致检索有效地返回您过去分配给该密钥的任何值。
  • 访问性能是O(n)(n是存储条目的数量,并假设从表中检索lua的值为O(1));结合第一点,此哈希表的性能将随着使用
  • 而降低

(另请注意,如果任何表具有循环引用,kikito&#34; s&#34;等效&#34;函数将导致无限循环。)

如果您永远不需要更改/删除表格中的任何信息,那么kikito的答案就足够了。否则,必须更改元表,以便__newindex确保表格不存在:

t = setmetatable({}, {
    __newindex = function(tbl, key, value)
        for k,v in pairs(store) do
            if equivalent(k,key) then
                tbl[k] = value
                return
            end
        end
        store[key] = value
    end,
    __index = function(tbl, key)
        for k,v in pairs(store) do
            if equivalent(k, key) then return v end
        end
    end
})

正如您所建议的那样,完全不同的选择是编写自定义散列函数。这是一个可以利用它的HashTable:

local function HashTable(Hash, Equals)
    --Hash is an optional function that takes in any key and returns a key that lua can use (string or number). If you return false/nil, it will be assumed that you don't know how to hash that value.
    --    If Hash is not provided, table-keys should have a GetHash function or a .Hash field
    --Equals is an optional function that takes two keys and specify whether they are equal or not. This will be used when the same hash is returned from two keys.
    --    If Equals is not provided, items should have a Equals function; items are in this case assumed to not be equal if they are different types.
    local items = {} --Dict<hash, Dict<key, value>>
    local function GetHash(item)
        return Hash and Hash(item) or type(item) == "table" and (item.GetHash and item:GetHash() or item.Hash) or item
    end
    local function GetEquals(item1, item2)
        if Equals then return Equals(item1, item2) end
        local t1, t2 = type(item1), type(item2)
        if t1 ~= t2 then return false end
        if t1 == "table" and item1.Equals then
            return item1:Equals(item2)
        elseif t2 == "table" and item2.Equals then
            return item2:Equals(item1)
        end
        return false
    end
    return setmetatable({}, {
        __newindex = function(_, key, value)
            local hash = GetHash(key)
            local dict = items[hash]
            if not dict then
                if value ~= nil then --Only generate a table if it will be non-empty after assignment
                    items[hash] = {[key] = value}
                end
                return
            end
            for k, v in pairs(dict) do
                if GetEquals(key, k) then --Found the entry; update it
                    dict[k] = value
                    if value == nil then --check to see if dict is empty
                        if next(dict) == nil then
                            items[hash] = nil
                        end
                    end
                    return
                end
            end
            --This is a unique entry
            dict[key] = value
        end,
        __index = function(_, key)
            local hash = GetHash(key)
            local dict = items[hash]
            if not dict then return nil end
            for k, v in pairs(dict) do
                if GetEquals(key, k) then
                    return v
                end
            end
        end
    })
end

用法示例:

local h = HashTable(
    function(t) return t.a or 0 end, --Hash
    function(t1, t2) return t1.a == t2.a end) --Equals
h[{a=1}] = 1
print(h[{a=1}]) -- 1
h[{a=1}] = 2
print(h[{a=1}]) -- 2
print(h[{a=1,b=2}]) -- 2 because Hash/Equals only look at 'a'

当然,您希望获得更好的Hash / Equals功能。

只要你的键的哈希很少碰撞,这个类的表现应该是O(1)。

(注意:我已将此答案的上半部分作为对kikito的评论,但我此时并没有声誉。)

答案 3 :(得分:0)

我不确定你能做到这一点。您可以使用metatable定义表的相等性,但是没有办法定义哈希函数,我怀疑单独定义相等性就可以满足您的需求。您显然可以定义相等性,然后使用pairs()对表进行迭代并自己比较键,但这会将O(1)查找到O(n)

答案 4 :(得分:0)

我不太了解语言处理,关于你希望用你的程序达到的目标,但是如何收集这样的令牌呢:使用嵌套表结构,索引表只存储由第一个索引的表短语令牌,然后每个子表包含由第二个短语令牌...等索引的值...直到你到达一个短语最终令牌,将索引一个对应于他出现短语的数字值。

如果你有以下两个短语,也许用例子会更清楚:

  • 我喜欢香蕉。
  • 我喜欢辣妹。

您的索引将具有以下结构:

index["I"] = {
    ["like"] = {
        ["banana"] = 1,
        ["hot"] = {
            ["chick"] = 1
        }
    }    
}

通过这种方式,您可以通过单个遍历步骤计算频率,并在编制索引的同时计算出现次数,但正如我之前所说,它取决于您的目标是什么,它将意味着重新分裂您的短语以便通过索引查找出现的内容。

答案 5 :(得分:0)

kikito's answer 有一个解决方案的开端,但正如 chess123mate's answer 指出的那样,它是只写的(以及其他缺陷)。此解决方案并不能解决所有问题,但这是一个开始。 (它也非常非常慢。)

local function equivalent(stack)
    local a, b = stack.a, stack.b

    if type(a) ~= 'table' then return a == b end
    if a == b then return true end

    local top = stack.next
    while top ~= nil do
        if stack.a == a and stack.b == b then return true end
        top = stack.next
    end

    local counta, countb = 0, 0

    for k,va in pairs(a) do
        if not equivalent({a = va, b = b[k], next = stack}) then return false end
        counta = counta + 1
    end

    for _,_ in pairs(b) do countb = countb + 1 end

    return counta == countb
end

t = setmetatable({}, {
    __newindex = function(tbl, key, value)
        for k, _ in pairs(tbl) do
            if equivalent({a = k, b = key}) then rawset(tbl, k, value); return end
        end
        rawset(tbl, key, value)
    end,
    __index = function(tbl, key)
        for k, v in pairs(tbl) do
            if equivalent({a = k, b = key}) then return v end
        end
    end
})

Lua Playground link(或 GitHub Gist link 用于复制和粘贴到 Playground 中,如果您的浏览器讨厌我的最后一个)。