AdventOfCode2020/04/main.go

248 lines
6.8 KiB
Go

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(&current, 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(&current, strings.Split(entry, ":"))
}
}
}
// finally, check the final entry
if current.IsValid() {
valid = valid + 1
}
fmt.Println(valid)
}