从一个字典列表中创建一个无矛盾项的字典

3
这个问题的灵感来自于这个问题。我想从一个字典列表中获取一个字典,该字典应包含所有仅出现一次的字典的键/值对,或者所有字典都同意相关联的值。例如(取自上述帖子):
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}

所以我的问题是:这个能更加优雅地完成吗? < p >< em > 附带问题:是否可以在不修改基础字典的情况下更好地完成< code > next(iter(b)) (即不使用< code > b.pop() )?


2
只需要两个注释。在这里使用 b.pop() 是可以的,因为它只修改了您的新临时集合。没有其他方法可以从集合中获取单个项。因为集合没有顺序,也没有 myset[0] - Jochen Ritzel
4个回答

4

您的代码非常优雅,我能想到的改进只有用itertools.chain()替换嵌套的for循环,像这样:

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对。
  • 将列表转换回字典。

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)

不错!也可以使用 key = operator.itemgetter(0)。请注意,在Py3k中更加简洁,因为您可以使用dict和set的内置函数,并且无需调用.iteritems() - Katriel

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只会包含字典键,这些键始终是可哈希的。

这真的很简单,而且适用于其他解决方案都不行的情况,所以我会接受这个。 - hochl

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())

如果我的函数集更强大,我可能可以将其缩减为一行代码,但今天似乎不行。


不幸的是,对于 [{'a': 3, 'b': 89, 'd': 2}, {'a': 3, 'c': 99, 'b': 89}, {'a': 3, 'c': 33, 'b': 42}, {'x': 5}] 这个列表会失败(虽然我很喜欢集合操作的想法)。此外,我认为你可以将语句压缩成 tmp=[set(d.iteritems()) for d in dicts]; return dict(set.intersection(*tmp).union(set.difference(*tmp))) - hochl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接