2023-12-13 08:32:53 -07:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
_ "embed"
|
|
|
|
"fmt"
|
|
|
|
"slices"
|
|
|
|
"strings"
|
|
|
|
|
2023-12-15 15:13:24 -07:00
|
|
|
aoc "go.sour.is/advent-of-code"
|
2023-12-13 08:32:53 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
// var log = aoc.Log
|
|
|
|
|
|
|
|
func main() { aoc.MustResult(aoc.Runner(run)) }
|
|
|
|
|
|
|
|
type result struct {
|
|
|
|
valuePT1 int
|
|
|
|
valuePT2 int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r result) String() string { return fmt.Sprintf("%#v", r) }
|
|
|
|
|
|
|
|
func run(scan *bufio.Scanner) (*result, error) {
|
|
|
|
var matches []int
|
2023-12-13 11:30:58 -07:00
|
|
|
var matches2 []int
|
2023-12-13 08:32:53 -07:00
|
|
|
|
|
|
|
for scan.Scan() {
|
|
|
|
text := scan.Text()
|
|
|
|
status, text, ok := strings.Cut(text, " ")
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-12-13 11:30:58 -07:00
|
|
|
// part 1 - brute force
|
2023-12-13 08:32:53 -07:00
|
|
|
grouping := aoc.SliceMap(aoc.Atoi, strings.Split(text, ",")...)
|
|
|
|
pattern := []rune(status)
|
2023-12-13 11:30:58 -07:00
|
|
|
// sp := spring{pattern: pattern, grouping: grouping, missingNo: countQuestion(pattern)}
|
|
|
|
// matches = append(matches, sp.findMatches())
|
|
|
|
matches = append(matches, countPossible(pattern, grouping))
|
|
|
|
|
|
|
|
// part 2 - NFA
|
|
|
|
b, a := status, text
|
|
|
|
bn, an := "", ""
|
|
|
|
for i := 0; i < 5; i++ {
|
|
|
|
bn, an = bn+b+"?", an+a+","
|
|
|
|
}
|
|
|
|
b, a = strings.TrimSuffix(bn, "?"), strings.TrimSuffix(an, ",")
|
|
|
|
matches2 = append(matches2, countPossible([]rune(b), aoc.SliceMap(aoc.Atoi, strings.Split(a, ",")...)))
|
2023-12-13 08:32:53 -07:00
|
|
|
}
|
|
|
|
|
2023-12-13 11:30:58 -07:00
|
|
|
return &result{valuePT1: aoc.Sum(matches...), valuePT2: aoc.Sum(matches2...)}, nil
|
2023-12-13 08:32:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
type spring struct {
|
|
|
|
pattern []rune
|
|
|
|
grouping []int
|
|
|
|
missingNo int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *spring) findMatches() int {
|
|
|
|
matches := 0
|
|
|
|
for _, pattern := range s.genPatterns() {
|
|
|
|
pattern := []rune(pattern)
|
|
|
|
target := make([]rune, len(s.pattern))
|
|
|
|
i := 0
|
|
|
|
for j, r := range s.pattern {
|
|
|
|
if r == '?' {
|
|
|
|
target[j] = pattern[i]
|
|
|
|
i++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
target[j] = r
|
|
|
|
}
|
|
|
|
|
|
|
|
if slices.Equal(countGroupings(target), s.grouping) {
|
|
|
|
matches++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return matches
|
|
|
|
}
|
|
|
|
func (s *spring) genPatterns() []string {
|
|
|
|
buf := &strings.Builder{}
|
|
|
|
combinations := aoc.Power2(s.missingNo)
|
|
|
|
lis := make([]string, 0, combinations)
|
|
|
|
for i := 0; i < combinations; i++ {
|
|
|
|
for b := 0; b < s.missingNo; b++ {
|
|
|
|
if i>>b&0b1 == 1 {
|
|
|
|
buf.WriteRune('#')
|
|
|
|
} else {
|
|
|
|
buf.WriteRune('.')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lis = append(lis, buf.String())
|
|
|
|
buf.Reset()
|
|
|
|
}
|
|
|
|
|
|
|
|
return lis
|
|
|
|
}
|
2023-12-13 11:30:58 -07:00
|
|
|
func countPossible(s []rune, c []int) int {
|
|
|
|
pos := 0
|
|
|
|
|
|
|
|
cstates := map[state]int{{}: 1} // current state
|
|
|
|
nstates := map[state]int{} // next state
|
|
|
|
|
|
|
|
for len(cstates) > 0 {
|
|
|
|
for st, num := range cstates {
|
|
|
|
si, ci, cc, expdot := st.springIndex, st.groupIndex, st.continuous, st.expectDot
|
2023-12-15 15:13:24 -07:00
|
|
|
|
2023-12-13 11:30:58 -07:00
|
|
|
// have we reached the end?
|
|
|
|
if si == len(s) {
|
|
|
|
if ci == len(c) {
|
|
|
|
pos += num
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case (s[si] == '#' || s[si] == '?') && ci < len(c) && !expdot:
|
|
|
|
// we are still looking for broken springs
|
|
|
|
if s[si] == '?' && cc == 0 {
|
|
|
|
// we are not in a run of broken springs, so ? can be working
|
|
|
|
nstates[state{si + 1, ci, cc, expdot}] += num
|
|
|
|
}
|
|
|
|
|
|
|
|
cc++
|
|
|
|
|
|
|
|
if cc == c[ci] {
|
|
|
|
// we've found the full next contiguous section of broken springs
|
|
|
|
ci++
|
|
|
|
cc = 0
|
|
|
|
expdot = true // we only want a working spring next
|
|
|
|
}
|
|
|
|
|
|
|
|
nstates[state{si + 1, ci, cc, expdot}] += num
|
|
|
|
|
|
|
|
case (s[si] == '.' || s[si] == '?') && cc == 0:
|
|
|
|
// we are not in a contiguous run of broken springs
|
|
|
|
expdot = false
|
|
|
|
nstates[state{si + 1, ci, cc, expdot}] += num
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// swap and clear previous states
|
|
|
|
cstates, nstates = nstates, cstates
|
|
|
|
clear(nstates)
|
|
|
|
}
|
|
|
|
return pos
|
|
|
|
}
|
|
|
|
|
|
|
|
type state struct {
|
|
|
|
springIndex int
|
|
|
|
groupIndex int
|
|
|
|
continuous int
|
|
|
|
expectDot bool
|
|
|
|
}
|
2023-12-13 08:32:53 -07:00
|
|
|
|
|
|
|
func countQuestion(pattern []rune) int {
|
|
|
|
count := 0
|
|
|
|
for _, r := range pattern {
|
|
|
|
if r == '?' {
|
|
|
|
count++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return count
|
|
|
|
}
|
|
|
|
func countGroupings(pattern []rune) []int {
|
|
|
|
var groupings []int
|
|
|
|
inGroup := false
|
|
|
|
for _, r := range pattern {
|
|
|
|
|
|
|
|
if r == '#' {
|
|
|
|
if !inGroup {
|
|
|
|
groupings = append(groupings, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
inGroup = true
|
|
|
|
groupings[len(groupings)-1]++
|
|
|
|
|
|
|
|
}
|
|
|
|
if inGroup && r != '#' {
|
|
|
|
inGroup = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return groupings
|
|
|
|
}
|