【问题标题】:A data-structure for 1:1 mappings in python?python中1:1映射的数据结构?
【发布时间】:2010-10-26 05:21:16
【问题描述】:

我有一个问题,需要将键与值进行可逆的 1:1 映射。

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

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

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

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

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

  • 我的应用程序要求它应该非常快并且使用尽可能少的内存。
  • 结构必须是可变的,并且强烈希望改变对象不应该导致它变慢(例如强制完全重新索引)
  • 我们可以保证键或值(或两者)都是整数
  • 很可能需要该结构来存储数千甚至数百万个项目。
  • 保证键和值是唯一的,即 len(set(x)) == len(x) for for x in [D.keys(), D.valueies()]

【问题讨论】:

  • 这本词典有多大?您确定两份副本不适合内存吗?

标签: python data-structures


【解决方案1】:

另一种选择是制作一个新的 联合两个字典的类, 一种用于每种查找。那 很可能会很快,但会 使用两倍的内存 单个字典。

不是真的。你测量过吗?由于两个字典都会使用对相同对象的引用作为键和值,因此所消耗的内存将只是字典结构。这比 两倍 少很多,而且无论您的数据大小如何,它都是一个固定数量。

我的意思是不会复制实际数据。因此,您几乎不会花费额外的内存。

例子:

a = "some really really big text spending a lot of memory"

number_to_text = {1: a}
text_to_number = {a: 1}

只存在一个“非常大”字符串的副本,因此您最终只花费了更多的内存。这通常是负担得起的。

如果您没有花费至少足够的内存来存储反向查找哈希表(其中这正是您的“联合两个dicts”解决方案中正在做的事情)。

【讨论】:

  • 我认为这是一个很好的解决方案。但是,维护字典(内存和计算)的开销会加倍,因为现在有两个。我怀疑这个开销与问题的其余部分相比会很小。
  • @Doug:您正在以接近 O(1) 的查找速度来换取维护第二个字典的开销。我看不到另一种不会重复努力的方法。
  • @Doug & nosklo:我只想强调 nosklo 的观点。这个问题是时间和空间之间权衡的经典示例。如果要确保两端的快速查找,则需要维护两个字典。第二个字典是你为反向查找付出的代价。如果空间开销太大,则需要较慢的解决方案。进行快速反向查找的唯一方法是保留一些类型的信息来执行此操作...
  • 顺便补充一句,这不是空间开销的数量级差异,它节省时间的数量级差异。字典的空间保​​持在 O(n)...没有第二个字典的反向查找也是 O(n),但改进为 O(1)...现在这都是理论上的,我们可能关心不变的因素 - 但我只是说这对我来说似乎是一个公平和好的权衡。
  • text_to_number = {v: k for k, v in number_to_text.items()}
【解决方案2】:
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]

【讨论】:

  • 这个类在这个例子中失败了:{1 : 2, 2 : 4}一个逆向方法必须被实现,恕我直言。
  • @PaulPichaureau 这不是 1:1 映射,您正在考虑更通用的 可逆 字典。在双射 (1:1) 情况下,如果输入了 1 -> 2,那么假设 2 -> 1 也必须是。您不能在不违反先决条件的情况下添加然后添加2 -> 4。可以在add() 中添加一个简单的检查,例如if (k in self.d or v in self.d): # drop or throw
  • if (k in self.d and self.d[k] != v): self.d.pop(self.d[k])
  • 如果 domain 和 codomain 不同或有不同的类型,这不起作用。
  • 公平地说,这是将确切的空间量存储为两个字典实现:p
【解决方案3】:

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

不是真的,因为他们只是持有对相同数据的两个引用。在我看来,这不是一个糟糕的解决方案。

您是否考虑过内存数据库查找?我不确定它的速度会如何比较,但在关系数据库中的查找速度可能非常快。

【讨论】:

  • 2-dicts 类是迄今为止最好的!
【解决方案4】:

这是我自己解决这个问题的方法:http://github.com/spenthil/pymathmap/blob/master/pymathmap.py

我们的目标是使其对用户尽可能透明。唯一引入的重要属性是partner

OneToOneDict 的子类来自 dict - 我知道 isn't generally recommended,但我认为我已经涵盖了常见的用例。后端非常简单,它 (dict1) 为“合作伙伴”OneToOneDict (dict2) 保留一个弱引用,这是它的倒数。当dict1 被修改时,dict2 也会相应地更新,反之亦然。

来自文档字符串:

>>> 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'

当我有空闲时间时,我打算制作一个不会两次存储东西的版本。不知道什么时候会这样:)

【讨论】:

    【解决方案5】:

    假设您有一个键来查找更复杂的可变对象,只需将该键设为该对象的属性即可。看起来你最好稍微考虑一下数据模型。

    【讨论】:

    • 在这种情况下我不能 - 一侧的对象是 numpy.int64s - 应用程序的目的是使一个非常简朴的数字图论类适应看起来更自然的 Pythonic。
    • 在这种情况下,轻量级就可以了。
    【解决方案6】:

    “我们可以保证键或值(或两者)都是整数”

    这写得很奇怪——“键或值(或两者)”感觉不对。要么它们都是整数,要么它们不都是整数。

    听起来他们都是整数。

    或者,听起来您正在考虑用整数值替换目标对象,因此您只有一个整数引用的副本。这是一种虚假的经济。只保留目标对象。所有 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) tuples。

    一个“插入”变成两个插入,每个插入一个堆结构列表。

    一个键查找在一个队列中;值查找在另一个队列中。使用bisect(list,item) 进行查找。

    【讨论】:

    • 这是一个相当明确的声明:每个键/值对中至少有一个项目是整数,有时两者都是整数。
    • 为什么是迂回声明?为什么不直接列出所涉及的数据类型?逻辑可能很清楚,但对算法设计毫无用处。 “非此即彼”通常是异或。但是“或两者”意味着它是一个包容性的或。这意味着任何类型的组合(2 个非整数除外)都是有效的。让优化变得困难。
    【解决方案7】:

    碰巧我发现自己一直在问这个问题(尤其是昨天)。我同意制作两本词典的方法。做一些基准测试,看看它占用了多少内存。我从来不需要让它可变,但如果它有任何用处,我是这样抽象它的:

    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')]
    

    【讨论】:

      【解决方案8】:

      使用 sqlite 怎么样?只需创建一个带有两列表的 :memory: 数据库。您甚至可以添加索引,然后通过任一索引进行查询。如果您将经常使用它,请将其包装在一个类中。

      【讨论】:

      • 根据要求,使用 DB 进行此查找可能会比双字典花费更多的内存和 CPU 周期。
      • 就我而言,这太慢了!
      猜你喜欢
      • 2010-09-20
      • 2012-06-25
      • 2018-02-05
      • 2010-09-30
      • 2012-05-09
      • 1970-01-01
      • 1970-01-01
      • 2017-08-17
      • 2011-10-21
      相关资源
      最近更新 更多