2012-03-28 63 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