Solve day 3 part 2

master
Nick Krichevsky 2023-12-03 11:38:08 -05:00
parent e0a9c21bde
commit 0aaf41e31d
1 changed files with 114 additions and 32 deletions

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -35,10 +36,11 @@ func main() {
inputLines := strings.Split(strings.TrimSpace(input), "\n") inputLines := strings.Split(strings.TrimSpace(input), "\n")
fmt.Printf("Part 1: %d\n", part1(inputLines)) fmt.Printf("Part 1: %d\n", part1(inputLines))
fmt.Printf("Part 2: %d\n", part2(inputLines))
} }
func part1(inputLines []string) int { func part1(inputLines []string) int {
symbolPositions := findSymbols(inputLines) symbolPositions := findParts(inputLines, isSymbol)
partNumberCandidates := []Coordinate{} partNumberCandidates := []Coordinate{}
for _, symbolPos := range symbolPositions { for _, symbolPos := range symbolPositions {
candidates := findNumbersAdjacentTo(inputLines, symbolPos) candidates := findNumbersAdjacentTo(inputLines, symbolPos)
@ -46,42 +48,21 @@ func part1(inputLines []string) int {
} }
total := 0 total := 0
// keep track of digits we've already scanned; it's possible one number
// is adjacent to two parts, and we don't wanna double count
scanned := map[Coordinate]struct{}{} scanned := map[Coordinate]struct{}{}
for _, candidate := range partNumberCandidates { for _, candidate := range partNumberCandidates {
if _, ok := scanned[candidate]; ok { if _, ok := scanned[candidate]; ok {
continue continue
} }
line := inputLines[candidate.row] scannedNumber, scannedDigits, err := scanPartNumber(inputLines, candidate)
backwardsBuf := strings.Builder{}
// Scan backward
for i := candidate.col - 1; i >= 0; i-- {
char := line[i]
if !unicode.IsDigit(rune(char)) {
break
}
scanned[Coordinate{row: candidate.row, col: i}] = struct{}{}
backwardsBuf.WriteByte(char)
}
forwardsBuf := strings.Builder{}
// Scan forward
for i := candidate.col; i < len(line); i++ {
char := line[i]
if !unicode.IsDigit(rune(char)) {
break
}
scanned[Coordinate{row: candidate.row, col: i}] = struct{}{}
forwardsBuf.WriteByte(char)
}
fullNumber := reverseString(backwardsBuf.String()) + forwardsBuf.String()
scannedNumber, err := strconv.Atoi(fullNumber)
if err != nil { if err != nil {
panic(fmt.Sprintf("%s was not a number", fullNumber)) panic(fmt.Sprintf("candidate was invalid: %s", err))
}
for _, scannedDigit := range scannedDigits {
scanned[scannedDigit] = struct{}{}
} }
total += scannedNumber total += scannedNumber
@ -90,11 +71,30 @@ func part1(inputLines []string) int {
return total return total
} }
func findSymbols(inputLines []string) []Coordinate { func part2(inputLines []string) int {
gearPositions := findParts(inputLines, isGear)
totalRatio := 0
for _, gearPos := range gearPositions {
partNumbers, err := scanForGearPartNumbers(inputLines, gearPos)
if err != nil {
panic(fmt.Sprintf("gear scan failed: %s", err))
}
if len(partNumbers) == 2 {
totalRatio += partNumbers[0] * partNumbers[1]
}
}
return totalRatio
}
// findParts will find all parts in the input, using the given isPart function to tell
// if a character is a valid part.
func findParts(inputLines []string, isPart func(rune) bool) []Coordinate {
coords := []Coordinate{} coords := []Coordinate{}
for row, line := range inputLines { for row, line := range inputLines {
for col, char := range line { for col, char := range line {
if isSymbol(char) { if isPart(char) {
coord := Coordinate{ coord := Coordinate{
row: row, row: row,
col: col, col: col,
@ -108,6 +108,7 @@ func findSymbols(inputLines []string) []Coordinate {
return coords return coords
} }
// findNumbersAdjacentTo will find all the digit characters adjacent to a given coordinate
func findNumbersAdjacentTo(inputLines []string, coordinate Coordinate) []Coordinate { func findNumbersAdjacentTo(inputLines []string, coordinate Coordinate) []Coordinate {
coords := []Coordinate{} coords := []Coordinate{}
for dRow := -1; dRow <= 1; dRow++ { for dRow := -1; dRow <= 1; dRow++ {
@ -132,10 +133,91 @@ func findNumbersAdjacentTo(inputLines []string, coordinate Coordinate) []Coordin
return coords return coords
} }
// scanPartNumber takes an initial digit character and attempts to complete it by scanning left and right from that
// position. Returns the found part number and the positions scanned.
func scanPartNumber(inputLines []string, knownDigitPosition Coordinate) (int, []Coordinate, error) {
if !unicode.IsDigit(rune(inputLines[knownDigitPosition.row][knownDigitPosition.col])) {
return 0, nil, errors.New("position was not a numeric char")
}
line := inputLines[knownDigitPosition.row]
scanned := []Coordinate{}
backwardsBuf := strings.Builder{}
// Scan backward
for i := knownDigitPosition.col - 1; i >= 0; i-- {
char := line[i]
if !unicode.IsDigit(rune(char)) {
break
}
scanned = append(scanned, Coordinate{row: knownDigitPosition.row, col: i})
backwardsBuf.WriteByte(char)
}
forwardsBuf := strings.Builder{}
// Scan forward
for i := knownDigitPosition.col; i < len(line); i++ {
char := line[i]
if !unicode.IsDigit(rune(char)) {
break
}
scanned = append(scanned, Coordinate{row: knownDigitPosition.row, col: i})
forwardsBuf.WriteByte(char)
}
fullNumber := reverseString(backwardsBuf.String()) + forwardsBuf.String()
scannedNumber, err := strconv.Atoi(fullNumber)
if err != nil {
panic(fmt.Sprintf("%s was not a number", fullNumber))
}
return scannedNumber, scanned, nil
}
// scanForGearPartNumbers will find all part numbers adjacent to the given gear character position
func scanForGearPartNumbers(inputLines []string, gearPosition Coordinate) ([]int, error) {
if !isGear(rune(inputLines[gearPosition.row][gearPosition.col])) {
return nil, errors.New("position is not gear")
}
candidates := findNumbersAdjacentTo(inputLines, gearPosition)
// Keep track of the digits scanned, as it is possible for a single part number to be adjacent
// to the gear in multiple places
scanned := map[Coordinate]struct{}{}
adjacentPartNumbers := []int{}
for _, candidate := range candidates {
if _, ok := scanned[candidate]; ok {
continue
}
scannedNumber, scannedDigits, err := scanPartNumber(inputLines, candidate)
if err != nil {
// Strictly a programmer error, as this would imply that findNumbersAdjacentTo
// didn't find us a number
panic(fmt.Sprintf("candidate was invalid: %s", err))
}
for _, scannedDigit := range scannedDigits {
scanned[scannedDigit] = struct{}{}
}
adjacentPartNumbers = append(adjacentPartNumbers, scannedNumber)
}
return adjacentPartNumbers, nil
}
func isSymbol(r rune) bool { func isSymbol(r rune) bool {
return r != '.' && !unicode.IsDigit(r) return r != '.' && !unicode.IsDigit(r)
} }
func isGear(r rune) bool {
return r == '*'
}
func reverseString(s string) string { func reverseString(s string) string {
buffer := strings.Builder{} buffer := strings.Builder{}
for i := len(s) - 1; i >= 0; i-- { for i := len(s) - 1; i >= 0; i-- {