Files
aoc2020/four/day_four.go
2020-12-04 10:33:01 -04:00

225 lines
4.0 KiB
Go

package four
import (
"bufio"
"fmt"
"os"
"regexp"
"strconv"
"strings"
)
type passport struct {
byr, iyr, eyr, hgt, hcl, ecl, pid, cid string
}
func (p *passport) validate(strict bool, optional ...string) bool {
valid := true
requiredFields := []string{"byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid", "cid"}
filtered := []string{}
// remove optional fields
for _, f := range requiredFields {
required := true
for _, o := range optional {
if f == o {
required = false
break
}
}
if required {
filtered = append(filtered, f)
}
}
for _, f := range filtered {
if strict {
if !p.validateField(f) {
valid = false
}
} else {
if p.getField(f) == "" {
valid = false
}
}
}
return valid
}
func (p *passport) validateField(code string) bool {
switch code {
case "byr":
year, err := strconv.Atoi(p.byr)
if err != nil {
return false
}
return year >= 1920 && year <= 2002
case "iyr":
year, err := strconv.Atoi(p.iyr)
if err != nil {
return false
}
return year >= 2010 && year <= 2020
case "eyr":
year, err := strconv.Atoi(p.eyr)
if err != nil {
return false
}
return year >= 2020 && year <= 2030
case "hgt":
var height int
var unit string
count, err := fmt.Sscanf(p.hgt, "%d%s", &height, &unit)
if count != 2 || err != nil {
return false
}
if unit == "in" {
return height >= 59 && height <= 76
}
return height >= 150 && height <= 193
case "hcl":
if len(p.hcl) != 7 {
return false
}
var validColour = regexp.MustCompile(`^#[a-z0-9]+$`)
return validColour.MatchString(p.hcl)
case "ecl":
switch p.ecl {
case "amb", "blu", "brn", "gry", "grn", "hzl", "oth":
return true
default:
return false
}
case "pid":
if len(p.pid) != 9 {
return false
}
var validColour = regexp.MustCompile(`^[0-9]+$`)
return validColour.MatchString(p.pid)
default:
return false
}
}
func (p *passport) getField(code string) string {
switch code {
case "byr":
return p.byr
case "iyr":
return p.iyr
case "eyr":
return p.eyr
case "hgt":
return p.hgt
case "hcl":
return p.hcl
case "ecl":
return p.ecl
case "pid":
return p.pid
case "cid":
return p.cid
default:
return ""
}
}
func (p *passport) setField(code, value string) {
switch code {
case "byr":
p.byr = value
case "iyr":
p.iyr = value
case "eyr":
p.eyr = value
case "hgt":
p.hgt = value
case "hcl":
p.hcl = value
case "ecl":
p.ecl = value
case "pid":
p.pid = value
case "cid":
p.cid = value
default:
return
}
}
type customs struct {
passports []passport
}
func (c *customs) load(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
var p *passport
for scanner.Scan() {
if scanner.Text() == "" {
// Blank line means we have moved on to the next passport
if p != nil {
c.passports = append(c.passports, *p)
p = nil
}
continue
}
if p == nil {
p = &passport{}
}
fields := strings.Split(scanner.Text(), " ")
for _, f := range fields {
var key, value string
count, err := fmt.Sscanf(f, "%3s:%s", &key, &value)
if count != 2 || err != nil {
return fmt.Errorf("Unable to parse field %s: %w", f, err)
}
p.setField(key, value)
}
}
// commit the last passport if there isn't a blank line
if p != nil {
c.passports = append(c.passports, *p)
}
return nil
}
func (c *customs) check(strict bool, optional ...string) int {
valid := 0
for _, p := range c.passports {
if p.validate(strict, optional...) {
valid++
}
}
return valid
}
// PartOne find all the valid passports
func PartOne() string {
customs := customs{}
customs.load("four/passports.txt")
validPassports := customs.check(false, "cid")
return fmt.Sprintf("Found %d valid passports", validPassports)
}
// PartTwo Strictly find all the valid passports
func PartTwo() string {
customs := customs{}
customs.load("four/passports.txt")
validPassports := customs.check(true, "cid")
return fmt.Sprintf("Found %d strictly valid passports", validPassports)
}