Add (slow) day 12 part 1
parent
4042526ca9
commit
02e748ff16
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue