AdventOfCode2020/16/main.go
2020-12-16 19:15:23 -05:00

233 lines
5.1 KiB
Go

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