mirror of
https://github.com/RussellChamp/advent-of-code-2021.git
synced 2026-10-27 21:52:32 +00:00
day 9 and 10
This commit is contained in:
1 parent
c7ea0afedc
commit
41c5e86e7c
6 files changed
+479
-1
No files matched your search
+215
@@ -0,0 +1,215 @@
|
|||||||
|
/* 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
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"AoC2021/utils/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDay10(t *testing.T) {
|
||||||
|
assert.IsTrue(t, true, "cool")
|
||||||
|
assert.EqualsStr(t, "foo", "bar", "baz")
|
||||||
|
}
|
||||||
+233
@@ -0,0 +1,233 @@
|
|||||||
|
/* Day 9: Smoke Basin */
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"AoC2021/utils/arrays"
|
||||||
|
"AoC2021/utils/log"
|
||||||
|
"AoC2021/utils/timer"
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func check(e error) {
|
||||||
|
if e != nil {
|
||||||
|
log.Println(log.DEBUG, "Oh snap!")
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.Println(log.NORMAL, "--- Day 9: Smoke Basin ---")
|
||||||
|
timer.Start()
|
||||||
|
part1()
|
||||||
|
timer.Tick()
|
||||||
|
log.Println(log.NORMAL)
|
||||||
|
|
||||||
|
part2()
|
||||||
|
timer.Tick()
|
||||||
|
log.Println(log.NORMAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func part1() {
|
||||||
|
log.Println(log.NORMAL, "* Part 1 *")
|
||||||
|
log.Println(log.NORMAL, " Goal: Find all of the low points on your heightmap.")
|
||||||
|
log.Println(log.NORMAL, " Answer: What is the sum of the risk levels of all low points on your heightmap?")
|
||||||
|
|
||||||
|
points, lines := loadPoints()
|
||||||
|
minPoints := findMinPoints(points)
|
||||||
|
|
||||||
|
riskLevelSum := 0
|
||||||
|
for _, p := range minPoints {
|
||||||
|
riskLevelSum += getRiskLevel(points, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf(log.NORMAL, "Read %d lines from input\n", lines)
|
||||||
|
log.Printf(log.DIAGNOSTIC, "Min points are:")
|
||||||
|
for _, minPoint := range minPoints {
|
||||||
|
log.Printf(log.DIAGNOSTIC, " (%d, %d)", minPoint.col, minPoint.row)
|
||||||
|
}
|
||||||
|
log.Println(log.DIAGNOSTIC)
|
||||||
|
log.Printf(log.NORMAL, "The risk point total of all min points is %d\n", riskLevelSum)
|
||||||
|
}
|
||||||
|
|
||||||
|
func part2() {
|
||||||
|
log.Println(log.NORMAL, "* Part 2 *")
|
||||||
|
log.Println(log.NORMAL, " Goal: Find the three largest basins.")
|
||||||
|
log.Println(log.NORMAL, " Answer: What do you get if you multiply together the sizes of the three largest basins?")
|
||||||
|
|
||||||
|
points, lines := loadPoints()
|
||||||
|
minPoints := findMinPoints(points)
|
||||||
|
basins := getBasins(points, minPoints)
|
||||||
|
sort.Slice(basins, func(i, j int) bool { return basins[j].size < basins[i].size })
|
||||||
|
|
||||||
|
log.Printf(log.NORMAL, "Read %d lines from input\n", lines)
|
||||||
|
if len(basins) < 3 {
|
||||||
|
panic("expected to find at least 3 basins!")
|
||||||
|
}
|
||||||
|
log.Printf(log.NORMAL, "The three largest basins are")
|
||||||
|
for _, b := range basins[:3] {
|
||||||
|
log.Printf(log.NORMAL, " (%d, %d): %d", b.minPoint.col, b.minPoint.row, b.size)
|
||||||
|
}
|
||||||
|
log.Println(log.NORMAL)
|
||||||
|
totalValue := basins[0].size * basins[1].size * basins[2].size
|
||||||
|
log.Printf(log.NORMAL, "The multiplied value is %d\n", totalValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadPoints() ([][]int, int) {
|
||||||
|
input, err := os.Open("./input.txt")
|
||||||
|
check(err)
|
||||||
|
defer input.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(input)
|
||||||
|
lines := 0
|
||||||
|
var points [][]int
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
// Read in a line and convert each character into an int in the array
|
||||||
|
values := arrays.MapStrToInt(strings.Split(scanner.Text(), ""), func(s string) int { i, _ := strconv.Atoi(s); return i })
|
||||||
|
points = append(points, values)
|
||||||
|
log.Printf(log.DIAGNOSTIC, "Line %d: Read in %d values\n", lines+1, len(values))
|
||||||
|
lines++
|
||||||
|
}
|
||||||
|
|
||||||
|
return points, lines
|
||||||
|
}
|
||||||
|
|
||||||
|
type Point struct {
|
||||||
|
row, col int
|
||||||
|
}
|
||||||
|
|
||||||
|
func findMinPoints(points [][]int) []Point {
|
||||||
|
var minPoints []Point
|
||||||
|
for rowIdx, row := range points {
|
||||||
|
for colIdx := range row {
|
||||||
|
if isMinPoint(points, rowIdx, colIdx) {
|
||||||
|
minPoints = append(minPoints, Point{rowIdx, colIdx})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return minPoints
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the passed point actually exists within the list
|
||||||
|
func isPointValid(points [][]int, row, col int) bool {
|
||||||
|
if row < 0 || row >= len(points) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if col < 0 || col >= len(points[row]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the current point is a minimum
|
||||||
|
func isMinPoint(points [][]int, row, col int) bool {
|
||||||
|
// it is a minimum point if all valid points around it have a higher value
|
||||||
|
value := points[row][col]
|
||||||
|
// LEFT
|
||||||
|
if isPointValid(points, row, col-1) && points[row][col-1] <= value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// RIGHT
|
||||||
|
if isPointValid(points, row, col+1) && points[row][col+1] <= value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// UP
|
||||||
|
if isPointValid(points, row-1, col) && points[row-1][col] <= value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// DOWN
|
||||||
|
if isPointValid(points, row+1, col) && points[row+1][col] <= value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRiskLevel(points [][]int, point Point) int {
|
||||||
|
return 1 + points[point.row][point.col]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Basin struct {
|
||||||
|
minPoint Point
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBasins(points [][]int, minPoints []Point) []Basin {
|
||||||
|
var basins []Basin
|
||||||
|
for _, minPoint := range minPoints {
|
||||||
|
size := getBasinSize(points, minPoint)
|
||||||
|
basins = append(basins, Basin{minPoint, size})
|
||||||
|
}
|
||||||
|
|
||||||
|
return basins
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBasinSize(points [][]int, minPoint Point) int {
|
||||||
|
// to calculate the size of a basin
|
||||||
|
// * start at the min point
|
||||||
|
basinPoints := []Point{minPoint}
|
||||||
|
|
||||||
|
// * recursively check all adjacent points
|
||||||
|
expandBasin(&basinPoints, minPoint, points)
|
||||||
|
|
||||||
|
// * if the point is LESS THAN 9, NOT OFF THE EDGE, and NOT ALREADY IN THE BAISN
|
||||||
|
// * then add it to the basin and recursively keep going!
|
||||||
|
return len(basinPoints)
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns whether the point should be added to a basin
|
||||||
|
// note: this is a naive and doesn't check that the point should actually exist in the given basin but rather if it meets a few criteria
|
||||||
|
func addToBasin(allPoints [][]int, basinPoints []Point, row, col int) bool {
|
||||||
|
// only add valid points
|
||||||
|
if !isPointValid(allPoints, row, col) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// that are not already in the list
|
||||||
|
if contains(basinPoints, Point{row, col}) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// and have a value less than 9
|
||||||
|
return allPoints[row][col] < 9
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(points []Point, point Point) bool {
|
||||||
|
for _, p := range points {
|
||||||
|
if p.row == point.row && p.col == point.col {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandBasin(basinPoints *[]Point, fromPoint Point, allPoints [][]int) {
|
||||||
|
// LEFT
|
||||||
|
if addToBasin(allPoints, *basinPoints, fromPoint.row, fromPoint.col-1) {
|
||||||
|
newPoint := Point{fromPoint.row, fromPoint.col - 1}
|
||||||
|
*basinPoints = append(*basinPoints, newPoint)
|
||||||
|
expandBasin(basinPoints, newPoint, allPoints)
|
||||||
|
}
|
||||||
|
// RIGHT
|
||||||
|
if addToBasin(allPoints, *basinPoints, fromPoint.row, fromPoint.col+1) {
|
||||||
|
newPoint := Point{fromPoint.row, fromPoint.col + 1}
|
||||||
|
*basinPoints = append(*basinPoints, newPoint)
|
||||||
|
expandBasin(basinPoints, newPoint, allPoints)
|
||||||
|
}
|
||||||
|
// UP
|
||||||
|
if addToBasin(allPoints, *basinPoints, fromPoint.row-1, fromPoint.col) {
|
||||||
|
newPoint := Point{fromPoint.row - 1, fromPoint.col}
|
||||||
|
*basinPoints = append(*basinPoints, newPoint)
|
||||||
|
expandBasin(basinPoints, newPoint, allPoints)
|
||||||
|
}
|
||||||
|
// DOWN
|
||||||
|
if addToBasin(allPoints, *basinPoints, fromPoint.row+1, fromPoint.col) {
|
||||||
|
newPoint := Point{fromPoint.row + 1, fromPoint.col}
|
||||||
|
*basinPoints = append(*basinPoints, newPoint)
|
||||||
|
expandBasin(basinPoints, newPoint, allPoints)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"AoC2021/utils/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDay9(t *testing.T) {
|
||||||
|
assert.IsTrue(t, true, "cool")
|
||||||
|
assert.EqualsStr(t, "foo", "bar", "baz")
|
||||||
|
}
|
||||||
@@ -7,5 +7,5 @@ import (
|
|||||||
|
|
||||||
func TestDay#DAY#(t *testing.T) {
|
func TestDay#DAY#(t *testing.T) {
|
||||||
assert.IsTrue(t, true, "cool")
|
assert.IsTrue(t, true, "cool")
|
||||||
assert.StrEquals(t, "foo", "bar", "baz")
|
assert.EqualsStr(t, "foo", "bar", "baz")
|
||||||
}
|
}
|
||||||
@@ -47,3 +47,11 @@ func MapStrToInt(input []string, fn func(string) int) []int {
|
|||||||
}
|
}
|
||||||
return retVals
|
return retVals
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Reverse(input []string) []string {
|
||||||
|
var output []string
|
||||||
|
for idx := len(input) - 1; idx >= 0; idx-- {
|
||||||
|
output = append(output, input[idx])
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
Reference in new issue
Block a user