adventofcode-2020/day4/part2/main.go

173 lines
3.6 KiB
Go

package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
"regexp"
"strconv"
"strings"
)
func and(f ...func(string) bool) func(string) bool {
return func(s string) bool {
for _, fs := range f {
if !fs(s) {
return false
}
}
return true
}
}
func or(f ...func(string) bool) func(string) bool {
return func(s string) bool {
for _, fs := range f {
if fs(s) {
return true
}
}
return false
}
}
func rx(regex string) func(string) bool {
rx := regexp.MustCompile(regex)
return rx.MatchString
}
func numrange(min int, max int) func(string) bool {
rx := regexp.MustCompile(`\d+`)
return func(val string) bool {
n := rx.FindString(val)
num, err := strconv.Atoi(n)
if err != nil {
panic(err)
}
if num < min || num > max {
return false
}
return true
}
}
func unit(unit string, min int, max int) func(string) bool {
rxc := rx(fmt.Sprintf(`^\d+%s$`, unit))
return and(rxc, numrange(min, max))
}
func digitsOnly(digits int) func(string) bool {
return rx(fmt.Sprintf(`^\d{%d}$`, digits))
}
func digits(digits int, min int, max int) func(string) bool {
rxc := digitsOnly(digits)
return and(rxc, numrange(min, max))
}
func main() {
fields := []string{
"byr",
"iyr",
"eyr",
"hgt",
"hcl",
"ecl",
"pid",
"cid",
}
validation := map[string]func(val string) bool{
"byr": digits(4, 1920, 2002),
"iyr": digits(4, 2010, 2020),
"eyr": digits(4, 2020, 2030),
"hgt": or(unit("cm", 150, 193), unit("in", 59, 76)),
"hcl": rx(`^#[0-9a-f]{6}$`),
"ecl": rx(`^(amb|blu|brn|gry|grn|hzl|oth)$`),
"pid": digitsOnly(9),
}
optionalFields := []string{
"cid",
}
f, err := os.Open("input")
if err != nil {
panic(err)
}
validPasses := 0
currentPassValues := map[string]string{}
currentPassIsInvalid := false
r := bufio.NewReader(f)
// log.Println("=== New pass ===")
for {
line, err := r.ReadString('\n')
if err != io.EOF && err != nil {
panic(nil)
}
if err == io.EOF {
line = ""
}
line = strings.TrimSpace(line)
if len(currentPassValues) <= 0 && err == io.EOF {
break
}
if len(currentPassValues) <= 0 && len(line) == 0 {
continue
}
if len(currentPassValues) > 0 && (err == io.EOF || len(line) == 0) {
// Check if all required fields can be found
b:
for _, fieldName := range fields {
if fieldValue, ok := currentPassValues[fieldName]; !ok {
// field is missing
for _, optionalFieldName := range optionalFields {
if optionalFieldName == fieldName {
// skip this field for check
log.Printf("-- %s is missing (optional)", fieldName)
break b
}
}
log.Printf("-- %s is missing", fieldName)
currentPassIsInvalid = true
break b
} else if validator, ok := validation[fieldName]; ok {
if !validator(fieldValue) {
log.Printf("-- %s (%s) failed validation", fieldName, fieldValue)
currentPassIsInvalid = true
break b
}
}
}
if currentPassIsInvalid {
log.Println("** INVALID", currentPassValues)
} else {
log.Println("** VALID", currentPassValues)
validPasses++
}
// log.Println("=== New pass ===")
currentPassValues = map[string]string{}
currentPassIsInvalid = false
}
if !currentPassIsInvalid {
passFields := strings.Fields(line)
for _, field := range passFields {
fieldSplit := strings.SplitN(field, ":", 2)
fieldName := fieldSplit[0]
fieldValue := fieldSplit[1]
// log.Printf("<- %s %s\n", fieldName, fieldValue)
if _, ok := currentPassValues[fieldName]; ok {
currentPassIsInvalid = true
log.Println("-- dupe:", fieldName)
}
currentPassValues[fieldName] = fieldValue
}
}
}
log.Printf("%d passes valid\n", validPasses)
}