day 21
This commit is contained in:
parent
20a5361a64
commit
fff5b9e4a5
27
aoc/arena.c
27
aoc/arena.c
|
|
@ -6,7 +6,7 @@ arena_init(void *mem, ptrdiff_t size) {
|
|||
Arena *arena = mem;
|
||||
arena->mem = arena + 1;
|
||||
arena->top = arena->mem;
|
||||
arena->lim = arena->mem + size;
|
||||
arena->lim = (char *) mem + size;
|
||||
return arena;
|
||||
}
|
||||
|
||||
|
|
@ -15,9 +15,32 @@ align_forward(void *ptr, ptrdiff_t align) {
|
|||
return (void *)(((uintptr_t)ptr + align - 1) & ~(align - 1));
|
||||
}
|
||||
|
||||
static void
|
||||
pretty_print_size(char buf[64], ptrdiff_t size) {
|
||||
if (size < 1024) {
|
||||
sprintf(buf, "%ld bytes", size);
|
||||
}
|
||||
else if (size < 1024 * 1024) {
|
||||
sprintf(buf, "%ld KB", size / 1024);
|
||||
}
|
||||
else if (size < 1024 * 1024 * 1024) {
|
||||
sprintf(buf, "%ld MB", size / (1024 * 1024));
|
||||
}
|
||||
else {
|
||||
sprintf(buf, "%ld GB", size / (1024 * 1024 * 1024));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
arena_oom(Arena *arena, ptrdiff_t size, ptrdiff_t align) {
|
||||
loge("Out of memory in arena %p: requested size %ld, align %ld", arena, size, align);
|
||||
char used[64];
|
||||
pretty_print_size(used, (uintptr_t) arena->top - (uintptr_t) arena->mem);
|
||||
char available[64];
|
||||
pretty_print_size(available, (uintptr_t) arena->lim - (uintptr_t) arena->top);
|
||||
char requested[64];
|
||||
pretty_print_size(requested, size);
|
||||
loge("Out of memory in arena %p: %s used, %s available, %s requested, %ld alignment",
|
||||
arena, used, available, requested, align);
|
||||
BP();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
935A
|
||||
319A
|
||||
480A
|
||||
789A
|
||||
176A
|
||||
|
|
@ -0,0 +1,514 @@
|
|||
#include "aoc.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct Vec2i {
|
||||
i32 x, y;
|
||||
} Vec2i;
|
||||
|
||||
typedef enum {
|
||||
TABLE_NUMPAD = 0,
|
||||
TABLE_DIRECTIONS = 1,
|
||||
} Table;
|
||||
|
||||
typedef enum {
|
||||
D_LEFT,
|
||||
D_UP,
|
||||
D_RIGHT,
|
||||
D_DOWN,
|
||||
D_A,
|
||||
|
||||
NUM_DIRECTION_KEYS,
|
||||
} DirectionKey;
|
||||
|
||||
typedef enum {
|
||||
N_0,
|
||||
N_1,
|
||||
N_2,
|
||||
N_3,
|
||||
N_4,
|
||||
N_5,
|
||||
N_6,
|
||||
N_7,
|
||||
N_8,
|
||||
N_9,
|
||||
N_A,
|
||||
|
||||
NUM_NUMPAD_KEYS,
|
||||
} NumpadKey;
|
||||
|
||||
static const Vec2i TABLES[2][128] = {
|
||||
[TABLE_NUMPAD] = {
|
||||
[N_0] = { 1, 0 },
|
||||
[N_A] = { 2, 0 },
|
||||
[N_1] = { 0, 1 },
|
||||
[N_2] = { 1, 1 },
|
||||
[N_3] = { 2, 1 },
|
||||
[N_4] = { 0, 2 },
|
||||
[N_5] = { 1, 2 },
|
||||
[N_6] = { 2, 2 },
|
||||
[N_7] = { 0, 3 },
|
||||
[N_8] = { 1, 3 },
|
||||
[N_9] = { 2, 3 },
|
||||
},
|
||||
[TABLE_DIRECTIONS] = {
|
||||
[D_A] = { 2, 1 },
|
||||
[D_UP] = { 1, 1 },
|
||||
[D_LEFT] = { 0, 0 },
|
||||
[D_DOWN] = { 1, 0 },
|
||||
[D_RIGHT] = { 2, 0 },
|
||||
},
|
||||
};
|
||||
|
||||
typedef DYNAMIC_ARRAY(str) Strings;
|
||||
|
||||
static Strings NUMPAD_PATHS[NUM_NUMPAD_KEYS][NUM_NUMPAD_KEYS]; // PATHS_TABLE[a][b] gives a list of strings that each represent a path from a to b
|
||||
static Strings DIRECTION_PATHS[NUM_DIRECTION_KEYS][NUM_DIRECTION_KEYS];
|
||||
|
||||
static DirectionKey
|
||||
parse_direction_key(u8 ch) {
|
||||
switch (ch) {
|
||||
case '<': return D_LEFT;
|
||||
case '^': return D_UP;
|
||||
case '>': return D_RIGHT;
|
||||
case 'v': return D_DOWN;
|
||||
case 'A': return D_A;
|
||||
default: ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
static NumpadKey
|
||||
parse_numpad_key(u8 ch) {
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
return N_0 + (ch - '0');
|
||||
}
|
||||
else if (ch == 'A') {
|
||||
return N_A;
|
||||
}
|
||||
else {
|
||||
NOT_REACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
Arena *arena;
|
||||
isize len, cap;
|
||||
u8 *data;
|
||||
} StrBuf;
|
||||
|
||||
static inline bool
|
||||
is_valid_position(Table table, i32 x, i32 y) {
|
||||
if (table == TABLE_NUMPAD) {
|
||||
if (x < 0 || x > 2) return false;
|
||||
if (y < 0 || y > 3) return false;
|
||||
if (x == 0 && y == 0) return false;
|
||||
return true;
|
||||
}
|
||||
else if (table == TABLE_DIRECTIONS) {
|
||||
if (x < 0 || x > 2) return false;
|
||||
if (y < 0 || y > 1) return false;
|
||||
if (x == 0 && y == 1) return false;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
NOT_REACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
static str
|
||||
sb_copy_str(StrBuf *buf, Arena *arena) {
|
||||
str result = {0};
|
||||
if (buf->len > 0) {
|
||||
result.data = ARENA_ALLOC_ARRAY(arena, u8, buf->len);
|
||||
result.len = buf->len;
|
||||
memcpy(result.data, buf->data, buf->len);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
sb_push_str(StrBuf *sb, str s) {
|
||||
ASSERT(sb->arena);
|
||||
if (s.len > 0) {
|
||||
if (sb->len + s.len > sb->cap) {
|
||||
isize new_cap = sb->cap + MIN(64, s.len);
|
||||
u8 *new_data = ARENA_ALLOC_ARRAY(sb->arena, u8, new_cap);
|
||||
ASSERT(new_data);
|
||||
if (sb->len > 0) {
|
||||
memcpy(new_data, sb->data, sb->len);
|
||||
}
|
||||
sb->cap = new_cap;
|
||||
sb->data = new_data;
|
||||
}
|
||||
ASSERT(sb->len + s.len <= sb->cap);
|
||||
memcpy(sb->data + sb->len, s.data, s.len);
|
||||
sb->len += s.len;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
enumerate_paths(Arena *arena, Table table, i32 x, i32 y, i32 dx, i32 dy, StrBuf *acc, Strings *strings) {
|
||||
if (!is_valid_position(table, x, y)) return;
|
||||
|
||||
if (dx == 0 && dy == 0) {
|
||||
*push(strings, arena) = sb_copy_str(acc, arena);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dx > 0) {
|
||||
sb_push_str(acc, STR(">"));
|
||||
enumerate_paths(arena, table, x + 1, y, dx - 1, dy, acc, strings);
|
||||
acc->len--;
|
||||
}
|
||||
else if (dx < 0) {
|
||||
sb_push_str(acc, STR("<"));
|
||||
enumerate_paths(arena, table, x - 1, y, dx + 1, dy, acc, strings);
|
||||
acc->len--;
|
||||
}
|
||||
|
||||
if (dy > 0) {
|
||||
sb_push_str(acc, STR("^"));
|
||||
enumerate_paths(arena, table, x, y + 1, dx, dy - 1, acc, strings);
|
||||
acc->len--;
|
||||
}
|
||||
else if (dy < 0) {
|
||||
sb_push_str(acc, STR("v"));
|
||||
enumerate_paths(arena, table, x, y - 1, dx, dy + 1, acc, strings);
|
||||
acc->len--;
|
||||
}
|
||||
}
|
||||
|
||||
static Strings
|
||||
enumerate_paths_by_key(Arena *arena, Table table, u8 start_key, u8 end_key) {
|
||||
i32 start_x = TABLES[table][start_key].x;
|
||||
i32 start_y = TABLES[table][start_key].y;
|
||||
i32 dx = TABLES[table][end_key].x - start_x;
|
||||
i32 dy = TABLES[table][end_key].y - start_y;
|
||||
StrBuf acc = { .arena = arena };
|
||||
Strings strings = {0};
|
||||
enumerate_paths(arena, table, start_x, start_y, dx, dy, &acc, &strings);
|
||||
return strings;
|
||||
}
|
||||
|
||||
str sb_str(StrBuf *buf) {
|
||||
return (str) { buf->data, buf->len };
|
||||
}
|
||||
|
||||
static void
|
||||
build_sequences(Arena *arena, Table table, str code, i32 index,
|
||||
u8 prev_key, StrBuf *current_path, Strings *result)
|
||||
{
|
||||
if (!current_path) {
|
||||
current_path = ARENA_ALLOC(arena, StrBuf);
|
||||
current_path->arena = arena;
|
||||
}
|
||||
if (index == code.len) {
|
||||
*push(result, arena) = sb_copy_str(current_path, arena);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
u8 key;
|
||||
Strings *paths = NULL;
|
||||
if (table == TABLE_NUMPAD) {
|
||||
key = parse_numpad_key(code.data[index]);
|
||||
ASSERT(prev_key < NUM_NUMPAD_KEYS);
|
||||
ASSERT(key < NUM_NUMPAD_KEYS);
|
||||
paths = &NUMPAD_PATHS[prev_key][key];
|
||||
}
|
||||
else if (table == TABLE_DIRECTIONS) {
|
||||
key = parse_direction_key(code.data[index]);
|
||||
ASSERT(prev_key < NUM_DIRECTION_KEYS);
|
||||
ASSERT(key < NUM_DIRECTION_KEYS);
|
||||
paths = &DIRECTION_PATHS[prev_key][key];
|
||||
}
|
||||
else {
|
||||
NOT_REACHABLE();
|
||||
}
|
||||
ASSERT(paths);
|
||||
for (isize i = 0; i < paths->len; i++) {
|
||||
sb_push_str(current_path, paths->data[i]);
|
||||
sb_push_str(current_path, STR("A"));
|
||||
build_sequences(arena, table, code, index + 1, key, current_path, result);
|
||||
current_path->len -= paths->data[i].len + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
init_tables(Arena *arena) {
|
||||
for (isize i = 0; i < NUM_NUMPAD_KEYS; i++) {
|
||||
for (isize j = 0; j < NUM_NUMPAD_KEYS; j++) {
|
||||
NUMPAD_PATHS[i][j] = enumerate_paths_by_key(arena, TABLE_NUMPAD, i, j);
|
||||
}
|
||||
}
|
||||
|
||||
for (isize i = 0; i < NUM_DIRECTION_KEYS; i++) {
|
||||
for (isize j = 0; j < NUM_DIRECTION_KEYS; j++) {
|
||||
DIRECTION_PATHS[i][j] = enumerate_paths_by_key(arena, TABLE_DIRECTIONS, i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct CacheEntry {
|
||||
struct CacheEntry *next;
|
||||
u64 hash;
|
||||
str code;
|
||||
i32 depth;
|
||||
i64 value;
|
||||
} CacheEntry;
|
||||
|
||||
#define CACHE_EXP 16
|
||||
#define CACHE_SIZE (1ull << CACHE_EXP)
|
||||
#define CACHE_MASK (CACHE_SIZE - 1)
|
||||
|
||||
typedef struct Cache {
|
||||
Arena *arena;
|
||||
|
||||
CacheEntry *entries[CACHE_SIZE];
|
||||
isize count;
|
||||
} Cache;
|
||||
|
||||
static u64
|
||||
hash_pair(str code, i32 depth) {
|
||||
// FNV-1a
|
||||
u64 hash = 14695981039346656037ull;
|
||||
for (isize i = 0; i < code.len; i++) {
|
||||
hash ^= code.data[i];
|
||||
hash *= 1099511628211ull;
|
||||
}
|
||||
hash ^= depth;
|
||||
hash *= 1099511628211ull;
|
||||
return hash;
|
||||
}
|
||||
|
||||
static void
|
||||
cache_init(Cache *cache, Arena *arena) {
|
||||
cache->arena = arena;
|
||||
memset(cache->entries, 0, sizeof(cache->entries));
|
||||
}
|
||||
|
||||
static CacheEntry *
|
||||
cache_get(Cache *cache, str code, i32 depth) {
|
||||
u64 hash = hash_pair(code, depth);
|
||||
u64 index = hash & CACHE_MASK;
|
||||
for (CacheEntry *e = cache->entries[index]; e; e = e->next) {
|
||||
if (e->hash == hash && e->depth == depth && str_eq(e->code, code)) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static str
|
||||
copy_str(Arena *destination, str s) {
|
||||
str result = {0};
|
||||
if (s.len > 0) {
|
||||
result.data = ARENA_ALLOC_ARRAY(destination, u8, s.len);
|
||||
result.len = s.len;
|
||||
memcpy(result.data, s.data, s.len);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
cache_print_stats(Cache *cache) {
|
||||
isize total_size = 0;
|
||||
for (isize i = 0; i < CACHE_SIZE; i++) {
|
||||
for (CacheEntry *e = cache->entries[i]; e; e = e->next) {
|
||||
printf("Code: " STR_FMT " Depth: %d Value: %ld\n", STR_ARG(e->code), e->depth, e->value);
|
||||
total_size += e->code.len + sizeof(CacheEntry);
|
||||
}
|
||||
}
|
||||
printf("Total size occupied by cache: %ld\n", total_size);
|
||||
}
|
||||
|
||||
static void
|
||||
cache_put(Cache *cache, str code, i32 depth, i64 value) {
|
||||
CacheEntry *entry = cache_get(cache, code, depth);
|
||||
if (entry) {
|
||||
entry->value = value;
|
||||
}
|
||||
else {
|
||||
u64 hash = hash_pair(code, depth);
|
||||
u64 index = hash & CACHE_MASK;
|
||||
CacheEntry *entry = ARENA_ALLOC(cache->arena, CacheEntry);
|
||||
entry->hash = hash;
|
||||
entry->code = copy_str(cache->arena, code);
|
||||
entry->depth = depth;
|
||||
entry->value = value;
|
||||
entry->next = cache->entries[index];
|
||||
cache->entries[index] = entry;
|
||||
cache->count++;
|
||||
}
|
||||
}
|
||||
|
||||
static i64
|
||||
find_shortest_dpad(Arena temp, str code, i32 depth, Cache *cache) {
|
||||
ASSERT(code.len > 0);
|
||||
CacheEntry *cache_entry = NULL;
|
||||
i64 result = 0;
|
||||
if (depth == 0) {
|
||||
ASSERT(code.len > 0);
|
||||
result = code.len;
|
||||
}
|
||||
else if ((cache_entry = cache_get(cache, code, depth))) {
|
||||
ASSERT(cache_entry->value > 0);
|
||||
result = cache_entry->value;
|
||||
}
|
||||
else {
|
||||
str iter = code;
|
||||
str sub;
|
||||
while (iter.len > 0) {
|
||||
{
|
||||
str_find_result result = str_find_left(iter, STR("A"));
|
||||
sub = str_sub(iter, 0, result.found ? result.offset + 1 : iter.len);
|
||||
}
|
||||
iter = str_sub(iter, sub.len, iter.len);
|
||||
ASSERT(sub.data[sub.len - 1] == 'A');
|
||||
Strings sequences = {0};
|
||||
build_sequences(&temp, TABLE_DIRECTIONS, sub, 0, D_A, NULL, &sequences);
|
||||
i64 shortest = INT64_MAX;
|
||||
str shortest_str = STR("");
|
||||
for (isize i = 0; i < sequences.len; i++) {
|
||||
ASSERT(sequences.data[i].data);
|
||||
i64 candidate = find_shortest_dpad(temp, sequences.data[i], depth - 1, cache);
|
||||
ASSERT(candidate > 0);
|
||||
shortest = MIN(shortest, candidate);
|
||||
}
|
||||
ASSERT(shortest > 0);
|
||||
result += shortest;
|
||||
}
|
||||
cache_put(cache, code, depth, result);
|
||||
}
|
||||
ASSERT(result > 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
static i64
|
||||
find_shortest(Arena temp, str code, i32 depth, Cache *cache) {
|
||||
Strings sequences = {0};
|
||||
build_sequences(&temp, TABLE_NUMPAD, code, 0, N_A, NULL, &sequences);
|
||||
i64 shortest = INT64_MAX;
|
||||
for (isize i = 0; i < sequences.len; i++) {
|
||||
i64 candidate = find_shortest_dpad(temp, sequences.data[i], depth - 1, cache);
|
||||
shortest = MIN(shortest, candidate);
|
||||
}
|
||||
return shortest;
|
||||
}
|
||||
|
||||
static i64
|
||||
parse_numeric_prefix(Arena *arena, str s) {
|
||||
isize end = 0;
|
||||
while (end < s.len && s.data[end] >= '0' && s.data[end] <= '9') {
|
||||
end++;
|
||||
}
|
||||
str prefix = { s.data, end };
|
||||
return parse_i64(prefix, *arena);
|
||||
}
|
||||
|
||||
static u8 NUMPAD[4][3] = {
|
||||
{ '\0', '0', 'A' },
|
||||
{ '1', '2', '3' },
|
||||
{ '4', '5', '6' },
|
||||
{ '7', '8', '9' },
|
||||
};
|
||||
|
||||
static u8 DPAD[2][3] = {
|
||||
{ '<', 'v', '>' },
|
||||
{ '\0', '^', 'A' },
|
||||
};
|
||||
|
||||
static str
|
||||
test(Arena *perm, Arena temp, str input, i32 depth) {
|
||||
if (depth == 0) {
|
||||
str result = copy_str(perm, input);
|
||||
return result;
|
||||
}
|
||||
i32 x, y;
|
||||
if (depth == 1) {
|
||||
x = 2;
|
||||
y = 0;
|
||||
}
|
||||
else {
|
||||
x = 2;
|
||||
y = 1;
|
||||
}
|
||||
StrBuf output = { .arena = &temp };
|
||||
for (isize i = 0; i < input.len; i++) {
|
||||
u8 ch = input.data[i];
|
||||
switch (ch) {
|
||||
case '>' : x++; break;
|
||||
case '<' : x--; break;
|
||||
case '^' : y++; break;
|
||||
case 'v' : y--; break;
|
||||
case 'A':
|
||||
{
|
||||
u8 symbol;
|
||||
if (depth == 1) {
|
||||
ASSERT(x >= 0);
|
||||
ASSERT(x < 3);
|
||||
ASSERT(y >= 0);
|
||||
ASSERT(y < 4);
|
||||
ASSERT(!(x == 0 && y == 0));
|
||||
symbol = NUMPAD[y][x];
|
||||
}
|
||||
else {
|
||||
ASSERT(x >= 0);
|
||||
ASSERT(x < 3);
|
||||
ASSERT(y >= 0);
|
||||
ASSERT(y < 2);
|
||||
ASSERT(!(x == 0 && y == 1));
|
||||
symbol = DPAD[y][x];
|
||||
}
|
||||
str s = { &symbol, 1 };
|
||||
sb_push_str(&output, s);
|
||||
} break;
|
||||
default: NOT_REACHABLE();
|
||||
}
|
||||
if (depth == 1) {
|
||||
ASSERT(x >= 0);
|
||||
ASSERT(x < 3);
|
||||
ASSERT(y >= 0);
|
||||
ASSERT(y < 4);
|
||||
ASSERT(!(x == 0 && y == 0));
|
||||
}
|
||||
else {
|
||||
ASSERT(x >= 0);
|
||||
ASSERT(x < 3);
|
||||
ASSERT(y >= 0);
|
||||
ASSERT(y < 2);
|
||||
ASSERT(!(x == 0 && y == 1));
|
||||
}
|
||||
}
|
||||
return test(perm, temp, sb_str(&output), depth - 1);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
Arena *perm = make_arena(Megabytes(2));
|
||||
Arena *temp = make_arena(Megabytes(1));
|
||||
Tokens lines = read_lines(perm, argv[1]);
|
||||
|
||||
ASSERT(str_eq(test(perm, *temp, STR("<A^A>^^AvvvA"), 1), STR("029A")));
|
||||
ASSERT(str_eq(test(perm, *temp, STR("v<<A>>^A<A>AvA<^AA>A<vAAA>^A"), 2), STR("029A")));
|
||||
ASSERT(str_eq(test(perm, *temp, STR("<vA<AA>>^AvAA<^A>A<v<A>>^AvA^A<vA>^A<v<A>^A>AAvA^A<v<A>A>^AAAvA<^A>A"), 3), STR("029A")));
|
||||
|
||||
init_tables(perm);
|
||||
|
||||
Cache *cache = ARENA_ALLOC(perm, Cache);
|
||||
cache_init(cache, perm);
|
||||
|
||||
i64 part_1 = 0;
|
||||
i64 part_2 = 0;
|
||||
for (isize i = 0; i < lines.len; i++) {
|
||||
str line = str_trim(lines.tokens[i]);
|
||||
/* printf(STR_FMT "\n", STR_ARG(line)); */
|
||||
i64 prefix = parse_numeric_prefix(temp, line);
|
||||
i64 shortest_part_1 = find_shortest(*temp, line, 1 + 2, cache);
|
||||
i64 shortest_part_2 = find_shortest(*temp, line, 1 + 25, cache);
|
||||
part_1 += shortest_part_1 * prefix;
|
||||
part_2 += shortest_part_2 * prefix;
|
||||
}
|
||||
printf("%ld\n", part_1);
|
||||
printf("%ld\n", part_2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
029A
|
||||
980A
|
||||
179A
|
||||
456A
|
||||
379A
|
||||
Loading…
Reference in New Issue