256 lines
4.9 KiB
Go
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"))
|
|
}
|