Day 4: Part 1 and 2
This commit is contained in:
224
four/day_four.go
Normal file
224
four/day_four.go
Normal file
@@ -0,0 +1,224 @@
|
||||
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)
|
||||
}
|
Reference in New Issue
Block a user