Python3 как объединить два списка диктов по уникальным ключам

у меня есть два списка:

list1 = [ {'sth': 13, 'important_key1': 'AA', 'important_key2': '3'}, {'oh!': 14, 'important_key1': 'FF', 'important_key2': '4'}, {'sth_else': 'abc', 'important_key1': 'ZZ', 'important_key2': '5'}]
list2 = [ {'why-not': 'tAk', 'important_key1': 'GG', 'important_key2': '4'}, {'hmmm': 'no', 'important_key1': 'AA', 'important_key2': '3'}]

Я хочу вернуть список с объектами только из list1, но если то же самое important_key1 и important_key2 находится в любом элементе в list2 я хочу, чтобы этот элемент из list2.

таким образом, выход должен быть:

[ {'hmmm': 'no', 'important_key1': 'AA', 'important_key2': '3'}, {'oh!': 14, 'important_key1': 'FF', 'important_key2': '4'}, {'sth_else': 'abc', 'important_key1': 'ZZ', 'important_key2': '5'}]

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

это "нормальный" способ:

list1 = [ {'sth': 13, 'important_key1': 'AA', 'important_key2': '3'}, {'oh!': 14, 'important_key1': 'FF', 'important_key2': '4'}]
list2 = [ {'hmmm': 'no', 'important_key1': 'AA', 'important_key2': '3'}, {'why-not': 'tAk', 'important_key1': 'GG', 'important_key2': '4'}]

final_list = []
for element in list1:
    there_was_in_list2 = False
    for another_element in list2:
        if element['important_key1'] == another_element['important_key1'] and element['important_key2'] == another_element['important_key2']:
            final_list.append(another_element)
            there_was_in_list2 = True
            break
    if not there_was_in_list2:
        final_list.append(element)
print(final_list)

is есть ли подходящие для Python способ сделать это?

5 ответов


вы можете конвертировать list2 в дикт, индексированный кортежем значений важных ключей в list2, а затем использовать его, чтобы определить, если же ключи в list1 имеют те же значения, что вы перебираете list1 в понимании списка, так что сложность времени уменьшается до O(n) от вашего O (n*m):

keys = ['important_key1', 'important_key2']
d2 = {tuple(d[k] for k in keys): d for d in list2[::-1]}
print([d2.get(tuple(d[k] for k in keys), d) for d in list1])

это выводит (с вашим входным сигналом образца):

[{'hmmm': 'no', 'important_key1': 'AA', 'important_key2': '3'}, {'oh!': 14, 'important_key1': 'FF', 'important_key2': '4'}, {'sth_else': 'abc', 'important_key1': 'ZZ', 'important_key2': '5'}]

как вы описали в своем вопросе, только {'sth': 13, 'important_key1': 'AA', 'important_key2': '3'} на list1 будет заменить на {'hmmm': 'no', 'important_key1': 'AA', 'important_key2': '3'} потому что только этим dict имеет как important_key1 и important_key2 соответствие тем из дикт в list2.


Вы можете использовать список понимание:

list1 = [{'sth': 13, 'important_key1': 'AA', 'important_key2': '3'}, {'oh!': 14, 'important_key1': 'FF', 'important_key2': '4'}]
list2 = [{'hmmm': 'no', 'important_key1': 'AA', 'important_key2': '3'}, {'why-not': 'tAk', 'important_key1': 'GG', 'important_key2': '4'}]
vals = ['important_key1', 'important_key2']
new_list = [[c if any(c[a] == i[a] for a in vals) else i for c in list2] for i in list1]
final_result = [i[0] for i in new_list if i]

выход:

[{'hmmm': 'no', 'important_key1': 'AA', 'important_key2': '3'}, {'oh!': 14, 'important_key1': 'FF', 'important_key2': '4'}]

вы можете сэкономить there_was_in_list2 переменной с помощью for...else. The else оператор будет выполнен, когда предыдущий for цикл завершен нормально (т. е. он не был "сломан").

final_list = []
for element in list1:
    for another_element in list2:
        if element['important_key1'] == another_element['important_key1'] and element['important_key2'] == another_element['important_key2']:
            final_list.append(another_element)
            break
    else:
        final_list.append(element)

Если вы хотите, чтобы ваш код был более кратким, но поддерживал читаемость, вы можете заменить второй цикл for комбинированным next и filter:

final_list.append(next(
    filter(lambda x: ..., list2),
    element  # Default in case filter yields nothing.
))

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

l3 = l1[:]

for idx, item in enumerate(l2):    
    for x, i in enumerate(l1):
        k = list(zip(item.values(), i.values())) 
        if len(set(k[1])) < len(k[1]) and len(set(k[2])) < len(k[2]):
            l3[x] = item

print(l3)
(xenial)vash@localhost:~/python/stack_overflow/sept$ python3.7 uniq.py
[{'hmmm': 'no', 'important_key1': 'AA', 'important_key2': '3'}, {'oh!':14, 'important_key1': 'FF', 'important_key2': '4'},
{'sth_else': 'abc', 'important_key1': 'ZZ', 'important_key2': '5'}]