diff --git a/16/main.go b/16/main.go new file mode 100644 index 0000000..0a2d30b --- /dev/null +++ b/16/main.go @@ -0,0 +1,229 @@ +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 + 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) +}