Refactor day 19 to not use opaque functions

master
Nick Krichevsky 2023-12-19 19:58:13 -05:00
parent 6196d1c5f0
commit 22e2499b0d
1 changed files with 80 additions and 45 deletions

View File

@ -10,6 +10,22 @@ import (
"strings" "strings"
) )
type PartRatingType rune
const (
RatingTypeX PartRatingType = 'x'
RatingTypeM PartRatingType = 'm'
RatingTypeA PartRatingType = 'a'
RatingTypeS PartRatingType = 's'
)
type ComparisonOperator rune
const (
OperatorGreater ComparisonOperator = '>'
OperatorLess ComparisonOperator = '<'
)
type Part struct { type Part struct {
XtremelyCoolRating int XtremelyCoolRating int
MusicalRating int MusicalRating int
@ -17,13 +33,44 @@ type Part struct {
ShinyRating int ShinyRating int
} }
type Rule struct {
Conditions []RuleCondition
FallbackDestination string
}
type RuleCondition struct { type RuleCondition struct {
PartRatingFunc func(Part) int PartRatingType PartRatingType
OperatorFunc func(int, int) bool Operator ComparisonOperator
Operand int Operand int
SuccessDestination string SuccessDestination string
} }
func (part Part) Rating(ratingType PartRatingType) int {
switch ratingType {
case RatingTypeX:
return part.XtremelyCoolRating
case RatingTypeM:
return part.MusicalRating
case RatingTypeA:
return part.AerodynamicRating
case RatingTypeS:
return part.ShinyRating
default:
panic(fmt.Sprintf("invalid rating type %c", ratingType))
}
}
func (operator ComparisonOperator) Compare(a, b int) bool {
switch operator {
case OperatorGreater:
return a > b
case OperatorLess:
return a < b
default:
panic(fmt.Sprintf("invalid operator %c", operator))
}
}
func main() { func main() {
if len(os.Args) != 2 && len(os.Args) != 3 { if len(os.Args) != 2 && len(os.Args) != 3 {
fmt.Fprintf(os.Stderr, "Usage: %s inputfile\n", os.Args[0]) fmt.Fprintf(os.Stderr, "Usage: %s inputfile\n", os.Args[0])
@ -64,10 +111,15 @@ func main() {
fmt.Printf("Part 1: %d\n", part1(rules, parts)) fmt.Printf("Part 1: %d\n", part1(rules, parts))
} }
func part1(rules map[string]func(Part) string, parts []Part) int { func part1(rules map[string]Rule, parts []Part) int {
ruleFuncs := make(map[string]func(Part) string, len(rules))
for ruleName, rule := range rules {
ruleFuncs[ruleName] = buildRuleFunc(rule)
}
acceptedParts := []Part{} acceptedParts := []Part{}
for _, part := range parts { for _, part := range parts {
accepted, err := isPartAccepted(rules, part) accepted, err := isPartAccepted(ruleFuncs, part)
if err != nil { if err != nil {
panic(fmt.Sprintf("could not process part %v: %s", part, err)) panic(fmt.Sprintf("could not process part %v: %s", part, err))
} }
@ -135,26 +187,26 @@ func parsePart(input string) (Part, error) {
}, nil }, nil
} }
func parseRules(inputLines []string) (map[string]func(Part) string, error) { func parseRules(inputLines []string) (map[string]Rule, error) {
rules := make(map[string]func(Part) string, len(inputLines)) rules := make(map[string]Rule, len(inputLines))
for i, rawRule := range inputLines { for i, rawRule := range inputLines {
ruleName, findDest, err := parseRule(rawRule) ruleName, rule, err := parseRule(rawRule)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid rule #%d: %w", i, err) return nil, fmt.Errorf("invalid rule #%d: %w", i, err)
} }
rules[ruleName] = findDest rules[ruleName] = rule
} }
return rules, nil return rules, nil
} }
func parseRule(rawRule string) (string, func(Part) string, error) { func parseRule(rawRule string) (string, Rule, error) {
declarationsPattern := regexp.MustCompile(`^([a-z]+)\{((?:[xmas][<>]\d+:[a-zAR]+,)+)([a-zAR]+)\}$`) declarationsPattern := regexp.MustCompile(`^([a-z]+)\{((?:[xmas][<>]\d+:[a-zAR]+,)+)([a-zAR]+)\}$`)
declarationMatches := declarationsPattern.FindStringSubmatch(rawRule) declarationMatches := declarationsPattern.FindStringSubmatch(rawRule)
if declarationMatches == nil { if declarationMatches == nil {
fmt.Println(rawRule) fmt.Println(rawRule)
return "", nil, errors.New("malformed declarations") return "", Rule{}, errors.New("malformed declarations")
} }
name := declarationMatches[1] name := declarationMatches[1]
@ -163,17 +215,20 @@ func parseRule(rawRule string) (string, func(Part) string, error) {
conditions, err := parseRuleConditions(rawConditions) conditions, err := parseRuleConditions(rawConditions)
if err != nil { if err != nil {
return "", nil, fmt.Errorf("parse conditions: %w", err) return "", Rule{}, fmt.Errorf("parse conditions: %w", err)
} }
destinationFunc := buildRuleFunc(conditions, fallbackDestination) rule := Rule{
Conditions: conditions,
FallbackDestination: fallbackDestination,
}
return name, destinationFunc, nil return name, rule, nil
} }
func buildRuleFunc(conditions []RuleCondition, fallbackDestination string) func(Part) string { func buildRuleFunc(rule Rule) func(Part) string {
baseFunc := func(Part) string { baseFunc := func(Part) string {
return fallbackDestination return rule.FallbackDestination
} }
// We must store all of the destination functions, otherwise we will // We must store all of the destination functions, otherwise we will
@ -183,12 +238,12 @@ func buildRuleFunc(conditions []RuleCondition, fallbackDestination string) func(
return destFuncs[0](part) return destFuncs[0](part)
} }
for i := len(conditions) - 1; i >= 0; i-- { for i := len(rule.Conditions) - 1; i >= 0; i-- {
condition := conditions[i] condition := rule.Conditions[i]
lastFunc := destFuncs[len(destFuncs)-1] lastFunc := destFuncs[len(destFuncs)-1]
ruleDestFunc := func(part Part) string { ruleDestFunc := func(part Part) string {
value := condition.PartRatingFunc(part) value := part.Rating(condition.PartRatingType)
if condition.OperatorFunc(value, condition.Operand) { if condition.Operator.Compare(value, condition.Operand) {
return condition.SuccessDestination return condition.SuccessDestination
} else { } else {
return lastFunc(part) return lastFunc(part)
@ -205,18 +260,6 @@ func buildRuleFunc(conditions []RuleCondition, fallbackDestination string) func(
func parseRuleConditions(rawConditions string) ([]RuleCondition, error) { func parseRuleConditions(rawConditions string) ([]RuleCondition, error) {
conditionPattern := regexp.MustCompile(`^([xmas])([<>])(\d+):([a-zAR]+)$`) conditionPattern := regexp.MustCompile(`^([xmas])([<>])(\d+):([a-zAR]+)$`)
operatorFuncs := map[string]func(int, int) bool{
"<": func(i1, i2 int) bool { return i1 < i2 },
">": func(i1, i2 int) bool { return i1 > i2 },
}
variableFuncs := map[string]func(Part) int{
"x": func(part Part) int { return part.XtremelyCoolRating },
"m": func(part Part) int { return part.MusicalRating },
"a": func(part Part) int { return part.AerodynamicRating },
"s": func(part Part) int { return part.ShinyRating },
}
splitRawConditions := strings.Split(strings.TrimRight(rawConditions, ","), ",") splitRawConditions := strings.Split(strings.TrimRight(rawConditions, ","), ",")
conditions := make([]RuleCondition, len(splitRawConditions)) conditions := make([]RuleCondition, len(splitRawConditions))
for i, rawCondition := range splitRawConditions { for i, rawCondition := range splitRawConditions {
@ -225,8 +268,10 @@ func parseRuleConditions(rawConditions string) ([]RuleCondition, error) {
return nil, fmt.Errorf("malformed condition %q", rawCondition) return nil, fmt.Errorf("malformed condition %q", rawCondition)
} }
variable := conditionMatches[1] // These first two are definitely safe, because the pattern restricts
operator := conditionMatches[2] // these values to single-chars that are available in their types
ratingType := PartRatingType(conditionMatches[1][0])
operator := ComparisonOperator(conditionMatches[2][0])
rawOperand := conditionMatches[3] rawOperand := conditionMatches[3]
destination := conditionMatches[4] destination := conditionMatches[4]
@ -236,19 +281,9 @@ func parseRuleConditions(rawConditions string) ([]RuleCondition, error) {
panic(fmt.Sprintf("could not parse condition: %s", err)) panic(fmt.Sprintf("could not parse condition: %s", err))
} }
operatorFunc, ok := operatorFuncs[operator]
if !ok {
panic(fmt.Sprintf("invalid operator %s", operator))
}
variableFunc, ok := variableFuncs[variable]
if !ok {
panic(fmt.Sprintf("invalid rating variable %s", variable))
}
conditions[i] = RuleCondition{ conditions[i] = RuleCondition{
PartRatingFunc: variableFunc, PartRatingType: ratingType,
OperatorFunc: operatorFunc, Operator: operator,
Operand: operand, Operand: operand,
SuccessDestination: destination, SuccessDestination: destination,
} }