Skip to content

Commit

Permalink
Day 19, Part 1
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeladler committed Dec 22, 2023
1 parent 68d863c commit ee9cd85
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 0 deletions.
Binary file added input/day19.txt
Binary file not shown.
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ days = {
'day16': [ 'src/day16/solve.c' ],
'day17': [ 'src/day17/solve.c' ],
'day18': [ 'src/day18/solve.c' ],
'day19': [ 'src/day19/solve.c' ],
# XXX: marker
}

Expand Down
82 changes: 82 additions & 0 deletions puzzle/day19.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@

--- Day 19: Aplenty ---

The Elves of Gear Island are thankful for your help and send you on your way. They even have a hang glider that someone stole from Desert Island; since you're already going that direction, it would help them a lot if you would use it to get
down there and return it to them.

As you reach the bottom of the relentless avalanche of machine parts, you discover that they're already forming a formidable heap. Don't worry, though - a group of Elves is already here organizing the parts, and they have a system.

To start, each part is rated in each of four categories:

x: Extremely cool looking
m: Musical (it makes a noise when you hit it)
a: Aerodynamic
s: Shiny

Then, each part is sent through a series of workflows that will ultimately accept or reject the part. Each workflow has a name and contains a list of rules; each rule specifies a condition and where to send the part if the condition is
true. The first rule that matches the part being considered is applied immediately, and the part moves on to the destination described by the rule. (The last rule in each workflow has no condition and always applies if reached.)

Consider the workflow ex{x>10:one,m<20:two,a>30:R,A}. This workflow is named ex and contains four rules. If workflow ex were considering a specific part, it would perform the following steps in order:

Rule "x>10:one": If the part's x is more than 10, send the part to the workflow named one.
Rule "m<20:two": Otherwise, if the part's m is less than 20, send the part to the workflow named two.
Rule "a>30:R": Otherwise, if the part's a is more than 30, the part is immediately rejected (R).
Rule "A": Otherwise, because no other rules matched the part, the part is immediately accepted (A).

If a part is sent to another workflow, it immediately switches to the start of that workflow instead and never returns. If a part is accepted (sent to A) or rejected (sent to R), the part immediately stops any further processing.

The system works, but it's not keeping up with the torrent of weird metal shapes. The Elves ask if you can help sort a few parts and give you the list of workflows and some part ratings (your puzzle input). For 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}

The workflows are listed first, followed by a blank line, then the ratings of the parts the Elves would like you to sort. All parts begin in the workflow named in. In this example, the five listed parts go through the following workflows:

{x=787,m=2655,a=1222,s=2876}: in -> qqz -> qs -> lnx -> A
{x=1679,m=44,a=2067,s=496}: in -> px -> rfg -> gd -> R
{x=2036,m=264,a=79,s=2244}: in -> qqz -> hdj -> pv -> A
{x=2461,m=1339,a=466,s=291}: in -> px -> qkq -> crn -> R
{x=2127,m=1623,a=2188,s=1013}: in -> px -> rfg -> A

Ultimately, three parts are accepted. Adding up the x, m, a, and s rating for each of the accepted parts gives 7540 for the part with x=787, 4623 for the part with x=2036, and 6951 for the part with x=2127. Adding all of the ratings for all
of the accepted parts gives the sum total of 19114.

Sort through all of the parts you've been given; what do you get if you add together all of the rating numbers for all of the parts that ultimately get accepted?

Your puzzle answer was 391132.

The first half of this puzzle is complete! It provides one gold star: *

--- Part Two ---

Even with your help, the sorting process still isn't fast enough.

One of the Elves comes up with a new plan: rather than sort parts individually through all of these workflows, maybe you can figure out in advance which combinations of ratings will be accepted or rejected.

Each of the four ratings (x, m, a, s) can have an integer value ranging from a minimum of 1 to a maximum of 4000. Of all possible distinct combinations of ratings, your job is to figure out which ones will be accepted.

In the above example, there are 167409079868000 distinct combinations of ratings that will be accepted.

Consider only your list of workflows; the list of part ratings that the Elves wanted you to sort is no longer relevant. How many distinct combinations of ratings will be accepted by the Elves' workflows?

Answer:

Although it hasn't changed, you can still get your puzzle input.

You can also [Shareon Twitter Mastodon] this puzzle.

199 changes: 199 additions & 0 deletions src/day19/solve.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
* Author: Michael Adler
*
* Copyright: 2023 Michael Adler
*
* SPDX-License-Identifier: Apache-2.0
*/

#include "solve.h"
#include "aoc/all.h"

#include <slice99.h>
#include <xxhash.h>

#define MAX_RULES 32
#define UNDEFINED -1

typedef enum { LT, GT, JUMP } rule_kind_e;

typedef struct {
rule_kind_e kind;
char variable;
int value;
CharSlice99 destination;
} rule_t;

typedef struct {
CharSlice99 name;
rule_t rule[MAX_RULES];
int rule_count;
} workflow_t;

typedef struct {
int x, m, a, s;
} data_t;

#define P
#define T workflow_t
#include <ust.h>

/*
static size_t rule_t_hash(rule_t *item) {
const size_t prime = 31;
size_t hash = 17;
// Combine the hash of each struct member
hash = hash * prime + (size_t)item->kind;
hash = hash * prime + (size_t)item->variable;
hash = hash * prime + (size_t)item->value;
hash = hash * prime + (size_t)XXH3_64bits(item->destination.ptr, item->destination.len);
return hash;
}
static int rule_t_equal(rule_t *lhs, rule_t *rhs) {
return lhs->kind == rhs->kind && lhs->variable == rhs->variable && lhs->value == rhs->value &&
CharSlice99_primitive_eq(lhs->destination, rhs->destination);
}
*/

static size_t workflow_t_hash(workflow_t *wf) {
const size_t prime = 31;
size_t hash = 17;
hash = hash * prime + (size_t)XXH3_64bits(wf->name.ptr, wf->name.len);
return hash;
}

static int workflow_t_equal(workflow_t *lhs, workflow_t *rhs) { return CharSlice99_primitive_eq(lhs->name, rhs->name); }

// apply the rule and return the destination
static inline bool rule_matches(rule_t *self, data_t data) {
if (self->kind == JUMP) return true;
int data_value;
switch (self->variable) {
case 'x': data_value = data.x; break;
case 'm': data_value = data.m; break;
case 'a': data_value = data.a; break;
case 's': data_value = data.s; break;
}
if (self->kind == LT) { return data_value < self->value; }
return data_value > self->value;
}

static inline CharSlice99 workflow_next(workflow_t *wf, data_t data) {
for (int i = 0; i < wf->rule_count; i++) {
if (rule_matches(&wf->rule[i], data)) { return wf->rule[i].destination; }
}
log_error("no rule matches");
abort();
}

void solve(char *buf, size_t buf_size, Solution *result) {
int part1 = 0, part2 = 0;
size_t pos = 0;

_cleanup_(ust_workflow_t_free) ust_workflow_t workflows = ust_workflow_t_init(workflow_t_hash, workflow_t_equal);
ust_workflow_t_reserve(&workflows, 128);

while (1) {
workflow_t wf = {.rule_count = 0};

size_t start = pos;
while (buf[pos] != '{') pos++;
wf.name = CharSlice99_new(&buf[start], pos - start);
pos++;

// next are the rules
while (1) {
char op = buf[pos + 1]; // look ahead
rule_t *rule = &wf.rule[wf.rule_count];
if (op == '<' || op == '>') {
rule->kind = op == '<' ? LT : GT;
rule->variable = buf[pos];
pos += 2;
rule->value = aoc_parse_nonnegative(buf, &pos);
pos++;
size_t start = pos;
while (buf[pos] != ',') pos++;
rule->destination = CharSlice99_new(&buf[start], pos - start);
pos++;
wf.rule_count++;
} else { // this is the last rule, a jump
rule->kind = JUMP;
size_t start = pos;
while (buf[pos] != '}') pos++;
rule->destination = CharSlice99_new(&buf[start], pos - start);
wf.rule_count++;
break;
}
}

log_debug(">> workflow %.*s has %d rules:", wf.name.len, wf.name.ptr, wf.rule_count);
for (int i = 0; i < wf.rule_count; i++) {
rule_t *rule = &wf.rule[i];
if (rule->kind == JUMP) {
log_debug("RULE: %.*s", rule->destination.len, rule->destination.ptr);
} else {
log_debug("RULE: %c%c%d:%.*s", rule->variable, rule->kind == LT ? '<' : '>', rule->value,
rule->destination.len, rule->destination.ptr);
}
}
ust_workflow_t_insert(&workflows, wf);

aoc_parse_seek(buf, &pos, '\n');
pos++;
if (buf[pos] == '\n') break; // workflows finished, inputs are next
}

ust_workflow_t_node *start_node = ust_workflow_t_find(&workflows, (workflow_t){.name = CharSlice99_from_str("in")});
assert(start_node != NULL);

pos++;
while (pos < buf_size) {
if (buf[pos++] == '{') {
data_t data = {.x = UNDEFINED, .m = UNDEFINED, .a = UNDEFINED, .s = UNDEFINED};
while (pos < buf_size) {
char var = buf[pos];
pos += 2;
int value = aoc_parse_nonnegative(buf, &pos);
switch (var) {
case 'x': data.x = value; break;
case 'm': data.m = value; break;
case 'a': data.a = value; break;
case 's': data.s = value; break;
}
if (buf[pos] == '}') break;
pos++;
}
log_debug(">> input: x=%d, m=%d, a=%d, s=%d", data.x, data.m, data.a, data.s);

workflow_t *current = &start_node->key;
while (1) {
CharSlice99 next = workflow_next(current, data);
log_debug("%.*s -> %.*s", current->name.len, current->name.ptr, next.len, next.ptr);
if (CharSlice99_primitive_eq(next, CharSlice99_from_str("A"))) {
part1 += data.x + data.a + data.m + data.s;
log_debug("accepted");
break;
} else if (CharSlice99_primitive_eq(next, CharSlice99_from_str("R"))) {
log_debug("rejected");
break;
}
current = &ust_workflow_t_find(&workflows, (workflow_t){.name = next})->key;
}
}
}

aoc_itoa(part1, result->part1, 10);
aoc_itoa(part2, result->part2, 10);
}

int solve_input(const char *fname, Solution *result) {
char buf[1 << 15];
int n = aoc_io_read_input(fname, buf, sizeof(buf));
if (n <= 0) {
fprintf(stderr, "Failed to read %s\n", fname);
return -1;
}
solve(buf, n, result);
return 0;
}
47 changes: 47 additions & 0 deletions src/day19/solve_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Author: Michael Adler
*
* Copyright: 2023 Michael Adler
*
* SPDX-License-Identifier: Apache-2.0
*/

#define CTEST_MAIN

#include "ctest.h"
#include "solve.h"

CTEST(day19, example) {
char *buf = "px{a<2006:qkq,m>2090:A,rfg}\n\
pv{a>1716:R,A}\n\
lnx{m>1548:A,A}\n\
rfg{s<537:gd,x>2440:R,A}\n\
qs{s>3448:A,lnx}\n\
qkq{x<1416:A,crn}\n\
crn{x>2662:A,R}\n\
in{s<1351:px,qqz}\n\
qqz{s>2770:qs,m<1801:hdj,R}\n\
gd{a>3333:R,R}\n\
hdj{m>838:A,pv}\n\
\n\
{x=787,m=2655,a=1222,s=2876}\n\
{x=1679,m=44,a=2067,s=496}\n\
{x=2036,m=264,a=79,s=2244}\n\
{x=2461,m=1339,a=466,s=291}\n\
{x=2127,m=1623,a=2188,s=1013}\n";
Solution solution;
solve(buf, strlen(buf), &solution);
ASSERT_STR("19114", solution.part1);
ASSERT_STR("0", solution.part2);
}

#ifdef HAVE_INPUTS
CTEST(day19, real) {
Solution solution;
solve_input("input/" DAY ".txt", &solution);
ASSERT_STR("391132", solution.part1);
ASSERT_STR("0", solution.part2);
}
#endif

int main(int argc, const char *argv[]) { return ctest_main(argc, argv); }

0 comments on commit ee9cd85

Please sign in to comment.