advent-of-code/2023/day-3/main.go
Dan Anglin 76583b3dda
All checks were successful
/ test (pull_request) Successful in 25s
refactor: add common functions to internal package
It's time to add common functions to the internal 'common' package.

- Add the function that read the contents from a file to a list.
- Add the function that parses a list of integers from a string.

...and more to come later...
2023-12-05 19:43:52 +00:00

143 lines
3.9 KiB
Go

package main
import (
"fmt"
"log"
"strconv"
"unicode"
"codeflow.dananglin.me.uk/apollo/advent-of-code/internal/common"
)
const (
dot = '.'
potentialGear = '*'
)
type solution struct {
sumOfValidEnginePartNumbers int
sumOfGearRatios int
}
func main() {
schematic, err := common.ReadFile("2023/day-3/files/input")
if err != nil {
log.Fatalf("ERROR: Unable to retrieve the engine schematic; %v\n", err)
}
answer, err := fixTheGondola(schematic)
if err != nil {
log.Fatalf("ERROR: Unable to calculate the sum of all valid engine part numbers; %v\n", err)
}
fmt.Printf("[Part 1] The sum of all valid engine part numbers: %d\n", answer.sumOfValidEnginePartNumbers)
fmt.Printf("[Part 2] The sum of all Gear Ratios: %d\n", answer.sumOfGearRatios)
}
func fixTheGondola(schematic []string) (solution, error) {
var (
partNumSum = 0
partNum = ""
validPartNum = false
maxY = len(schematic) - 1
maxX = len(schematic[0]) - 1
mapOfPotentialGears = make(map[string][]int)
potentialGearPos = ""
)
for y := range schematic {
for x, v := range schematic[y] {
if unicode.IsDigit(v) {
partNum = partNum + string(v)
if !validPartNum {
switch {
// check above left
case ((y - 1) >= 0) && ((x - 1) >= 0) && isSymbol(rune(schematic[y-1][x-1])):
validPartNum = true
if schematic[y-1][x-1] == potentialGear {
potentialGearPos = fmt.Sprintf("%d-%d", y-1, x-1)
}
// check straight above
case ((y - 1) >= 0) && isSymbol(rune(schematic[y-1][x])):
validPartNum = true
if schematic[y-1][x] == potentialGear {
potentialGearPos = fmt.Sprintf("%d-%d", y-1, x)
}
// check above right
case ((y - 1) >= 0) && ((x + 1) <= maxX) && isSymbol(rune(schematic[y-1][x+1])):
validPartNum = true
if schematic[y-1][x+1] == potentialGear {
potentialGearPos = fmt.Sprintf("%d-%d", y-1, x+1)
}
// check left
case ((x - 1) >= 0) && isSymbol(rune(schematic[y][x-1])):
validPartNum = true
if schematic[y][x-1] == potentialGear {
potentialGearPos = fmt.Sprintf("%d-%d", y, x-1)
}
// check right
case ((x + 1) <= maxX) && isSymbol(rune(schematic[y][x+1])):
validPartNum = true
if schematic[y][x+1] == potentialGear {
potentialGearPos = fmt.Sprintf("%d-%d", y, x+1)
}
// check below left
case ((y + 1) <= maxY) && ((x - 1) >= 0) && isSymbol(rune(schematic[y+1][x-1])):
validPartNum = true
if schematic[y+1][x-1] == potentialGear {
potentialGearPos = fmt.Sprintf("%d-%d", y+1, x-1)
}
// check straight below
case ((y + 1) <= maxY) && isSymbol(rune(schematic[y+1][x])):
validPartNum = true
if schematic[y+1][x] == potentialGear {
potentialGearPos = fmt.Sprintf("%d-%d", y+1, x)
}
// check below right
case ((y + 1) <= maxY) && ((x + 1) <= maxX) && isSymbol(rune(schematic[y+1][x+1])):
validPartNum = true
if schematic[y+1][x+1] == potentialGear {
potentialGearPos = fmt.Sprintf("%d-%d", y+1, x+1)
}
}
}
} else {
if validPartNum {
value, err := strconv.Atoi(partNum)
if err != nil {
return solution{}, fmt.Errorf("unable to convert %q to int; %w", partNum, err)
}
partNumSum = partNumSum + value
if len(potentialGearPos) > 0 {
mapOfPotentialGears[potentialGearPos] = append(mapOfPotentialGears[potentialGearPos], value)
}
}
partNum = ""
validPartNum = false
potentialGearPos = ""
}
}
}
// Calculate the sum of all gear ratios
sumGearRatios := 0
for _, v := range mapOfPotentialGears {
if len(v) == 2 {
sumGearRatios = sumGearRatios + (v[0] * v[1])
}
}
answer := solution{
sumOfValidEnginePartNumbers: partNumSum,
sumOfGearRatios: sumGearRatios,
}
return answer, nil
}
func isSymbol(r rune) bool {
return !unicode.IsDigit(r) && (r != dot)
}