You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

259 lines
5.1 KiB
Go

package day19
import (
"fmt"
"log"
"regexp"
"strconv"
"strings"
)
type Rule struct {
category byte
operator byte
value int
action string
}
type Part struct {
xVal int
mVal int
aVal int
sVal int
}
type Workflow []Rule
func ParseWorkflows(lines []string) map[string]Workflow {
workflows := map[string]Workflow{}
var re1 = regexp.MustCompile(`^([a-z]+){(([^,}]+,)+)([a-zA-Z]+)}$`)
var re2 = regexp.MustCompile(`^([a-z]+)(<|>)([0-9]+):([a-zA-Z]+)$`)
for _, line := range lines {
matches1 := re1.FindStringSubmatch(strings.TrimSpace(line))
if len(matches1) != 5 {
log.Fatalf("Regexp for %s should only have 5 parts and got %d", line, len(matches1))
}
wfName := matches1[1]
wfExp := strings.TrimSuffix(matches1[2], ",")
wfLast := matches1[4]
expressions := strings.Split(wfExp, ",")
wf := Workflow{}
for _, exp := range expressions {
matches2 := re2.FindStringSubmatch(exp)
if len(matches2) != 5 {
log.Fatalf("Regexp for %s should only have 5 parts and got %d", line, len(matches1))
}
val, _ := strconv.Atoi(matches2[3])
rule := Rule{
category: matches2[1][0],
operator: matches2[2][0],
value: val,
action: matches2[4],
}
wf = append(wf, rule)
}
wf = append(wf, Rule{
category: 'F',
operator: 0,
action: wfLast,
})
workflows[wfName] = wf
}
return workflows
}
func ParsParts(lines []string) []Part {
parts := []Part{}
var re = regexp.MustCompile(`^{x=([0-9]+),m=([0-9]+),a=([0-9]+),s=([0-9]+)}$`)
for _, line := range lines {
matches := re.FindStringSubmatch(strings.TrimSpace(line))
if len(matches) != 5 {
log.Fatalf("Regexp for %s should only have 5 parts and got %d", line, len(matches))
}
xVal, _ := strconv.Atoi(matches[1])
mVal, _ := strconv.Atoi(matches[2])
aVal, _ := strconv.Atoi(matches[3])
sVal, _ := strconv.Atoi(matches[4])
part := Part{xVal, mVal, aVal, sVal}
parts = append(parts, part)
}
return parts
}
func evalRule(partVal int, operator byte, ruleVal int, action string) string {
switch {
case operator == '<' && partVal < ruleVal:
return action
case operator == '>' && partVal > ruleVal:
return action
}
return ""
}
func isAccepted(part Part, workflows map[string]Workflow) bool {
nextAction := "in"
for {
wf := workflows[nextAction]
nextAction = ""
for _, rule := range wf {
switch rule.category {
case 'F':
nextAction = rule.action
case 'x':
nextAction = evalRule(part.xVal, rule.operator, rule.value, rule.action)
case 'm':
nextAction = evalRule(part.mVal, rule.operator, rule.value, rule.action)
case 'a':
nextAction = evalRule(part.aVal, rule.operator, rule.value, rule.action)
case 's':
nextAction = evalRule(part.sVal, rule.operator, rule.value, rule.action)
}
if nextAction == "A" {
return true
}
if nextAction == "R" {
return false
}
if nextAction != "" {
break // move to next workflow
}
}
}
}
func Part1(lines []string) int {
idx := 0
for i, line := range lines {
if strings.TrimSpace(line) == "" {
idx = i
break
}
}
Workflows := ParseWorkflows(lines[:idx])
parts := ParsParts(lines[idx+1:])
fmt.Println(len(Workflows))
fmt.Println(len(parts))
sum := 0
for _, part := range parts {
if isAccepted(part, Workflows) {
sum += part.aVal + part.mVal + part.sVal + part.xVal
}
}
return sum
}
func countParts(mins Part, maxs Part) int {
result := 1
result *= maxs.xVal - mins.xVal + 1
result *= maxs.mVal - mins.mVal + 1
result *= maxs.aVal - mins.aVal + 1
result *= maxs.sVal - mins.sVal + 1
return result
}
func traverseWF(wfName string, workflows map[string]Workflow, mins Part, maxs Part) int {
if wfName == "R" {
return 0
}
if wfName == "A" {
return countParts(mins, maxs)
}
wf := workflows[wfName]
result := 0
for _, rule := range wf {
nextMins := mins
nextMaxs := maxs
switch rule.operator {
case 0:
result += traverseWF(rule.action, workflows, mins, maxs)
case '<':
switch rule.category {
case 'x':
nextMaxs.xVal = rule.value - 1
mins.xVal = rule.value
case 'm':
nextMaxs.mVal = rule.value - 1
mins.mVal = rule.value
case 'a':
nextMaxs.aVal = rule.value - 1
mins.aVal = rule.value
case 's':
nextMaxs.sVal = rule.value - 1
mins.sVal = rule.value
}
result += traverseWF(rule.action, workflows, nextMins, nextMaxs)
case '>':
switch rule.category {
case 'x':
nextMins.xVal = rule.value + 1
maxs.xVal = rule.value
case 'm':
nextMins.mVal = rule.value + 1
maxs.mVal = rule.value
case 'a':
nextMins.aVal = rule.value + 1
maxs.aVal = rule.value
case 's':
nextMins.sVal = rule.value + 1
maxs.sVal = rule.value
}
result += traverseWF(rule.action, workflows, nextMins, nextMaxs)
}
}
return result
}
func Part2(lines []string) int {
idx := 0
for i, line := range lines {
if strings.TrimSpace(line) == "" {
idx = i
break
}
}
workflows := ParseWorkflows(lines[:idx])
fmt.Println(len(workflows))
mins := Part{1, 1, 1, 1}
maxs := Part{4000, 4000, 4000, 4000}
result := traverseWF("in", workflows, mins, maxs)
return result
}