Python中的字典解构和重构

时间:2016-02-08 20:17:27

标签: python dictionary

我有多个词典。字典之间存在很多重叠,但它们并不完全相同。

a = {'a':1,'b':2,'c':3}
b = {'a':1,'c':3, 'd':4}
c = {'a':1,'c':3}

我试图弄清楚如何将这些分解成最原始的部分,然后以最有效的方式重建字典。换句话说,如何通过键入每个键/值对最少次数(理想情况下一次)来解构和重建字典。它还意味着创建可以组合的最小数量的集合,以创建所有可能的集合。

在上面的例子中。它可以分解为:

c = {'a':1,'c':3}
a = dict(c.items() + {'b':2})
b = dict(c.items() + {'d':4})

我正在寻找有关如何在Python中处理此问题的建议。

实际上,我有大约60个词典,其中许多都有重叠的值。我试图最小化我必须键入每个k / v对的次数,以最大限度地减少潜在的拼写错误,并且更容易级联更新特定键的不同值。

理想的输出将是构建所有词典所需的最基本的词典以及重构的公式。

2 个答案:

答案 0 :(得分:0)

这是一个解决方案。它在任何方面都不是最有效的,但它可能会让你知道如何继续。

a = {'a':1,'b':2,'c':3}
b = {'a':1,'c':3, 'd':4}
c = {'a':1,'c':3}

class Cover:
    def __init__(self,*dicts):
        # Our internal representation is a link to any complete subsets, and then a dictionary of remaining elements
        mtx = [[-1,{}] for d in dicts]
        for i,dct in enumerate(dicts):
            for j,odct in enumerate(dicts):
                if i == j: continue # we're always a subset of ourself
                # if everybody in A is in B, create the reference
                if all( k in dct for k in odct.keys() ):
                    mtx[i][0] = j
                    dif = {key:value for key,value in dct.items() if key not in odct}
                    mtx[i][1].update(dif)
                    break 

        for i,m in enumerate(mtx):
            if m[1] == {}: m[1] = dict(dicts[i].items())

        self.mtx = mtx

    def get(self, i):
        r = { key:val for key, val in self.mtx[i][1].items()} 
        if (self.mtx[i][0] > 0): # if we had found a subset, add that
            r.update(self.mtx[self.mtx[i][0]][1])
        return r


cover = Cover(a,b,c) 

print(a,b,c)
print('representation',cover.mtx)
# prints [[2, {'b': 2}], [2, {'d': 4}], [-1, {'a': 1, 'c': 3}]]
# The "-1" In the third element indicates this is a building block that cannot be reduced; the "2"s indicate that these should build from the 2th element 
print('a',cover.get(0)) 
print('b',cover.get(1))
print('c',cover.get(2))

这个想法非常简单:如果任何地图是完整的子集,请用复制品代替参考。对于某些矩阵组合,压缩肯定会适得其反,并且可以很容易地改进

简单改进

更改get的第一行,或者使用问题中暗示的更简洁的字典添加代码,可能会立即提高可读性。

我们不会检查最大的子集,这可能是值得的。

实现很幼稚,不做任何优化

更大的改进

还可以实现一种分层实现,其中“构建块”字典形成根节点,树被下降以构建更大的字典。如果您的数据是分层的,那么这只会是有益的。

(注意:在python3中测试)

答案 1 :(得分:0)

在脚本下面生成重建词典的脚本。

例如,考虑一下这本字典词典:

>>>dicts
{'d2': {'k4': 'k4', 'k1': 'k1'},
 'd0': {'k2': 'k2', 'k4': 'k4', 'k1': 'k1', 'k3': 'k3'},
 'd4': {'k4': 'k4', 'k0': 'k0', 'k1': 'k1'},
 'd3': {'k0': 'k0', 'k1': 'k1'},
 'd1': {'k2': 'k2', 'k4': 'k4'}}

为清楚起见,我们继续使用集合,因为关联键值可以在其他地方完成。

sets= {k:set(v.keys()) for k,v in dicts.items()} 

>>>sets
{'d2': {'k1', 'k4'},
 'd0': {'k1', 'k2', 'k3', 'k4'},
 'd4': {'k0', 'k1', 'k4'},
 'd3': {'k0', 'k1'},
 'd1': {'k2', 'k4'}}

现在计算距离(要添加的键数和/和从一个dict到另一个dict的删除):

df=pd.DataFrame(dicts)
charfunc=df.notnull()
distances=pd.DataFrame((charfunc.values.T[...,None] != charfunc.values).sum(1),
                       df.columns,df.columns)

>>>>distances 
    d0  d1  d2  d3  d4
d0   0   2   2   4   3
d1   2   0   2   4   3
d2   2   2   0   2   1
d3   4   4   2   0   1
d4   3   3   1   1   0

然后编写脚本的脚本。我们的想法是从最短的集合开始,然后在每个步骤中构建最近的集合:

script=open('script.py','w')
dicoto=df.count().argmin() # the shortest set
script.write('res={}\nres['+repr(dicoto)+']='+str(sets[dicoto])+'\ns=[\n')
done=[]
todo=df.columns.tolist()
while True :
    done.append(dicoto)
    todo.remove(dicoto)
    if not todo : break
    table=distances.loc[todo,done]
    ito,ifrom=np.unravel_index(table.values.argmin(),table.shape)
    dicofrom=table.columns[ifrom]
    setfrom=sets[dicofrom]
    dicoto=table.index[ito]
    setto=sets[dicoto]
    toadd=setto-setfrom
    toremove=setfrom-setto
    script.write(('('+repr(dicoto)+','+str(toadd)+','+str(toremove)+','
    +repr(dicofrom)+'),\n').replace('set',''))
script.write("""]
for dt,ta,tr,df in s:
    d=res[df].copy()
    d.update(ta)
    for k in tr: d.remove(k)
    res[dt]=d
""")    
script.close()

和生成的文件script.py

res={}
res['d1']={'k2', 'k4'}
s=[
('d0',{'k1', 'k3'},(),'d1'),
('d2',{'k1'},{'k2'},'d1'),
('d4',{'k0'},(),'d2'),
('d3',(),{'k4'},'d4'),
]
for dt,ta,tr,df in s:
    d=res[df].copy()
    d.update(ta)
    for k in tr: d.remove(k)
    res[dt]=d

测试:

>>> %run script.py
>>> res==sets
True

使用像这里的随机词组,脚本大小约为大词组(Nd=Nk=100)的大小的80%。但对于大的重叠,这个比例肯定会更好。

补充:生成此类词汇的脚本。

from pylab import *
import pandas as pd
Nd=5 # number of dicts
Nk=5 # number of keys per dict
index=['k'+str(j) for j in range(Nk)]
columns=['d'+str(i) for i in range(Nd)]
charfunc=pd.DataFrame(randint(0,2,(Nk,Nd)).astype(bool),index=index,columns=columns)
dicts={i : { j:j for j in charfunc.index if charfunc.ix[j,i]} for i in charfunc.columns}