2012-03-28 65 views
3

这个问题的灵感来自于this question。我希望从字典列表中获取字典,该字典应包含所有仅包含一次的字典或所有字典在关联值上达成一致的字典中的所有键/值对。示例(从上述发布拍摄):从词典列表中创建一个不相矛盾的词典

dicts = [dict(a=3, b=89, d=2), dict(a=3, b=89, c=99), dict(a=3, b=42, c=33)] 
print dict_itersection(dicts) 

应该产生

{'a': 3, 'd': 2} 

我当前的实现看起来是这样的:

import collections 

def dict_intersection(dicts): 
     c=collections.defaultdict(set) 
     for d in dicts: 
       for a, b in d.iteritems(): 
         c[a].add(b) 
     return {a: next(iter(b)) for a, b in c.iteritems() if len(b) == 1} 

所以我的问题:可以这样做更优雅?

Sidequestion:可以next(iter(b))而不底层字典(即,不b.pop())的变形例进行更好?

+2

它只需要两条评论。 'b.pop()'在这里没问题,因为它只会修改你的新的临时集合。没有其他方法可以从一组中获取单个项目。因为集合没有顺序,因此没有'myset [0]' – 2012-03-28 14:15:11

回答

3

到目前为止,所有的解决方案都假设所有的字典值都是可散列的。由于没有这个假设,代码不会变得更慢,而且更复杂一些,所以我会放弃它。下面是为支持!=所有值兼容版本:

def dict_intersection(dicts): 
    result = {} 
    conflicting = set() 
    for d in dicts: 
     for k, v in d.iteritems(): 
      if k not in conflicting and result.setdefault(k, v) != v: 
       del result[k] 
       conflicting.add(k) 
    return result 

设定conflicting将只包含字典键,这将永远是哈希的。

+0

这非常简单,适用于所有其他解决方案都不行的情况,所以我会接受这个。 – hochl 2012-03-28 15:00:22

4

你很接近我想象的那么优雅。我会做的唯一改变是更换了嵌套的循环与itertools.chain()“版的迭代器,像这样:

import collections 

def dict_intersection(dicts): 
     c=collections.defaultdict(set) 
     for k,v in itertools.chain(*[d.iteritems() for d in dicts]): 
       c[k].add(v) 
     return {a: next(iter(b)) for a, b in c.iteritems() if len(b) == 1} 

编辑(1):下面的代码回答一个稍微不同的问题 - 如何获得在至少两个输入字典中出现具有相同键和值的任何条目。

我从另一个问题的意见答案:

dict(
    [k for k,count in 
    collections.Counter(itertools.chain(*[d.iteritems() for d in dicts])).iteritems() 
    if count > 1] 
    ) 

这名义上是“一个衬里”,但我多行,它在(希望)使它更清楚一点。

它的工作原理是(从内部开始,工作了)方式:

  • 使用itertools.chain()克服所有的字典中的元素的迭代器。
  • 使用collections.Counter()来计算每个key, value对在字典中出现的次数。
  • 使用列表理解过滤Counter对于那些出现至少两次的key, value对。
  • 将列表转换为字典。
+0

这为我的例子打印'{'a':3,'b':89}'... – hochl 2012-03-28 13:47:50

+0

啊,你稍微修改了这个问题。只需一秒... – 2012-03-28 13:49:40

4
dicts = [dict(a=3, b=89, d=2), dict(a=3, b=89, c=99), dict(a=3, b=42, c=33)] 

data = {} 
for d in dicts: 
    for k, v in d.iteritems(): 
     data.setdefault(k, set()).add(v) 
out = dict((k, v.pop()) for k, v in data.iteritems() if len(v) == 1) 

# out == {'a': 3, 'd': 2} 

...或者一个班轮:

import itertools as it 

dict((k, v.pop()[1]) for k,v in ((k, set(v)) for k, v in it.groupby(sorted(it.chain(*(d.iteritems() for d in dicts))), key=lambda x: x[0])) if len(v) == 1) 
+0

Wut ............ – 2012-03-28 14:03:43

+0

Neato!也可以使用'key = operator.itemgetter(0)'。请注意,Py3k中这是更好的,因为你可以使用dict和设置自由度,并且你不需要调用'.iteritems()'。 – katrielalex 2012-03-28 14:20:02

1

要获得交集:

dict(reduce(lambda x, y: x & y, map(set, map(lambda x: x.iteritems(), dicts)))) 

当然,这种下降的唯一值,所以我们需要得到补充:

dict(reduce(lambda x, y: x - y, map(set, map(lambda x: x.iteritems(), dicts)))) 

结合所产生的字典给我们的结果集:

def dict_intersection(d): 
    x = dict(reduce(lambda x, y: x & y, map(set, map(lambda x: x.iteritems(), dicts)))) 
    y = dict(reduce(lambda x, y: x - y, map(set, map(lambda x: x.iteritems(), dicts)))) 
    return dict(x.items() + y.items()) 

如果我的一套福更强今天我能得到它归结为一个衬垫,而不是好像。

+0

不幸的是,对于'['a':3,'b':89,'d':2},{'a':3,'c':99,'b':89},{'' a':3,'c':33,'b':42},{'x':5}]'(尽管我很喜欢set操作的想法)。此外,我认为你可以将你的语句压缩到'tmp = [set(d.iteritems())for d in dicts];返回字典(set.intersection(* tmp).union(set.difference(* tmp)))' – hochl 2012-03-28 14:49:08