>>> 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]]
itertoolsoffre souvent les plus rapides et les plus puissantes solutions à ce genre de problèmes, et il est bien la peine de se connaît très bien -)
Edit : comme je le mentionne dans un commentaire, les efforts d'optimisation normaux se concentrent sur les gros intrants (l'approche big-O) car c'est tellement plus facile que cela offre de bons retours sur efforts. Mais parfois (essentiellement pour les «goulots d'étranglement tragiquement cruciaux» dans les boucles internes profondes de code qui repoussent les limites des limites de performances), il peut être nécessaire d'entrer beaucoup plus en détail, en fournissant des distributions de probabilité, en décidant quelles mesures de performance optimiser (peut-être la limite supérieure ou le 90e centile est plus important qu'une moyenne ou une médiane, en fonction de ses applications), en effectuant des vérifications éventuellement heuristiques au début pour choisir différents algorithmes en fonction des caractéristiques des données d'entrée, etc.
Des mesures soigneuses des performances "ponctuelles" (code A vs code B pour une entrée spécifique) font partie de ce processus extrêmement coûteux, et le module de bibliothèque standard est timeitutile ici. Cependant, il est plus facile de l'utiliser à l'invite du shell. Par exemple, voici un court module pour présenter l'approche générale de ce problème, enregistrez-le sous 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))
Notez le contrôle de cohérence (effectué lorsque vous venez de le faire python nodup.py) et la technique de levage de base (rendre les noms globaux constants locaux à chaque fonction pour la vitesse) pour mettre les choses sur un pied d'égalité.
Nous pouvons maintenant effectuer des vérifications sur la petite liste d'exemples:
$ 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
confirmant que l'approche quadratique a des constantes suffisamment petites pour la rendre intéressante pour les petites listes avec peu de valeurs dupliquées. Avec une courte liste sans doublons:
$ 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
l'approche quadratique n'est pas mauvaise, mais les méthodes tri et groupby sont meilleures. Etc.
Si (comme l'obsession de la performance le suggère), cette opération est au cœur de la boucle interne de votre application repoussant les limites, cela vaut la peine d'essayer le même ensemble de tests sur d'autres échantillons d'entrée représentatifs, en détectant éventuellement une mesure simple qui pourrait vous permettre de manière heuristique choisissez l'une ou l'autre approche (mais la mesure doit être rapide, bien sûr).
Cela vaut également la peine d'envisager de conserver une représentation différente pour k- pourquoi doit-il s'agir d'une liste de listes plutôt que d'un ensemble de tuples en premier lieu? Si la tâche de suppression des doublons est fréquente et que le profilage montre qu'il s'agit du goulot d'étranglement des performances du programme, conserver un ensemble de tuples tout le temps et obtenir une liste de listes à partir de celui-ci uniquement si et où cela est nécessaire, peut être globalement plus rapide, par exemple.