kikito 的回答不错,但有一些缺陷:
- 如果您执行两次
t[{a=1}] = true,store 将包含两个表(散列表的生命周期内存泄漏)
- 一旦你已经存储了值,修改它就不起作用,你也不能删除它。尝试更改它可能会导致检索返回您过去分配给该键的任何值。
- 访问性能是 O(n)(n 是存储条目的数量,假设从表中检索 lua 的值是 O(1));结合第一点,这个哈希表的性能会随着使用而下降
(另请注意,如果任何表具有循环引用,kikito 的“等效”函数将导致无限循环。)
如果您永远不需要更改/删除表格中的任何信息,那么 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 的评论,但我目前没有这样做的声誉。)