J'ai une liste de listes en Python:
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
Et je veux en supprimer les éléments en double. Était-ce une liste normale et non des listes que je pourrais utiliser set
. Mais malheureusement, cette liste ne peut pas être hachée et ne peut pas constituer un ensemble de listes. Seulement des tuples. Je peux donc transformer toutes les listes en tuples, puis utiliser set et revenir en listes. Mais ce n'est pas rapide.
Comment cela peut-il être fait de la manière la plus efficace?
Le résultat de la liste ci-dessus devrait être:
k = [[5, 6, 2], [1, 2], [3], [4]]
Je me fiche de la préservation de l'ordre.
Remarque: cette question est similaire mais pas tout à fait ce dont j'ai besoin. Recherche SO mais n'a pas trouvé le double exact.
Analyse comparative:
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)
"loop in" (méthode quadratique) le plus rapide de tous pour les listes courtes. Pour les longues listes, c'est plus rapide que tout le monde sauf la méthode groupby. Est-ce que ça a du sens?
Pour la liste courte (celle du code), 100000 itérations:
[set] Elapsed: 1.3900001049
[sort] Elapsed: 0.891000032425
[groupby] Elapsed: 0.780999898911
[loop in] Elapsed: 0.578000068665
Pour une liste plus longue (celle du code dupliquée 5 fois):
[set] Elapsed: 3.68700003624
[sort] Elapsed: 3.43799996376
[groupby] Elapsed: 1.03099989891
[loop in] Elapsed: 1.85900020599
Réponses:
itertools
offre 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
timeit
utile 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 sousnodup.py
: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:
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:
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.la source
Faites-le manuellement, créez une nouvelle
k
liste et ajoutez des entrées introuvables jusqu'à présent:Simple à comprendre, et vous conservez l'ordre de la première occurrence de chaque élément si cela est utile, mais je suppose que sa complexité est quadratique lorsque vous recherchez l'ensemble de
new_k
chaque élément.la source
k = ([[1, 2], [4], [5, 6, 2], [1, 2], [3], [5, 2], [6], [8], [9]] +[[x] for x in range(1000)]) *5
montrera bien le comportement quadratiqueJe ne sais pas si c'est forcément plus rapide, mais vous n'avez pas besoin d'utiliser des tuples et des ensembles.
la source
random
et chronométrertime
.Toutes les
set
solutions liées à ce problème jusqu'à présent nécessitent la création d'unset
avant l'itération.Il est possible de rendre cet ordre paresseux et en même temps de préserver l'ordre, en itérant la liste des listes et en ajoutant à un "vu"
set
. Ensuite, ne donnez une liste que si elle n'est pas trouvée dans ce trackerset
.Cette
unique_everseen
recette est disponible dans laitertools
documentation . Il est également disponible dans latoolz
bibliothèque tierce :Notez que la
tuple
conversion est nécessaire car les listes ne sont pas hachables.la source
Même votre «longue» liste est assez courte. De plus, les avez-vous choisis pour correspondre aux données réelles? Les performances varieront en fonction de l'aspect réel de ces données. Par exemple, vous avez une courte liste répétée encore et encore pour faire une liste plus longue. Cela signifie que la solution quadratique est linéaire dans vos benchmarks, mais pas en réalité.
Pour les listes réellement volumineuses, le code défini est votre meilleur choix: il est linéaire (bien que gourmand en espace). Les méthodes de tri et de groupby sont O (n log n) et la méthode en boucle est évidemment quadratique, vous savez donc comment elles évolueront lorsque n devient vraiment grand. Si c'est la taille réelle des données que vous analysez, alors qui s'en soucie? C'est minuscule.
Au passage, je constate une accélération notable si je ne forme pas de liste intermédiaire pour faire l'ensemble, c'est-à-dire si je remplace
avec
La vraie solution peut dépendre de plus d'informations: êtes-vous sûr qu'une liste de listes est vraiment la représentation dont vous avez besoin?
la source
La liste des tuple et {} peut être utilisée pour supprimer les doublons
la source
Créez un dictionnaire avec tuple comme clé et imprimez les clés.
la source
Cela devrait fonctionner.
la source
Étrangement, les réponses ci-dessus suppriment les «doublons», mais que faire si je veux également supprimer la valeur dupliquée ?? Ce qui suit devrait être utile et ne crée pas de nouvel objet en mémoire!
et le o / p est:
la source
Une autre solution probablement plus générique et plus simple consiste à créer un dictionnaire indexé par la version chaîne des objets et à obtenir les valeurs () à la fin:
Le hic, c'est que cela ne fonctionne que pour les objets dont la représentation sous forme de chaîne est une clé unique assez bonne (ce qui est vrai pour la plupart des objets natifs).
la source