Add (slow) day 12 part 1

master
Nick Krichevsky 2023-12-13 08:45:54 -05:00
parent 4042526ca9
commit 02e748ff16
1 changed files with 211 additions and 0 deletions

211
day12/main.go Normal file
View File

@ -0,0 +1,211 @@
package main
import (
"errors"
"fmt"
"io"
"os"
"regexp"
"slices"
"strconv"
"strings"
)
type SpringState int
const (
SpringStateOperational SpringState = iota
SpringStateDamaged
SpringStateUnknown
)
type SpringStates []SpringState
type Record struct {
states SpringStates
sequences []int
}
func (states SpringStates) Print() {
for _, state := range states {
switch state {
case SpringStateDamaged:
fmt.Print("#")
case SpringStateOperational:
fmt.Print(".")
case SpringStateUnknown:
fmt.Print("?")
default:
panic("invalid state")
}
}
fmt.Println()
}
func (r Record) EvaluateUnknownStates() []SpringStates {
res := []SpringStates{}
for _, possibleStates := range getAllPossibleStates(r.states, 0) {
if r.matchesSequence(possibleStates) {
res = append(res, possibleStates)
}
}
return res
}
func (r Record) matchesSequence(states SpringStates) bool {
sequenceCountCursor := 0
// states.Print()
damageCount := 0
needSpace := false
for _, state := range states {
if sequenceCountCursor > len(r.sequences)-1 && state == SpringStateDamaged {
return false
} else if sequenceCountCursor > len(r.sequences)-1 {
continue
}
sequenceCount := r.sequences[sequenceCountCursor]
if needSpace && state != SpringStateOperational {
return false
} else if needSpace {
needSpace = false
continue
} else if state == SpringStateOperational && damageCount > 0 && damageCount < sequenceCount {
return false
}
if state == SpringStateDamaged {
damageCount++
} else if state == SpringStateOperational {
damageCount = 0
}
if damageCount == sequenceCount {
sequenceCountCursor++
damageCount = 0
needSpace = true
}
}
return sequenceCountCursor == len(r.sequences)
}
func main() {
if len(os.Args) != 2 && len(os.Args) != 3 {
fmt.Fprintf(os.Stderr, "Usage: %s inputfile\n", os.Args[0])
os.Exit(1)
}
inputFilename := os.Args[1]
inputFile, err := os.Open(inputFilename)
if err != nil {
panic(fmt.Sprintf("could not open input file: %s", err))
}
defer inputFile.Close()
inputBytes, err := io.ReadAll(inputFile)
if err != nil {
panic(fmt.Sprintf("could not read input file: %s", err))
}
input := strings.TrimSpace(string(inputBytes))
inputLines := strings.Split(input, "\n")
records, err := parseRecords(inputLines)
if err != nil {
panic(fmt.Sprintf("failed to parse input: %s", err))
}
fmt.Printf("Part 1: %d\n", part1(records))
}
func part1(records []Record) int {
total := 0
for _, record := range records {
possibilities := record.EvaluateUnknownStates()
total += len(possibilities)
}
return total
}
func getAllPossibleStates(states SpringStates, trackingIdx int) []SpringStates {
if trackingIdx > len(states)-1 {
return []SpringStates{}
} else if states[trackingIdx] != SpringStateUnknown {
return getAllPossibleStates(states, trackingIdx+1)
}
ifOperational := slices.Clone(states)
ifOperational[trackingIdx] = SpringStateOperational
ifDamaged := slices.Clone(states)
ifDamaged[trackingIdx] = SpringStateDamaged
if slices.Index(states[trackingIdx+1:], SpringStateUnknown) == -1 {
return []SpringStates{ifOperational, ifDamaged}
}
possibilities := []SpringStates{}
possibilities = append(possibilities, getAllPossibleStates(ifOperational, trackingIdx+1)...)
possibilities = append(possibilities, getAllPossibleStates(ifDamaged, trackingIdx+1)...)
return possibilities
}
func parseRecords(inputLines []string) ([]Record, error) {
return tryParse(inputLines, parseRecord)
}
func parseRecord(inputLine string) (Record, error) {
lineRegexp := regexp.MustCompile(`^([#.?]+) ((?:\d+,)*\d+)$`)
matches := lineRegexp.FindStringSubmatch(inputLine)
if matches == nil {
return Record{}, errors.New("malformed input line")
}
states, err := parseSpringStates(matches[1])
if err != nil {
return Record{}, fmt.Errorf("spring state: %w", err)
}
sequences, err := tryParse(strings.Split(matches[2], ","), strconv.Atoi)
if err != nil {
return Record{}, fmt.Errorf("sequences: %w", err)
}
return Record{states: states, sequences: sequences}, nil
}
func parseSpringStates(inputStates string) ([]SpringState, error) {
states := make([]SpringState, len(inputStates))
for i, char := range inputStates {
switch char {
case '.':
states[i] = SpringStateOperational
case '#':
states[i] = SpringStateDamaged
case '?':
states[i] = SpringStateUnknown
default:
return nil, fmt.Errorf("invalid spring state char, %c", char)
}
}
return states, nil
}
func tryParse[T any](items []string, parse func(string) (T, error)) ([]T, error) {
res := make([]T, 0, len(items))
for i, item := range items {
parsed, err := parse(item)
if err != nil {
return nil, fmt.Errorf("invalid item #%d: %w", i+1, err)
}
res = append(res, parsed)
}
return res, nil
}