Files
2021-12-10 11:22:03 -05:00

216 lines
4.9 KiB
Go

/* Day 10: Syntax Scoring */
package main
import (
"AoC2021/utils/arrays"
"AoC2021/utils/log"
"AoC2021/utils/timer"
"bufio"
"os"
"sort"
"strings"
)
func check(e error) {
if e != nil {
log.Println(log.DEBUG, "Oh snap!")
panic(e)
}
}
func main() {
log.Println(log.NORMAL, "--- Day 10: Syntax Scoring ---")
timer.Start()
part1()
timer.Tick()
log.Println(log.NORMAL)
part2()
timer.Tick()
log.Println(log.NORMAL)
}
func part1() {
log.LOG_LEVEL = log.DEBUG
log.Println(log.NORMAL, "* Part 1 *")
log.Println(log.NORMAL, " Goal: Find the first illegal character in each corrupted line of the navigation subsystem.")
log.Println(log.NORMAL, " Answer: What is the total syntax error score for those errors?")
input, err := os.Open("./input.txt")
check(err)
defer input.Close()
scanner := bufio.NewScanner(input)
lines := 0
tokenCount := 0
totalScore := 0
for scanner.Scan() {
line := scanner.Text()
_, errScore := validateNavLine(line)
totalScore += errScore
lines++
tokenCount += len(line)
}
log.Printf(log.NORMAL, "Read %d lines with %d tokens from input\n", lines, tokenCount)
log.Printf(log.NORMAL, "The total syntax error score is %d\n", totalScore)
}
func part2() {
log.Println(log.NORMAL, "* Part 2 *")
log.Println(log.NORMAL, " Goal: Find the completion string for each incomplete line, score the completion strings, and sort the scores.")
log.Println(log.NORMAL, " Answer: What is the middle score?")
input, err := os.Open("./input.txt")
check(err)
defer input.Close()
scanner := bufio.NewScanner(input)
lines := 0
tokenCount := 0
var autoScores []int
for scanner.Scan() {
line := scanner.Text()
lines++
tokenCount += len(line)
remainingStr, errScore := validateNavLine(line)
if errScore > 0 || len(remainingStr) == 0 {
continue
}
autoScores = append(autoScores, getAutoScore(remainingStr))
}
// stort the scores
sort.Slice(autoScores, func(i, j int) bool { return autoScores[i] < autoScores[j] })
// find the middle-ist index
middleIdx := len(autoScores) / 2
log.Printf(log.NORMAL, "Read %d lines with %d tokens from input\n", lines, tokenCount)
log.Printf(log.NORMAL, "Autocompleted %d lines\n", len(autoScores))
log.Printf(log.NORMAL, "The middle-ist auto-correct score at pos %d is %d\n", middleIdx, autoScores[middleIdx])
}
// validates the nav line and returns a non-zero value as a syntax error score
func BAD_validateNavLine(line string) int {
// split the line into tokens
tokens := strings.Split(line, "")
invalidStr := "An invalid token '%s' was found at position %3d while validating string: %s\n"
// I thought of doing this generically but there are only 4 hard-coded options so just do it manually
parens, brackets, braces, gators := 0, 0, 0, 0
for idx, token := range tokens {
switch token {
case "(":
parens++
case "[":
brackets++
case "{":
braces++
case "<":
gators++
case ")":
if parens > 0 {
parens--
} else {
log.Printf(log.DEBUG, invalidStr, token, idx, line)
return 3
}
case "]":
if brackets > 0 {
brackets--
} else {
log.Printf(log.DEBUG, invalidStr, token, idx, line[:idx+1])
return 57
}
case "}":
if braces > 0 {
braces--
} else {
log.Printf(log.DEBUG, invalidStr, token, idx, line)
return 1197
}
case ">":
if gators > 0 {
gators--
} else {
log.Printf(log.DEBUG, invalidStr, token, idx, line)
return 25137
}
}
}
return 0
}
func validateNavLine(line string) (string, int) {
reducedLine := reduceNavLine(line)
if len(reducedLine) == 0 {
return reducedLine, 0
}
// find the first closing token
firstCloseTokenIdx := strings.IndexAny(reducedLine, ")]}>")
// if no closing tokens are found, this is just an incomplete line (not corrupt)
if firstCloseTokenIdx == -1 {
return reducedLine, 0
}
log.Printf(log.DIAGNOSTIC, "First invalid token is at %d on %s\n", firstCloseTokenIdx, reducedLine)
switch reducedLine[firstCloseTokenIdx] {
case ')':
return reducedLine, 3
case ']':
return reducedLine, 57
case '}':
return reducedLine, 1197
case '>':
return reducedLine, 25137
}
return reducedLine, 0
}
func reduceNavLine(line string) string {
//recursively remove empty chunks from the line
oldLen := len(line)
for {
line = strings.ReplaceAll(line, "()", "")
line = strings.ReplaceAll(line, "[]", "")
line = strings.ReplaceAll(line, "{}", "")
line = strings.ReplaceAll(line, "<>", "")
if len(line) == oldLen {
break
}
oldLen = len(line)
}
return line
}
// get an auto-complete score from a line that has already been reduced
func getAutoScore(reducedLine string) int {
// we'll have to auto-complete tokens in REVERSE order of the opening tokens in the reducedLine
score := 0
for _, token := range arrays.Reverse(strings.Split(reducedLine, "")) {
score *= 5
switch token {
case "(":
score += 1
case "[":
score += 2
case "{":
score += 3
case "<":
score += 4
}
}
return score
}