adventofcode/2020/day16/main.go

224 lines
4.2 KiB
Go

package main
import (
"fmt"
"strings"
h "git.bullercodeworks.com/brian/adventofcode/helpers"
)
var Mine *Ticket
var ValidTickets []*Ticket
var AllFields []*Field
var FieldMap map[int]string
var UnknownFields map[int]bool
func main() {
fmt.Println("# Day 16")
inp := h.StdinToStringSlice()
FieldMap = make(map[int]string)
UnknownFields = make(map[int]bool)
findValidTickets(inp)
solveMyTicket()
}
const (
Criteria = iota
MyTicket
NearbyTickets
)
func findValidTickets(inp []string) {
var invalids []int
section := Criteria
for k := range inp {
if inp[k] == "" {
// End of a section
section++
fmt.Println("Section", section)
continue
} else if inp[k] == "your ticket:" || inp[k] == "nearby tickets:" {
continue
}
switch section {
case Criteria:
pts := strings.Split(inp[k], ": ")
f := Field{
Index: -1,
Name: pts[0],
}
ranges := strings.Split(pts[1], " or ")
for rk := range ranges {
vals := strings.Split(ranges[rk], "-")
f.AddRange(h.Atoi(vals[0]), h.Atoi(vals[1]))
}
AllFields = append(AllFields, &f)
UnknownFields[k] = true
case MyTicket:
vals := strings.Split(inp[k], ",")
var pts []int
for vk := range vals {
pts = append(pts, h.Atoi(vals[vk]))
}
Mine = NewTicket(k, pts)
case NearbyTickets:
vals := strings.Split(inp[k], ",")
var pts []int
for vk := range vals {
pts = append(pts, h.Atoi(vals[vk]))
}
t := NewTicket(k, pts)
if t.Valid {
ValidTickets = append(ValidTickets, t)
} else {
for _, iv := range t.InvalidValues {
invalids = append(invalids, iv)
}
}
}
}
var sum int
for k := range invalids {
sum = sum + invalids[k]
}
fmt.Println("## Part 1")
fmt.Println("Ticket Scanning Error Rate:", sum)
}
func solveMyTicket() {
fmt.Println()
var restarts int
for restarts < len(AllFields) {
for k := 0; k < len(AllFields); k++ {
f := AllFields[k]
if f.Index != -1 {
continue
}
poss := findCommonPossibilities(f.Name)
if len(poss) == 1 {
foundField(poss[0], f.Name)
k = 0
restarts++
}
}
}
Mine.SetFields(AllFields)
ans := 1
for _, f := range Mine.Fields {
if strings.HasPrefix(f.Name, "departure") {
ans = ans * f.Value
}
}
fmt.Println("## Part 2")
fmt.Println("Answer:", ans)
}
func foundField(num int, name string) {
for _, f := range AllFields {
if f.Name == name {
f.Index = num
}
}
FieldMap[num] = name
delete(UnknownFields, num)
}
func findCommonPossibilities(name string) []int {
var poss, not []int
for k := range UnknownFields {
poss = append(poss, k)
}
for _, t := range ValidTickets {
for fk, fv := range t.PossibleFieldMap {
if !h.StringSliceContains(fv, name) {
not = append(not, fk)
}
}
}
// Remove all of 'not' from 'poss'
for k := range not {
var rem int
for pk := range poss {
if poss[pk] == not[k] {
rem = pk
break
}
}
if len(poss) == 0 {
return poss
}
poss = append(poss[:rem], poss[rem+1:]...)
}
return poss
}
type Range struct {
Min int
Max int
}
type Field struct {
Index int
Name string
Ranges []Range
Value int
}
func (f *Field) ValidForValue(val int) bool {
for _, r := range f.Ranges {
if val >= r.Min && val <= r.Max {
return true
}
}
return false
}
func (f *Field) AddRange(min, max int) {
f.Ranges = append(f.Ranges, Range{Min: min, Max: max})
}
type Ticket struct {
Number int
Fields map[string]*Field
UnnamedFields []int
PossibleFieldMap map[int][]string
InvalidValues []int
Valid bool
}
func NewTicket(num int, inp []int) *Ticket {
t := Ticket{
Number: num,
Fields: make(map[string]*Field),
PossibleFieldMap: make(map[int][]string),
Valid: true,
}
for k := range inp {
t.UnnamedFields = append(t.UnnamedFields, inp[k])
var found bool
for _, f := range AllFields {
if f.ValidForValue(inp[k]) {
found = true
t.PossibleFieldMap[k] = append(t.PossibleFieldMap[k], f.Name)
}
}
if !found {
t.InvalidValues = append(t.InvalidValues, inp[k])
t.Valid = false
}
}
return &t
}
func (t *Ticket) DefineField(f Field) {
f.Value = t.UnnamedFields[f.Index]
t.Fields[f.Name] = &f
}
func (t *Ticket) SetFields(fields []*Field) {
for _, f := range fields {
t.DefineField(*f)
}
}