2020-12-17 00:13:15 +00:00
|
|
|
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
|
2020-12-17 00:15:23 +00:00
|
|
|
// 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.
|
2020-12-17 00:13:15 +00:00
|
|
|
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)
|
|
|
|
}
|