Advent of Code 4: „Passport Processing” – Lösungsaustausch

Das Advent of Code-Puzzle „Passport Processing“ für heute ist ab jetzt verfügbar!

In diesem Thread könnt ihr eure Lösungen posten, oder euch über das Puzzle austauschen.

Quellcode bitte in dieser Syntax posten („python“ durch die verwendete Sprache ersetzen):

```python
print("hello world")
```

Python

#!/usr/bin/env python3

import collections.abc
import re
import typing


class PassportException(Exception):
    def __init__(self, message: str, values: typing.Any = None):
        self.message = message
        self.values = values
        super().__init__()

    def __str__(self):
        return f"{self.message}: {self.values}"


def main():
    passports = parse_input("04-input.txt")
    print(f"Number of valid passports: {validate_passports(passports)} "
          f"of {len(passports)}")


class Passport:
    def __init__(self, entry: str) -> None:
        p = {}

        for field in entry.split():
            key, value = field.split(":")
            p[key] = value
        self.byr = p.get("byr", None)
        self.iyr = p.get("iyr", None)
        self.eyr = p.get("eyr", None)
        self.hgt = p.get("hgt", None)
        self.hcl = p.get("hcl", None)
        self.ecl = p.get("ecl", None)
        self.pid = p.get("pid", None)
        self.cid = p.get("cid", None)
        self.hgt_unit: str
        self.hgt_val: int

    def __str__(self):
        if self.isvalid():
            return f"valid Passport: {self.__dict__}"
        return (f"invalid Passport: {self.__dict__}, error:"
                f" {self.invalid_exception}")

    def iscomplete(self) -> bool:
        return (self.byr and self.iyr and self.eyr and self.hgt and self.hcl
                and self.ecl and self.pid)

    def isvalid(self) -> bool:
        try:
            if not self.iscomplete():
                raise PassportException("Fields incomplete")

            if not (self.byr.isdecimal() and self.iyr.isdecimal()
                    and self.eyr.isdecimal()):
                raise PassportException("Nondecimal Year",
                                        values={
                                            "byr": self.byr,
                                            "iyr": self.byr,
                                            "eyr": self.eyr
                                        })
            if not (1920 <= int(self.byr) <= 2002
                    and 2010 <= int(self.iyr) <= 2020
                    and 2020 <= int(self.eyr) <= 2030):
                raise PassportException("Invalid year",
                                        values={
                                            "byr": self.byr,
                                            "iyr": self.byr,
                                            "eyr": self.eyr
                                        })

            self.hgt_val = self.hgt[:-2]
            self.hgt_unit = self.hgt[-2:]
            if not self.hgt_val.isdecimal():
                raise PassportException("Nondecimal Height value",
                                        values={
                                            "hgt": self.hgt,
                                            "hgt_val": self.hgt_val,
                                            "hgt_unit": self.hgt_unit
                                        })
            if self.hgt_unit == "cm":
                if not 150 <= int(self.hgt_val) <= 193:
                    raise PassportException("Invalid height value (cm)",
                                            values={
                                                "hgt": self.hgt,
                                                "hgt_val": self.hgt_val,
                                                "hgt_unit": self.hgt_unit
                                            })
            elif self.hgt_unit == "in":
                if not 59 <= int(self.hgt_val) <= 76:
                    raise PassportException("Invalid height value (inch)",
                                            values={
                                                "hgt": self.hgt,
                                                "hgt_val": self.hgt_val,
                                                "hgt_unit": self.hgt_unit
                                            })
            else:
                raise PassportException("Invalid heigth unit",
                                        values={
                                            "hgt": self.hgt,
                                            "hgt_val": self.hgt_val,
                                            "hgt_unit": self.hgt_unit
                                        })

            if not re.fullmatch(r"#[0-9a-f]{6}", self.hcl):
                raise PassportException("Invalid hair color", values=self.hcl)

            if self.ecl not in ("amb", "blu", "brn", "gry", "grn", "hzl",
                                "oth"):
                raise PassportException("Invalid eye color", values=self.ecl)

            if not re.fullmatch(r"\d{9}", self.pid):
                raise PassportException("Invalid passport id", values=self.pid)
        except PassportException as e:
            self.invalid_exception = e
            return False

        return True


def parse_input(filename: str) -> list[dict]:
    passports = []
    with open(filename, "r") as file:
        for entry in file.read().split("\n\n"):
            passports.append(Passport(entry))
    return passports


def validate_passports(passports: collections.abc.Iterable[Passport]) -> int:
    valid = 0
    for p in passports:
        valid += p.isvalid()
        print(p)
    return valid


def validate_fields(passport: dict[str, str]) -> bool:
    mandatory = {"byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"}
    fields = set(passport.keys())
    if fields >= mandatory:
        return True
    return False


if __name__ == "__main__":
    main()

C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace Day4 {
    internal static class Program {
        static IEnumerable<Dictionary<string, string>> ParseInput(string inputFileName) {
            var inputFile = File.ReadAllText(inputFileName);
            var input = inputFile.Split("\n\n");

            var inputData = new List<Dictionary<string, string>>();
            foreach (var ds in input) {
                var kvs = ds.Replace("\n", " ").Split(" ");
                var dsKv = new Dictionary<string, string>();
                foreach (var kvstring in kvs) {
                    var kv = kvstring.Split(":");
                    // Console.WriteLine($"{kv[0]}:{kv[1]}");
                    dsKv.Add(kv[0], kv[1]);
                }

                inputData.Add(dsKv);
            }

            return inputData;
        }

        public static bool IsWithin(this int value, int minimum, int maximum) {
            return value >= minimum && value <= maximum;
        }

        static void Main(string[] args) {
            var input = ParseInput(args[0]);

            var enumerable = input as Dictionary<string, string>[] ?? input.ToArray();
            Console.WriteLine(
                $"Solving with input: \n{string.Join("\n", enumerable.Select(x => string.Join(" ", x.Select(y => $"{y.Key}:{y.Value}"))))}");
            Console.WriteLine($"Part 1 result: {Solve1(enumerable).ToString()}");
            Console.WriteLine($"Part 2 result: {Solve2(enumerable).ToString()}");
        }

        static int Solve1(IEnumerable<Dictionary<string, string>> input) {
            return input.Count(dataset =>
                dataset.ContainsKey("byr") && dataset.ContainsKey("iyr") && dataset.ContainsKey("eyr") &&
                dataset.ContainsKey("hgt") && dataset.ContainsKey("hcl") && dataset.ContainsKey("ecl") &&
                dataset.ContainsKey("pid"));
        }

        static int Solve2(IEnumerable<Dictionary<string, string>> input) {
            var byrRegex = new Regex(@"\d{4}");
            const int byrMin = 1920;
            const int byrMax = 2002;

            var iyrRegex = byrRegex;
            const int iyrMin = 2010;
            const int iyrMax = 2020;

            var eyrRegex = byrRegex;
            const int eyrMin = 2020;
            const int eyrMax = 2030;

            var hgtRegexIn = new Regex(@"(\d+)(?:in)");
            const int hgtInMin = 59;
            const int hgtInMax = 76;

            var hgtRegexCm = new Regex(@"(\d+)(?:cm)");
            const int hgtCmMin = 150;
            const int hgtCmMax = 193;

            var hclRegex = new Regex(@"#[0-9a-f]{6}");

            var eclRegex = new Regex(@"(amb)|(blu)|(brn)|(gry)|(grn)|(hzl)|(oth)");

            var pidRegex = new Regex(@"^\d{9}\b");

            return (from dataset in input
                where dataset.ContainsKey("byr") && dataset.ContainsKey("iyr") && dataset.ContainsKey("eyr") &&
                      dataset.ContainsKey("hgt") && dataset.ContainsKey("hcl") && dataset.ContainsKey("ecl") &&
                      dataset.ContainsKey("pid")
                where byrRegex.IsMatch(dataset["byr"]) && Convert.ToInt32(dataset["byr"]).IsWithin(byrMin, byrMax)
                where iyrRegex.IsMatch(dataset["iyr"]) && Convert.ToInt32(dataset["iyr"]).IsWithin(iyrMin, iyrMax)
                where eyrRegex.IsMatch(dataset["eyr"]) && Convert.ToInt32(dataset["eyr"]).IsWithin(eyrMin, eyrMax)
                where hgtRegexCm.Match(dataset["hgt"]).Success && Convert
                    .ToInt32(hgtRegexCm.Match(dataset["hgt"]).Groups[1].Value)
                    .IsWithin(hgtCmMin, hgtCmMax) || (hgtRegexIn.Match(dataset["hgt"]).Success && Convert
                    .ToInt32(hgtRegexIn.Match(dataset["hgt"]).Groups[1].Value)
                    .IsWithin(hgtInMin, hgtInMax))
                where hclRegex.IsMatch(dataset["hcl"])
                where eclRegex.IsMatch(dataset["ecl"])
                select dataset).Count(dataset => pidRegex.IsMatch(dataset["pid"]));
        }
    }
}