@Bolitho: Du könntest nach der Schleife noch mal testen ob in `passport` Werte sind und falls ja an passports anhängen. Der Test ist hier nicht nötig weil die Datei auf jeden Fall mindestens einen Reisepass enthält, aber in realen Programmen sollte man auch mit einer leeren Datei rechnen. Oder Du fügst den eingelesenen Daten künstlich eine Leerzeile hinzu, beispielsweise mit `itertools.chain()`, oder wenn man denn unbedingt alle Zeilen in eine Liste einlesen muss (warum eigentlich?) mit `append()`. Ich bin diese Entscheidung mit `more_itertools.split_at()` losgeworden.
Anmerkungen zum Quelltext: Die Kommentare im Code sind fast alle überflüssig, weil sie nur das beschreiben was der Code recht offensichtlich macht.
Dann wieder alles global und keine Unterscheidung zwischen Variablen und Konstanten. `required_fields` ist eine Konstante, sollte also KOMPLETT_GROSS geschrieben werden.
Variablen am Anfang definieren statt da wo sie verwendet werden ist so eine komische Unart die wohl noch immer von C, Pascal, und ähnlichen Sprachen kommt, wo die Sprache das (zumindest früher) erzwungen hat. Und zwar nicht weil das für Menschen einfach zu verstehen ist und zu verständlichem Quelltext führt, sondern weil die Compiler das einfacher verarbeiten konnten.
`f` ist kein guter Name. Wie wäre es mit `lines`? Und dann das `readlines()` weglassen — ich verstehe nicht warum ich diese dusselige Methode immer noch so oft sehe. Die sollte abgeschafft werden. Anfänger kommen dann gar nicht erst auf die Idee die zu verwenden, und wer dann tatsächlich mal eine Liste braucht, verwendet einfach `list()`. Was dann auch gleich mit jedem anderen iterierbaren Objekt funktioniert, und nicht nur mit Datei-Objekten. Ist ja nicht so, dass man diese dusselige `readlines()`-Methode tatsächlich brauchen würde.
Das entfernen des Zeilenendes würde ich vor dem Verarbeiten der Zeile machen. Ich finde ein ``if line:`` lesbarer als da tatsächlich einen Vergleich mit "\n" zu machen.
Was soll denn ``[x for x in line.split(" ")]``? Die `split()`-Methode liefert doch bereits eine Liste, da muss man nicht mit einer „list comprehension“ die Elemente in *noch* eine Liste kopieren.
Dann muss man nicht alles an einen Namen binden.
Da nicht wirklich sicher ist was in den Werten steht, wäre es sicherer wenn man das Aufteilen an ":" auf eine Fundstelle beschränkt. Falls ":" auch im Wert vorkommen kann. Dann wären wir bei:
Code: Alles auswählen
for field in line.split():
key, value = field.split(":", 1)
passport[key] = value
Die `dict.update()`-Methode nimmt iterierbare Objekte mit Schlüssel/Wert-Paaren. Womit das zum Einzeiler wird:
Code: Alles auswählen
passport.update(field.split(":", 1) for field in line.split())
Das zählen der gültigen Reisepässe könnte man mit Mengenoperationen machen, und die ``for``-Schleife durch `sum()` ersetzen.
Ungetestet:
Code: Alles auswählen
#!/usr/bin/env python3
# Tag 4 - Aufgabe 1
# https://adventofcode.com/2020/day/4
REQUIRED_FIELDS = frozenset(["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"])
def main():
passports = []
with open("input_day4.txt", "r") as lines:
passport = {}
for line in lines:
line = line.strip()
if line:
passport.update(field.split(":", 1) for field in line.split())
else:
passports.append(passport)
passport = {}
#
# Last passport that has no blank line after.
#
if passport:
passports.append(passport)
valid_passports = sum(
REQUIRED_FIELDS <= set(passport) for passport in passports
)
print(valid_passports)
if __name__ == "__main__":
main()