8

Ich habe ein einfaches Skript geschrieben, um ein "logisches Puzzle" zu lösen, die Art von Puzzle aus der Schule, wo man eine Reihe von Regeln bekommt und dann die Lösung für Probleme finden kann wie "Es gibt fünf Musiker namens A, B , C, D und E spielen in einem Konzert, jeder spielt nacheinander ... wenn A vor B geht und D nicht zuletzt ... wie ist die Reihenfolge, wer wann spielt? " usw.Gibt es ein Python-Idiom zum Auswerten einer Liste von Funktionen/Ausdrücken mit Kurzschlüssen?

mögliche Lösungen zu bewerten, schrieb ich jede „Regel“ als eine separate Funktion, die, wenn eine mögliche Lösung (dargestellt einfach als Liste von Strings) gültig ist, zum Beispiel

#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... 

ich bewerten würde Ich bin daran interessiert, den Pythonic-Weg zu finden, um zu testen, ob eine mögliche Lösung alle solchen Regeln besteht, mit der Fähigkeit, die Bewertung von Regeln zu stoppen, nachdem die erste fehlgeschlagen ist.

Zuerst schrieb ich eine möglichst einfache Sache:

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

Aber das schien ziemlich hässlich. Ich dachte, vielleicht könnte ich dies mit so etwas wie eine Liste Verständnis etwas elegantere lesen ...

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

... aber dann merkte ich, dass, da die Liste Verständnis vor der all() Funktion erzeugt wird, wird beurteilt, dass dies hat den Nebeneffekt, dass sie überhaupt nicht kurzgeschlossen sind - jede Regel wird ausgewertet, selbst wenn die erste False zurückgibt.

Also meine Frage ist: Gibt es eine mehr Pythonic/functional Möglichkeit, eine Liste von True/False Ausdrücke, mit Kurzschlüssen bewerten zu können, ohne die Notwendigkeit, eine lange Liste von return f1(s) and f2(s) and f3(s) ... zu schreiben?

Antwort

13

Verwenden Sie ein generator expression:

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

syntaktischer Zucker: Sie können die zusätzliche Klammern weglassen:

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

Ein Generator ist (grundsätzlich) ein Objekt mit einer .next() Methode, die das nächste Element zurückzugibt in einigen iterierbaren. Dies bedeutet, dass sie nützliche Dinge tun können, wie zum Beispiel das Lesen einer Datei in Blöcken, ohne sie in den Speicher zu laden, oder das Iterieren zu riesigen Ganzzahlen. Sie können über sie transparent mit for Schleifen iterieren; Python behandelt es hinter den Kulissen. Zum Beispiel ist range ein Generator in Py3k.

Sie können Ihren eigenen Generator Ausdrücke rollen durch die yield Anweisung anstelle return in einer Funktionsdefinition verwendet:

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

und Python behandelt die staatliche Sparfunktion und so weiter. Sie sind genial!

+1

Also ist der grundlegende Unterschied hier die Klammern in 'return all ([r (Lösung) für r in Regeln])', damit keine Liste aller Ergebnisse zu erstellen, bevor 'all()' ausgewertet wird? –

+2

Ja. In beiden Fällen wird das Argument für "all" ausgewertet, bevor es übergeben wird, aber die Auswertung eines Listenverständnisses erstellt die gesamte Liste im Speicher, während die Auswertung eines Generatorausdrucks ein Generatorobjekt erzeugt, das die Elemente bei Bedarf lädt. – katrielalex

+0

Perfekt, das macht sehr viel Sinn - danke –