Я нашел много примеров, но ни одного с повторяющимися элементами.
У меня есть 2 списка dict:
d1 = [{"id": 1, "value": 2}, {"id": 1, "value": 2}]
d2 = [{"id": 1, "value": 2}, {"id": 4, "value": 4}]
Чего я хочу добиться, так это:
d1-d2
[{"id": 1, "value": 2}]
d2-d1
[{"id": 4, "value": 4}]
diff(d1, d2)
[{"id": 1, "value": 2}, {"id": 4, "value": 4}]
У ваших диктов всегда одни и те же ключи? Вы можете преобразовать их в кортежи, затем вы можете поместить их в наборы и использовать над ними все операции над наборами.
Также смотрите stackoverflow.com/questions/1151658/python-hashable-dicts
В д1 у меня старая коллекция постов, в д2 новая. Вычитая d1-d2, я хочу получить элементы, которые мне нужно удалить (они есть в списке 1, но не во 2). Вычитая d2-d1, я хочу получить элементы, которые мне нужно добавить (они есть в d2, но не в d1).
Не элегантно, но это, кажется, работает:
def diff(d1, d2):
found = []
to_return = []
for item in d1:
if item in found:
continue
found += [item]
to_return += [item] * (len(list(filter(lambda x : x == item, d1))) - len(list(filter(lambda x : x == item, d2))))
return to_return
Здесь есть элегантное решение с использованием фильтра, но я просто недостаточно умен, чтобы найти его.
Довольно сложный, может быть сложно поддерживать код.
Да, приведенный выше ответ лучше; здесь есть однострочный ответ, но его не стоит искать
Я оставлю это как еще один пример, если кому-то не все равно
Вы можете сделать что-то вроде этого, но это не так красиво:
def sub(d1, d2):
d2_ = d2.copy()
res = []
for elem in d1:
if elem in d2_:
d2_.remove(elem)
else:
res.append(elem)
return res
Затем вы можете использовать sub(d1, d2) + sub(d2, d1)
, чтобы получить diff(d1, d2)
.
Предполагая, что ваши значения, а не только ключи, в каждом словаре хэшируются, вы можете сделать frozenset
s каждого dict
s .items()
, собрать их в коллекции. Счетчик и выполнить операцию, подобную xor, над Counter
s, чтобы получить разрозненные подсчеты. Затем вы просто снова сглаживаете Counter
и конвертируете frozenset
обратно в dict
s:
from collections import Counter
def diff(l1, l2):
c1 = Counter(frozenset(d.items()) for d in l1)
c2 = Counter(frozenset(d.items()) for d in l2)
c1xc2 = (c1 - c2) + (c2 - c1) # Equivalent to c1 ^ c2 if they were sets, but we need counts, and Counter doesn't support ^
# elements() flattens the Counter to iterable of repeated values, map(dict, ...)
# converts frozensets back to dicts
return list(map(dict, c1xc2.elements()))
Более простая операция вычисления d1 - d2
или d2 - d1
оставлена в качестве упражнения; ключом во всех случаях является преобразование ваших list
из dict
в Counter
из frozenset
из dict
из .items()
, выполнение операции, которая вам нужна, с -
, а затем обратное преобразование в list
из dict
.
Это должно быть довольно эффективно (примерно O(n)
при комбинированных размерах ввода); для его улучшения потребуется гораздо больше пользовательского кода, который, честно говоря, не стоит заморачиваться.
Это отличный ответ +1. Обратите внимание, что вы можете легко определить функцию sub
с таким подходом.
@Moosefeather: Яр. Я оставил это как упражнение (в конце концов, это просто упрощение функции diff
). Может также позволить ОП написать это, чтобы убедиться, что они понимают diff
в первую очередь; если они этого не сделают, они могут задать продолжение в комментариях здесь.
@ShadowRanger Это также должно работать с tuple
вместо frozenset
.
@ChihebNexus: В современном Python с порядком вставки dict
это будет работать, если все dict
будут построены в одном порядке. Но если это не так, например. если бы d2
было определено, как показано в вопросе ОП, но d1
было определено как d1 = [{"value": 2, "id": 1}, {"value": 2, "id": 1}]
(value
определено до id
, противоположное ОП), tuple
сломалось бы (из-за заказа), в то время как frozenset
продолжал бы работать. Я бы не хотел полагаться на правильность порядка вставки. Это ничего вам не даст; значения по-прежнему должны быть хешируемыми для Counter
.
@ShadowRanger Хороший вопрос. я забыл, что упорядочение dict может сломать счетчик.
Разве вы не хотите объединения двух списков? Это не похоже на разницу (которая заявлена как «элементы в первом списке, но не во втором»), по крайней мере, для функции «diff».