adventofcode/2023/day19/main.go

204 lines
4.4 KiB
Go

package main
import (
"fmt"
"strings"
h "git.bullercodeworks.com/brian/adventofcode/helpers"
)
func main() {
inp := h.StdinToStringSlice()
part1(inp)
fmt.Println()
part2(inp)
}
func parse(input []string) ([]*Part, map[string]*Workflow) {
var parts []*Part
workflows := make(map[string]*Workflow)
var parseParts bool
for i := range input {
if input[i] == "" {
parseParts = true
continue
}
if parseParts {
// Parse Parts
parts = append(parts, NewPart(input[i]))
} else {
// Parse Rules
w := NewWorkflow(input[i])
workflows[w.name] = w
}
}
// Add 'Accepted' Workflow
workflows["A"] = &Workflow{
name: "A",
process: func(p *Part, workflows map[string]*Workflow) bool {
return true
},
}
// Add 'Rejected' Workflow
workflows["R"] = &Workflow{
name: "R",
process: func(p *Part, workflows map[string]*Workflow) bool {
return false
},
}
return parts, workflows
}
func part1(input []string) {
parts, workflows := parse(input)
var total int
for i := range parts {
if workflows["in"].process(parts[i], workflows) {
total += parts[i].Value()
}
}
fmt.Println("# Part 1")
fmt.Println(total)
}
func part2(input []string) {
_, workflows := parse(input)
fmt.Println("# Part 2")
fmt.Println(findAcceptable(workflows, "in", map[byte]Range{'x': DefaultRange(), 'm': DefaultRange(), 'a': DefaultRange(), 's': DefaultRange()}))
}
func findAcceptable(workflows map[string]*Workflow, w string, ranges map[byte]Range) int {
var result int
if w == "R" {
return result
} else if w == "A" {
result = 1
for _, r := range ranges {
result *= r.end - r.start + 1
}
return result
}
currWrk := workflows[w]
for _, rule := range currWrk.rawRules {
updRanges := copyRangeMap(ranges)
if rule.cmp == '<' {
updRng := getRangeFor(updRanges, rule.cat)
updRng.end = rule.val - 1
updRanges[rule.cat] = updRng
upd := getRangeFor(ranges, rule.cat)
upd.start = rule.val
ranges[rule.cat] = upd
result += findAcceptable(workflows, rule.res, updRanges)
} else if rule.cmp == '>' {
updRng := getRangeFor(updRanges, rule.cat)
updRng.start = rule.val + 1
updRanges[rule.cat] = updRng
upd := getRangeFor(ranges, rule.cat)
upd.end = rule.val
ranges[rule.cat] = upd
result += findAcceptable(workflows, rule.res, updRanges)
} else {
result += findAcceptable(workflows, rule.res, ranges)
}
}
return result
}
func copyRangeMap(ranges map[byte]Range) map[byte]Range {
n := make(map[byte]Range)
for k, v := range ranges {
n[k] = v
}
return n
}
func getRangeFor(ranges map[byte]Range, cat byte) Range {
if v, ok := ranges[cat]; ok {
return v
}
fmt.Println(ranges)
fmt.Println("Requested:", string(cat))
panic("Range not found")
}
type Range struct {
start, end int
}
func DefaultRange() Range { return Range{start: 1, end: 4000} }
type Part struct {
categories map[byte]int
x, m, a, s int
}
func NewPart(input string) *Part {
p := Part{categories: make(map[byte]int)}
fmt.Sscanf(input, "{x=%d,m=%d,a=%d,s=%d}", &p.x, &p.m, &p.a, &p.s)
p.categories['x'] = p.x
p.categories['m'] = p.m
p.categories['a'] = p.a
p.categories['s'] = p.s
return &p
}
func (p *Part) Value() int { return p.x + p.m + p.a + p.s }
type Rule struct {
cat byte
cmp byte
val int
res string
}
func NewRule(input string) Rule {
t := strings.Index(input, ":")
if t >= 0 {
return Rule{
cat: input[0],
cmp: input[1],
val: h.Atoi(input[2:t]),
res: input[t+1:],
}
} else {
return Rule{res: input}
}
}
func (r Rule) String() string {
return fmt.Sprintf("%s%s%d:%s", string(r.cat), string(r.cmp), r.val, r.res)
}
type Workflow struct {
name string
rawRules []Rule
process func(*Part, map[string]*Workflow) bool
}
func NewWorkflow(input string) *Workflow {
start := strings.Index(input, "{")
w := Workflow{
name: input[:start],
}
rules := strings.Split(strings.Trim(input[start:], "{}"), ",")
for i := range rules {
w.rawRules = append(w.rawRules, NewRule(rules[i]))
}
w.process = func(p *Part, workflows map[string]*Workflow) bool {
for _, rule := range w.rawRules {
switch rule.cmp {
case '>':
if p.categories[rule.cat] > rule.val {
return workflows[rule.res].process(p, workflows)
}
case '<':
if p.categories[rule.cat] < rule.val {
return workflows[rule.res].process(p, workflows)
}
default:
return workflows[rule.res].process(p, workflows)
}
}
return true
}
return &w
}