web-dev-qa-db-fra.com

En Python, quel est l'algorithme le plus rapide pour éliminer les doublons d'une liste afin que tous les éléments soient uniques * tout en préservant l'ordre *?

Par exemple:

>>> x = [1, 1, 2, 'a', 'a', 3]
>>> unique(x)
[1, 2, 'a', 3]

Supposons que les éléments de liste sont hachables.

Clarification : Le résultat doit conserver le premier duplicaté dans la liste. Par exemple, [1, 2, 3, 2, 3, 1] devient [1, 2, 3].

42
J Miller
def unique(items):
    found = set([])
    keep = []

    for item in items:
        if item not in found:
            found.add(item)
            keep.append(item)

    return keep

print unique([1, 1, 2, 'a', 'a', 3])
31
Terhorst

À l'aide de:

lst = [8, 8, 9, 9, 7, 15, 15, 2, 20, 13, 2, 24, 6, 11, 7, 12, 4, 10, 18, 13, 23, 11, 3, 11, 12, 10, 4, 5, 4, 22, 6, 3, 19, 14, 21, 11, 1, 5, 14, 8, 0, 1, 16, 5, 10, 13, 17, 1, 16, 17, 12, 6, 10, 0, 3, 9, 9, 3, 7, 7, 6, 6, 7, 5, 14, 18, 12, 19, 2, 8, 9, 0, 8, 4, 5]

Et en utilisant le module TIMOIT:

$ python -m timeit -s 'import uniquetest' 'uniquetest.etchasketch(uniquetest.lst)'

Et ainsi de suite pour les différentes autres fonctions (que j'ai nommées d'après leurs affiches), j'ai les résultats suivants (sur ma première génération Intel MacBook Pro):

Allen:                  14.6 µs per loop [1]
Terhorst:               26.6 µs per loop
Tarle:                  44.7 µs per loop
ctcherry:               44.8 µs per loop
Etchasketch 1 (short):  64.6 µs per loop
Schinckel:              65.0 µs per loop
Etchasketch 2:          71.6 µs per loop
Little:                 89.4 µs per loop
Tyler:                 179.0 µs per loop

[1] Notez que Allen modifie la liste en place - je pense que cela a biaisé l'heure, en ce que le module timeit exécute le code 100000 fois et 99999 d'entre eux sont avec la liste Dupe-Droit.


Résumé: La mise en oeuvre directe avec des ensembles gagne sur des doublures confuses :-)

18
John Fouhy

Voici la solution la plus rapide jusqu'à présent (pour l'entrée suivante):

def del_dups(seq):
    seen = {}
    pos = 0
    for item in seq:
        if item not in seen:
            seen[item] = True
            seq[pos] = item
            pos += 1
    del seq[pos:]

lst = [8, 8, 9, 9, 7, 15, 15, 2, 20, 13, 2, 24, 6, 11, 7, 12, 4, 10, 18, 
       13, 23, 11, 3, 11, 12, 10, 4, 5, 4, 22, 6, 3, 19, 14, 21, 11, 1, 
       5, 14, 8, 0, 1, 16, 5, 10, 13, 17, 1, 16, 17, 12, 6, 10, 0, 3, 9, 
       9, 3, 7, 7, 6, 6, 7, 5, 14, 18, 12, 19, 2, 8, 9, 0, 8, 4, 5]
del_dups(lst)
print(lst)
# -> [8, 9, 7, 15, 2, 20, 13, 24, 6, 11, 12, 4, 10, 18, 23, 3, 5, 22, 19, 14, 
#     21, 1, 0, 16, 17]

Le dictionnaire recherche est légèrement plus rapide que l'ensemble en Python 3.

14
jfs

Ce qui va être le plus rapide dépend de quel pourcentage de votre liste est des duplicats. S'il s'agit de presque tous les doublons, avec peu d'articles uniques, la création d'une nouvelle liste sera probablement plus rapide. Si ce sont surtout des articles uniques, les supprimer de la liste d'origine (ou d'une copie) sera plus rapide.

Voici un pour modifier la liste en place:

def unique(items):
  seen = set()
  for i in xrange(len(items)-1, -1, -1):
    it = items[i]
    if it in seen:
      del items[i]
    else:
      seen.add(it)

Les itérations en arrière sur les indices garantissent que l'élimination des articles n'affecte pas l'itération.

13
Allen

C'est la méthode de la place la plus rapide que j'ai trouvée (en supposant une grande proportion de doublons):

def unique(l):
    s = set(); n = 0
    for x in l:
        if x not in s: s.add(x); l[n] = x; n += 1
    del l[n:]

Ceci est de 10% plus rapide que l'implémentation d'Allen, sur laquelle elle est basée (chronométré avec le temps-à-tête.Repeat, JIT compilé par PSYCO). Il conserve la première instance de tout double.

repton-Infinity: Je serais intéressé si vous pouviez confirmer mes timings.

9
James Hopkin

Variation de générateur obligatoire:

def unique(seq):
  seen = set()
  for x in seq:
    if x not in seen:
      seen.add(x)
      yield x
7
Constantin

Cela peut être le moyen le plus simple:

list(OrderedDict.fromkeys(iterable))

À partir de Python 3.5, commandé est maintenant mis en œuvre dans C, il s'agissait donc du plus bref, du plus propre et le plus rapide.

7
Raymond Hettinger

Bon mot:

new_list = reduce(lambda x,y: x+[y][:1-int(y in x)], my_list, [])
5
Tyler

Une doublure sur place pour cela:

>>> x = [1, 1, 2, 'a', 'a', 3]
>>> [ item for pos,item in enumerate(x) if x.index(item)==pos ]
[1, 2, 'a', 3]
4
Mario Ruggier

Tiré de - http://www.peterbe.com/plog/uniqifiers-benchmark

def f5(seq, idfun=None):  
    # order preserving 
    if idfun is None: 
        def idfun(x): return x 
    seen = {} 
    result = [] 
    for item in seq: 
        marker = idfun(item) 
        # in old Python versions: 
        # if seen.has_key(marker) 
        # but in new ones: 
        if marker in seen: continue 
        seen[marker] = 1 
        result.append(item) 
    return result
4
ctcherry

C'est le plus rapide, comparant toutes les choses à partir de cette question longue discussion et les autres réponses données ici, en référence à cela Benchmark . Il est encore 25% plus rapide que la fonction la plus rapide de la discussion, f8. Merci à David Kirby pour l'idée.

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

Comparaison de temps:

$ python uniqifiers_benchmark.py 
* f8_original 3.76
* uniquify 3.0
* terhorst 5.44
* terhorst_localref 4.08
* del_dups 4.76
4
Michael

Vous pouvez réellement faire quelque chose de vraiment cool dans Python pour résoudre ceci. Vous pouvez créer une compréhension de la liste qui se référencerait telle qu'elle est construite. Comme suit:

   # remove duplicates...
   def unique(my_list):
       return [x for x in my_list if x not in locals()['_[1]'].__self__]

Edit: J'ai supprimé le "Self", et cela fonctionne sur Mac OS X, Python 2.5.1.

Le "secret" de Python est la référence "Secret" de Python à la nouvelle liste. Ce qui précède, bien sûr, est un peu désordonné, mais vous pourriez l'adapter à vos besoins si nécessaire. Par exemple, vous pouvez réellement écrire une fonction qui renvoie une référence à la compréhension; Cela ressemblerait plus comme:

return [x for x in my_list if x not in this_list()]

3
Jake

Supprimer les duplicats et la préservation de la commande:

Il s'agit d'un 2-doubleur rapide qui exploite des fonctionnalités intégrées de compréhensions de liste et de dict.

x = [1, 1, 2, 'a', 'a', 3]

tmpUniq = {} # temp variable used below 
results = [tmpUniq.setdefault(i,i) for i in x if i not in tmpUniq]

print results
[1, 2, 'a', 3]

La fonction DIC.SETDEFAULTS () renvoie la valeur ainsi que l'ajoutant au TEMP DICT directement dans la compréhension de la liste. En utilisant les fonctions intégrées et que les hachages de la DICE fonctionnent pour optimiser l'efficacité du processus.

2
Scot

has_key in python est O (1). L'insertion et la récupération d'un hachage sont également O (1). Boucles à deux articles deux fois, donc O (n).

def unique(list):
  s = {}
  output = []
  for x in list:
    count = 1
    if(s.has_key(x)):
      count = s[x] + 1

    s[x] = count
  for x in list:
    count = s[x]
    if(count > 0):
      s[x] = 0
      output.append(x)
  return output
1
etchasketch

Il y a de grandes solutions efficaces ici. Cependant, pour quiconque n'est pas concerné par la solution absolue la plus efficace O(n), j'irais avec la solution simple O(n^2*log(n)) Solution:

def unique(xs):
    return sorted(set(xs), key=lambda x: xs.index(x))

ou la solution la plus efficace de deux doublures O(n*log(n)) Solution:

def unique(xs):
    positions = dict((e,pos) for pos,e in reversed(list(enumerate(xs))))
    return sorted(set(xs), key=lambda x: positions[x])
1
Eli Courtwright

O (n) si dict est hachage, O(nlogn) si dict est d'arbre et simple, fixé. Merci à Matthew pour la suggestion. Désolé, je ne connais pas les types sous-jacents.

def unique(x):    
  output = []
  y = {}
  for item in x:
    y[item] = ""

  for item in x:
    if item in y:
      output.append(item)

  return output
1
Wesley Tarle

Voici deux recettes de la documentation itTools:

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 ifilterfalse(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

def unique_justseen(iterable, key=None):
    "List unique elements, preserving order. Remember only the element just seen."
    # unique_justseen('AAAABBBCCDAABBB') --> A B C D A B
    # unique_justseen('ABBCcAD', str.lower) --> A B C A D
    return imap(next, imap(itemgetter(1), groupby(iterable, key)))
1
Raymond Hettinger
x = [] # Your list  of items that includes Duplicates

# Assuming that your list contains items of only immutable data types

dict_x = {} 

dict_x = {item : item for i, item in enumerate(x) if item not in dict_x.keys()}
# Average t.c. = O(n)* O(1) ; furthermore the dict comphrehension and generator like behaviour of enumerate adds a certain efficiency and Pythonic feel to it.

x = dict_x.keys() # if you want your output in list format 
0
BigDataGuy
>>> def unique(list):
...   y = []
...   for x in list:
...     if x not in y:
...       y.append(x)
...   return y
0
etchasketch

Si vous sortez la liste vide de l'appel à définir () dans la réponse de Terhost, vous obtenez un coup de pouce peu de vitesse.

Modifier: trouvé = set ([])
[.____] à: trouvé = set ()

Cependant, vous n'avez pas besoin de l'ensemble du tout.

def unique(items):
    keep = []

    for item in items:
        if item not in keep:
            keep.append(item)

    return keep

Utiliser Timeit, j'ai reçu ces résultats:

avec ensemble ([]) - 4.97210427363
[.____] avec ensemble () - 4.65712377445
[.____] sans set - 3.44865284975

0
user18695