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
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
|
|
}
|