Python'daki listedeki yinelenen dikdörtgeni kaldır


153

Bir dikte listesi var ve aynı anahtar ve değer çiftleriyle dikleri kaldırmak istiyorum.

Bu liste için: [{'a': 123}, {'b': 123}, {'a': 123}]

Bunu iade etmek istiyorum: [{'a': 123}, {'b': 123}]

Başka bir örnek:

Bu liste için: [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]

Bunu iade etmek istiyorum: [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]


Çözmeye çalıştığınız asıl sorun hakkında daha fazla bilgi verebilir misiniz? Bu garip bir sorun gibi görünüyor.
gfortune

Ben birkaç dikte listesini birleştiriyorum ve yinelenen var. Bu kopyaları kaldırmam gerekiyor.
Brenden

Stackoverflow.com/questions/480214/… ' da kullanmadan bir cevapta bir çözüm buldumset()
Sebastian Wagner

Yanıtlar:


242

Bunu dene:

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

Strateji, sözlük listesini, başlıkların sözlüğün öğelerini içerdiği bir grup listesine dönüştürmektir. Tupler karma olabileceğinden, kopyaları set( burada bir set kavrama kullanarak daha eski python alternatifi set(tuple(d.items()) for d in l)) kullanarak kaldırabilir ve bundan sonra tuples ile sözlükleri yeniden oluşturabilirsiniz dict.

nerede:

  • l orijinal liste
  • d listedeki sözlüklerden biridir
  • t sözlükten oluşturulan tupleslardan biridir

Düzenleme: Siparişi korumak istiyorsanız, yukarıdaki tek astar çalışmaz, çünkü setbunu yapmayacaktır. Ancak, birkaç satır kodla bunu da yapabilirsiniz:

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

Örnek çıktı:

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

Not: @alexis'in işaret ettiği gibi, aynı anahtar ve değerlere sahip iki sözlük aynı demetle sonuçlanmayabilir. Farklı bir anahtar ekleme / kaldırma geçmişinden geçtiklerinde bu olabilir. Sorununuz buysa, d.items()önerdiği gibi sıralamayı düşünün .


35
Güzel bir çözüm ama bir hata var: d.items()belirli bir sırayla öğeleri iade etmek garanti edilmez. Sen yapmalıyım tuple(sorted(d.items()))aynı anahtar-değer çiftleri için farklı dizilerini alamadım sağlamak için.
alexis

@alexis Birkaç test yaptım ve gerçekten haklısın. Aralarına çok sayıda anahtar eklenir ve daha sonra kaldırılırsa, durum böyle olabilir. Yorumunuz için çok teşekkürler.
jcollado

Güzel. Tüm konuşmayı okumamış olan gelecekteki okuyucuların yararına cevabınıza düzeltmeyi ekledim.
alexis

2
Not, jsonyaptığım gibi bir modülden bu
dikte listesine yüklerseniz bu çalışmaz

2
Bu, bu durumda geçerli bir çözümdür, ancak iç içe sözlükler söz konusu olduğunda çalışmaz
Lorenzo Belli

51

Liste kavrayışlarına dayanan bir başka tek astar:

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

Burada dictkarşılaştırmayı kullanabileceğimiz için , yalnızca ilk listenin geri kalanında olmayan öğeleri saklarız (bu nosyona sadece indeks üzerinden erişilebilir n, dolayısıyla kullanımı enumerate).


2
Bu aynı zamanda ilk cevaba göre listelerden oluşan sözlüklerin bir listesi için de geçerlidir
gbozee

1
bu, üst yanıtın aksine, sözlüklerinizde değer olarak paylaşılamayan bir türünüz olduğunda da işe yarar.
Steve Rossiter

1
Burada, amaç, anahtar değil yinelenen değerleri kaldırmak bu cevabım kodunu görmektir
Jamil Noyda

Bu çok verimsiz bir kod. if i not in d[n + 1:]tüm dikte listesi üzerinde yinelenir ( nancak toplam işlem sayısını yarıya indirir) ve sözlüğünüzdeki her öğe için bu kontrolü yaparsınız, böylece bu kod O (n ^ 2) zaman karmaşıklığıdır
Boris

sözlük olarak sözlükler için değer olarak çalışmaz
Roko Mijic

23

Diziselleştirilmiş JSON nesneleri gibi iç içe sözlükler üzerinde çalışıyorsanız diğer yanıtlar çalışmaz. Bu durumda şunları kullanabilirsiniz:

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
Harika! hile, dik nesnenin bir kümeye doğrudan eklenememesi, dump () ile json nesnesine dönüştürülmesi gerektiğidir.
Reihan_amn

20

Üçüncü taraf bir paket kullanmak uygunsa, şunları kullanabilirsiniz 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}]

Orijinal listenin sırasını korur ve ut, daha yavaş bir algoritmaya geri dönerek sözlükler gibi shahable öğelerini de işleyebilir ( orijinal listedeki öğeler ve orijinal listedeki benzersiz öğeler O(n*m)nerede ). Hem anahtarların hem de değerlerin yıkanabilir olması durumunda , "benzersizlik testi" için yıkanabilir öğeler oluşturmak üzere bu işlevin bağımsız değişkenini kullanabilirsiniz (böylece çalışır ).nmO(n)keyO(n)

Bir sözlük söz konusu olduğunda (sıradan bağımsız olarak karşılaştırır), söz konusu ifadeyi bunun gibi bir başka veri yapısıyla eşlemeniz gerekir, örneğin frozenset:

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

tupleEşit sözlüklerin aynı sıraya sahip olması gerekmediğinden (sıralama olmadan) basit bir yaklaşım kullanmamanız gerektiğini unutmayın ( ekleme siparişinin - mutlak sipariş değil - garanti edildiği Python 3.7'de bile ):

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

Ve anahtarlar sıralanabilir değilse, diziyi sıralamak işe yaramayabilir:

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

Karşılaştırma

Bu yaklaşımların performansının nasıl karşılaştırıldığını görmenin yararlı olabileceğini düşündüm, bu yüzden küçük bir kıyaslama yaptım. Karşılaştırma grafikleri, kopyalar içermeyen bir listeye dayalı olarak zamana göre liste boyutudur (rasgele seçilmişse, birkaç veya daha fazla kopya eklersem çalışma zamanı önemli ölçüde değişmez). Bu bir günlük-log grafiğidir, böylece tüm aralık kapsanır.

Mutlak zamanlar:

resim açıklamasını buraya girin

En hızlı yaklaşıma göre zamanlamalar:

resim açıklamasını buraya girin

Dört gözün ikinci yaklaşımı burada en hızlısıdır. unique_everseenİle yaklaşım keyfonksiyonu ancak korur sipariş etmenizi hızlı bir yaklaşım, ikinci sırada yer almaktadır. Jcollado ve dört gözün diğer yaklaşımları da neredeyse hızlıdır. Anahtarsız kullanılan yaklaşım unique_everseenve Emmanuel ve Scorpil'in çözümleri daha uzun listeler için çok yavaştır ve O(n*n)bunun yerine çok daha kötü davranır O(n). stpk s ile yaklaşım jsondeğil O(n*n)ama çok daha yavaşO(n) yaklaşımlardan .

Karşılaştırma ölçütlerini yeniden oluşturma kodu:

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)

Tamlık için, yalnızca kopyaları içeren bir listenin zamanlaması şöyledir:

# 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)}

resim açıklamasını buraya girin

Zamanlamalar fonksiyon unique_everseenolmadan önemli ölçüde değişmez key, bu durumda en hızlı çözüm budur. Ancak, bu işlev, paylaşılamaz değerlere sahip bu işlev için en iyi durumdur (temsili değildir) çünkü çalışma zamanı, listedeki benzersiz değerlerin miktarına bağlıdır: O(n*m)bu durumda sadece 1'dir ve bu nedenle çalışır O(n).


Feragatname: Ben yazarım iteration_utilities.


15

Bazen eski tip döngüler hala faydalıdır. Bu kod jcollado'nunkinden biraz daha uzun, ama okunması çok kolay:

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İçinde range(0, len(a))gerekli değildir.
Juan Antonio

12

Siparişi korumak istiyorsanız,

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

Sipariş önemli değilse, yapabilirsiniz

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

Not: python 3'te, ikinci yaklaşımınız dict_valuesliste yerine serileştirilemeyen bir çıktı verir . Her şeyi tekrar bir listeye dökmeniz gerekiyor. list(frozen.....)
saran3h

12

İş akışınızda Pandalar kullanıyorsanız, bir sözlük listesini doğrudan yapıcıya beslemektir pd.DataFrame. Ardından gerekli sonuç için drop_duplicatesve to_dictyöntemlerini kullanın .

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

Evrensel bir yanıt değil , ancak listeniz aşağıdaki gibi bir tuşa göre sıralanırsa :

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

o zaman çözüm şu kadar basittir:

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

Sonuç:

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

İç içe sözlüklerle çalışır ve (tabii ki) düzeni korur.


1

Bir set kullanabilirsiniz, ancak diktleri yıkanabilir bir türe dönüştürmeniz gerekir.

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)

Eşsiz şimdi eşittir

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

Kararları geri almak için:

[dict(x) for x in unique]

'İn sırası d.iteritems()garanti edilmez - bu nedenle' kopyalar 'ile sonuçlanabilir unique.
danodonovan

-1

İşte iki katlanmış bir liste kavrayışı ile hızlı bir tek satırlık çözüm (@Emmanuel çözümüne dayalı).

Bu a, tüm diktünün tümüyle eşleşip eşleşmediğini kontrol etmek yerine her dikte birincil anahtar olarak tek bir anahtar (örneğin ) kullanır

[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'nin istediği bu değildi, ama beni bu konuya getiren şeydi, bu yüzden bulduğum çözümü yayınlayacağımı düşündüm


-1

Çok kısa değil, okunması kolay:

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)

Şimdi, liste list_of_data_uniqbenzersiz dikte sahip olacak.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.