代码之家  ›  专栏  ›  技术社区  ›  Salim Fadhley

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

  •  30
  • Salim Fadhley  · 技术社区  · 17 年前

    我有一个问题,它需要键到值的1:1可逆映射。

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

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

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

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

    有没有更好的结构我可以使用?

    • 我的应用程序要求这应该非常快,并且使用尽可能少的内存。
    • 结构必须是可变的,强烈希望改变对象不会使其变慢(例如强制完全重新索引)。
    • 我们可以保证键或值(或两者)都是整数
    • 很可能需要这个结构来存储数千个或数百万个项目。
    • 键和值保证是唯一的,即len(set(x))==len(x)for x in[d.keys(),d.valuies()]
    8 回复  |  直到 16 年前
        1
  •  9
  •   Murali VP    17 年前
    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
  •  27
  •   nosklo    17 年前

    另一种选择是做一个新的 将两个字典组合在一起的类, 每种查找一个。那 很可能很快,但是 占用的内存是 单笔录

    不是真的。你量过了吗?因为这两个词典都使用 相同对象 作为键和值,所用的内存将只是字典结构。这比 两次 并且是一个固定的ammount,无论您的数据大小如何。

    我的意思是实际数据不会被复制。所以你会花一点额外的记忆。

    例子:

    a = "some really really big text spending a lot of memory"
    
    number_to_text = {1: a}
    text_to_number = {a: 1}
    

    只存在一个“真正大”字符串的副本,所以您最终只需要花费多一点内存。这通常是可以负担得起的。

    我无法想象一个解决方案,如果你不花费 至少 足够的内存来存储反向查找哈希表(这正是在“Unite Two”中所做的操作 dict S“解决方案”。

        3
  •  5
  •   Shane C. Mason    17 年前

    另一种选择是创建一个新类,该类将两个字典合并在一起,每种查找一个。这很可能会占用一个听写机两倍的内存。

    不是真的,因为它们只包含对同一数据的两个引用。在我看来,这是一个不错的解决方案。

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

        4
  •  2
  •   Community Mohan Dere    9 年前

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

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

    OneToOneDict 子类 dict -我知道 isn't generally recommended 但是我认为我已经涵盖了常见的用例。后端非常简单,( dict1 )对“合伙人”保持弱势 关于信息技术 ( dict2 )这是相反的。什么时候? 命令1 被修改 命令2 也相应更新,反之亦然。

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

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

        5
  •  1
  •   Pete Kirkham    17 年前

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

        6
  •  1
  •   S.Lott    17 年前

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

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

    听起来它们都是整数。

    或者,听起来您正在考虑用一个整数值替换目标对象,这样您只有一个副本被一个整数引用。这是一个错误的经济。只保留目标对象。所有的python对象实际上都是引用。几乎没有真正的复制完成。

    假设您只有两个整数,并且可以对这对整数中的任何一个进行查找。一种方法是使用堆队列或对分模块来维护整数键值元组的有序列表。

    见 http://docs.python.org/library/heapq.html#module-heapq

    见 http://docs.python.org/library/bisect.html#module-bisect

    你有一堆 (key,value) 元组。或者,如果基础对象更复杂, (key,object 元组。

    你还有一堆 (value,key) 元组。或者,如果基础对象更复杂, (otherkey,object) 元组。

    一个“insert”变成两个insert,每个heapq结构列表一个。

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

        7
  •  1
  •   ShawnMilo    17 年前

    使用sqlite怎么样?只需创建一个带有两列表的:内存:数据库。您甚至可以添加索引,然后按任意一个进行查询。如果你经常用到的话,把它包在课堂上。

        8
  •  0
  •   David Berger    17 年前

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

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