173 lines
3.6 KiB
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)
|
|
}
|