web-dev-qa-db-fra.com

Obtenir les n premiers éléments uniques de Python list

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?

46
xssChauhan

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):

  • vous auriez des recherches de O(1)+O(2)+...+O(5001)
  • le mien aurait 5001*O(1) lookup + mémoire pour set( {1,2,3,4,5,6})
47
Patrick Artner

Vous pouvez adapter le populaire itertoolsunique_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]
23
jpp

Si vos objets sont hashable (ints 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]

Améliorations

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)
9
Azat Ibrakov

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}
6
Kasrâmvd

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]
6
Jindra Helcl

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)
5
grapes

É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]
4
pylang

Utiliser set avec sorted+ key

sorted(set(a), key=list(a).index)[:5]
Out[136]: [1, 2, 3, 4, 5]
4
WeNYoBen

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.

4
cdlane

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]
0
prashant rana

Exemple de liste:

a = [1, 2, 2, 3, 3, 4, 5, 6]

La fonction renvoie tout ou nombre d'éléments uniques nécessaires à partir de la liste

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))

Sortie:

output

0
Quanti Monati