파이썬에서 목록에서 중복 dict 제거


153

dicts 목록이 있으며 동일한 키 및 값 쌍으로 dicts를 제거하고 싶습니다.

이 목록의 경우 : [{'a': 123}, {'b': 123}, {'a': 123}]

나는 이것을 돌려주고 싶다 : [{'a': 123}, {'b': 123}]

다른 예시:

이 목록의 경우 : [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]

나는 이것을 돌려주고 싶다 : [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]


해결하려는 실제 문제에 대해 더 자세히 말씀해 주시겠습니까? 이것은 이상한 문제처럼 보입니다.
gfortune

몇 가지 dicts 목록을 결합하고 있으며 중복 항목이 있습니다. 따라서 중복을 제거해야합니다.
Brenden

나는 stackoverflow.com/questions/480214/… 에서 해결책을 찾지 set()
Sebastian Wagner

답변:


242

이 시도:

[dict(t) for t in {tuple(d.items()) for d in l}]

전략은 사전 목록을 튜플에 사전 항목이 포함 된 튜플 목록으로 변환하는 것입니다. 튜플을 해시 할 수 있기 때문에 set( 여기에서 설정된 파이썬을 사용하면 오래된 파이썬 대안이 될 것입니다 set(tuple(d.items()) for d in l))을 사용하여 복제본을 제거 하고 그 후에 튜플에서 사전을 다시 만들 수 있습니다dict .

어디:

  • l 원래 목록입니다
  • d 목록의 사전 중 하나입니다.
  • t 사전에서 만든 튜플 중 하나입니다.

편집 : 순서를 유지하려면 위의 한 줄짜리가 작동하지 않으므로 작동 set하지 않습니다. 그러나 몇 줄의 코드로 다음을 수행 할 수도 있습니다.

l = [{'a': 123, 'b': 1234},
        {'a': 3222, 'b': 1234},
        {'a': 123, 'b': 1234}]

seen = set()
new_l = []
for d in l:
    t = tuple(d.items())
    if t not in seen:
        seen.add(t)
        new_l.append(d)

print new_l

출력 예 :

[{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]

참고 : @alexis가 지적한 것처럼 동일한 키와 값을 가진 두 개의 사전이 동일한 튜플을 생성하지 않을 수 있습니다. 다른 키 추가 / 제거 키 기록을 거치면 발생할 수 있습니다. 그것이 문제의 경우라면, d.items()그가 제안한대로 정렬을 고려하십시오 .


35
좋은 해결책이지만 버그가 있습니다 : d.items()특정 순서로 요소를 반환한다고 보장하지는 않습니다. 당신은 어떻게해야 tuple(sorted(d.items()))같은 키 - 값 쌍에 대해 서로 다른 튜플을하지 않도록 할 수 있습니다.
Alexis

@alexis 나는 몇 가지 테스트를했고 실제로 당신은 맞습니다. 많은 키가 사이에 추가되고 나중에 제거되면 그럴 수 있습니다. 귀하의 의견에 감사드립니다.
jcollado

멋있는. 나는 전체 대화를 읽지 못할 미래 독자들의 이익을 위해 귀하의 답변에 픽스를 추가했습니다.
Alexis

2
json내가 한 것처럼 모듈 에서
dict

2
이것은이 경우에 유효한 솔루션이지만 중첩 된 사전의 경우에는 작동하지 않습니다
Lorenzo Belli

51

목록 이해에 기반한 또 하나의 라이너 :

>>> d = [{'a': 123}, {'b': 123}, {'a': 123}]
>>> [i for n, i in enumerate(d) if i not in d[n + 1:]]
[{'b': 123}, {'a': 123}]

여기서는 dict비교 를 사용할 수 있으므로 나머지 초기 목록에없는 요소 만 유지합니다 (이 개념은 index를 통해서만 액세스 할 수 n있으므로를 사용합니다 enumerate).


2
하여 첫 번째 대답 비해 이것은 또한 목록으로 구성 사전의 목록을 작동
gbozee

1
이것은 최고 답변과 달리 해시 형을 사전에 값으로 가질 수있을 때도 작동합니다.
Steve Rossiter

1
여기에, 목적은 키가 아닌 중복 값을 제거이 답변의 코드를 참조하는 것입니다
자밀 Noyda

이것은 매우 비효율적 인 코드입니다. if i not in d[n + 1:]전체 dicts 목록을 반복 n하지만 ( 총 작업 수의 절반에 해당) 사전의 모든 요소에 대해 해당 검사를 수행하므로이 코드는 O (n ^ 2) 시간 복잡성
Boris

사전을 값으로 사용하는 사전에는 작동하지 않습니다.
Roko Mijic

22

역 직렬화 된 JSON 객체와 같은 중첩 된 사전에서 작업하는 경우 다른 답변이 작동하지 않습니다. 이 경우 다음을 사용할 수 있습니다.

import json
set_of_jsons = {json.dumps(d, sort_keys=True) for d in X}
X = [json.loads(t) for t in set_of_jsons]

1
큰! 트릭은 dict 객체를 세트에 직접 추가 할 수 없으며 dump ()를 통해 json 객체로 변환해야한다는 것입니다.
Reihan_amn

18

타사 패키지를 사용해도 괜찮다면 다음을 사용할 수 있습니다 iteration_utilities.unique_everseen.

>>> from iteration_utilities import unique_everseen
>>> l = [{'a': 123}, {'b': 123}, {'a': 123}]
>>> list(unique_everseen(l))
[{'a': 123}, {'b': 123}]

그것은 원래 목록의 순서를 유지하고 유타도 (느린 알고리즘에 다시 하락에 의해 사전 같은 unhashable 항목을 처리 할 수있는 원본 목록의 요소입니다 원래 목록 대신에 독특한 요소 ). 키와 값을 모두 해시 할 수있는 경우 해당 함수 의 인수를 사용 하여 "고유성 테스트"에 대한 해시 가능한 항목을 만들 수 있습니다 (에서 작동 ).O(n*m)nmO(n)keyO(n)

사전 (순서와 무관하게 비교하는)의 경우,이를 비교하는 다른 데이터 구조에 맵핑해야합니다 frozenset. 예를 들면 다음과 같습니다.

>>> list(unique_everseen(l, key=lambda item: frozenset(item.items())))
[{'a': 123}, {'b': 123}]

tuple동일한 사전이 반드시 같은 순서를 가질 필요가 없기 때문에 (정렬하지 않고) 간단한 접근 방식을 사용해서는 안됩니다 (정렬 순서가 아닌 삽입 순서 가 보장 되는 Python 3.7에서도 ).

>>> d1 = {1: 1, 9: 9}
>>> d2 = {9: 9, 1: 1}
>>> d1 == d2
True
>>> tuple(d1.items()) == tuple(d2.items())
False

키를 정렬 할 수 없으면 튜플 정렬도 작동하지 않을 수 있습니다.

>>> d3 = {1: 1, 'a': 'a'}
>>> tuple(sorted(d3.items()))
TypeError: '<' not supported between instances of 'str' and 'int'

기준

이러한 접근 방식의 성능이 어떻게 비교되는지 보는 것이 도움이 될 것이라고 생각했기 때문에 작은 벤치 마크를 수행했습니다. 벤치 마크 그래프는 중복이 포함되지 않은 목록을 기반으로 한 시간 대 목록 크기입니다 (임의로 선택되었으므로 중복을 많이 추가하면 런타임이 크게 변경되지 않습니다). 로그-로그 플롯이므로 전체 범위가 포함됩니다.

절대 시간 :

여기에 이미지 설명을 입력하십시오

가장 빠른 접근 방식과 관련된 타이밍 :

여기에 이미지 설명을 입력하십시오

fourtheye 의 두 번째 접근 방식 이 가장 빠릅니다. 이 기능을 사용한 unique_everseen접근 방식 key은 두 번째이지만 주문을 유지하는 가장 빠른 접근 방식입니다. 다른에서 접근 jcolladothefourtheye은 거의 빠르다. unique_everseen키를 사용 하지 않고 EmmanuelScorpil 의 솔루션을 사용하는 방법 은 목록이 길어질수록 속도가 매우 느리며 O(n*n)대신에 훨씬 나빠 O(n)집니다. stpk 의 접근 방식 json은 그렇지 O(n*n)않지만 비슷한 O(n)접근 방식 보다 훨씬 느립니다 .

벤치 마크를 재현하는 코드 :

from simple_benchmark import benchmark
import json
from collections import OrderedDict
from iteration_utilities import unique_everseen

def jcollado_1(l):
    return [dict(t) for t in {tuple(d.items()) for d in l}]

def jcollado_2(l):
    seen = set()
    new_l = []
    for d in l:
        t = tuple(d.items())
        if t not in seen:
            seen.add(t)
            new_l.append(d)
    return new_l

def Emmanuel(d):
    return [i for n, i in enumerate(d) if i not in d[n + 1:]]

def Scorpil(a):
    b = []
    for i in range(0, len(a)):
        if a[i] not in a[i+1:]:
            b.append(a[i])

def stpk(X):
    set_of_jsons = {json.dumps(d, sort_keys=True) for d in X}
    return [json.loads(t) for t in set_of_jsons]

def thefourtheye_1(data):
    return OrderedDict((frozenset(item.items()),item) for item in data).values()

def thefourtheye_2(data):
    return {frozenset(item.items()):item for item in data}.values()

def iu_1(l):
    return list(unique_everseen(l))

def iu_2(l):
    return list(unique_everseen(l, key=lambda inner_dict: frozenset(inner_dict.items())))

funcs = (jcollado_1, Emmanuel, stpk, Scorpil, thefourtheye_1, thefourtheye_2, iu_1, jcollado_2, iu_2)
arguments = {2**i: [{'a': j} for j in range(2**i)] for i in range(2, 12)}
b = benchmark(funcs, arguments, 'list size')

%matplotlib widget
import matplotlib as mpl
import matplotlib.pyplot as plt
plt.style.use('ggplot')
mpl.rcParams['figure.figsize'] = '8, 6'

b.plot(relative_to=thefourtheye_2)

완전성을 위해 여기에는 중복 항목 만 포함 된 목록의 타이밍이 있습니다.

# this is the only change for the benchmark
arguments = {2**i: [{'a': 1} for j in range(2**i)] for i in range(2, 12)}

여기에 이미지 설명을 입력하십시오

기능 이 unique_everseen없는 경우를 제외하고 타이밍이 크게 바뀌지 않습니다 key.이 경우 가장 빠른 솔루션입니다. : 그것의 실행 목록에서 고유 한 값의 양에 따라 달라집니다 때문에 그 unhashable 값으로 해당 기능의 바로 최상의 경우 (그래서 대표하지 않음)의 O(n*m)이 경우에는 단지 1 따라서 그것은에서 실행입니다 O(n).


면책 조항 : 나는의 저자 해요 iteration_utilities.


15

때로는 구식 루프가 여전히 유용합니다. 이 코드는 jcollado보다 약간 길지만 읽기 쉽습니다.

a = [{'a': 123}, {'b': 123}, {'a': 123}]
b = []
for i in range(0, len(a)):
    if a[i] not in a[i+1:]:
        b.append(a[i])

0의는 range(0, len(a))필요하지 않습니다.
Juan Antonio

12

주문을 유지하려면 할 수 있습니다

from collections import OrderedDict
print OrderedDict((frozenset(item.items()),item) for item in data).values()
# [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]

순서가 중요하지 않으면 할 수 있습니다

print {frozenset(item.items()):item for item in data}.values()
# [{'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]

참고 : 파이썬 3에서는 두 번째 접근 방식 dict_values이 목록 대신 직렬화 할 수없는 출력을 제공 합니다. 모든 것을 다시 목록으로 캐스팅해야합니다. list(frozen.....)
saran3h

12

워크 플로에서 팬더를 사용하는 경우 사전 목록을 pd.DataFrame생성자에 직접 공급하는 옵션이 있습니다. 그런 다음 필요한 결과에 사용 drop_duplicatesto_dict방법을 사용하십시오.

import pandas as pd

d = [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]

d_unique = pd.DataFrame(d).drop_duplicates().to_dict('records')

print(d_unique)

[{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]

3

보편적 인 대답 은 아니지만 목록 이 다음과 같은 키로 정렬 되는 경우 :

l=[{'a': {'b': 31}, 't': 1},
   {'a': {'b': 31}, 't': 1},
 {'a': {'b': 145}, 't': 2},
 {'a': {'b': 25231}, 't': 2},
 {'a': {'b': 25231}, 't': 2}, 
 {'a': {'b': 25231}, 't': 2}, 
 {'a': {'b': 112}, 't': 3}]

솔루션은 다음과 같이 간단합니다.

import itertools
result = [a[0] for a in itertools.groupby(l)]

결과:

[{'a': {'b': 31}, 't': 1},
{'a': {'b': 145}, 't': 2},
{'a': {'b': 25231}, 't': 2},
{'a': {'b': 112}, 't': 3}]

중첩 된 사전과 함께 작동하며 순서를 유지합니다.


1

세트를 사용할 수 있지만 dicts를 해시 가능 유형으로 바꿔야합니다.

seq = [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]
unique = set()
for d in seq:
    t = tuple(d.iteritems())
    unique.add(t)

고유는 이제

set([(('a', 3222), ('b', 1234)), (('a', 123), ('b', 1234))])

받아쓰기를하려면 :

[dict(x) for x in unique]

의 순서 d.iteritems()는 보장되지 않으므로에서 '중복'으로 끝날 수 있습니다 unique.
danodonovan

-1

다음은 이중 중첩 목록 이해 기능을 갖춘 빠른 ​​한 줄 솔루션입니다 (@Emmanuel의 솔루션을 기반으로 함).

a전체 dict가 일치하는지 확인하는 대신 각 dict에서 단일 키 (예 :)를 기본 키로 사용합니다.

[i for n, i in enumerate(list_of_dicts) if i.get(primary_key) not in [y.get(primary_key) for y in list_of_dicts[n + 1:]]]

OP가 요청한 것이 아니지만이 스레드로 나를 데려온 것이므로 결국 솔루션을 게시 할 것이라고 생각했습니다.


-1

너무 짧지는 않지만 읽기 쉽습니다.

list_of_data = [{'a': 123}, {'b': 123}, {'a': 123}]

list_of_data_uniq = []
for data in list_of_data:
    if data not in list_of_data_uniq:
        list_of_data_uniq.append(data)

이제 목록 list_of_data_uniq에는 고유 한 dicts가 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.