我遇到了一个问题,需要将键的1:1可逆映射到值。
这意味着有时我想找到给定键的值,但有时我想找到给定值的键。键和值都保证唯一。
x = D[y]
y == D.inverse[x]
显而易见的解决方案是每次我想要反向查找时简单地反转字典:反转字典非常简单,there's a recipe here but for a large dictionary it can be very slow。
另一种选择是创建一个新的类,它将两个字典统一起来,每个字典对应一种查找。这很可能很快,但会消耗两倍于单个字典的内存。
那么我可以使用更好的结构吗?
答案 0 :(得分:27)
另一种选择是创造新的 统一两个词典的类, 每种查找一个。那 最有可能很快,但会 用尽了两倍的内存 单一字典。
不是真的。你测量过了吗?由于两个字典都会使用对相同对象的引用作为键和值,因此花费的内存将只是字典结构。这比 两次 少很多,并且无论您的数据大小如何,它都是固定的ammount。
我的意思是不会复制实际数据。所以你要花很少的额外记忆。
示例:
a = "some really really big text spending a lot of memory"
number_to_text = {1: a}
text_to_number = {a: 1}
只存在“真正大”字符串的单个副本,因此您最终只需花费更多内存。这通常是可以承受的。
我无法想象一个解决方案,如果您不花费至少足够的内存来存储反向查找哈希表(其中正是你在“联合两个dict
s”解决方案中所做的一切。
答案 1 :(得分:9)
class TwoWay:
def __init__(self):
self.d = {}
def add(self, k, v):
self.d[k] = v
self.d[v] = k
def remove(self, k):
self.d.pop(self.d.pop(k))
def get(self, k):
return self.d[k]
答案 2 :(得分:5)
另一种选择是创建一个新的类,它将两个字典统一起来,每个字典一个>一种查找。这很可能会占用单个字典的两倍内存。
不是真的,因为他们只会持有两个相同数据的引用。在我看来,这不是一个糟糕的解决方案。
您是否考虑过内存数据库查找?我不确定它在速度上的比较,但关系数据库中的查找可以非常快。
答案 3 :(得分:2)
以下是我自己解决此问题的方法:http://github.com/spenthil/pymathmap/blob/master/pymathmap.py
目标是使其尽可能对用户透明。唯一引入的重要属性是partner
。
OneToOneDict
的 dict
个子类 - 我知道isn't generally recommended,但我认为我已经涵盖了常见用例。后端非常简单,它(dict1
)保持弱对象为“伙伴”OneToOneDict
(dict2
),这是它的反向。修改dict1
后,dict2
也会相应更新,反之亦然。
来自docstring:
>>> dict1 = OneToOneDict()
>>> dict2 = OneToOneDict()
>>> dict1.partner = dict2
>>> assert(dict1 is dict2.partner)
>>> assert(dict2 is dict1.partner)
>>> dict1['one'] = '1'
>>> dict2['2'] = '1'
>>> dict1['one'] = 'wow'
>>> assert(dict1 == dict((v,k) for k,v in dict2.items()))
>>> dict1['one'] = '1'
>>> assert(dict1 == dict((v,k) for k,v in dict2.items()))
>>> dict1.update({'three': '3', 'four': '4'})
>>> assert(dict1 == dict((v,k) for k,v in dict2.items()))
>>> dict3 = OneToOneDict({'4':'four'})
>>> assert(dict3.partner is None)
>>> assert(dict3 == {'4':'four'})
>>> dict1.partner = dict3
>>> assert(dict1.partner is not dict2)
>>> assert(dict2.partner is None)
>>> assert(dict1.partner is dict3)
>>> assert(dict3.partner is dict1)
>>> dict1.setdefault('five', '5')
>>> dict1['five']
'5'
>>> dict1.setdefault('five', '0')
>>> dict1['five']
'5'
当我获得一些空闲时间时,我打算制作一个不会存储两次的版本。不管怎么说,不管怎么说:)
答案 4 :(得分:1)
假设您有一个用于查找更复杂的可变对象的键,只需将该键设为该对象的属性即可。看起来你可能会更好地考虑一下数据模型。
答案 5 :(得分:1)
“我们可以保证密钥或值(或两者)都是整数”
这是奇怪的写 - “关键或价值(或两者)”感觉不对。要么它们都是整数,要么它们不是全部整数。
听起来他们都是整数。
或者,听起来您正在考虑用整数值替换目标对象,因此您只有一个整数引用的副本。这是一种虚假的经济。只需保留目标对象。所有Python对象实际上都是引用。实际复制很少。
让我们假设您只有两个整数,并且可以对其中任何一个进行查找。一种方法是使用堆队列或bisect模块来维护整数键值元组的有序列表。
请参阅http://docs.python.org/library/heapq.html#module-heapq
请参阅http://docs.python.org/library/bisect.html#module-bisect
你有一个heapq (key,value)
元组。或者,如果您的基础对象更复杂,则为(key,object
)元组。
你有另一个heapq (value,key)
元组。或者,如果您的基础对象更复杂,(otherkey,object)
元组。
“insert”变为两个插入,每个heapq结构列表一个。
密钥查找在一个队列中;值查找位于另一个队列中。使用bisect(list,item)
进行查找。
答案 6 :(得分:1)
使用sqlite怎么样?只需创建一个:memory:带有两列表的数据库。您甚至可以添加索引,然后通过任一方查询。如果你要经常使用它,请把它包装在课堂上。
答案 7 :(得分:0)
碰巧我发现自己一直在问这个问题(特别是昨天)。我同意制作两本词典的方法。做一些基准测试,看看它需要多少内存。我从来不需要让它变得可变,但这是我如何抽象它,如果有任何用途:
class BiDict(list):
def __init__(self,*pairs):
super(list,self).__init__(pairs)
self._first_access = {}
self._second_access = {}
for pair in pairs:
self._first_access[pair[0]] = pair[1]
self._second_access[pair[1]] = pair[0]
self.append(pair)
def _get_by_first(self,key):
return self._first_access[key]
def _get_by_second(self,key):
return self._second_access[key]
# You'll have to do some overrides to make it mutable
# Methods such as append, __add__, __del__, __iadd__
# to name a few will have to maintain ._*_access
class Constants(BiDict):
# An implementation expecting an integer and a string
get_by_name = BiDict._get_by_second
get_by_number = BiDict._get_by_first
t = Constants(
( 1, 'foo'),
( 5, 'bar'),
( 8, 'baz'),
)
>>> print t.get_by_number(5)
bar
>>> print t.get_by_name('baz')
8
>>> print t
[(1, 'foo'), (5, 'bar'), (8, 'baz')]