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'}]