Listeler listesinden kopyaları kaldırma


116

Python'da bir liste listem var:

k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]

Ve ondan yinelenen öğeleri kaldırmak istiyorum. Normal bir liste olsaydı kullanabileceğim listelerin değil set. Ancak ne yazık ki bu liste hashable değildir ve bir dizi liste oluşturamaz. Sadece tuplelar. Böylece tüm listeleri demetlere çevirebilirim, sonra set ve listelere geri dönebilirim. Ama bu hızlı değil.

Bu en verimli şekilde nasıl yapılabilir?

Yukarıdaki listenin sonucu şöyle olmalıdır:

k = [[5, 6, 2], [1, 2], [3], [4]]

Düzeni korumak umurumda değil.

Not: Bu soru benzer, ancak tam olarak ihtiyacım olan şey değil. SO arandı ancak tam olarak yinelenen öğe bulunamadı


Kıyaslama:

import itertools, time


class Timer(object):
    def __init__(self, name=None):
        self.name = name

    def __enter__(self):
        self.tstart = time.time()

    def __exit__(self, type, value, traceback):
        if self.name:
            print '[%s]' % self.name,
        print 'Elapsed: %s' % (time.time() - self.tstart)


k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [5, 2], [6], [8], [9]] * 5
N = 100000

print len(k)

with Timer('set'):
    for i in xrange(N):
        kt = [tuple(i) for i in k]
        skt = set(kt)
        kk = [list(i) for i in skt]


with Timer('sort'):
    for i in xrange(N):
        ks = sorted(k)
        dedup = [ks[i] for i in xrange(len(ks)) if i == 0 or ks[i] != ks[i-1]]


with Timer('groupby'):
    for i in xrange(N):
        k = sorted(k)
        dedup = list(k for k, _ in itertools.groupby(k))

with Timer('loop in'):
    for i in xrange(N):
        new_k = []
        for elem in k:
            if elem not in new_k:
                new_k.append(elem)

Kısa listeler için en hızlısı "döngü içinde" (ikinci dereceden yöntem). Uzun listeler için, groupby yöntemi dışında herkesten daha hızlıdır. Bu mantıklı mı?

Kısa liste için (koddaki), 100000 yineleme:

[set] Elapsed: 1.3900001049
[sort] Elapsed: 0.891000032425
[groupby] Elapsed: 0.780999898911
[loop in] Elapsed: 0.578000068665

Daha uzun liste için (koddaki liste 5 kez çoğaltılmıştır):

[set] Elapsed: 3.68700003624
[sort] Elapsed: 3.43799996376
[groupby] Elapsed: 1.03099989891
[loop in] Elapsed: 1.85900020599

1
"Bu hızlı değil" derken, zamanını ayarladığınızı ve başvurunuz için yeterince hızlı olmadığını mı yoksa hızlı olmadığını mı düşünüyorsunuz?
Torsten Marek

@Torsten, akıllıca bir yöntem olmak için çok fazla kopyalama gibi görünüyor. üzgünüm, içgüdüsel duygu. listeleri
demetlere

@zaharpopov: Python böyle çalışmıyor, hiçbir şey kopyalanmayacak , sadece mevcut öğeler için yeni kaplar (ints için olsa da hemen hemen aynı)
Jochen Ritzel

3
1. "k" sıralanmış varyanta geri döndüğünden, sıralama kullanan yöntemlerin zamanlamaları düşürülür. 2. Son yöntem daha hızlıdır çünkü test verilerini oluşturma şekliniz size en fazla 4 farklı öğe bırakır. Sth deneyin. K = [[int (u) for u str için (random.randrange (1, 1000))], _ aralığında _ için (100)]
Torsten Marek

@Torsten: sabit teşekkürler. ancak yine de döngü yöntemi hızlıdır,
10'lu

Yanıtlar:


167
>>> k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
>>> import itertools
>>> k.sort()
>>> list(k for k,_ in itertools.groupby(k))
[[1, 2], [3], [4], [5, 6, 2]]

itertoolsgenellikle sorunların bu tür için en hızlı ve en güçlü çözümler sunmaktadır ve iyi aşina elde değer! -)

Düzenleme : Bir yorumda bahsettiğim gibi, normal optimizasyon çabaları büyük girdilere (büyük-O yaklaşımı) odaklanır, çünkü çabalardan iyi getiri sağlamak çok daha kolaydır. Ancak bazen (esasen, performans sınırlarının sınırlarını zorlayan derin iç kod döngülerindeki "trajik olarak kritik darboğazlar" için), olasılık dağılımları sağlayarak, hangi performans ölçütlerinin optimize edileceğine karar vererek (belki üst sınır veya 90. yüzdelik, bir kişinin uygulamalarına bağlı olarak ortalama veya medyandan daha önemlidir), başlangıçta giriş verisi özelliklerine bağlı olarak farklı algoritmalar seçmek için muhtemelen sezgisel kontroller gerçekleştirir ve benzeri.

"Nokta" performansının dikkatli ölçümleri (belirli bir girdi için kod A ve kod B) bu son derece maliyetli sürecin bir parçasıdır ve standart kitaplık modülü timeitburada yardımcı olur. Ancak, bir kabuk isteminde kullanmak daha kolaydır. Örneğin, burada bu soruna yönelik genel yaklaşımı gösteren kısa bir modül var, şu şekilde kaydedin nodup.py:

import itertools

k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]

def doset(k, map=map, list=list, set=set, tuple=tuple):
  return map(list, set(map(tuple, k)))

def dosort(k, sorted=sorted, xrange=xrange, len=len):
  ks = sorted(k)
  return [ks[i] for i in xrange(len(ks)) if i == 0 or ks[i] != ks[i-1]]

def dogroupby(k, sorted=sorted, groupby=itertools.groupby, list=list):
  ks = sorted(k)
  return [i for i, _ in itertools.groupby(ks)]

def donewk(k):
  newk = []
  for i in k:
    if i not in newk:
      newk.append(i)
  return newk

# sanity check that all functions compute the same result and don't alter k
if __name__ == '__main__':
  savek = list(k)
  for f in doset, dosort, dogroupby, donewk:
    resk = f(k)
    assert k == savek
    print '%10s %s' % (f.__name__, sorted(resk))

python nodup.pyHer şeyi eşit seviyeye getirmek için akıl sağlığı kontrolünü (yeni yaptığınızda gerçekleştirilir ) ve temel kaldırma tekniğini (hız için her bir işleve yerel sabit küresel isimler yapın) not edin.

Şimdi küçük örnek listesinde kontroller yapabiliriz:

$ python -mtimeit -s'import nodup' 'nodup.doset(nodup.k)'
100000 loops, best of 3: 11.7 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.dosort(nodup.k)'
100000 loops, best of 3: 9.68 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.dogroupby(nodup.k)'
100000 loops, best of 3: 8.74 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.donewk(nodup.k)'
100000 loops, best of 3: 4.44 usec per loop

ikinci dereceden yaklaşımın, birkaç yinelenen değere sahip küçük listeler için onu çekici kılacak kadar küçük sabitlere sahip olduğunu teyit eder. Yinelenmeyen kısa bir liste ile:

$ python -mtimeit -s'import nodup' 'nodup.donewk([[i] for i in range(12)])'
10000 loops, best of 3: 25.4 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.dogroupby([[i] for i in range(12)])'
10000 loops, best of 3: 23.7 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.doset([[i] for i in range(12)])'
10000 loops, best of 3: 31.3 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.dosort([[i] for i in range(12)])'
10000 loops, best of 3: 25 usec per loop

ikinci dereceden yaklaşım fena değil, ancak sıralama ve gruplama daha iyi. Vs vs.

Eğer (performans takıntısının da gösterdiği gibi) bu işlem sınırları zorlama uygulamanızın çekirdek iç döngüsündeyse, diğer temsili girdi örneklerinde aynı test setini denemeye değer, muhtemelen bulgusal olarak size izin verebilecek bazı basit önlemleri tespit etmeye değer. yaklaşımlardan birini veya diğerini seçin (ama tabii ki önlem hızlı olmalıdır).

Aynı zamanda farklı bir temsil tutmayı düşünmeye değer k- neden ilk etapta bir dizi tuple yerine bir liste listesi olmak zorunda? Yinelenen kaldırma görevi sık sıksa ve profil oluşturma, bunun programın performans darboğazı olduğunu gösteriyorsa, her zaman bir dizi tuple tutmak ve bunlardan yalnızca gerektiğinde ve gerektiğinde listelerin bir listesini almak, genel olarak daha hızlı olabilir.


@alex alternatif için teşekkürler. bu yöntem, danben'inki ile aynı hızda,% birkaç daha hızlı
zaharpopov

@alex: tuhaf bir şekilde bu, daha kısa listeler için saf ikinci dereceden bir yöntemden daha yavaştır (soru düzenlemesine bakın)
zaharpopov

@zaharpopov: Bu sadece sizin özel durumunuzda böyledir, cf. soruya yorumum.
Torsten Marek

@zaharpopov, liste ve alt liste uzunluklarının olasılık dağılımını ve yineleme olasılığını verirseniz, herhangi bir kod için çalışma zamanı olasılık dağılımını hesaplamak / ölçmek ve ihtiyacınız olan ölçüyü (medyan, ortalama, 90. yüzdelik, her neyse). Çok düşük yatırım getirisi nedeniyle neredeyse hiç yapılmaz: Normalde, düşük algoritmaların performansı gerçekten çok kötü etkileyeceği çok daha kolay olan büyük girdilere (büyük-O yaklaşımı) odaklanır. Ve yine de Q'nuzda herhangi bir olasılık dağılımı belirttiğinizi görmüyorum ;-).
Alex Martelli

@zaharpov, beğendiğine sevindim!
Alex Martelli

21

Manuel olarak yapmak, yeni bir kliste oluşturmak ve şu ana kadar bulunmayan girişleri eklemek:

k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
new_k = []
for elem in k:
    if elem not in new_k:
        new_k.append(elem)
k = new_k
print k
# prints [[1, 2], [4], [5, 6, 2], [3]]

Anlaşılması basit ve her bir öğenin ilk oluşum sırasını koruyorsanız, bu yararlı olmalıdır, ancak new_kher öğe için bütününü ararken karmaşıklık açısından ikinci dereceden olduğunu tahmin ediyorum .


@paul: çok garip - bu yöntem diğerlerinden daha hızlı
zaharpopov

Bu yöntemin çok uzun listelerde daha hızlı olmayacağından şüpheleniyorum. Bu, uygulamanıza bağlı olacaktır: Eğer gerçekten sadece iki kopya içeren altı elemanlı listeleriniz varsa, o zaman herhangi bir çözüm muhtemelen yeterince hızlı olacaktır ve en net kodu kullanmalısınız.
Paul Stephenson

@zaharpopov, Aynı listeyi defalarca kopyaladığınız için kıyaslamanızda ikinci dereceden değil. Doğrusal köşe kasa ile kıyaslama yapıyorsunuz.
Mike Graham

k = ([[1, 2], [4], [5, 6, 2], [1, 2], [3], [5, 2], [6], [8], [9]] +[[x] for x in range(1000)]) *5ikinci dereceden davranışı güzel bir şekilde gösterecek
John La Rooy

17
>>> k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
>>> k = sorted(k)
>>> k
[[1, 2], [1, 2], [3], [4], [4], [5, 6, 2]]
>>> dedup = [k[i] for i in range(len(k)) if i == 0 or k[i] != k[i-1]]
>>> dedup
[[1, 2], [3], [4], [5, 6, 2]]

Daha hızlı olup olmadığını bilmiyorum, ancak tuples ve setler kullanmak zorunda değilsiniz.


Teşekkür ederim danben. bu, tuple'lara döndükten sonra 'ayarlamak' ve ardından listelere geri dönmekten daha hızlı mı?
zaharpopov

Her iki deduping yöntemleri yazma kullanarak bazı rasgele listeleri oluşturmak - O testi kolayca olabilir randomve birlikte sefere time.
danben

4

setŞimdiye kadar bu soruna ilişkin tüm ilgili çözümler, setyinelemeden önce bir bütün oluşturmayı gerektirir .

Listelerin listesini yineleyerek ve bir "görülen" e ekleyerek bu tembellik yapmak ve aynı zamanda düzeni korumak mümkündür set. O zaman sadece bu izleyicide yoksa bir liste oluşturun set.

Bu unique_everseentarif itertools dokümanlarda mevcuttur . 3. taraf toolzkitaplığında da mevcuttur :

from toolz import unique

k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]

# lazy iterator
res = map(list, unique(map(tuple, k)))

print(list(res))

[[1, 2], [4], [5, 6, 2], [3]]

Not tuplelisteleri hashable değildir çünkü dönüşüm gereklidir.


3

"Uzun" listeniz bile oldukça kısa. Ayrıca, bunları gerçek verilerle eşleşecek şekilde mi seçtiniz? Performans, bu verilerin gerçekte neye benzediğine göre değişecektir. Örneğin, daha uzun bir liste yapmak için tekrar tekrar tekrarlanan kısa bir listeniz var. Bu, ikinci dereceden çözümün kıyaslamalarınızda doğrusal olduğu, ancak gerçekte olmadığı anlamına gelir.

Aslında büyük listeler için, set kodu sizin en iyi seçeneğinizdir - doğrusaldır (yer açsa da). Sıralama ve gruplama yöntemleri O (n log n) ve döngü içi yöntem açıkça ikinci dereceden, bu nedenle n gerçekten büyüdükçe bunların nasıl ölçekleneceğini biliyorsunuz. Analiz ettiğiniz verilerin gerçek boyutu buysa, kimin umurunda? Küçük.

Bu arada, seti yapmak için bir ara liste oluşturmazsam, yani değiştirirsem gözle görülür bir hızlanma görüyorum.

kt = [tuple(i) for i in k]
skt = set(kt)

ile

skt = set(tuple(i) for i in k)

Gerçek çözüm daha fazla bilgiye bağlı olabilir: Bir liste listesinin gerçekten ihtiyacınız olan temsil olduğundan emin misiniz?


3

Tuple ve {} listesi, kopyaları kaldırmak için kullanılabilir

>>> [list(tupl) for tupl in {tuple(item) for item in k }]
[[1, 2], [5, 6, 2], [3], [4]]
>>> 

1

Anahtar olarak başlıkla bir sözlük oluşturun ve tuşları yazdırın.

  • anahtar olarak tuple ve değer olarak dizin içeren sözlük oluştur
  • sözlük anahtarlarının listesini yazdır

k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]

dict_tuple = {tuple(item): index for index, item in enumerate(k)}

print [list(itm) for itm in dict_tuple.keys()]

# prints [[1, 2], [5, 6, 2], [3], [4]]

1

Bu çalışmalı.

k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]

k_cleaned = []
for ele in k:
    if set(ele) not in [set(x) for x in k_cleaned]:
        k_cleaned.append(ele)
print(k_cleaned)

# output: [[1, 2], [4], [5, 6, 2], [3]]

0

Garip bir şekilde, yukarıdaki yanıtlar 'kopyaları' kaldırır, ancak ya yinelenen değeri de kaldırmak istersem? Aşağıdakiler kullanışlı olmalı ve bellekte yeni bir nesne oluşturmaz!

def dictRemoveDuplicates(self):
    a=[[1,'somevalue1'],[1,'somevalue2'],[2,'somevalue1'],[3,'somevalue4'],[5,'somevalue5'],[5,'somevalue1'],[5,'somevalue1'],[5,'somevalue8'],[6,'somevalue9'],[6,'somevalue0'],[6,'somevalue1'],[7,'somevalue7']]


print(a)
temp = 0
position = -1
for pageNo, item in a:
    position+=1
    if pageNo != temp:
        temp = pageNo
        continue
    else:
        a[position] = 0
        a[position - 1] = 0
a = [x for x in a if x != 0]         
print(a)

ve o / p:

[[1, 'somevalue1'], [1, 'somevalue2'], [2, 'somevalue1'], [3, 'somevalue4'], [5, 'somevalue5'], [5, 'somevalue1'], [5, 'somevalue1'], [5, 'somevalue8'], [6, 'somevalue9'], [6, 'somevalue0'], [6, 'somevalue1'], [7, 'somevalue7']]
[[2, 'somevalue1'], [3, 'somevalue4'], [7, 'somevalue7']]

-1

Muhtemelen daha genel ve daha basit bir başka çözüm, nesnelerin dize sürümüyle anahtarlanmış bir sözlük oluşturmak ve sonunda değerleri () almaktır:

>>> dict([(unicode(a),a) for a in [["A", "A"], ["A", "A"], ["A", "B"]]]).values()
[['A', 'B'], ['A', 'A']]

İşin püf noktası, bunun yalnızca dize temsili yeterince iyi benzersiz bir anahtar olan nesneler için işe yaramasıdır (bu, çoğu yerel nesne için doğrudur).

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.