279 lines
4.8 KiB
Go
279 lines
4.8 KiB
Go
package days
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const ()
|
|
|
|
var (
|
|
rules = make(map[string][]int)
|
|
myTicket = make([]int, 0)
|
|
otherTickets = make([][]int, 0)
|
|
ruleRegex = regexp.MustCompile(`(.+): (\d+)-(\d+) or (\d+)-(\d+)`)
|
|
)
|
|
|
|
func atoi(val string) int {
|
|
iVal, err := strconv.Atoi(val)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return iVal
|
|
}
|
|
|
|
func atol(val string) int64 {
|
|
iVal, err := strconv.ParseInt(val, 10, 64)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return iVal
|
|
}
|
|
|
|
func msDuration(startTime time.Time) time.Duration {
|
|
return time.Since(startTime)
|
|
}
|
|
|
|
// Day16 runs day 16
|
|
func Day16() {
|
|
start := time.Now()
|
|
makeList()
|
|
log.Printf("Q16MakeList took %s\n", msDuration(start))
|
|
start = time.Now()
|
|
part1()
|
|
log.Printf("Q16Part1 took %s\n", msDuration(start))
|
|
start = time.Now()
|
|
part2()
|
|
log.Printf("Q16Part2 took %s\n", msDuration(start))
|
|
}
|
|
|
|
func makeList() {
|
|
bytes, err := ioutil.ReadFile("16input.txt")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fileStr := string(bytes)
|
|
mode := 0
|
|
for _, line := range strings.Split(fileStr, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if len(line) == 0 {
|
|
mode++
|
|
continue
|
|
}
|
|
|
|
switch mode {
|
|
case 0:
|
|
matches := ruleRegex.FindStringSubmatch(line)
|
|
if matches == nil {
|
|
panic("no regex match")
|
|
}
|
|
|
|
rules[matches[1]] = []int{
|
|
atoi(matches[2]),
|
|
atoi(matches[3]),
|
|
atoi(matches[4]),
|
|
atoi(matches[5]),
|
|
}
|
|
break
|
|
|
|
case 1:
|
|
if line == "your ticket:" {
|
|
continue
|
|
}
|
|
|
|
for _, val := range strings.Split(line, ",") {
|
|
myTicket = append(myTicket, atoi(val))
|
|
}
|
|
break
|
|
|
|
case 2:
|
|
if line == "nearby tickets:" {
|
|
continue
|
|
}
|
|
|
|
otherTicket := make([]int, 0)
|
|
for _, val := range strings.Split(line, ",") {
|
|
otherTicket = append(otherTicket, atoi(val))
|
|
}
|
|
|
|
otherTickets = append(otherTickets, otherTicket)
|
|
break
|
|
|
|
case 3:
|
|
panic("unexpected")
|
|
}
|
|
}
|
|
}
|
|
|
|
func isValidForAnyRule(val int) bool {
|
|
for rule := range rules {
|
|
if isValidForRule(val, rule) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func isValidForRule(val int, rule string) bool {
|
|
if (val >= rules[rule][0] && val <= rules[rule][1]) || (val >= rules[rule][2] && val <= rules[rule][3]) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func isValidTicket(ticket []int) bool {
|
|
for _, val := range ticket {
|
|
if !isValidForAnyRule(val) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func part1() {
|
|
invalidValues := make([]int, 0)
|
|
for _, ticket := range otherTickets {
|
|
for _, val := range ticket {
|
|
if !isValidForAnyRule(val) {
|
|
invalidValues = append(invalidValues, val)
|
|
}
|
|
}
|
|
}
|
|
|
|
result := 0
|
|
for _, val := range invalidValues {
|
|
result += val
|
|
}
|
|
|
|
log.Printf("Q16part1: invalid sum=%d\n", result)
|
|
}
|
|
|
|
func part2() {
|
|
validTickets := make([][]int, 0)
|
|
for _, ticket := range otherTickets {
|
|
if isValidTicket(ticket) {
|
|
validTickets = append(validTickets, ticket)
|
|
}
|
|
}
|
|
|
|
ruleMatches := make(map[string][]int)
|
|
|
|
for ruleName := range rules {
|
|
numCols := len(validTickets[0])
|
|
for checkCol := 0; checkCol < numCols; checkCol++ {
|
|
ruleValid := true
|
|
for _, ticket := range validTickets {
|
|
if !isValidForRule(ticket[checkCol], ruleName) {
|
|
ruleValid = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if ruleValid {
|
|
ruleMatches[ruleName] = append(ruleMatches[ruleName], checkCol)
|
|
}
|
|
}
|
|
|
|
if _, ok := ruleMatches[ruleName]; !ok {
|
|
panic("didn't map column to rule")
|
|
}
|
|
}
|
|
|
|
finalMap := make(map[string]int)
|
|
ruleMatched := func(test string) bool {
|
|
for name := range finalMap {
|
|
if name == test {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
valMatched := func(test int) bool {
|
|
for _, val := range finalMap {
|
|
if val == test {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
for len(finalMap) < len(ruleMatches) {
|
|
for ruleName, ruleVals := range ruleMatches {
|
|
if ruleMatched(ruleName) {
|
|
continue
|
|
}
|
|
|
|
unmatchedVals := 0
|
|
unmatchedValIdx := 0
|
|
for idx, val := range ruleVals {
|
|
if !valMatched(val) {
|
|
unmatchedVals++
|
|
unmatchedValIdx = idx
|
|
}
|
|
}
|
|
if unmatchedVals == 1 {
|
|
finalMap[ruleName] = ruleVals[unmatchedValIdx]
|
|
continue
|
|
}
|
|
|
|
for _, val := range ruleVals {
|
|
if valMatched(val) {
|
|
continue
|
|
}
|
|
|
|
numMatches := 0
|
|
for checkRule, checkVals := range ruleMatches {
|
|
if ruleMatched(checkRule) {
|
|
continue
|
|
}
|
|
|
|
for _, checkVal := range checkVals {
|
|
if checkVal == val {
|
|
numMatches++
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if numMatches == 1 {
|
|
finalMap[ruleName] = val
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ruleForColumn := func(col int) string {
|
|
for name, checkVal := range finalMap {
|
|
if col == checkVal {
|
|
return name
|
|
}
|
|
}
|
|
|
|
panic(fmt.Sprintf("no rule found for column %d", col))
|
|
}
|
|
|
|
finalVal := 1
|
|
for idx, val := range myTicket {
|
|
rule := ruleForColumn(idx)
|
|
if strings.HasPrefix(rule, "departure ") {
|
|
finalVal *= val
|
|
}
|
|
}
|
|
|
|
log.Printf("Q16part2: departures multiplied=%d\n", finalVal)
|
|
}
|