xticks barplot python python-3.x generator

barplot - Obteniendo los primeros n elementos únicos de la lista de Python



pandas plot (10)

¡Hay respuestas realmente sorprendentes para esta pregunta, que son rápidas, compactas y brillantes! La razón por la que estoy poniendo este código aquí es porque creo que hay muchos casos en los que no le importa perder ni un microsegundo ni una sola vez en su código para resolver una sola tarea una sola vez.

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)

Tengo una lista de python donde los elementos se pueden repetir.

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

Quiero obtener los primeros n elementos únicos de la lista. Entonces, en este caso, si quiero los primeros 5 elementos únicos, serían:

[1,2,3,4,5]

He encontrado una solución usando generadores:

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

En uso:

>>> i = iterate(a, 5) >>> [e for e in i] [1,2,3,4,5]

Tengo dudas de que esta sea la solución más óptima. ¿Existe una estrategia alternativa que pueda implementar para escribirla de una manera más pitónica y eficiente?


¿Por qué no usar algo como esto?

>>> a = [1, 2, 2, 3, 3, 4, 5, 6] >>> list(set(a))[:5] [1, 2, 3, 4, 5]


Aquí hay un enfoque de Pythonic que usa 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}


Puede usar OrderedDict o, desde Python 3.7, un dict ordinario, ya que se implementan para preservar el orden de inserción. Tenga en cuenta que esto no funcionará con conjuntos.

N = 3 a = [1, 2, 2, 3, 3, 3, 4] d = {x: True for x in a} list(d.keys())[:N]


Puedes adaptar la popular receta de itertools unique_everseen :

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]

Alternativamente, como lo sugiere @Chris_Rands, puede usar itertools.islice para extraer un número fijo de valores de un generador no limitado:

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]

Tenga en cuenta que la receta unique_everseen está disponible en bibliotecas de terceros a través de more_itertools.unique_everseen o toolz.unique , por lo que podría usar:

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 sus objetos son hashable (los int s son hashable), puede escribir la función de utilidad utilizando el método fromkeys de collections.OrderedDict (o comenzando desde Python3.7 un dict simple, ya que se ordenaron officially ) como

from collections import OrderedDict def nub(iterable): """Returns unique elements preserving order.""" return OrderedDict.fromkeys(iterable).keys()

y luego la implementación de iterate se puede simplificar para

from itertools import islice def iterate(itr, upper=5): return islice(nub(itr), upper)

O si quieres siempre una list como salida.

def iterate(itr, upper=5): return list(nub(itr))[:upper]

Mejoras

Como @Chris_Rands mencionó, esta solución recorre toda la colección y podemos mejorar esto escribiendo la utilidad nub en una forma de generator como otros ya lo hicieron:

def nub(iterable): seen = set() add_seen = seen.add for element in iterable: if element in seen: continue yield element add_seen(element)


Suponiendo que los elementos estén ordenados como se muestra, esta es una oportunidad para divertirse con la función groupby en 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)))

Actualizado para usar islice lugar de enumerate por @ juanpa.arrivillaga. Ni siquiera necesita un set para realizar un seguimiento de los duplicados.


Usaría un set para recordar lo que se vio y regresaría del generador cuando haya seen suficiente:

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

Salida:

[1,2,3,4]

De acuerdo con PEP-479 , debería return de los generadores, no raise StopIteration (gracias a @khelwood y @iBug por ese comentario), uno nunca aprende.

Con 3.6 obtienes una advertencia desaprobada, con 3.7 da RuntimeErrors: Plan de transición si aún utilizas la función raise StopIteration

Su solución que usa el elif element not in itr[:index] and count<upper: usa las búsquedas O(k) , siendo k la longitud de la porción; el uso de un conjunto reduce esto a las búsquedas O(1) pero usa más memoria porque El set también debe ser mantenido. Es un intercambio de velocidad y memoria: lo que es mejor depende de la aplicación / datos.

Considere [1,2,3,4,4,4,4,5] vs [1]*1000+[2]*1000+[3]*1000+[4]*1000+[5]*1000+[6] :

Para 6 unicos (en lista más larga):

  • tendría búsquedas de O(1)+O(2)+...+O(5001)
  • el mío tendría 5001*O(1) búsqueda + memoria para el set( {1,2,3,4,5,6})

Utilizando set con sorted+ key

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


Dado

import itertools as it a = [1, 2, 2, 3, 3, 4, 5, 6]

Código

Una simple lista de comprensión (similar a la respuesta de @cdlane).

[k for k, _ in it.groupby(a)][:5] # [1, 2, 3, 4, 5]

Alternativamente, en Python 3.6+:

list(dict.fromkeys(a))[:5] # [1, 2, 3, 4, 5]