Files
aoc2020/sixteen/day_sixteen.go
James Griffin 86b00e46a4 Day 16: Part 2
Signed-off-by: James Griffin <james@unsupervised.ca>
2020-12-16 12:35:36 -04:00

256 lines
4.9 KiB
Go

package sixteen
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
type ticket struct {
values []int
}
type constraint struct {
min, max int
}
type constraints []constraint
type ticketScanner struct {
rules map[string]constraints
me *ticket
nearby []ticket
valid []ticket
fields []string
}
func (ts *ticketScanner) load(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
ts.rules = make(map[string]constraints)
var rules, myticket bool
scanner := bufio.NewScanner(file)
for scanner.Scan() {
if scanner.Text() == "" {
continue
}
if scanner.Text() == "your ticket:" {
rules = true
continue
}
if !rules {
parts := strings.Split(scanner.Text(), ":")
if len(parts) != 2 {
return fmt.Errorf("Unable to parse rule %s", scanner.Text())
}
ranges := []constraint{}
c := strings.Split(parts[1], " or ")
for _, a := range c {
b := constraint{}
count, err := fmt.Sscanf(a, "%d-%d", &b.min, &b.max)
if count != 2 || err != nil {
return fmt.Errorf("Unable to parse %s", a)
}
ranges = append(ranges, b)
}
ts.rules[parts[0]] = ranges
continue
}
if scanner.Text() == "nearby tickets:" {
myticket = true
continue
}
if !myticket {
t := &ticket{}
values := strings.Split(scanner.Text(), ",")
for _, v := range values {
intValue, err := strconv.Atoi(v)
if err != nil {
return err
}
t.values = append(t.values, intValue)
}
ts.me = t
continue
}
t := ticket{}
values := strings.Split(scanner.Text(), ",")
for _, v := range values {
intValue, err := strconv.Atoi(v)
if err != nil {
return err
}
t.values = append(t.values, intValue)
}
ts.nearby = append(ts.nearby, t)
}
return nil
}
func (c *constraints) valid(input int) bool {
for _, r := range *c {
if input >= r.min && input <= r.max {
return true
}
}
return false
}
func (ts *ticketScanner) nearbyErrorRate() int {
errorRate := 0
for _, t := range ts.nearby {
for _, value := range t.values {
valid := false
for _, rule := range ts.rules {
if rule.valid(value) {
valid = true
break
}
}
if !valid {
errorRate += value
}
}
}
return errorRate
}
func (ts *ticketScanner) scanValid() {
for _, t := range ts.nearby {
validTicket := true
for _, v := range t.values {
validValue := false
for _, r := range ts.rules {
if r.valid(v) {
validValue = true
break
}
}
if !validValue {
validTicket = false
break
}
}
if validTicket {
ts.valid = append(ts.valid, t)
}
}
}
func (ts *ticketScanner) determineFields() {
ts.fields = make([]string, len(ts.rules))
possibilities := map[string][]int{}
for name, r := range ts.rules {
possibilities[name] = []int{}
for i := 0; i < len(ts.fields); i++ {
isValid := true
for j := 0; j < len(ts.valid); j++ {
if !r.valid(ts.valid[j].values[i]) {
isValid = false
}
}
if isValid {
possibilities[name] = append(possibilities[name], i)
}
}
}
for i := 0; i < len(ts.fields); i++ {
if ts.fields[i] != "" {
continue
}
options := []string{}
for name, indexes := range possibilities {
for _, index := range indexes {
if i == index {
options = append(options, name)
}
}
}
if len(options) == 1 {
ts.fields[i] = options[0]
possibilities = removeIndex(i, options[0], possibilities)
continue
}
min := len(ts.fields)
minKey := ""
for _, option := range options {
if len(possibilities[option]) < min {
min = len(possibilities[option])
minKey = option
}
}
ts.fields[i] = minKey
possibilities = removeIndex(i, minKey, possibilities)
}
}
func removeIndex(index int, used string, possibilities map[string][]int) map[string][]int {
updatedPossibilites := make(map[string][]int)
for key, indexes := range possibilities {
if used == key {
continue
}
updatedIndexes := []int{}
for _, i := range indexes {
if i != index {
updatedIndexes = append(updatedIndexes, i)
}
}
updatedPossibilites[key] = updatedIndexes
}
return updatedPossibilites
}
func (ts *ticketScanner) fieldProduct(prefix string) int {
product := 1
for i, f := range ts.fields {
if strings.Contains(f, prefix) {
product *= ts.me.values[i]
}
}
return product
}
// PartOne What is my nearby ticket scanning error rate
func PartOne() string {
ts := ticketScanner{}
if err := ts.load("sixteen/input.txt"); err != nil {
return err.Error()
}
return fmt.Sprintf("The nearby scanning error rate is %d", ts.nearbyErrorRate())
}
// PartTwo What is the product of all fields beginning with "departure"
func PartTwo() string {
ts := ticketScanner{}
if err := ts.load("sixteen/input.txt"); err != nil {
return err.Error()
}
ts.scanValid()
ts.determineFields()
return fmt.Sprintf("The product of departure is %d", ts.fieldProduct("departure"))
}