我有多个词典。字典之间存在很多重叠,但它们并不完全相同。
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对的次数,以最大限度地减少潜在的拼写错误,并且更容易级联更新特定键的不同值。
理想的输出将是构建所有词典所需的最基本的词典以及重构的公式。
答案 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}