mirror of
https://github.com/RussellChamp/advent-of-code-2021.git
synced 2026-10-27 21:52:32 +00:00
215 lines
4.9 KiB
Go
215 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
|
||
|
|
}
|