From 8f5ca202356b4b4a4550a69b2122238f683a6483 Mon Sep 17 00:00:00 2001 From: Michael Adler Date: Mon, 1 Jan 2024 22:31:22 +0100 Subject: [PATCH] Day 20, Part 1 --- input/day20.txt | Bin 0 -> 814 bytes meson.build | 1 + puzzle/day20.md | 136 +++++++++++++++++++++++++ src/day20/solve.c | 224 +++++++++++++++++++++++++++++++++++++++++ src/day20/solve_test.c | 47 +++++++++ 5 files changed, 408 insertions(+) create mode 100644 input/day20.txt create mode 100644 puzzle/day20.md create mode 100644 src/day20/solve.c create mode 100644 src/day20/solve_test.c diff --git a/input/day20.txt b/input/day20.txt new file mode 100644 index 0000000000000000000000000000000000000000..3af66ed142595a914ddab3889b485af9a537b393 GIT binary patch literal 814 zcmV+}1JV2dM@dveQdv+`0Q%7iimFf_322qSAp)? z>Sd00s#$#o#mLZE)-HwOa4mk?Z_pcEtHzZwO;WLiHZ--vr7I$F^tvAmH^xFTzLe^wX66KwboA#+>4cco|07#F!l|MI#k8c>uBXF4E~` z0yp^3BM*DCm(n^goOEbeuG}oto3!1X*uN`l+C4|wNn_~7bbkt!(QlTN7V#B^@JynT#a%D7N>5-J5>o`GJOJ_a=BgcV<-I(Np zysUQCLD-Yrc9dxyJ0c+yg;n2L`pIf5S5>iZ=hr_h?pG=P|BH_5u!>t{P2EZj>V_Is zA@Y?8rO?`fZ&%uAH?by`-^nv#Mpqr8g%~ZSW1sJm@Q~@^_;Cs=oJMHFMhbbfZQ+9F zH4|po1D6i6VKM?KNe1xvJ@)R5^v@Wbt|kcvy`MgTv_IA)x#Uv-{J&qpSLqsNqNO$K zj7H$le}hAaB4&CspBhp+L#b)FBI7l(djIuD>#HYGzfh7q`ILYNG|lSw+>K$`T@%3viFAyg>6RE(H_b0}(o(YO zH@1u*;xf9W4uSB8Kx@y)4*wnx2Wm)#)dNttNFkpp3;{(4-GO6cjmMhnw04nC@!J-e z_^ddfhm}V6AwqY?JJmIRD)QBeMVjnIw8m5Tnx2$=Hd>6J3C=9q42jrk>w=9VeeZuz zVDE<-f1w!v1FCa#4WBa;{FtwvYATti1U}CSvzp#O$7fQ@k?~k6kA(*oHZgdkRa;i? sclW*-#bW&OU+34^tBgf>j}FaB8-rn4*&oF literal 0 HcmV?d00001 diff --git a/meson.build b/meson.build index d341c73..8fbdbee 100644 --- a/meson.build +++ b/meson.build @@ -51,6 +51,7 @@ days = { 'day17': [ 'src/day17/solve.c' ], 'day18': [ 'src/day18/solve.c' ], 'day19': [ 'src/day19/solve.c' ], + 'day20': [ 'src/day20/solve.c' ], # XXX: marker } diff --git a/puzzle/day20.md b/puzzle/day20.md new file mode 100644 index 0000000..3e72f93 --- /dev/null +++ b/puzzle/day20.md @@ -0,0 +1,136 @@ + +--- Day 20: Pulse Propagation --- + +With your help, the Elves manage to find the right parts and fix all of the machines. Now, they just need to send the command to boot up the machines and get the sand flowing again. + +The machines are far apart and wired together with long cables. The cables don't connect to the machines directly, but rather to communication modules attached to the machines that perform various initialization tasks and also act as communication relays. + +Modules communicate using pulses. Each pulse is either a high pulse or a low pulse. When a module sends a pulse, it sends that type of pulse to each module in its list of destination modules. + +There are several different types of modules: + +Flip-flop modules (prefix %) are either on or off; they are initially off. If a flip-flop module receives a high pulse, it is ignored and nothing happens. However, if a flip-flop module receives a low pulse, it flips between on and off. If it was off, it turns on and sends +a high pulse. If it was on, it turns off and sends a low pulse. + +Conjunction modules (prefix &) remember the type of the most recent pulse received from each of their connected input modules; they initially default to remembering a low pulse for each input. When a pulse is received, the conjunction module first updates its memory for +that input. Then, if it remembers high pulses for all inputs, it sends a low pulse; otherwise, it sends a high pulse. + +There is a single broadcast module (named broadcaster). When it receives a pulse, it sends the same pulse to all of its destination modules. + +Here at Desert Machine Headquarters, there is a module with a single button on it called, aptly, the button module. When you push the button, a single low pulse is sent directly to the broadcaster module. + +After pushing the button, you must wait until all pulses have been delivered and fully handled before pushing it again. Never push the button if modules are still processing pulses. + +Pulses are always processed in the order they are sent. So, if a pulse is sent to modules a, b, and c, and then module a processes its pulse and sends more pulses, the pulses sent to modules b and c would have to be handled first. + +The module configuration (your puzzle input) lists each module. The name of the module is preceded by a symbol identifying its type, if any. The name is then followed by an arrow and a list of its destination modules. For example: + +broadcaster -> a, b, c +%a -> b +%b -> c +%c -> inv +&inv -> a + +In this module configuration, the broadcaster has three destination modules named a, b, and c. Each of these modules is a flip-flop module (as indicated by the % prefix). a outputs to b which outputs to c which outputs to another module named inv. inv is a conjunction +module (as indicated by the & prefix) which, because it has only one input, acts like an inverter (it sends the opposite of the pulse type it receives); it outputs to a. + +By pushing the button once, the following pulses are sent: + +button -low-> broadcaster +broadcaster -low-> a +broadcaster -low-> b +broadcaster -low-> c +a -high-> b +b -high-> c +c -high-> inv +inv -low-> a +a -low-> b +b -low-> c +c -low-> inv +inv -high-> a + +After this sequence, the flip-flop modules all end up off, so pushing the button again repeats the same sequence. + +Here's a more interesting example: + +broadcaster -> a +%a -> inv, con +&inv -> b +%b -> con +&con -> output + +This module configuration includes the broadcaster, two flip-flops (named a and b), a single-input conjunction module (inv), a multi-input conjunction module (con), and an untyped module named output (for testing purposes). The multi-input conjunction module con watches the +two flip-flop modules and, if they're both on, sends a low pulse to the output module. + +Here's what happens if you push the button once: + +button -low-> broadcaster +broadcaster -low-> a +a -high-> inv +a -high-> con +inv -low-> b +con -high-> output +b -high-> con +con -low-> output + +Both flip-flops turn on and a low pulse is sent to output! However, now that both flip-flops are on and con remembers a high pulse from each of its two inputs, pushing the button a second time does something different: + +button -low-> broadcaster +broadcaster -low-> a +a -low-> inv +a -low-> con +inv -high-> b +con -high-> output + +Flip-flop a turns off! Now, con remembers a low pulse from module a, and so it sends only a high pulse to output. + +Push the button a third time: + +button -low-> broadcaster +broadcaster -low-> a +a -high-> inv +a -high-> con +inv -low-> b +con -low-> output +b -low-> con +con -high-> output + +This time, flip-flop a turns on, then flip-flop b turns off. However, before b can turn off, the pulse sent to con is handled first, so it briefly remembers all high pulses for its inputs and sends a low pulse to output. After that, flip-flop b turns off, which causes con +to update its state and send a high pulse to output. + +Finally, with a on and b off, push the button a fourth time: + +button -low-> broadcaster +broadcaster -low-> a +a -low-> inv +a -low-> con +inv -high-> b +con -high-> output + +This completes the cycle: a turns off, causing con to remember only low pulses and restoring all modules to their original states. + +To get the cables warmed up, the Elves have pushed the button 1000 times. How many pulses got sent as a result (including the pulses sent by the button itself)? + +In the first example, the same thing happens every time the button is pushed: 8 low pulses and 4 high pulses are sent. So, after pushing the button 1000 times, 8000 low pulses and 4000 high pulses are sent. Multiplying these together gives 32000000. + +In the second example, after pushing the button 1000 times, 4250 low pulses and 2750 high pulses are sent. Multiplying these together gives 11687500. + +Consult your module configuration; determine the number of low pulses and high pulses that would be sent after pushing the button 1000 times, waiting for all pulses to be fully handled after each push of the button. What do you get if you multiply the total number of low +pulses sent by the total number of high pulses sent? + +Your puzzle answer was 929810733. + +The first half of this puzzle is complete! It provides one gold star: * + +--- Part Two --- + +The final machine responsible for moving the sand down to Island Island has a module attached named rx. The machine turns on when a single low pulse is sent to rx. + +Reset all modules to their default states. Waiting for all pulses to be fully handled after each button press, what is the fewest number of button presses required to deliver a single low pulse to the module named rx? + +Answer: + +Although it hasn't changed, you can still get your puzzle input. + +You can also [Shareon Twitter Mastodon] this puzzle. + diff --git a/src/day20/solve.c b/src/day20/solve.c new file mode 100644 index 0000000..ce70620 --- /dev/null +++ b/src/day20/solve.c @@ -0,0 +1,224 @@ +/* + * Author: Michael Adler + * + * Copyright: 2023 Michael Adler + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "solve.h" +#include "aoc/all.h" +#include "slice99.h" + +#include + +#define MAX_NAME_LEN 4 +#define MAX_OUTPUTS 8 +#define MAX_CONJUNCTION_ITEMS 32 + +typedef enum { BROADCAST, FLIP_FLOP, CONJUNCTION } kind_e; + +typedef enum { PULSE_LOW, PULSE_HIGH } pulse_e; + +typedef struct { + CharSlice99 name; + pulse_e pulse; +} conjunction_item_t; + +typedef struct { + conjunction_item_t items[MAX_CONJUNCTION_ITEMS]; + int len; +} conjunction_item_list; + +typedef union { + bool on; + conjunction_item_list list; +} module_state_t; + +typedef struct { + CharSlice99 name; + kind_e kind; + module_state_t state; + CharSlice99 output[MAX_OUTPUTS]; + int output_count; +} module_t; + +#define P +#define T module_t +#include + +typedef struct { + CharSlice99 source, destination; + pulse_e input; +} state_t; + +#define P +#define T state_t +#include + +static size_t module_t_hash(module_t *item) { return XXH3_64bits(item->name.ptr, item->name.len); } +static int module_t_equal(module_t *lhs, module_t *rhs) { return CharSlice99_primitive_eq(lhs->name, rhs->name); } + +void solve(char *buf, size_t buf_size, Solution *result) { + i64 part1 = 0, part2 = 0; + + _cleanup_(ust_module_t_free) ust_module_t modules = ust_module_t_init(module_t_hash, module_t_equal); + + size_t pos = 0; + while (pos < buf_size) { + char c = buf[pos]; + + module_t m = {.kind = BROADCAST, .output_count = 0}; + if (c == '%') { + m.kind = FLIP_FLOP; + m.state.on = false; + pos++; + } else if (c == '&') { + m.kind = CONJUNCTION; + pos++; + } + int len = 0; + while (buf[pos + len] != ' ') { len++; } + m.name = CharSlice99_new(&buf[pos], len); + pos += len; + log_debug(">> module name: %.*s", m.name.len, m.name.ptr); + + aoc_parse_seek(buf, &pos, '>'); + pos += 2; + + while (1) { + len = 0; + while (buf[pos + len] != ',' && buf[pos + len] != '\n') { len++; } + m.output[m.output_count++] = CharSlice99_new(&buf[pos], len); + pos += len; + if (buf[pos] == '\n') { break; } + pos += 2; // skip comma + } + for (int i = 0; i < m.output_count; i++) { log_debug("\toutput: %.*s", m.output[i].len, m.output[i].ptr); } + + aoc_parse_seek(buf, &pos, '\n'); + pos++; + + ust_module_t_insert(&modules, m); + } + + // initialize conjunction items with their incoming signals to low + foreach (ust_module_t, &modules, it) { + module_t *m = &it.node->key; + CharSlice99 source = m->name; + for (int i = 0; i < m->output_count; i++) { + CharSlice99 destination = m->output[i]; + ust_module_t_node *node = ust_module_t_find(&modules, (module_t){.name = destination}); + if (node == NULL) continue; + module_t *dest_module = &node->key; + if (dest_module->kind == CONJUNCTION) { + bool found = false; + for (int j = 0; j < dest_module->state.list.len; j++) { + if (CharSlice99_primitive_eq(dest_module->state.list.items[j].name, source)) { + found = true; + dest_module->state.list.items[j].pulse = PULSE_LOW; + break; + } + } + if (!found) { + log_debug("%.*s is an input to %.*s", source.len, source.ptr, destination.len, destination.ptr); + int len = dest_module->state.list.len; + dest_module->state.list.items[len] = (conjunction_item_t){.name = source, .pulse = PULSE_LOW}; + dest_module->state.list.len = len + 1; + } + } + } + } + + _cleanup_(que_state_t_free) que_state_t queue = que_state_t_init(); + i64 high_count = 0, low_count = 0; + for (int round = 1; round <= 1000; round++) { + que_state_t_push(&queue, (state_t){.source = CharSlice99_from_str("button"), + .destination = CharSlice99_from_str("broadcaster"), + .input = PULSE_LOW}); + while (!que_state_t_empty(&queue)) { + state_t *current = que_state_t_front(&queue); + log_debug("%.*s -%c-> %.*s", current->source.len, current->source.ptr, + current->input == PULSE_HIGH ? 'H' : 'L', current->destination.len, current->destination.ptr); + if (current->input == PULSE_HIGH) { + high_count++; + } else { + low_count++; + } + + ust_module_t_node *node = ust_module_t_find(&modules, (module_t){.name = current->destination}); + if (node) { + module_t *m = &node->key; + switch (m->kind) { + case BROADCAST: + for (int i = 0; i < m->output_count; i++) { + state_t item = { + .source = current->destination, + .destination = m->output[i], + .input = current->input, + }; + que_state_t_push(&queue, item); + } + break; + case FLIP_FLOP: + if (current->input == PULSE_LOW) { + m->state.on = !m->state.on; + for (int i = 0; i < m->output_count; i++) { + state_t item = { + .source = current->destination, + .destination = m->output[i], + .input = m->state.on ? PULSE_HIGH : PULSE_LOW, + }; + que_state_t_push(&queue, item); + } + } + break; + case CONJUNCTION: + // the conjunction module first updates its memory for that input + for (int i = 0; i < m->state.list.len; i++) { + conjunction_item_t *item = &m->state.list.items[i]; + if (CharSlice99_primitive_eq(item->name, current->source)) { + item->pulse = current->input; + break; + } + } + // if high pulses for all inputs, it sends a low pulse; otherwise, it sends a high pulse + pulse_e pulse = PULSE_LOW; + for (int i = 0; i < m->state.list.len; i++) { + conjunction_item_t *item = &m->state.list.items[i]; + if (item->pulse == PULSE_LOW) { + pulse = PULSE_HIGH; + break; + } + } + for (int i = 0; i < m->output_count; i++) { + state_t item = { + .source = current->destination, + .destination = m->output[i], + .input = pulse, + }; + que_state_t_push(&queue, item); + } + break; + } + } + que_state_t_pop(&queue); + } + log_debug("round %d: low_count: %ld, high_count: %ld", round, low_count, high_count); + } + part1 += high_count * low_count; + + snprintf(result->part1, sizeof(result->part1), "%ld", part1); + snprintf(result->part2, sizeof(result->part2), "%ld", part2); +} + +int solve_input(const char *fname, Solution *result) { + char buf[1 << 14]; + 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; +} diff --git a/src/day20/solve_test.c b/src/day20/solve_test.c new file mode 100644 index 0000000..46a3b81 --- /dev/null +++ b/src/day20/solve_test.c @@ -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(day20, example1) { + char *buf = "broadcaster -> a, b, c\n\ +%a -> b\n\ +%b -> c\n\ +%c -> inv\n\ +&inv -> a\n"; + Solution solution; + solve(buf, strlen(buf), &solution); + ASSERT_STR("32000000", solution.part1); + // ASSERT_STR("0", solution.part2); +} + +CTEST(day20, example2) { + char *buf = "broadcaster -> a\n\ +%a -> inv, con\n\ +&inv -> b\n\ +%b -> con\n\ +&con -> output\n"; + Solution solution; + solve(buf, strlen(buf), &solution); + ASSERT_STR("11687500", solution.part1); + // ASSERT_STR("0", solution.part2); +} + +#ifdef HAVE_INPUTS +CTEST(day20, real) { + Solution solution; + solve_input("input/" DAY ".txt", &solution); + ASSERT_STR("929810733", solution.part1); + // ASSERT_STR("0", solution.part2); +} +#endif + +int main(int argc, const char *argv[]) { return ctest_main(argc, argv); }