package main import ( "bufio" "fmt" "os" "strconv" "strings" ) func main() { partOne() partTwo() } type passport struct { byr string //(Birth Year) iyr string //(Issue Year) eyr string //(Expiration Year) hgt string //(Height) hcl string //(Hair Color) ecl string //(Eye Color) pid string //(Passport ID) cid string //(Country ID) } func addToPassport(p *passport, i []string) { if i[0] == "byr" { p.byr = i[1] } if i[0] == "iyr" { p.iyr = i[1] } if i[0] == "eyr" { p.eyr = i[1] } if i[0] == "hgt" { p.hgt = i[1] } if i[0] == "hcl" { p.hcl = i[1] } if i[0] == "ecl" { p.ecl = i[1] } if i[0] == "pid" { p.pid = i[1] } if i[0] == "cid" { p.cid = i[1] } } func (p passport) IsPassport() bool { return p.byr != "" && p.iyr != "" && p.eyr != "" && p.hgt != "" && p.hcl != "" && p.ecl != "" && p.pid != "" && p.cid != "" } func (p passport) IsCredentials() bool { return p.byr != "" && p.iyr != "" && p.eyr != "" && p.hgt != "" && p.hcl != "" && p.ecl != "" && p.pid != "" && p.cid == "" } func (p passport) IsValid() bool { // byr (Birth Year) - four digits; at least 1920 and at most 2002. i, err := strconv.Atoi(p.byr) if err != nil || i < 1920 || i > 2002 { return false } // iyr (Issue Year) - four digits; at least 2010 and at most 2020. i, err = strconv.Atoi(p.iyr) if err != nil || i < 2010 || i > 2020 { return false } // eyr (Expiration Year) - four digits; at least 2020 and at most 2030. i, err = strconv.Atoi(p.eyr) if err != nil || i < 2020 || i > 2030 { return false } // hgt (Height) - a number followed by either cm or in: // If cm, the number must be at least 150 and at most 193. // If in, the number must be at least 59 and at most 76. i, j, err := parseHeight(p.hgt) if err != nil || (j != "in" && j != "cm") || (j == "in" && (i < 59 || i > 76)) || (j == "cm" && (i < 150 || i > 193)) { return false } // hcl (Hair Color) - a # followed by exactly six characters 0-9 or a-f. j, err = parseColor(p.hcl) if err != nil || len(j) > 6 || len(j) < 1 { return false } // ecl (Eye Color) - exactly one of: amb blu brn gry grn hzl oth. if !(p.ecl == "amb" || p.ecl == "blu" || p.ecl == "brn" || p.ecl == "gry" || p.ecl == "grn" || p.ecl == "hzl" || p.ecl == "oth") { return false } // pid (Passport ID) - a nine-digit number, including leading zeroes. j, err = parsePassportID(p.pid) if err != nil || len(j) != 9 { return false } // cid (Country ID) - ignored, missing or not. return true } func parseHeight(s string) (int, string, error) { var measurement, unit []rune for _, r := range s { switch { case r == 'c' || r == 'm' || r == 'i' || r == 'n': unit = append(unit, r) case r >= '0' && r <= '9': measurement = append(measurement, r) default: return 0, "", fmt.Errorf("invalid") } } m, err := strconv.Atoi(string(measurement)) return m, string(unit), err } func parseColor(s string) (string, error) { var color []rune for i, r := range s { switch { case (r == '#' && i != 0) || (r != '#' && i == 0): return "", fmt.Errorf("invalid or missing #") case r == '#' && i == 0: continue case (r >= '0' && r <= '9') || (r >= 'a' && r <= 'f'): color = append(color, r) default: return "", fmt.Errorf("invalid rune") } } return string(color), nil } func parsePassportID(s string) (string, error) { var number []rune for _, r := range s { switch { case (r >= '0' && r <= '9'): number = append(number, r) default: return "", fmt.Errorf("invalid") } } return string(number), nil } // Passport data is validated in batch files (your puzzle input). // Each passport is represented as a sequence of key:value pairs separated by spaces or newlines. // Passports are separated by blank lines. // Here is an example batch file containing four passports: // // ecl:gry pid:860033327 eyr:2020 hcl:#fffffd // byr:1937 iyr:2017 cid:147 hgt:183cm // // iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884 // hcl:#cfa07d byr:1929 // // hcl:#ae17e1 iyr:2013 // eyr:2024 // ecl:brn pid:760753108 byr:1931 // hgt:179cm // // hcl:#cfa07d eyr:2025 pid:166559648 // iyr:2011 ecl:brn hgt:59in // // The first passport is valid - all eight fields are present. // The second passport is invalid - it is missing hgt (the Height field). // The third passport is interesting; the only missing field is cid, so it looks like data from North Pole Credentials, not a passport at all! // Surely, nobody would mind if you made the system temporarily ignore missing cid fields. Treat this "passport" as valid. // The fourth passport is missing two fields, cid and byr. // Missing cid is fine, but missing any other field is not, so this passport is invalid. // According to the above rules, your improved system would report 2 valid passports. // Count the number of valid passports - those that have all required fields. // Treat cid as optional. In your batch file, how many passports are valid? func partOne() { f, _ := os.Open("input") reader := bufio.NewReader(f) scanner := bufio.NewScanner(reader) valid := 0 current := passport{} for scanner.Scan() { line := scanner.Text() if line == "" { if current.IsCredentials() || current.IsPassport() { valid = valid + 1 } current = passport{} } else { for _, entry := range strings.Split(line, " ") { addToPassport(¤t, strings.Split(entry, ":")) } } } // finally, check the final entry if current.IsCredentials() || current.IsPassport() { valid = valid + 1 } fmt.Println(valid) } // You can continue to ignore the cid field, but each other field has strict rules about what values are valid for automatic validation: // byr (Birth Year) - four digits; at least 1920 and at most 2002. // iyr (Issue Year) - four digits; at least 2010 and at most 2020. // eyr (Expiration Year) - four digits; at least 2020 and at most 2030. // hgt (Height) - a number followed by either cm or in: // If cm, the number must be at least 150 and at most 193. // If in, the number must be at least 59 and at most 76. // hcl (Hair Color) - a # followed by exactly six characters 0-9 or a-f. // ecl (Eye Color) - exactly one of: amb blu brn gry grn hzl oth. // pid (Passport ID) - a nine-digit number, including leading zeroes. // cid (Country ID) - ignored, missing or not. // Your job is to count the passports where all required fields are both present and valid according to the above rules. func partTwo() { f, _ := os.Open("input") reader := bufio.NewReader(f) scanner := bufio.NewScanner(reader) valid := 0 current := passport{} for scanner.Scan() { line := scanner.Text() if line == "" { if current.IsValid() { valid = valid + 1 } current = passport{} } else { for _, entry := range strings.Split(line, " ") { addToPassport(¤t, strings.Split(entry, ":")) } } } // finally, check the final entry if current.IsValid() { valid = valid + 1 } fmt.Println(valid) }