Pregunta

Me pregunto si hay una razón por la que no hay first(iterable) en Python las funciones integradas, algo similar a any(iterable) y all(iterable) (puede ser escondido en un stdlib módulo en algún lugar, pero no lo veo en itertools). first iba a realizar un corto-circuito generador de la evaluación, de modo que innecesaria (y un número potencialmente infinito) de operaciones puede ser evitado;es decir,

def identity(item):
    return item

def first(iterable, predicate=identity):
    for item in iterable:
        if predicate(item):
            return item
    raise ValueError('No satisfactory value found')

De esta manera se pueden expresar cosas como:

denominators = (2, 3, 4, 5)
lcd = first(i for i in itertools.count(1)
    if all(i % denominators == 0 for denominator in denominators))

Es evidente que usted no puede hacer list(generator)[0] en ese caso, ya que el generador no terminar.

O si usted tiene un montón de expresiones regulares para el partido contra (útil cuando todos tienen el mismo groupdict interfaz):

match = first(regex.match(big_text) for regex in regexes)

Se ahorra un montón de procesamiento innecesario, evitando list(generator)[0] y en cortocircuito en una coincidencia positiva.

¿Fue útil?

Solución

Si usted tiene un iterador, sólo puede llamar a su método next. Algo así como:

In [3]: (5*x for x in xrange(2,4)).next()
Out[3]: 10

Otros consejos

Hay un paquete de PyPI llamada “primera” que hace esto:

>>> from first import first
>>> first([0, None, False, [], (), 42])
42

Así es como se puede utilizar para devolver el primer número impar, por ejemplo:

>> first([2, 14, 7, 41, 53], key=lambda x: x % 2 == 1)
7

Si lo que desea es devolver el primer elemento del iterador independientemente de si es verdad o no, hacer esto:

>>> first([0, None, False, [], (), 42], key=lambda x: True)
0

Es un paquete muy pequeño: sólo contiene esta función, que no tiene dependencias, y funciona en Python 2 y 3. Es un solo archivo, por lo que ni siquiera tiene que instalarlo para usarlo

De hecho, aquí es casi todo el código fuente (de la versión 2.0.1, por Hynek Schlawack, publicado bajo la licencia MIT):

def first(iterable, default=None, key=None):
    if key is None:
        for el in iterable:
            if el:
                return el
    else:
        for el in iterable:
            if key(el):
                return el
    return default

Le pregunté a una pregunta similares recientemente (¡Gracias marcado como duplicado de esta cuestión por ahora). Mi preocupación era que también me ha gustado usar empotrados solamente para resolver el problema de encontrar el primer verdadero valor de un generador. Mi propia solución entonces fue la siguiente:

x = next((v for v in (f(x) for x in a) if v), False)

En el ejemplo de la búsqueda de la expresión regular este primer partido sería el siguiente (no es la primera coincidencia de patrones!):

patterns = [ r'\d+', r'\s+', r'\w+', r'.*' ]
text = 'abc'
firstMatch = next(
  (match for match in
    (re.match(pattern, text) for pattern in patterns)
   if match),
  False)

No evalúa el predicado dos veces (como tendría que hacer si se devuelve sólo el patrón) y no usar hacks como locales en comprensiones.

Pero tiene dos generadores anidados donde la lógica dictaría a utilizar sólo uno. Por lo que una solución mejor sería bueno.

Hay un iterador "corte" en itertools. Emula las operaciones de división que estamos familiarizados en Python. Lo que estamos buscando es algo similar a esto:

myList = [0,1,2,3,4,5]
firstValue = myList[:1]

El equivalente utilizando itertools para iteradores:

from itertools import islice
def MyGenFunc():
    for i in range(5):
        yield i

mygen = MyGenFunc()
firstValue = islice(mygen, 0, 1)
print firstValue 

Hay cierta ambigüedad en su pregunta. Su definición de primero y el ejemplo de expresiones regulares implica que exista una prueba booleana. Pero el ejemplo denominadores tiene explícitamente una cláusula if; por lo que es solamente una coincidencia que cada entero pasa a ser verdad.

Parece que la combinación de al lado y itertools.ifilter le dará lo que quiere.

match = next(itertools.ifilter(None, (regex.match(big_text) for regex in regexes)))

Haskell hace uso de lo que se acaba de describir, ya que la función take (o como la función parcial take 1, técnicamente). Python Cookbook tiene generador de envolturas escritos que realizan la misma funcionalidad como takeWhile, drop, y <=> en Haskell.

Pero por qué eso no es un built-in, su conjetura es tan buena como la mía.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top