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) }