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 }