248 lines
6.8 KiB
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(¤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)
|
||
|
}
|