Siparişi korurken listeden kopyaları nasıl kaldırırsınız?


770

Düzeni korurken Python'daki kopyaları listeden kaldıran yerleşik bir öğe var mı? Yinelenenleri kaldırmak için bir set kullanabileceğimi biliyorum, ancak bu orijinal siparişi yok eder. Ben de kendimi bu şekilde yuvarlayabileceğimi biliyorum:

def uniq(input):
  output = []
  for x in input:
    if x not in output:
      output.append(x)
  return output

( Bu kod örneği için gevşeyin .)

Ama kendimi mümkünse yerleşik veya daha fazla Pythonic deyimden yararlanmak istiyorum.

İlgili soru: Python'da, sıraları korurken tüm öğelerin benzersiz olması için bir listeden kopyaları kaldırmak için en hızlı algoritma nedir?

Yanıtlar:


762

Burada bazı alternatifler var: http://www.peterbe.com/plog/uniqifiers-benchmark

En hızlı olanı:

def f7(seq):
    seen = set()
    seen_add = seen.add
    return [x for x in seq if not (x in seen or seen_add(x))]

Neden atama seen.addiçin seen_addyerine çağırmak seen.add? Python dinamik bir dildir ve seen.addher yinelemenin çözümü yerel bir değişkenin çözümlenmesinden daha maliyetlidir. seen.addyinelemeler arasında değişmiş olabilir ve çalışma zamanı bunu göz ardı edecek kadar akıllı değildir. Güvenli oynamak için, nesneyi her seferinde kontrol etmelidir.

Bu işlevi aynı veri kümesinde çok kullanmayı planlıyorsanız, belki de sipariş edilen bir kümeyle daha iyi durumda olursunuz: http://code.activestate.com/recipes/528878/

Operasyon başına O (1) yerleştirme, silme ve üye kontrolü.

(Küçük ek not: seen.add()her zaman geri döner None, bu nedenle oryukarıdakiler, mantıksal testin ayrılmaz bir parçası olarak değil, yalnızca belirli bir güncellemeyi denemenin bir yolu olarak bulunur.)


20
@ JesseDhillon seen.addyinelemeler arasında değişmiş olabilir ve çalışma zamanı bunu ortadan kaldıracak kadar akıllı değildir. Güvenli oynamak için, nesneyi her seferinde kontrol etmelidir. - Bayt koduna ile bakarsanız, her yinelemede üye için dis.dis(f)yürütüldüğünü görebilirsiniz . ideone.com/tz1TllLOAD_ATTRadd
Markus Jarderot

5
Bunu bir liste listesinde denediğimde: TypeError: unhashable type: 'list'
Jens Timmerman

7
Çözümünüz en hızlı çözüm değil. Python 3'te (2 test etmedi) bu daha hızlıdır (300k giriş listesi - 0.045s (sizinki) ve 0.035s (bu)): seen = set (); x görünmüyorsa ve görünmüyorsa satırlarda x için x döndürür Gördüğünüz seen_add satırının hız efektini bulamadım
user136036

3
@ user136036 Lütfen testlerinize bağlanın. Onları kaç kez çalıştırdın? seen_addbir gelişmedir, ancak zamanlamalar sistem kaynaklarından etkilenebilir. Tam zamanlamaları görmek ister misiniz
jamylak

2
Python kodunu yazan herkes için, okunabilirlikten ve genel olarak kabul edilen Python kurallarından ödün vermeden önce, döngü başına birkaç nanosaniye daha sıkmak için gerçekten iki kez düşünmelisiniz. Test ile veya seen_add = seen.addtestsiz hız sadece% 1'lik bir artış sağlar. Bu pek önemli değil.
sleblanc

343

2016'yı düzenle

Raymond'un işaret ettiği gibi OrderedDict, C'de uygulanan python 3.5+'de , liste anlama yaklaşımı daha yavaş olacaktır OrderedDict(sonunda listeye gerçekten ihtiyacınız yoksa - ve o zaman bile, yalnızca giriş çok kısasa). Yani 3.5+ için en iyi çözüm OrderedDict.

Önemli Düzenleme 2015

@Abarnert'ın belirttiği gibi more_itertoolslibrary ( pip install more_itertools), liste kavramalarında okunamayan ( ) mutasyonlarunique_everseen olmadan bu sorunu çözmek için oluşturulmuş bir işlev içerir . Bu aynı zamanda en hızlı çözümdür:not seen.add

>>> from  more_itertools import unique_everseen
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(unique_everseen(items))
[1, 2, 0, 3]

Sadece tek bir kütüphane ithalat ve kesmek yok. Bu, unique_everseenaşağıdaki gibi görünen itertools tarifinin bir uygulamasından gelir :

def unique_everseen(iterable, key=None):
    "List unique elements, preserving order. Remember all elements ever seen."
    # unique_everseen('AAAABBBCCDAABBB') --> A B C D
    # unique_everseen('ABBCcAD', str.lower) --> A B C D
    seen = set()
    seen_add = seen.add
    if key is None:
        for element in filterfalse(seen.__contains__, iterable):
            seen_add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen_add(k)
                yield element

Python kabul ortak deyim (çalışır ancak hızı, şimdi kullanmak için optimize edilmemiştir ) Bu kullanımlar için :2.7+unique_everseencollections.OrderedDict

Oynatma Süresi: O (N)

>>> from collections import OrderedDict
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(OrderedDict.fromkeys(items))
[1, 2, 0, 3]

Bu çok daha güzel görünüyor:

seen = set()
[x for x in seq if x not in seen and not seen.add(x)]

ve çirkin kesmek kullanmıyor :

not seen.add(x)

hangi gerçeğine dayanır set.addhep döndüren bir yerinde yöntemidir Noneböylece not Nonedeğerlendirir için True.

Bununla birlikte, hack çözümünün aynı çalışma süresi karmaşıklığına (O (N)) sahip olmasına rağmen ham hızda daha hızlı olduğunu unutmayın.


5
Sadece anahtarları almak için bir tür özel dikte dönüştürmek? Sadece başka bir koltuk değneği.
Nakilon

3
@Nakilon Nasıl bir koltuk değneği olduğunu anlamıyorum. Değişken bir durum ortaya çıkarmaz, bu yüzden bu anlamda çok temiz. Dahili olarak, Python setleri dict () ( stackoverflow.com/questions/3949310/… ) ile uygulandığından , temel olarak yorumlayıcının zaten yaptıklarını yapıyorsunuz.
İmran

Sadece yan etkileri kullanın ve yapın [seen.add(x) for x in seq if x not in seen], ya da anlama yan etkilerini sevmiyorsanız sadece bir fordöngü kullanın : for x in seq: seen.add(x) if x not in seen else None(yine de bir astar, bu durumda bir astarın sahip olmaya çalışmak için aptalca bir özellik olduğunu düşünüyorum. çözüm.
ely

@EMS Bu düzeni korumaz. Sen de yapabilirsin seen = set(seq).
flornquake

1
@CommuSoft Kabul ediyorum, neredeyse neredeyse her zaman O (n) süper olası olmayan en kötü durum nedeniyle
jamylak

110

Python 2.7'de , kopyaları orijinal sırada tutarken yinelenenleri kaldırmanın yeni yolu:

>>> from collections import OrderedDict
>>> list(OrderedDict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

Python 3.5'te, OrderedDict'in bir C uygulaması vardır. Zamanlamalarım, bunun artık Python 3.5 için çeşitli yaklaşımların hem en hızlı hem de en kısa olduğunu gösteriyor.

Python 3.6'da düzenli diksiyon hem düzenli hem de kompakt hale geldi. (Bu özellik CPython ve PyPy için geçerlidir, ancak diğer uygulamalarda bulunmayabilir). Bu, siparişi korurken bize yeni ve hızlı bir tekilleştirme yolu sağlar:

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

Python 3.7'de normal diktenin her iki uygulamada da sipariş edilmesi garanti edilir. Yani, en kısa ve en hızlı çözüm:

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

@Max'a yanıt: 3.6 veya 3.7'ye geçtikten ve OrderedDict yerine normal dikteyi kullandığınızda , performansı gerçekten başka bir şekilde yenemezsiniz. Sözlük yoğun ve neredeyse hiç ek yükü olmayan bir listeye kolayca dönüşür. Hedef listesi, bir liste kavramada gerçekleşen tüm yeniden boyutlandırmaları kaydeden len (d) boyutuna göre önceden boyutlandırılmıştır. Ayrıca, dahili anahtar listesi yoğun olduğundan, işaretçilerin kopyalanması bir liste kopyası olarak neredeyse hızlıdır.


Sonunda bir listeye dönüştürmediğim sürece makinemdeki diğer yaklaşımlardan daha hızlı (python 3.5) OrderedDict. Bir listeye dönüştürmem gerekirse , küçük girdiler için liste kavrama yaklaşımı 1,5 kat daha hızlıdır. Bununla birlikte, bu çözüm çok daha temiz.
en fazla

7
Tek sorun, yinelenebilir "elemanların" yıkanabilir olması gerektiğidir - keyfi elemanlarla (listelerin bir listesi olarak) tekrarlanabilirler için eşdeğer olması iyi olur
Mr_and_Mrs_D

Bir dikte üzerinde ekleme talimatı yinelemesi, yinelenenleri kaldırmaktan daha fazla kullanım durumuna hizmet eden işlevler sağlar. Örneğin, bilimsel analizler, deterministik olmayan dikte yinelemesinin desteklemediği tekrarlanabilir hesaplamalara dayanır . Tekrarlanabilirlik, hesaplamalı bilimsel modellemede önemli bir güncel hedeftir, bu nedenle bu yeni özelliği memnuniyetle karşılıyoruz. Her ne kadar deterministik bir dikte ile inşa etmenin önemsiz olduğunu bilsem de, yüksek performanslı, deterministik set()daha saf kullanıcıların tekrarlanabilir kodlar geliştirmesine yardımcı olacaktır.
Arthur

41
sequence = ['1', '2', '3', '3', '6', '4', '5', '6']
unique = []
[unique.append(item) for item in sequence if item not in unique]

benzersiz → ['1', '2', '3', '6', '4', '5']


28
Bunun gerçekleştiğini belirtmek gerekirn^2
goncalopp

25
Ick. 2 grev: Üyelik testi için bir liste kullanma (yavaş, O (N)) ve yan etkiler için bir liste anlama (kullanma Nonesürecinde başka bir referans listesi oluşturma !)
Martijn Pieters

1
@MartijnPieters ile aynı fikirdeyim, yan etkilerin listenin anlaşılması için kesinlikle hiçbir neden yok . Bunun foryerine sadece bir döngü kullanın
jamylak

31

Ölü bir atı tekmelemek değil (bu soru çok eski ve zaten çok iyi cevapları var), ama burada birçok durumda oldukça hızlı ve kullanımı kolay olan pandalar kullanan bir çözüm var.

import pandas as pd

my_list = [0, 1, 2, 3, 4, 1, 2, 3, 5]

>>> pd.Series(my_list).drop_duplicates().tolist()
# Output:
# [0, 1, 2, 3, 4, 5]

27
from itertools import groupby
[ key for key,_ in groupby(sortedList)]

Listenin sıralanması bile gerekmez , yeterli koşul eşit değerlerin birlikte gruplandırılmasıdır.

Düzenleme: "Koruma sırası" listenin aslında sipariş olduğunu ima varsayılır. Durum böyle değilse, MizardX çözümü doğru çözümdür.

Topluluk düzenlemesi: Ancak bu, "yinelenen ardışık öğeleri tek bir öğeye sıkıştırmanın" en zarif yoludur.


1
Ama bu düzeni korumaz!

1
Hrm, bu sorunlu, çünkü eşit değerlerin bir kez liste üzerinde döngü yapmadan birlikte gruplandığını garanti edemem, bu zamana kadar bu kopyaları budamış olabilirdim.
Josh Glover

"Koruma düzeni" nin listenin gerçekten sıralandığını ima ettiğini varsaydım.
Rafał Dowgird

1
Belki de giriş listesinin özellikleri biraz belirsizdir. Değerlerin birlikte gruplanması bile gerekmez: [2, 1, 3, 1]. Peki hangi değerleri saklamak ve hangilerini silmek?

1
@igorkf Çiftlerin ikinci elemanının yoksayılması.
Rafał Dowgird

24

Eğer siparişi korumak istiyorsan,

bunu deneyebilirsiniz:

list1 = ['b','c','d','b','c','a','a']    
list2 = list(set(list1))    
list2.sort(key=list1.index)    
print list2

VEYA benzer şekilde bunu yapabilirsiniz:

list1 = ['b','c','d','b','c','a','a']  
list2 = sorted(set(list1),key=list1.index)  
print list2 

Bunu da yapabilirsiniz:

list1 = ['b','c','d','b','c','a','a']    
list2 = []    
for i in list1:    
    if not i in list2:  
        list2.append(i)`    
print list2

Ayrıca şu şekilde de yazılabilir:

list1 = ['b','c','d','b','c','a','a']    
list2 = []    
[list2.append(i) for i in list1 if not i in list2]    
print list2 

3
İlk iki cevabınız, listenin sıralamasının bir sıralama işlevi kullanılarak yeniden oluşturulabileceğini varsayar, ancak bu böyle olmayabilir.
Richard

5
Cevapların çoğu performansa odaklanmıştır. Performans konusunda endişelenecek kadar büyük olmayan listelerde, sıralanmış (set (list1), key = list1.index) gördüğüm en iyi şeydir. Ekstra içe aktarma yok, ekstra işlev yok, ekstra değişken yok ve oldukça basit ve okunabilir.
Derek Veit

23

In Python 3.7 ve üzeri, sözlükler vardır garantili onların anahtar kampanya siparişini hatırlamak. Cevabı bu sorunun mevcut durumu özetlemektedir.

Böylece OrderedDictçözüm eski hale gelir ve herhangi bir ithalat beyanı olmadan basit bir şekilde şunu verebiliriz:

>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> list(dict.fromkeys(lst))
[1, 2, 3, 4]

12

Çok eski bir soruya çok geç cevap için:

itertoolsTarifler kullanarak, bunu yapan bir fonksiyona sahip seendizi tekniğini ama:

  • Standart bir keyişlevi yerine getirir.
  • Görünüşte hack kullanmaz.
  • Döngüyü seen.addN kez aramak yerine ön bağlayarak optimize eder . (bunu f7da yapar, ancak bazı sürümlerde yoktur.)
  • Döngüyü kullanarak en iyi duruma getirir ifilterfalse, bu nedenle yalnızca Python'daki benzersiz öğelerin hepsini değil, döngüyü yapmanız gerekir. ( ifilterfalseTabii ki hala içinde hepsini yineliyorsunuz , ama bu C ve çok daha hızlı.)

Aslında daha hızlı f7mı? Verilerinize bağlıdır, bu yüzden test etmeniz ve görmeniz gerekir. Sonunda bir liste istiyorsanız, f7bir listcomp kullanır ve burada bunu yapmanın bir yolu yoktur. ( Jeneratörü doğrudan appendyerine kullanabilirsiniz yieldveya jeneratörü listişleve besleyebilirsiniz , ancak hiçbiri bir liste bileşenindeki LIST_APPEND kadar hızlı olamaz.) Her halükarda, genellikle birkaç mikrosaniyeyi sıkmak, süslemek istediğinizde DSU gerektirmeyen, kolay anlaşılır, tekrar kullanılabilir, önceden yazılmış bir işleve sahip olması önemlidir.

Tüm tariflerde olduğu gibi, içinde de mevcuttur more-iterools.

Sadece no-case'i istiyorsanız, keyaşağıdaki gibi basitleştirebilirsiniz:

def unique(iterable):
    seen = set()
    seen_add = seen.add
    for element in itertools.ifilterfalse(seen.__contains__, iterable):
        seen_add(element)
        yield element

Tamamen göz ardı ettim, more-itertoolsbu en iyi cevap. Basit from more_itertools import unique_everseen list(unique_everseen(items))Benimkinden çok daha hızlı bir yaklaşım ve kabul edilen cevaptan çok daha iyi, kütüphane indirmenin buna değer olduğunu düşünüyorum. Cevabımı topluluk wiki'ye gidiyorum ve bunu ekliyorum.
jamylak

12

Sadece harici bir modül böyle bir işlevsellik başka (çok ölçülebilir) uygulanmasını eklemek için 1 : iteration_utilities.unique_everseen:

>>> from iteration_utilities import unique_everseen
>>> lst = [1,1,1,2,3,2,2,2,1,3,4]

>>> list(unique_everseen(lst))
[1, 2, 3, 4]

zamanlamalar

Bazı zamanlamaları (Python 3.6) ve daha hızlı olmak üzere ben test tüm alternatiflerinden daha olduğunu bu gösteri yaptı OrderedDict.fromkeys, f7ve more_itertools.unique_everseen:

%matplotlib notebook

from iteration_utilities import unique_everseen
from collections import OrderedDict
from more_itertools import unique_everseen as mi_unique_everseen

def f7(seq):
    seen = set()
    seen_add = seen.add
    return [x for x in seq if not (x in seen or seen_add(x))]

def iteration_utilities_unique_everseen(seq):
    return list(unique_everseen(seq))

def more_itertools_unique_everseen(seq):
    return list(mi_unique_everseen(seq))

def odict(seq):
    return list(OrderedDict.fromkeys(seq))

from simple_benchmark import benchmark

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: list(range(2**i)) for i in range(1, 20)},
              'list size (no duplicates)')
b.plot()

resim açıklamasını buraya girin

Ve sadece bir fark yaratıp yaratmadığını kontrol etmek için daha fazla kopya ile bir test yaptım:

import random

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: [random.randint(0, 2**(i-1)) for _ in range(2**i)] for i in range(1, 20)},
              'list size (lots of duplicates)')
b.plot()

resim açıklamasını buraya girin

Ve sadece bir değer içeren biri:

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: [1]*(2**i) for i in range(1, 20)},
              'list size (only duplicates)')
b.plot()

resim açıklamasını buraya girin

Tüm bu durumlarda iteration_utilities.unique_everseenişlev en hızlıdır (bilgisayarımda).


Bu iteration_utilities.unique_everseenişlev aynı zamanda girişteki paylaşılamaz değerleri de işleyebilir (ancak değerler yıkanabilir olduğunda O(n*n)performans yerine bir performansla O(n)).

>>> lst = [{1}, {1}, {2}, {1}, {3}]

>>> list(unique_everseen(lst))
[{1}, {2}, {3}]

1 Feragatname: Bu paketin yazarıyım.


Bu çizginin gerekliliğini anlamıyorum: seen_add = seen.add- kriterler için bu gerekli mi?
Alex

@Alex Bu verilen yaklaşımdır bu cevap . Orada sormak daha mantıklı olurdu. Zamanlamaları karşılaştırmak için bu cevaptaki yaklaşımı kullandım.
MSeifert

dict.fromkeys()yöntemi grafiğinize ekleyebilir misiniz lütfen?
Boris

Zamanlamaları çabucak yapmak zorunda olduğumdan emin değilim. Sizden daha hızlı olduğunu ordereddict.fromkeysmu düşünüyorsunuz ?
MSeifert

"Bu iteration_utilities.unique_everseen işlevi de girişteki paylaşılamaz değerleri işleyebilir" - evet, bu gerçekten önemlidir. Eğer dikte dikte dikte vb. Bir listesi varsa, bu iş küçük ölçekli bile yapmak için tek yoldur.
Roko Mijic

6

MizardX'lara dayalı olarak hiçbir yıkanabilir tür (örn. Liste listesi) için:

def f7_noHash(seq)
    seen = set()
    return [ x for x in seq if str( x ) not in seen and not seen.add( str( x ) )]

3

Haskell'in nublisteler için işlevini tanımlamada kullanılan özyinelemeli fikri ödünç almak , bu özyinelemeli bir yaklaşım olacaktır:

def unique(lst):
    return [] if lst==[] else [lst[0]] + unique(filter(lambda x: x!= lst[0], lst[1:]))

Örneğin:

In [118]: unique([1,5,1,1,4,3,4])
Out[118]: [1, 5, 4, 3]

Büyüyen veri boyutları için denedim ve alt doğrusal zaman karmaşıklığı gördüm (kesin değil, ancak bunun normal veriler için iyi olması gerektiğini gösteriyor).

In [122]: %timeit unique(np.random.randint(5, size=(1)))
10000 loops, best of 3: 25.3 us per loop

In [123]: %timeit unique(np.random.randint(5, size=(10)))
10000 loops, best of 3: 42.9 us per loop

In [124]: %timeit unique(np.random.randint(5, size=(100)))
10000 loops, best of 3: 132 us per loop

In [125]: %timeit unique(np.random.randint(5, size=(1000)))
1000 loops, best of 3: 1.05 ms per loop

In [126]: %timeit unique(np.random.randint(5, size=(10000)))
100 loops, best of 3: 11 ms per loop

Ayrıca, bunun diğer operasyonlar tarafından benzersizliğe kolayca genelleştirilebileceğini ilginç düşünüyorum. Bunun gibi:

import operator
def unique(lst, cmp_op=operator.ne):
    return [] if lst==[] else [lst[0]] + unique(filter(lambda x: cmp_op(x, lst[0]), lst[1:]), cmp_op)

Örneğin, benzersizlik amaçları için "eşitlik" ile aynı tamsayıya yuvarlama kavramını kullanan bir işlevi aktarabilirsiniz, örneğin:

def test_round(x,y):
    return round(x) != round(y)

benzersiz (some_list, test_round), benzersizliğin artık geleneksel eşitlik (bu soruna herhangi bir set tabanlı veya dict-key tabanlı yaklaşım kullanarak ima edilir) anlamına gelmediği, bunun yerine yalnızca öğelerin yuvarlanabileceği her olası tamsayı K için K'ye yuvarlanan ilk öğe, örneğin:

In [6]: unique([1.2, 5, 1.9, 1.1, 4.2, 3, 4.8], test_round)
Out[6]: [1.2, 5, 1.9, 4.2, 3]

1
Birbirini izleyen her yinelemeli çağrının kullanımı filterönceki çağrıdan neredeyse hiç fayda sağlayamayacağından , benzersiz elemanların sayısı toplam eleman sayısına göre çok fazla olduğunda performansın düşeceğini unutmayın . Ancak, benzersiz öğelerin sayısı dizi boyutuna göre azsa, bu oldukça iyi performans göstermelidir.
ely

3

5 kat daha hızlı varyantı azaltır, ancak daha sofistike

>>> l = [5, 6, 6, 1, 1, 2, 2, 3, 4]
>>> reduce(lambda r, v: v in r[1] and r or (r[0].append(v) or r[1].add(v)) or r, l, ([], set()))[0]
[5, 6, 1, 2, 3, 4]

Açıklama:

default = (list(), set())
# use list to keep order
# use set to make lookup faster

def reducer(result, item):
    if item not in result[1]:
        result[0].append(item)
        result[1].add(item)
    return result

>>> reduce(reducer, l, default)[0]
[5, 6, 1, 2, 3, 4]

3

'_ [1]' sembolü ile yapıldığından bir liste kavrayışına başvurabilirsiniz.
Örneğin, aşağıdaki işlev benzersiz bir öğe listesini liste kavrayışına başvurarak sıralarını değiştirmeden listeler.

def unique(my_list): 
    return [x for x in my_list if x not in locals()['_[1]']]

Demo:

l1 = [1, 2, 3, 4, 1, 2, 3, 4, 5]
l2 = [x for x in l1 if x not in locals()['_[1]']]
print l2

Çıktı:

[1, 2, 3, 4, 5]

2
Ayrıca, bir set / dict (sürekli arama süresine sahip) oluşturma ve yalnızca önceden görülmemiş öğeleri ekleme işleminin doğrusal olacağı bir O (n ^ 2) işlemi yapacağını unutmayın.
ely

Bu sadece Python 2.6'ya inanıyorum. Ve evet bu O (N ^ 2)
jamylak

2

MizardX'in cevabı, çoklu yaklaşımların iyi bir koleksiyonunu verir.

Yüksek sesle düşünürken ortaya çıkan şey buydu:

mylist = [x for i,x in enumerate(mylist) if x not in mylist[i+1:]]

Çözümünüz güzel, ancak her öğenin son görünümünü alır. İlk görünümü almak için şunu kullanın: [i için x, x, mylist değilse x: sayum (mylist) [
Rivka

7
Listede arama yapmak bir O(n)işlem olduğundan ve her öğede gerçekleştirdiğiniz için çözümünüzün karmaşıklığı ortaya çıkar O(n^2). Bu sadece önemsiz bir sorun için kabul edilemez.
Nikita Volkov

2

İşte bunu yapmanın basit bir yolu:

list1 = ["hello", " ", "w", "o", "r", "l", "d"]
sorted(set(list1 ), key=lambda x:list1.index(x))

çıktı verir:

["hello", " ", "w", "o", "r", "l", "d"]

1

Bir çirkin liste anlama hack yapabilirsiniz.

[l[i] for i in range(len(l)) if l.index(l[i]) == i]

Tercih i,e in enumerate(l)et l[i] for i in range(len(l)).
Evpok

1

İle Nispeten etkin bir yaklaşım _sorted_bir numpydiziler:

b = np.array([1,3,3, 8, 12, 12,12])    
numpy.hstack([b[0], [x[0] for x in zip(b[1:], b[:-1]) if x[0]!=x[1]]])

Çıktılar:

array([ 1,  3,  8, 12])

1
l = [1,2,2,3,3,...]
n = []
n.extend(ele for ele in l if ele not in set(n))

O (1) kullanan bir jeneratör ifadesi, yeni listeye bir elemanın dahil edilip edilmeyeceğini belirlemek için bir kümeye bakar.


1
extendGenişletilen şeye (yani +1) bağlı olan, ancak set(n)her aşamada (doğrusal olan) yeniden hesaplanan bir jeneratör ifadesi ile akıllı kullanım ve bu, ikinci dereceden olmak için genel yaklaşımı engeller. Aslında, bu kesinlikle kullanmaktan neredeyse daha kötüdür ele in n. Tek bir üyelik testi için set yapmak set oluşturma masrafına değmez. Yine de - bu ilginç bir yaklaşım.
John Coleman

1

Basit bir özyinelemeli çözüm:

def uniquefy_list(a):
    return uniquefy_list(a[1:]) if a[0] in a[1:] else [a[0]]+uniquefy_list(a[1:]) if len(a)>1 else [a[0]]

1

Yinelenen değerleri bir sırayla ortadan kaldırmak, ancak kalan öğelerin sırasını korumak. Genel amaçlı jeneratör fonksiyonunun kullanımı.

# for hashable sequence
def remove_duplicates(items):
    seen = set()
    for item in items:
        if item not in seen:
            yield item
            seen.add(item)

a = [1, 5, 2, 1, 9, 1, 5, 10]
list(remove_duplicates(a))
# [1, 5, 2, 9, 10]



# for unhashable sequence
def remove_duplicates(items, key=None):
    seen = set()
    for item in items:
        val = item if key is None else key(item)
        if val not in seen:
            yield item
            seen.add(val)

a = [ {'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
list(remove_duplicates(a, key=lambda d: (d['x'],d['y'])))
# [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]

1

pandas kullanıcıları kontrol etmelisiniz pandas.unique.

>>> import pandas as pd
>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> pd.unique(lst)
array([1, 2, 3, 4])

İşlev bir NumPy dizisi döndürür. Gerekirse, tolistyöntemi kullanarak bir listeye dönüştürebilirsiniz .


1
Güzel bir. Bunun için asla panda kullanmayı hayal etmem ama işe yarıyor
seralouk

0

Bir astara ihtiyacınız varsa, belki de bu yardımcı olacaktır:

reduce(lambda x, y: x + y if y[0] not in x else x, map(lambda x: [x],lst))

... çalışmalıyım ama yanılıyorsam düzelt


Bir var koşullu ifade böylece 's iyi
code22

0

Rutin olarak kullanırsanız pandasve estetik performansa göre tercih edildiyse , yerleşik işlevi göz önünde bulundurun pandas.Series.drop_duplicates:

    import pandas as pd
    import numpy as np

    uniquifier = lambda alist: pd.Series(alist).drop_duplicates().tolist()

    # from the chosen answer 
    def f7(seq):
        seen = set()
        seen_add = seen.add
        return [ x for x in seq if not (x in seen or seen_add(x))]

    alist = np.random.randint(low=0, high=1000, size=10000).tolist()

    print uniquifier(alist) == f7(alist)  # True

Zamanlama:

    In [104]: %timeit f7(alist)
    1000 loops, best of 3: 1.3 ms per loop
    In [110]: %timeit uniquifier(alist)
    100 loops, best of 3: 4.39 ms per loop

0

bu düzeni koruyacak ve O (n) zamanında çalışacaktır. temel olarak fikir, bir kopya bulunan her yerde bir delik oluşturmak ve onu dibe batırmaktır. okuma ve yazma işaretçisini kullanır. bir yineleme bulunduğunda, yalnızca okuma işaretçisi ilerler ve yazma işaretçisi üzerine yazmak için yinelenen girişte kalır.

def deduplicate(l):
    count = {}
    (read,write) = (0,0)
    while read < len(l):
        if l[read] in count:
            read += 1
            continue
        count[l[read]] = True
        l[write] = l[read]
        read += 1
        write += 1
    return l[0:write]

0

İçe aktarılan modülleri veya setleri kullanmayan bir çözüm:

text = "ask not what your country can do for you ask what you can do for your country"
sentence = text.split(" ")
noduplicates = [(sentence[i]) for i in range (0,len(sentence)) if sentence[i] not in sentence[:i]]
print(noduplicates)

Çıktı verir:

['ask', 'not', 'what', 'your', 'country', 'can', 'do', 'for', 'you']

bu her seferinde O (N ** 2) karmaşıklık + liste dilimlemedir.
Jean-François Fabre

0

Yerinde bir yöntem

Bu yöntem ikinci derecelidir, çünkü listenin her öğesi için listeye doğrusal bir arama yaparız (bunun için listeyi yeniden düzenleme maliyetini eklememiz gerekir) del s ).

Bununla birlikte, listenin sonundan başlayıp soldaki alt listede bulunan her bir terimi kaldırarak kaynağa doğru ilerlersek yerinde çalışmak mümkündür.

Koddaki bu fikir basitçe

for i in range(len(l)-1,0,-1): 
    if l[i] in l[:i]: del l[i] 

Uygulamanın basit bir testi

In [91]: from random import randint, seed                                                                                            
In [92]: seed('20080808') ; l = [randint(1,6) for _ in range(12)] # Beijing Olympics                                                                 
In [93]: for i in range(len(l)-1,0,-1): 
    ...:     print(l) 
    ...:     print(i, l[i], l[:i], end='') 
    ...:     if l[i] in l[:i]: 
    ...:          print( ': remove', l[i]) 
    ...:          del l[i] 
    ...:     else: 
    ...:          print() 
    ...: print(l)
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5, 2]
11 2 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]: remove 2
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]
10 5 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4]: remove 5
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4]
9 4 [6, 5, 1, 4, 6, 1, 6, 2, 2]: remove 4
[6, 5, 1, 4, 6, 1, 6, 2, 2]
8 2 [6, 5, 1, 4, 6, 1, 6, 2]: remove 2
[6, 5, 1, 4, 6, 1, 6, 2]
7 2 [6, 5, 1, 4, 6, 1, 6]
[6, 5, 1, 4, 6, 1, 6, 2]
6 6 [6, 5, 1, 4, 6, 1]: remove 6
[6, 5, 1, 4, 6, 1, 2]
5 1 [6, 5, 1, 4, 6]: remove 1
[6, 5, 1, 4, 6, 2]
4 6 [6, 5, 1, 4]: remove 6
[6, 5, 1, 4, 2]
3 4 [6, 5, 1]
[6, 5, 1, 4, 2]
2 1 [6, 5]
[6, 5, 1, 4, 2]
1 5 [6]
[6, 5, 1, 4, 2]

In [94]:                                                                                                                             

Göndermeden önce 'yer' için cevapların gövdesini boşuna aradım. Başkaları sorunu benzer bir şekilde çözdüyse lütfen beni uyarın ve cevabımı en kısa zamanda kaldıracağım.
gboffi

Sadece l[:] = <one of the the faster methods>yerinde bir operasyon istiyorsanız kullanabilirsiniz , değil mi?
timgeb

@timgeb Evet ve hayır ... Ben ne zaman a=[1]; b=a; a[:]=[2]o zaman b==[2]değeridir Trueve biz yerinde bunu yapıyor diyebiliriz, yine de yeni bir liste olması yeni bir alan kullanıyor önerim, yeni verilerle eski verileri değiştirmek ve işaretlemek çöp toplama için eski veriler artık bir şey tarafından referans alınmadığından, yerinde çalıştığını söylemek, mümkün olduğunu gösterdiğim şeyle kavramı biraz germek demek ... verimsiz mi? evet, ama önceden söyledim.
gboffi

0

zmk'nin yaklaşımı çok hızlı olan ancak düzeni doğal olarak koruyan liste kavrayışı kullanır. Büyük / küçük harfe duyarlı dizelere uygulamak için kolayca değiştirilebilir. Bu aynı zamanda orijinal kasayı da korur.

def DelDupes(aseq) :
    seen = set()
    return [x for x in aseq if (x.lower() not in seen) and (not seen.add(x.lower()))]

Yakından ilişkili işlevler şunlardır:

def HasDupes(aseq) :
    s = set()
    return any(((x.lower() in s) or s.add(x.lower())) for x in aseq)

def GetDupes(aseq) :
    s = set()
    return set(x for x in aseq if ((x.lower() in s) or s.add(x.lower())))

0

Bir astar listesi kavraması:

values_non_duplicated = [value for index, value in enumerate(values) if value not in values[ : index]]

Değerin önceki bir konumda olmadığından emin olmak için bir koşul ekleyin

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.