8

Escribí un guión simple para resolver un "acertijo lógico", el tipo de acertijo de la escuela donde se le dan varias reglas y luego debe ser capaz de encontrar la solución para problemas como "Hay cinco músicos llamados A, B , C, D y E tocando en un concierto, cada uno toca uno después del otro ... si A va antes que B, y D no es el último ... ¿cuál es el orden de quién toca cuándo? etc.¿Hay un modismo de Python para evaluar una lista de funciones/expresiones con cortocircuitos?

para evaluar las posibles soluciones, escribí cada una "regla", como una función separada que evaluar si una solución posible (representado simplemente como una lista de cadenas) es válido, por ejemplo

#Fifth slot must be B or D 
def rule1(solution): 
    return solution[4] == 'B' or solution[4] == 'D' 

#There must be at least two spots between A and B 
def rule2(solution): 
    returns abs(solution.index('A') - solution.index('B')) >= 2 

#etc... 

I Estoy interesado en encontrar la forma Pythonic para probar si una posible solución pasa todas esas reglas, con la capacidad de dejar de evaluar las reglas después de que la primera haya fallado.

Al principio me escribió el más simple que sea posible:

def is_valid(solution): 
    return rule1(solution) and rule2(solution) and rule3(solution) and ... 

pero esto parecía bastante feo. Pensé que tal vez podría hacer que este leyó un poco más elegante, con algo así como una lista por comprensión ...

def is_valid(solution) 
    rules = [rule1, rule2, rule3, rule4, ... ] 
    return all([r(solution) for f in rules]) 

... pero luego me di cuenta de que, desde la comprensión de lista se genera antes de la función all() se evalúa, que esto tiene el efecto secundario de no estar en cortocircuito en absoluto: cada regla se evaluará incluso si la primera devuelve False.

Así que mi pregunta es: ¿hay una manera más Pythonic/funcional para poder evaluar una lista de True/False expresiones, con cortocircuitos, sin la necesidad de escribir una larga lista de return f1(s) and f2(s) and f3(s) ...?

Respuesta

13

Utilice un generator expression:

rules = [ rule1, rule2, rule3, rule4, ... ] 
rules_generator = (r(solution) for r in rules) 
return all(rules_generator) 

azúcar sintáctica: se puede omitir los paréntesis adicionales:

rules = [ rule1, rule2, rule3, rule4, ... ] 
return all(r(solution) for r in rules) 

Un generador es (básicamente) un objeto con un método .next(), que devuelve el siguiente elemento en algunos iterable. Esto significa que pueden hacer cosas útiles, como leer un archivo en fragmentos sin cargarlo todo en la memoria, o iterar hasta enteros enormes. Puede iterar sobre ellos con for bucles de forma transparente; Python lo maneja detrás de escena. Por ejemplo, range es un generador en Py3k.

se puede rodar sus propias expresiones generador personalizado mediante la instrucción yield en lugar de return en una definición de función:

def integers(): 
    i = 0 
    while True: 
     yield i 

y Python se encargará de guardar el estado de la función y así sucesivamente. ¡Son increíbles!

+1

Así que aquí la diferencia básica es omitir los corchetes en 'return all ([r (solution) for r in rules])', ¿no se creará una lista de todos los resultados antes de evaluar 'all()'? –

+2

Sí. En ambos casos, el argumento para 'todo' se evalúa antes de transmitirse, pero la evaluación de una lista de comprensión crea toda la lista en la memoria mientras que la evaluación de una expresión generadora crea un objeto generador, que carga los elementos a petición. – katrielalex

+0

Perfecto, esto tiene mucho sentido - gracias –

Cuestiones relacionadas