Сравнение нескольких словарей в Python - PullRequest
4 голосов
/ 24 ноября 2008

Я новичок в Python и столкнулся с проблемой, из-за которой я не могу выбраться из Google. Я построил графический интерфейс, используя wxPython и ObjectiveListView. В самом центре GUI имеется элемент управления списком, отображающий данные в виде строк X (данные загружаются пользователем) и пяти столбцов.

Когда пользователь выбирает несколько записей в элементе управления списком (нажимая CTRL или Shift при щелчке), модуль ObjectiveListView предоставляет мне список словарей, словарей, содержащих данные в строках элемента управления списком. Это именно то, что я хочу, хорошо!

Возвращенный список выглядит примерно так:

print MyList
[{'id':1023, 'type':'Purchase', 'date':'23.8.2008', 'sum':'-21,90', 'target':'Apple Store'}, {'id':1024, 'type':'Purchase', 'date':'24.8.2008', 'sum':'-21,90', 'target':'Apple Store'}, {'id':23, 'type':'Purchase', 'date':'2.8.2008', 'sum':'-21,90', 'target':'Apple Store'}]

Все словари имеют одинаковые ключи, но значения меняются. Значение id уникально. Здесь начинаются проблемы. Я хочу получить общие значения для всех элементов, выбранных пользователем. В приведенном выше списке они будут «сумма»: «- 21,90» и «цель»: «Apple Store».

Я не знаю, как правильно сравнивать слова в списке. Одна большая проблема заключается в том, что я заранее не знаю, сколько диктов содержит список, поскольку это решает пользователь.

У меня есть смутное представление о том, что списочные постижения были бы подходящим вариантом, но я знаю только, как сравнить два списка со списочными постижениями, а не n списками. Любая помощь будет оценена.

Ответы [ 5 ]

7 голосов
/ 24 ноября 2008

Мой ответ идентичен ответу Мэтью Тревора, за исключением одного различия:

>>> mysets = (set(x.items()) for x in MyList)
>>> reduce(set.intersection, mysets)
set([('sum', '-21,90'), ('type', 'Purchase'), ('target', 'Apple Store')])

Здесь я использую set.intersection вместо создания новой лямбды. На мой взгляд, это более читабельно, поскольку это интуитивно читается как «сокращение - это сокращение этого списка с помощью оператора пересечения множества». Это также должно быть намного быстрее, так как set.intersection является встроенной функцией Си.

Чтобы полностью ответить на ваш вопрос, вы можете извлечь значения, используя понимание списка:

>>> mysets = (set(x.items()) for x in MyList)
>>> result = reduce(set.intersection, mysets)
>>> values = [r[1] for r in result]
>>> values
['-21,90', 'Purchase', 'Apple Store']

Это закончилось бы для меня одной строкой. но это полностью зависит от вас:

>>> [r[1] for r in reduce(set.intersection, (set(x.items()) for x in myList))]
['-21,90', 'Purchase', 'Apple Store']
7 голосов
/ 24 ноября 2008
>>> mysets = (set(x.items()) for x in MyList)
>>> reduce(lambda a,b: a.intersection(b), mysets)
set([('sum', '-21,90'), ('type', 'Purchase'), ('target', 'Apple Store')])

Сначала я создал генератор, который преобразует список диктов в итеративную последовательность наборов пар ключ-значение. Вы можете использовать понимание списка здесь, но этот способ не преобразует весь список в еще один список, полезно, если вы не знаете, насколько он будет большим.

Затем я использовал метод Reduce, чтобы применить функцию, которая находит общие значения для каждого набора. Он находит пересечение набора 1 и набора 2, который сам по себе является набором, затем пересечение этого набора и набора 3 и т. Д. Генератор mysets будет с радостью передавать каждый набор по требованию в функцию сокращения до тех пор, пока это не будет сделано.

Я считаю, что Reduce устарело как встроенное в Python 3.0, но все еще должно быть доступно в functools.

Вы, конечно, могли бы сделать его однострочным, заменив mysets в редуцировании выражением генератора, но это снижает читабельность IMO. На практике я бы, вероятно, даже пошел бы еще дальше и разбил бы лямбду на собственную линию:

>>> mysets = (set(x.items()) for x in MyList)
>>> find_common = lambda a,b: a.intersection(b)
>>> reduce(find_common, mysets)
set([('sum', '-21,90'), ('type', 'Purchase'), ('target', 'Apple Store')])

И если вам нужен конечный результат, чтобы быть диктатом, просто оберните его так:

>>> dict(reduce(find_common, mysets))
{'sum': '-21,90', 'type': 'Purchase', 'target': 'Apple Store'}

dict может принять любой итератор пар ключ-значение, например, набор кортежей, возвращаемых в конце.

2 голосов
/ 24 ноября 2008

Во-первых, нам нужна функция для вычисления пересечения двух словарей:

def IntersectDicts( d1, d2 ) :
    return dict(filter(lambda (k,v) : k in d2 and d2[k] == v, d1.items()))

Тогда мы можем использовать его для обработки любого количества словарей:

result = reduce(IntersectDicts, MyList)
1 голос
/ 24 ноября 2008

Поскольку вы ищете только общий набор, вы можете сравнить ключи в первом словаре с ключами во всех других словарях:

common = {}
for k in MyList[0]:
    for i in xrange(1,len(MyList)):
        if MyList[0][k] != MyList[i][k]: continue
        common[k] = MyList[0][k]

>>> common
{'sum': '-21,90', 'type': 'Purchase', 'target': 'Apple Store'}
0 голосов
/ 24 ноября 2008

Извините, да, 'type': 'Закупка' также является одним из распространенных значений. Для редактирования вопроса необходимо войти в систему.

...