Создать словарь непротиворечивых предметов из списка словарей - PullRequest
3 голосов
/ 28 марта 2012

Этот вопрос вдохновлен этим вопросом .Я хотел бы получить словарь из списка словарей, который должен содержать все пары ключ / значение из всех словарей, которые либо содержатся только один раз, либо если все словари согласны с соответствующим значением.Пример (взят из вышеупомянутой публикации):

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}

Итак, мой вопрос: Можно ли сделать это более элегантно?

Дополнительный вопрос: можно ли сделать next(iter(b)) лучше без изменения базового словаря (т.е. не b.pop())?

Ответы [ 4 ]

4 голосов
/ 28 марта 2012
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)
4 голосов
/ 28 марта 2012

Твой довольно элегантен, насколько я могу представить. Единственное изменение, которое я хотел бы сделать, - заменить вложенный цикл for на itertools.chain() 'ed итератор, например:

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 пар, встречающихся как минимум дважды.
  • Преобразуйте список обратно в диктовку.
3 голосов
/ 28 марта 2012

Все решения до сих пор предполагают, что все значения словаря являются хэшируемыми. Поскольку код не станет медленнее и немного сложнее без этого предположения, я бы его отбросил. Вот версия, которая работает для всех значений, которые поддерживают !=:

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 будет содержать только ключи словаря, которые всегда могут быть хэшируемыми.

1 голос
/ 28 марта 2012

Чтобы получить пересечение:

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

Объединение полученных словарей дает нам результатset:

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

Если бы мой сет фу был сильнее, я мог бы поставить его на один лайнер, но не сегодня, как кажется.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...