J'ai une liste python où les éléments peuvent se répéter.
>>> a = [1,2,2,3,3,4,5,6]
Je veux obtenir les premiers n
éléments uniques de la liste. Donc, dans ce cas, si je veux les 5 premiers éléments uniques, ils seraient:
[1,2,3,4,5]
J'ai mis au point une solution utilisant des générateurs:
def iterate(itr, upper=5):
count = 0
for index, element in enumerate(itr):
if index==0:
count += 1
yield element
Elif element not in itr[:index] and count<upper:
count += 1
yield element
Utilisé:
>>> i = iterate(a, 5)
>>> [e for e in i]
[1,2,3,4,5]
Je doute que cette solution soit la plus optimale. Existe-t-il une stratégie alternative que je puisse mettre en œuvre pour l'écrire de manière plus efficace et plus pythonique?
J'utiliserais un set
pour rappeler ce qui a été vu et revenir du générateur lorsque vous avez assez de seen
:
a = [1,2,2,3,3,4,5,6]
def get_unique_N(iterable, N):
"""Yields (in order) the first N unique elements of iterable.
Might yield less if data too short."""
seen = set()
for e in iterable:
if e in seen:
continue
seen.add(e)
yield e
if len(seen) == N:
return
k = get_unique_N([1,2,2,3,3,4,5,6], 4)
print(list(k))
Sortie:
[1,2,3,4]
Selon PEP-479 vous devriez return
à partir de générateurs, pas raise StopIteration
- grâce à @ khelwood & @ iBug pour ce commentaire - on n’apprend jamais.
Avec 3.6, vous obtenez un avertissement déconseillé. Avec 3.7, il donne à RuntimeErrors: Plan de transition si vous utilisez toujours raise StopIteration
Votre solution utilisant Elif element not in itr[:index] and count<upper:
Utilise O(k)
recherches - avec k
la longueur de la tranche - utiliser un jeu réduit ceci à O(1)
recherches mais utilise davantage mémoire parce que l'ensemble doit également être conservé. C'est un compromis vitesse/mémoire - ce qui est mieux, c'est de dépendre de l'application/des données.
Considérons [1,2,3,4,4,4,4,5]
Vs [1]*1000+[2]*1000+[3]*1000+[4]*1000+[5]*1000+[6]
:
Pour 6 uniques (dans une liste plus longue):
O(1)+O(2)+...+O(5001)
5001*O(1)
lookup + mémoire pour set( {1,2,3,4,5,6})
Vous pouvez adapter le populaire itertools
unique_everseen
_ recette :
def unique_everseen_limit(iterable, limit=5):
seen = set()
seen_add = seen.add
for element in iterable:
if element not in seen:
seen_add(element)
yield element
if len(seen) == limit:
break
a = [1,2,2,3,3,4,5,6]
res = list(unique_everseen_limit(a)) # [1, 2, 3, 4, 5]
Alternativement, comme suggéré par @Chris_Rands, vous pouvez utiliser itertools.islice
pour extraire un nombre fixe de valeurs d’un générateur non limité:
from itertools import islice
def unique_everseen(iterable):
seen = set()
seen_add = seen.add
for element in iterable:
if element not in seen:
seen_add(element)
yield element
res = list(islice(unique_everseen(a), 5)) # [1, 2, 3, 4, 5]
Noter la unique_everseen
La recette est disponible dans les bibliothèques tierces via more_itertools.unique_everseen
ou toolz.unique
, vous pouvez donc utiliser:
from itertools import islice
from more_itertools import unique_everseen
from toolz import unique
res = list(islice(unique_everseen(a), 5)) # [1, 2, 3, 4, 5]
res = list(islice(unique(a), 5)) # [1, 2, 3, 4, 5]
Si vos objets sont hashable (int
s sont hashable), vous pouvez écrire une fonction d’utilitaire en utilisant fromkeys
méthode de collections.OrderedDict
class (ou à partir de Python3.7 un simple dict
, car ils sont devenus officiellement = ordonné) comme
from collections import OrderedDict
def nub(iterable):
"""Returns unique elements preserving order."""
return OrderedDict.fromkeys(iterable).keys()
puis la mise en oeuvre de iterate
peut être simplifiée
from itertools import islice
def iterate(itr, upper=5):
return islice(nub(itr), upper)
ou si vous voulez toujours un list
en sortie
def iterate(itr, upper=5):
return list(nub(itr))[:upper]
Comme @Chris_Rands l'a mentionné, cette solution parcourt l'intégralité de la collection et nous pouvons l'améliorer en écrivant l'utilitaire nub
sous la forme de générateur comme d'autres l'ont déjà fait:
def nub(iterable):
seen = set()
add_seen = seen.add
for element in iterable:
if element in seen:
continue
yield element
add_seen(element)
Voici une approche Pythonic utilisant itertools.takewhile()
:
In [95]: from itertools import takewhile
In [96]: seen = set()
In [97]: set(takewhile(lambda x: seen.add(x) or len(seen) <= 4, a))
Out[97]: {1, 2, 3, 4}
Vous pouvez utiliser OrderedDict
ou, puisque Python 3.7, un dict
ordinaire, car ils sont implémentés pour préserver l'ordre d'insertion. Notez que cela ne fonctionnera pas. avec des ensembles.
N = 3
a = [1, 2, 2, 3, 3, 3, 4]
d = {x: True for x in a}
list(d.keys())[:N]
Il existe des réponses vraiment étonnantes à cette question, rapides, compactes et brillantes! La raison pour laquelle je mets ici ce code est que je pense qu'il y a beaucoup de cas où vous ne vous souciez pas de perdre 1 microseconde ni de vouloir ajouter des bibliothèques dans votre code pour résoudre une tâche simple.
a = [1,2,2,3,3,4,5,6]
res = []
for x in a:
if x not in res: # yes, not optimal, but doesnt need additional dict
res.append(x)
if len(res) == 5:
break
print(res)
Étant donné
import itertools as it
a = [1, 2, 2, 3, 3, 4, 5, 6]
Code
Compréhension de liste simple (similaire à la réponse de @ cdlane).
[k for k, _ in it.groupby(a)][:5]
# [1, 2, 3, 4, 5]
Sinon, dans Python 3.6+:
list(dict.fromkeys(a))[:5]
# [1, 2, 3, 4, 5]
Utiliser set
avec sorted+ key
sorted(set(a), key=list(a).index)[:5]
Out[136]: [1, 2, 3, 4, 5]
En supposant que les éléments soient classés comme indiqué, c’est une occasion de vous amuser avec la fonction groupby
d’itertools:
from itertools import groupby, islice
def first_unique(data, upper):
return islice((key for (key, _) in groupby(data)), 0, upper)
a = [1, 2, 2, 3, 3, 4, 5, 6]
print(list(first_unique(a, 5)))
Mise à jour pour utiliser islice
au lieu de enumerate
par @ juanpa.arrivillaga. Vous n'avez même pas besoin de set
pour suivre les doublons.
Pourquoi ne pas utiliser quelque chose comme ça?
>>> a = [1, 2, 2, 3, 3, 4, 5, 6]
>>> list(set(a))[:5]
[1, 2, 3, 4, 5]
a = [1,2,2,3,3,4,5,6]
from collections import defaultdict
def function(lis,n):
dic = defaultdict(int)
sol=set()
for i in lis:
try:
if dic[i]:
pass
else:
sol.add(i)
dic[i]=1
if len(sol)>=n:
break
except KeyError:
pass
return list(sol)
print(function(a,3))
sortie
[1, 2, 3]
a = [1, 2, 2, 3, 3, 4, 5, 6]
1er argument - liste avec laquelle travailler, 2e argument (facultatif) - nombre d'éléments uniques (par défaut - Aucun - cela signifie que tous les éléments uniques seront retournés)
def unique_elements(lst, number_of_elements=None):
return list(dict.fromkeys(lst))[:number_of_elements]
Voici un exemple de fonctionnement. Le nom de la liste est "a", et nous devons obtenir 2 éléments uniques:
print(unique_elements(a, 2))