Solve day 1 part 2

master
Nick Krichevsky 2023-12-01 08:33:20 -05:00
parent abec66fa2f
commit e1fed75ced
1 changed files with 117 additions and 24 deletions

View File

@ -5,55 +5,147 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"slices"
"strconv" "strconv"
"strings" "strings"
"unicode"
) )
func filterChars(line string, criteria func(rune) bool) string { // filterToMatches find all overlapping matches of the stringset "valid" in "line"
validChars := []rune{} func filterToMatches(line string, valid []string) []string {
for _, char := range line { type match struct {
if criteria(char) { idx int
validChars = append(validChars, char) value string
}
matches := []match{}
start := 0
// While we _could_ advance by the length of the string this
// challenge allows for overlapping strings. We can be a bit
// naive by just advancing one char and working that way
for i := range line {
for _, matchCandidate := range valid {
if strings.HasPrefix(line[i:], matchCandidate) {
matches = append(matches, match{idx: start, value: matchCandidate})
break
}
} }
} }
return string(validChars) slices.SortFunc(matches, func(a, b match) int {
return a.idx - b.idx
})
output := []string{}
for _, item := range matches {
output = append(output, item.value)
}
return output
} }
func getCoordinate(line string) (int, error) { // smashToDigits will "smash" two digits together to form a two digit number
candidates := filterChars(line, unicode.IsDigit) func smashDigits(digit1, digit2 int) int {
return digit1*10 + digit2
}
// mapToDigits will convert a list of strings to an int, and return the aggregated results
// or return an error immediately if one is encountered. In other words, it is assumed
// every item in this list is a valid number according to the toDigit function
func mapToDigits(items []string, toDigit func(string) (int, error)) ([]int, error) {
output := []int{}
for _, item := range items {
n, err := toDigit(item)
if err != nil {
return nil, fmt.Errorf("convert %s: %w", item, err)
}
output = append(output, n)
}
return output, nil
}
// getCoordinate gets a coordinate from the given calibration value
func getCoordinate(line string, validNumbers []string, convertToNumber func(string) (int, error)) (int, error) {
candidates := filterToMatches(line, validNumbers)
if len(candidates) == 0 { if len(candidates) == 0 {
return 0, errors.New("invalid calibration value") return 0, errors.New("invalid calibration value")
} }
if len(candidates) == 1 { digits, err := mapToDigits(candidates, convertToNumber)
value, err := strconv.Atoi(candidates) if err != nil {
if err != nil { // Programmer error, given the above filter
// Programmer error, given the above filter panic(err)
panic("invalid numeric string " + candidates)
}
return value*10 + value, nil
} }
value1 := int(candidates[0] - '0') if len(digits) == 1 {
value2 := int(candidates[len(candidates)-1] - '0') return smashDigits(digits[0], digits[0]), nil
return value1*10 + value2, nil }
value1 := digits[0]
value2 := digits[len(candidates)-1]
return smashDigits(value1, value2), nil
} }
func part1(input []string) int { func solve(input []string, validNumbers []string, convert func(string) (int, error)) (int, error) {
total := 0 total := 0
for _, line := range input { for _, line := range input {
coordinate, err := getCoordinate(line) coordinate, err := getCoordinate(line, validNumbers, convert)
if err != nil { if err != nil {
panic(fmt.Sprintf("No valid coordinate on line '%s': %s\n", line, err)) return 0, fmt.Errorf("no valid coordinate on line '%s': %s", line, err)
} }
total += coordinate total += coordinate
} }
return total return total, nil
}
func part1(input []string) int {
digits := []string{
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
}
result, err := solve(input, digits, strconv.Atoi)
if err != nil {
panic(err)
}
return result
}
func part2(input []string) int {
digits := []string{
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
}
words := []string{
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
}
allPossible := make([]string, len(digits), len(digits)+len(words))
copy(allPossible, digits)
allPossible = append(allPossible, words...)
result, err := solve(input, allPossible, func(s string) (int, error) {
n, err := strconv.Atoi(s)
if err == nil {
return n, nil
}
idx := slices.Index(words, s)
if idx != -1 {
return idx, nil
}
return 0, errors.New("invalid digit")
})
if err != nil {
panic(err)
}
return result
} }
func main() { func main() {
@ -79,4 +171,5 @@ 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))
} }