package main import ( "bufio" "fmt" "os" "strconv" "strings" "time" ) func main() { start := time.Now() partOne() duration := time.Since(start) partTwo() duration2 := time.Since(start) fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration) } type valuerange struct { min int max int } type rule struct { name string ranges []valuerange field int } type ticket struct { values []int valid bool } func parseRule(s string) rule { q := strings.Split(s, ": ") ru := rule{ name: q[0], ranges: make([]valuerange, 0, 2), } r := strings.Split(q[1], " or ") for _, rr := range r { mm := strings.Split(rr, "-") min, _ := strconv.Atoi(mm[0]) max, _ := strconv.Atoi(mm[1]) ru.ranges = append(ru.ranges, valuerange{min: min, max: max}) } return ru } func parseTicket(s string) ticket { t := ticket{ values: []int{}, } vs := strings.Split(s, ",") for _, v := range vs { vi, _ := strconv.Atoi(v) t.values = append(t.values, vi) } return t } // return valid/invalid and the invalid value, if found func validateTicket(r []rule, t ticket) (bool, int) { for _, ta := range t.values { ok := false for _, ru := range r { outsideRule := (ta < ru.ranges[0].min || (ta > ru.ranges[0].max && ta < ru.ranges[1].min) || ta > ru.ranges[1].max) if ok || !outsideRule { ok = true } } if !ok { return false, ta } } return true, 0 } // returns a mapping of fields to possible rules func guessFields(rules []rule, tickets []ticket) map[int][]int { guesses := map[int][]int{} for ri, r := range rules { for i := range tickets[0].values { // loop over the fields on tickets, and... ok := true for _, t := range tickets { // loop over all the tickets... ta := t.values[i] // checking the current field value... outsideRule := (ta < r.ranges[0].min || (ta > r.ranges[0].max && ta < r.ranges[1].min) || ta > r.ranges[1].max) if !ok || outsideRule { // checking if any of the tickets fall outside our rule's range. ok = false } } if ok { // if all tickets pass, we've got a hit guesses[i] = append(guesses[i], ri) } } } return guesses } func reduce(g map[int][]int) (int, int) { for i, j := range g { if len(j) == 1 { return i, j[0] } } return 0, 0 } func remove(a []int, rule int) []int { for i, j := range a { if j == rule { a[i] = a[len(a)-1] a[len(a)-1] = 0 return a[:len(a)-1] } } return a } func partOne() { f, _ := os.Open("input") reader := bufio.NewReader(f) scanner := bufio.NewScanner(reader) rules := []rule{} tickets := []ticket{} invalidityRate := 0 seenAllRules := false seenMyTicket := false for scanner.Scan() { line := scanner.Text() // check for end of rules list if line == "" && !seenAllRules { seenAllRules = true scanner.Scan() // clear out the following line continue } // parse rules until we run out if !seenAllRules { rules = append(rules, parseRule(line)) continue } // parse own ticket, then clear for remaining tickets if !seenMyTicket { tickets = append(tickets, parseTicket(scanner.Text())) seenMyTicket = true scanner.Scan() // clear out the next two lines scanner.Scan() continue } // parse and validate other people's tickets t := parseTicket(line) ok, i := validateTicket(rules, t) if !ok { t.valid = false invalidityRate = invalidityRate + i } else { t.valid = true tickets = append(tickets, t) } } fmt.Println(invalidityRate) } func partTwo() { f, _ := os.Open("input") reader := bufio.NewReader(f) scanner := bufio.NewScanner(reader) rules := []rule{} myTicket := ticket{} tickets := []ticket{} seenAllRules := false seenMyTicket := false for scanner.Scan() { line := scanner.Text() // check for end of rules list if line == "" && !seenAllRules { seenAllRules = true scanner.Scan() // clear out the following line continue } // parse rules until we run out if !seenAllRules { rules = append(rules, parseRule(line)) continue } // parse own ticket, then clear for remaining tickets if !seenMyTicket { myTicket = parseTicket(scanner.Text()) tickets = append(tickets, myTicket) seenMyTicket = true scanner.Scan() // clear out the next two lines scanner.Scan() continue } // parse and validate other people's tickets t := parseTicket(line) ok, _ := validateTicket(rules, t) if ok { t.valid = true tickets = append(tickets, t) } } guesses := guessFields(rules, tickets) var lastRuleSolved int // iterate over the guesses list. on every iteration, only one field has only one rule remaining that's valid // save it off into the rules list, then remove that rule from all fields. // Luckily, we don't need any higher-order elimination logic. for i := 1; i < len(guesses); i++ { for i, g := range guesses { if len(g) == 1 { rules[g[0]].field = i lastRuleSolved = g[0] } } for i := range guesses { guesses[i] = remove(guesses[i], lastRuleSolved) } } result := 1 // first 6 rules are our "departure" rules for _, r := range rules[:6] { result = result * myTicket.values[r.field] } fmt.Println(result) }