mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-06-07 12:45:10 +02:00
2023-19 not the prettiest recursion...
This commit is contained in:
@@ -0,0 +1,240 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alexchao26/advent-of-code-go/cast"
|
||||||
|
"github.com/alexchao26/advent-of-code-go/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed input.txt
|
||||||
|
var input string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// do this in init (not main) so test file has same input
|
||||||
|
input = strings.TrimRight(input, "\n")
|
||||||
|
if len(input) == 0 {
|
||||||
|
panic("empty input.txt file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var part int
|
||||||
|
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||||
|
flag.Parse()
|
||||||
|
fmt.Println("Running part", part)
|
||||||
|
|
||||||
|
if part == 1 {
|
||||||
|
ans := part1(input)
|
||||||
|
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||||
|
fmt.Println("Output:", ans)
|
||||||
|
} else {
|
||||||
|
ans := part2(input)
|
||||||
|
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||||
|
fmt.Println("Output:", ans)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func part1(input string) int {
|
||||||
|
parts, workflowsMap := parseInput(input)
|
||||||
|
|
||||||
|
ans := 0
|
||||||
|
|
||||||
|
for _, p := range parts {
|
||||||
|
currentWorkflowName := "in"
|
||||||
|
for currentWorkflowName != "A" && currentWorkflowName != "R" {
|
||||||
|
wf := workflowsMap[currentWorkflowName]
|
||||||
|
for _, rule := range wf.ruleStrings {
|
||||||
|
if !strings.Contains(rule, ":") {
|
||||||
|
currentWorkflowName = rule
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
colonSplit := strings.Split(rule, ":")
|
||||||
|
output := colonSplit[1]
|
||||||
|
|
||||||
|
if strings.Contains(colonSplit[0], "<") {
|
||||||
|
conditionalParts := strings.Split(colonSplit[0], "<")
|
||||||
|
if p[conditionalParts[0]] < cast.ToInt(conditionalParts[1]) {
|
||||||
|
currentWorkflowName = output
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if strings.Contains(colonSplit[0], ">") {
|
||||||
|
conditionalParts := strings.Split(colonSplit[0], ">")
|
||||||
|
if p[conditionalParts[0]] > cast.ToInt(conditionalParts[1]) {
|
||||||
|
currentWorkflowName = output
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic("unexpected workflow rule conditional: " + rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentWorkflowName == "A" {
|
||||||
|
ans += p.sumRatings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
|
type part map[string]int
|
||||||
|
|
||||||
|
func (p part) sumRatings() int {
|
||||||
|
total := 0
|
||||||
|
for _, v := range p {
|
||||||
|
total += v
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
type workflow struct {
|
||||||
|
name string
|
||||||
|
ruleStrings []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInput(input string) (parts []part, workflowsMap map[string]workflow) {
|
||||||
|
inputParts := strings.Split(input, "\n\n")
|
||||||
|
|
||||||
|
workflowsMap = map[string]workflow{}
|
||||||
|
|
||||||
|
// process workflows
|
||||||
|
for _, line := range strings.Split(inputParts[0], "\n") {
|
||||||
|
lineParts := strings.Split(line, "{")
|
||||||
|
wf := workflow{
|
||||||
|
name: lineParts[0],
|
||||||
|
ruleStrings: strings.Split(lineParts[1][:len(lineParts[1])-1], ","),
|
||||||
|
}
|
||||||
|
workflowsMap[wf.name] = wf
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, line := range strings.Split(inputParts[1], "\n") {
|
||||||
|
withoutBraces := line[1 : len(line)-1]
|
||||||
|
p := part{}
|
||||||
|
for _, ratingStr := range strings.Split(withoutBraces, ",") {
|
||||||
|
ratingParts := strings.Split(ratingStr, "=")
|
||||||
|
p[ratingParts[0]] = cast.ToInt(ratingParts[1])
|
||||||
|
}
|
||||||
|
parts = append(parts, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts, workflowsMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func part2(input string) int {
|
||||||
|
_, workflowsMap := parseInput(input)
|
||||||
|
|
||||||
|
// 1 to 4000 bounds for each rating...
|
||||||
|
boundedParts := map[string][2]int{
|
||||||
|
"x": {1, 4000},
|
||||||
|
"m": {1, 4000},
|
||||||
|
"a": {1, 4000},
|
||||||
|
"s": {1, 4000},
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatePartBoundsAndSplit(boundedParts, workflowsMap, "in", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updatePartBoundsAndSplit(boundedParts map[string][2]int, workflowsMap map[string]workflow, currentWorkflow string, debugDepth int) int {
|
||||||
|
if currentWorkflow == "R" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if currentWorkflow == "A" {
|
||||||
|
product := 1
|
||||||
|
for _, bounds := range boundedParts {
|
||||||
|
product *= bounds[1] - bounds[0] + 1
|
||||||
|
}
|
||||||
|
return product
|
||||||
|
}
|
||||||
|
|
||||||
|
// split based on rules...
|
||||||
|
total := 0
|
||||||
|
|
||||||
|
// for each rule,
|
||||||
|
// the rule either passes and moves onto a different workflow,
|
||||||
|
// or fails and checks the next rule
|
||||||
|
// need to sum up both forks
|
||||||
|
//
|
||||||
|
// passing is handled via recursion, failing is handled via looping to the next rule
|
||||||
|
// in both cases the bounds need to be updated
|
||||||
|
for _, rule := range workflowsMap[currentWorkflow].ruleStrings {
|
||||||
|
// just the next workflow to go after
|
||||||
|
if !strings.Contains(rule, ":") {
|
||||||
|
nextWorkflowName := rule
|
||||||
|
total += updatePartBoundsAndSplit(boundedParts, workflowsMap, nextWorkflowName, debugDepth+1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
colonSplit := strings.Split(rule, ":")
|
||||||
|
nextWorkflowName := colonSplit[1]
|
||||||
|
|
||||||
|
if strings.Contains(colonSplit[0], "<") {
|
||||||
|
conditionalParts := strings.Split(colonSplit[0], "<")
|
||||||
|
ratingName := conditionalParts[0]
|
||||||
|
ratingTestValue := cast.ToInt(conditionalParts[1])
|
||||||
|
|
||||||
|
// fork the part that passes the < conditional
|
||||||
|
copyOfBounds := copyBoundedPartsMap(boundedParts)
|
||||||
|
copyOfBounds[ratingName] = [2]int{
|
||||||
|
copyOfBounds[ratingName][0],
|
||||||
|
ratingTestValue - 1,
|
||||||
|
}
|
||||||
|
// check that the new bounds are still valid
|
||||||
|
if copyOfBounds[ratingName][0] <= copyOfBounds[ratingName][1] {
|
||||||
|
total += updatePartBoundsAndSplit(copyOfBounds, workflowsMap, nextWorkflowName, debugDepth+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// second fork for failing the conditional, need to update the boundedParts to fail
|
||||||
|
boundedParts[ratingName] = [2]int{
|
||||||
|
ratingTestValue,
|
||||||
|
boundedParts[ratingName][1],
|
||||||
|
}
|
||||||
|
// check that the new bounds are still valid
|
||||||
|
if boundedParts[ratingName][0] > boundedParts[ratingName][1] {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if strings.Contains(colonSplit[0], ">") {
|
||||||
|
conditionalParts := strings.Split(colonSplit[0], ">")
|
||||||
|
ratingName := conditionalParts[0]
|
||||||
|
ratingTestValue := cast.ToInt(conditionalParts[1])
|
||||||
|
|
||||||
|
// fork the part that passes the > conditional
|
||||||
|
copyOfBounds := copyBoundedPartsMap(boundedParts)
|
||||||
|
copyOfBounds[ratingName] = [2]int{
|
||||||
|
ratingTestValue + 1,
|
||||||
|
copyOfBounds[ratingName][1],
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that the new bounds are still valid before recursing
|
||||||
|
if copyOfBounds[ratingName][0] <= copyOfBounds[ratingName][1] {
|
||||||
|
total += updatePartBoundsAndSplit(copyOfBounds, workflowsMap, nextWorkflowName, debugDepth+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// second fork for failing the conditional, need to update the boundedParts to fail
|
||||||
|
boundedParts[ratingName] = [2]int{
|
||||||
|
boundedParts[ratingName][0],
|
||||||
|
ratingTestValue,
|
||||||
|
}
|
||||||
|
// check that the new bounds are still valid
|
||||||
|
if boundedParts[ratingName][0] > boundedParts[ratingName][1] {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic("unexpected workflow rule conditional: " + rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyBoundedPartsMap(boundedParts map[string][2]int) map[string][2]int {
|
||||||
|
cp := map[string][2]int{}
|
||||||
|
for k, v := range boundedParts {
|
||||||
|
cp[k] = v
|
||||||
|
}
|
||||||
|
return cp
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var example = `px{a<2006:qkq,m>2090:A,rfg}
|
||||||
|
pv{a>1716:R,A}
|
||||||
|
lnx{m>1548:A,A}
|
||||||
|
rfg{s<537:gd,x>2440:R,A}
|
||||||
|
qs{s>3448:A,lnx}
|
||||||
|
qkq{x<1416:A,crn}
|
||||||
|
crn{x>2662:A,R}
|
||||||
|
in{s<1351:px,qqz}
|
||||||
|
qqz{s>2770:qs,m<1801:hdj,R}
|
||||||
|
gd{a>3333:R,R}
|
||||||
|
hdj{m>838:A,pv}
|
||||||
|
|
||||||
|
{x=787,m=2655,a=1222,s=2876}
|
||||||
|
{x=1679,m=44,a=2067,s=496}
|
||||||
|
{x=2036,m=264,a=79,s=2244}
|
||||||
|
{x=2461,m=1339,a=466,s=291}
|
||||||
|
{x=2127,m=1623,a=2188,s=1013}`
|
||||||
|
|
||||||
|
func Test_part1(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
input: example,
|
||||||
|
want: 19114,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
input: input,
|
||||||
|
want: 287054,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := part1(tt.input); got != tt.want {
|
||||||
|
t.Errorf("part1() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_part2(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
input: example,
|
||||||
|
want: 167409079868000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
input: input,
|
||||||
|
want: 131619440296497,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := part2(tt.input); got != tt.want {
|
||||||
|
t.Errorf("part2() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user