Как сравнить несколько ключевых значений из списка словарей?

У меня есть список словарей, которые имеют одну и ту же структуру в списке. Например:

test_data = [{'id':1, 'value':'one'}, {'id':2, 'value':'two'}, {'id':3, 'value':'three'}]

Что мне нужно сделать, это сравнить каждый из этих словарей и вернуть "похожие" словари на основе пары ключей значений. Например, учитывая ключ value и значение ***, я хочу найти все соответствующие словари, почти похожие на ***, которые в этом случае будут [{'id':1, 'value':'one'}].

difflib имеет функцию get_close_matches, который близок к тому, что мне нужно. Я могу извлечь значения конкретного ключа, используя понимание списка, а затем сравнить эти значения с моим поиском:

values = [ item['value'] for item in test_data ]
found_vals = get_close_matches('***', values) #returns ['one']

Что мне нужно, это сделать еще один шаг и связать все вместе с оригинальным словарем:

In [1]: get_close_dicts('***', test_data, 'value')
Out [1]: [{'id':1, 'value':'one'}]

Примечание. Список словарей довольно велик, и поэтому я надеюсь быть как можно более эффективным/быстрым.

3 ответа

Вы можете создать обратный поиск dict до запуска get_close_dicts в своих данных, так что, как только вы вернетесь к набору значений, вы можете использовать их для поиска соответствующих dict (s).

Если у вас есть уникальные значения для ваших ключей для ключа "значение", вы можете сделать:

reverselookup = {thedict['value']:thedict for thedict in test_data}

Если, однако, вам нужно обработать случай, когда несколько dicts будут иметь одинаковое значение для ключа "value", тогда вам нужно сопоставить все из них (это даст вам dict, где ключ - это значение в 'value', а значение - список dicts, которые имеют это значение):

from collections import defaultdict
reverselookup = defaultdict(list)
for testdict in test_data:
 reverselookup[testdict['value']].append(testdict)

Например, если у ваших тестовых данных был дополнительный dict в нем, например:

>>> test_data = [{'id':1, 'value':'one'}, {'id':2, 'value':'two'}, 
 {'id':3, 'value':'three'}, {'id':4, 'value':'three'}]

Тогда приведенная выше конструкция обратного поиска даст вам следующее:

{
 "three": [
 {
 "id": 3,
 "value": "three"
 },
 {
 "id": 4,
 "value": "three"
 }
 ],
 "two": [
 {
 "id": 2,
 "value": "two"
 }
 ],
 "one": [
 {
 "id": 1,
 "value": "one"
 }
 ]
}

Затем, после того, как у вас есть свои значения, просто загрузите dicts (тогда вы можете сцепить, если у вас есть список вариантов использования списков, нет необходимости в цепочке, если у вас есть первый вариант использования):

from itertools import chain 
chain(*[reverselookup[val] for val in found_vals])


Независимо от того, в какой-то момент вы в конечном итоге выполните итерацию через каждый словарь. Там не обойтись. Что вы можете сделать, так это получить всю работу, выполненную на этапе предварительной обработки, чтобы немедленно вызвать ваши фактические вызовы функции.

Как упоминал ValAyal, здесь нужен хороший словарь для обратного поиска. Я представляю словарь value_dict, где key - это значение из первого словаря, а value содержит как точные, так и аналогичные совпадения value. Возьмите этот пример с d1 и d2, которые находятся в вашем списке, который вы хотите выполнить. Если

d1 = {'id':1, 'value':'one'}
d2 = {'id':3, 'value':'***'}

Тогда:

value_dict["one"] = {"exact": [d1], "close": [d2]}
value_dict["***"] = {"exact": [d2], "close": [d1]}

Всякий раз, когда вы вставляете словарь, который имеет уже увиденное значение, вы можете сразу определить все точные и близкие соответствия (просто просмотрев это значение) и соответственно добавить в различные списки. Если у вас есть новое значение, которое не было замечено раньше, вам придется сравнить его со всеми значениями, находящимися в настоящее время в value_dict. Например, если вы хотите добавить

d3 = {'id':5, 'value':'one'}

Вы просмотрите value_dict["one"] и получите списки exact и close. Эти списки включают все остальные записи value_dict, которые необходимо изменить. Вам нужно добавить к точным совпадениям one и близкие совпадения ***; оба эти значения вы можете получить из возвращенных списков. В итоге вы получите

value_dict["one"] = {"exact": [d1, d3], "close": [d2]}
value_dict["***"] = {"exact": [d2], "close": [d1, d3]}

Итак, как только вся эта предварительная обработка будет выполнена, ваша функция станет проще: что-то вроде get_close_dicts(val) (я не знаю, что делает третий аргумент в вашем примере) может просто сделать return value_dict[val]["exact"] + value_dict[val]["close"]. Теперь у вас есть функция, которая дает немедленный ответ.

Шаг предварительной обработки довольно сложный, но получающееся ускорение в get_close_dicts, мы надеемся, восполнит его. Я могу подробно остановиться на этом, когда вернусь с работы, если вы хотите знать, как это реализовать. Надеюсь, это может дать вам хорошее представление о полезной структуре данных, и я не ужасно недооценил это.


Вы можете:

return [d for d in test_data if get_close_matches('***', [d['value'])]]

Обратите внимание, что get_close_matches может возвращать более одного результата.

licensed under cc by-sa 3.0 with attribution.