python中1:1映射的数据结构?

时间:2009-05-14 15:14:19

标签: python data-structures

我遇到了一个问题,需要将键的1:1可逆映射到值。

这意味着有时我想找到给定键的值,但有时我想找到给定值的键。键和值都保证唯一。

x = D[y]
y == D.inverse[x]

显而易见的解决方案是每次我想要反向查找时简单地反转字典:反转字典非常简单,there's a recipe here but for a large dictionary it can be very slow

另一种选择是创建一个新的类,它将两个字典统一起来,每个字典对应一种查找。这很可能很快,但会消耗两倍于单个字典的内存。

那么我可以使用更好的结构吗?

  • 我的应用程序要求它应该非常快并尽可能少地使用内存。
  • 结构必须是可变的,并且强烈希望变异对象不应该导致它变慢(例如强制完整的重新索引)
  • 我们可以保证键或值(或两者)都是整数
  • 可能需要使用该结构来存储数千或数百万件物品。
  • 钥匙& Valus保证是唯一的,即len(set(x))== len(x)代表[D.keys(),D.valuies()]中的x

8 个答案:

答案 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)保持弱对象为“伙伴”OneToOneDictdict2),这是它的反向。修改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')]