Python 对称字典,其中d[a][b]==d[b][a]

Python 对称字典,其中d[a][b]==d[b][a],python,inheritance,dictionary,Python,Inheritance,Dictionary,我在python中有一个算法,它为值对创建度量,其中m(v1,v2)==m(v2,v1)(即它是对称的)。我的想法是编写一个字典字典,其中这些值以一种内存高效的方式存储,这样就可以用任意顺序的键轻松检索它们。我喜欢从事物中继承,理想情况下,我喜欢编写一个对称dict,其中s_d[v1][v2]总是等于s_d[v2][v1],可能是根据某种排序关系检查哪个v较大,然后切换它们,以便总是首先提到较小的元素。i、 e.当调用s_d[5][2]=4时,dict of dict将把它们转过来,以便它们实际

我在python中有一个算法,它为值对创建度量,其中
m(v1,v2)==m(v2,v1)
(即它是对称的)。我的想法是编写一个字典字典,其中这些值以一种内存高效的方式存储,这样就可以用任意顺序的键轻松检索它们。我喜欢从事物中继承,理想情况下,我喜欢编写一个
对称dict
,其中
s_d[v1][v2]
总是等于
s_d[v2][v1]
,可能是根据某种排序关系检查哪个v较大,然后切换它们,以便总是首先提到较小的元素。i、 e.当调用
s_d[5][2]=4
时,dict of dict将把它们转过来,以便它们实际上被存储为
s_d[2][5]=4
,同样用于检索数据。
我也非常愿意使用更好的数据结构,但我更喜欢使用“is-a”关系的实现,而不是只使用dict并预处理一些函数参数的实现。

如图所示使用嵌套索引将非常困难。最好使用元组作为键。这样就可以对元组进行排序,并且可以访问封装的
dict
来获取值

d[2, 5] = 4
print d[5, 2]

一个明显的替代方法是使用
(v1,v2)
元组作为单个标准
dict
的键,并将
(v1,v2)
(v2,v1)
插入字典,使它们引用右侧的同一对象。

您可以使用a作为dict的键:

>>> s_d = {}
>>> s_d[frozenset([5,2])] = 4
>>> s_d[frozenset([2,5])]
4
编写
dict
的子类是相当简单的,该子类将iterables作为关键参数,然后在存储值时变成
frozenset

class SymDict(dict):
    def __getitem__(self, key):
        return dict.__getitem__(self, frozenset(key))

    def __setitem__(self, key, value):
        dict.__setitem__(self, frozenset(key), value)
这给了你:

>>> s_d = SymDict()
>>> s_d[5,2] = 4
>>> s_d[2,5]
4

作为Dave Webb的frozenset的替代方案,为什么不使用如下符号:

class SymDict(dict):
    def __getitem__(self, key):
        return dict.__getitem__(self, key if key[0] < key[1] else (key[1],key[0]))

    def __setitem__(self, key, value):
        dict.__setitem__(self, key if key[0] < key[1] else (key[1],key[0]), value)
class符号(dict):
def _u获取项目(自身,密钥):
返回指令.uuu getitem(self,如果键[0]<键[1],否则键[1],键[0]))
定义设置项(自身、键、值):
说明.uuu设置项(self,如果键[0]<键[1],否则键[1],键[0]),值)

通过快速测试,获取和设置项目的速度比使用冻结集快10%以上。无论如何,这只是另一个想法。但是,它的适应性不如frozenset,因为它实际上只设置为与长度为2的元组一起使用。据我从OP上所知,这似乎不是一个问题。

这里有一个稍微不同的方法,看起来很有希望。虽然
SymDict
类不是
dict
子类,但它的行为大部分类似于一个,并且只涉及一个私有字典。我认为一个有趣的特性是,它保留了您似乎想要的自然的
[]]
查找语法

class SymDict(object):
    def __init__(self, *args, **kwrds):
        self._mapping = _SubSymDict(*args, **kwrds)
    def __getitem__(self, key1):
        self._mapping.set_key1(key1)
        return self._mapping
    def __setitem__(self, key1, value):
        raise NotImplementedError
    def __str__(self):
        return '_mapping: ' + self._mapping.__str__()
    def __getattr__(self, name):
        return getattr(self._mapping, name)

class _SubSymDict(dict):
    def __init__(self, *args, **kwrds):
        dict.__init__(self, *args, **kwrds)
    def set_key1(self, key1):
        self.key1 = key1
    def __getitem__(self, key2):
        return dict.__getitem__(self, frozenset((self.key1, key2)))
    def __setitem__(self, key2, value):
        dict.__setitem__(self, frozenset((self.key1, key2)), value)

symdict = SymDict()
symdict[2][4] = 24
symdict[4][2] = 42

print 'symdict[2][4]:', symdict[2][4]
# symdict[2][4]: 42
print 'symdict[4][2]:', symdict[4][2]
# symdict[4][2]: 42
print 'symdict:', symdict
# symdict: _mapping: {frozenset([2, 4]): 42}

print symdict.keys()
# [frozenset([2, 4])]

为了改进Justin Peel的解决方案,您需要添加
\uuu delitem\uuuu
\uu包含更多字典操作的方法。因此,为了完整性

class SymDict(dict):
    def __getitem__(self, key):
        return dict.__getitem__(self, key if key[0] < key[1] else (key[1],key[0]))

    def __setitem__(self, key, value):
        dict.__setitem__(self, key if key[0] < key[1] else (key[1],key[0]), value)

    def __delitem__(self, key):
        return dict.__delitem__(self, key if key[0] < key[1] else (key[1],key[0]))

    def __contains__(self, key):
        return dict.__contains__(self, key if key[0] < key[1] else (key[1],key[0]))

不过,我不确定这是否涵盖了所有的基础,但对于我自己的代码来说已经足够了。

我会提取函数以提高可读性(对于patvarilly答案)

class符号(dict):
def _u获取项目(自身,密钥):
返回dict.\uuu getitem\uuu(self,self.symm(键))
定义设置项(自身、键、值):
dict.uuu setitem_uuuu(self,self.symm(key),value)
def uu delitem uu(self,key):
返回dict.uuu delitem(self,self.symm(键))
def___;包含_______;(自身,密钥):
返回指令。uuu包含(self,self.symm(键))
@静力学方法
def symm(钥匙):
如果键[0]小于键[1],则返回键(键[1],键[0])。

我考虑过这一点,但是值可能是非常复杂的结构,所以元组对于第一个元素来说总是非常冗余的(因为(v1,v2)和(v1,v3)将有两个v1的副本,如果我不引入一个新的结构来存储指向所有v的指针……)我想这会让它变得很不雅观。如果另一个版本很难的话,我可能会选择那个版本。。。在将[]运算符中给定的元组保存到父dict中之前,我必须重写哪个成员函数才能对其进行排序?您必须重写
\uuuu getitem\uuuuuuuu()
\uuuuuuu setitem\uuuuuuuuu()
方法@菲利克斯·亚历杭德罗·多姆贝克:除非你强迫它,否则Python不会复制。(v1,v2)和(v1,v3)将共享相同的v1。另外,创建元组的效率也很高。。。我的意思是:对于每个元组(v1,vn)到(v1,vm),dict中存储了一个不同的元组,这意味着指向v1的指针被存储了m-n次,并且所有元组元数据(不管是什么,我不认为python 2元组只占用2个指针的原始8字节)被存储了n-m次。这通常不是一个问题,但要写得尽可能干净(对于大n-m),我的想法是在第一个dict中存储指向v1的指针一次,并且只存储一次,然后再存储指向第二个dict的指针,而第二个dict又只保存指向m-n的指针。。。我不知道这是否有意义。我当然喜欢这个主意!我还是想去掉冗余,这样就不用保存第一个值两次,或者意外地保存两次。。。
>>> s_d = SymDict()
>>> s_d[2,5] = 4
>>> s_d[5,2]
4
>>> (5,2) in s_d
True
>>> del s_d[5,2]
>>> s_d
{}
class SymDict(dict):
def __getitem__(self, key):
    return dict.__getitem__(self, self.symm(key))

def __setitem__(self, key, value):
    dict.__setitem__(self, self.symm(key), value)

def __delitem__(self, key):
    return dict.__delitem__(self, self.symm(key))

def __contains__(self, key):
    return dict.__contains__(self, self.symm(key))


@staticmethod
def symm(key):
    return key if key[0] < key[1] else (key[1], key[0]).