/* aoc-c.c: Advent of Code 2022, day 16 * * Copyright (C) 2023 Bruno Raoult ("br") * Licensed under the GNU General Public License v3.0 or later. * Some rights reserved. See COPYING. * * You should have received a copy of the GNU General Public License along with this * program. If not, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include "br.h" #include "list.h" #include "pool.h" #include "debug.h" #include "bits.h" #include "aoc.h" pool_t *pool_valve; union val { u32 val; char str[3]; }; enum state { CLOSED, OPENED }; struct worker { struct valve *pos; int depth; int time; }; struct valve { int index; /* -1 for zero flow rate */ union val val; enum state state; int rate; int evalflow, evaltime; int playedflow, playedtime; struct hlist_node hlist; struct list_head index_sorted; struct list_head flow_sorted; struct list_head eval; int worker; struct list_head played; int ntunnels, tottunnels; struct valve **tunnels; /* array */ }; static struct graph { struct valve *aa; /* head ("AA") */ int npositive; /* only "AA" & working valves */ int nvalves; u64 opened; /* bitmask of opened valves */ u64 openable; /* bitmask of openable valves */ struct list_head index_sorted; /* TO REMOVE ? */ struct list_head flow_sorted; struct list_head eval; struct list_head played[2]; struct valve **indexed_all; int *dist; /* 2-D array */ } graph = { .aa = NULL, .npositive = 0, .nvalves = 0, .index_sorted = LIST_HEAD_INIT(graph.index_sorted), .flow_sorted = LIST_HEAD_INIT(graph.flow_sorted), .eval = LIST_HEAD_INIT(graph.eval), .played[0] = LIST_HEAD_INIT(graph.played[0]), .played[1] = LIST_HEAD_INIT(graph.played[1]), .indexed_all = NULL, .dist = NULL }; #define POS(a, b) ((a)*graph.nvalves + (b)) #define DIST(a, b) (graph.dist[POS((a), (b))]) static void print_valves() { struct valve *cur; printf("**** graph: .head=%p npositive=%d\n", graph.aa, graph.npositive); printf("index1: "); list_for_each_entry(cur, &graph.index_sorted, index_sorted) { printf("%d:%s ", cur->index, cur->val.str); } printf("\n"); printf("index2: "); for (int i = 0; i < graph.nvalves; ++i) { printf("%d:%s ", graph.indexed_all[i]->index, graph.indexed_all[i]->val.str); } printf("\n"); if (testmode()) { printf("distances:\n "); for (int i = 0; i < graph.nvalves; ++i) { printf(" %s", graph.indexed_all[i]->val.str); } printf("\n"); for (int i = 0; i < graph.nvalves; ++i) { printf("%s ", graph.indexed_all[i]->val.str); for (int j = 0; j < graph.nvalves; ++j) { printf("%5d ", DIST(i, j)); } printf("\n"); } } printf("flow_sorted: "); list_for_each_entry(cur, &graph.flow_sorted, flow_sorted) { printf("%s:%d ", cur->val.str, cur->rate); } printf("\n"); printf("openable: %#lx ", graph.openable); int pos, tmp; bit_for_each64_2(pos, tmp, graph.openable) { printf("%d ", pos); } printf("\n"); } #define PAD3 log(3, "%*s", _depth * 2, "") #define PAD4 log(4, "%*s", _depth * 2, "") /** * eval() - eval possible moves from @flow_sorted list. * @_depth: recursivity depth (for debug only, TODO: remove). * @valve: &starting valve (where we are). * @depth: remaining depth (-1: full depth). * @pick: max position (in @flow_sorted) to pick moves from (-1 for all). * @time: remaining time. * @pressure: total pressure per time unit so far. * * Find the "best" next move by evaluating up to @depth moves, using only the * first @pick elements in @flow_sorted list, and within @time remaining time. * * @depth and @picked may be linked, for instance to fully explore the first N * possibilities in @flow_sorted with a N depth. * * @Return: the current position eval. */ static struct valve *eval(int _depth, struct valve *pos, int depth, int pick, int time, int pressure) { struct valve *cur, *best = NULL, *sub; struct list_head *list_flow, *tmp; int _pick = pick, val = 0, val1, max = 0; PAD3; log(3, "EVAL _depth=%d pos=%d[%s] depth=%d pick=%d time=%d pressure=%d\n", _depth, pos->index, pos->val.str, depth, pick, time, pressure); list_for_each_safe(list_flow, tmp, &graph.flow_sorted) { cur = list_entry(list_flow, struct valve, flow_sorted); int d = DIST(pos->index, cur->index); PAD4; log(4, "dist(%s,%s) = %d\n", pos->val.str, cur->val.str, d); if (!--_pick) { PAD4; log(4, "pick exhausted\n"); continue; } if (time - (d + 1 + 1) < 0) { PAD4; log(4, "time exhausted\n"); continue; } val = (time - (d + 1)) * cur->rate; PAD4; log(4, "val=%d\n", val); if (depth > 0) { /* do not use list_del() here, to preserve prev/next pointers */ __list_del_entry(list_flow); sub = eval(_depth + 1, cur, depth - 1, pick, time - d - 1, pressure + pos->rate); list_flow->prev->next = list_flow; list_flow->next->prev = list_flow; } else { sub = NULL; } val1 = sub? sub->evalflow: 0; PAD3; log(3, "eval(%s->%s)= %5d = %d + %d", pos->val.str, cur->val.str, val+val1, val, val1); if (val + val1 > max) { max = val + val1; best = cur; log(3, " NEW MAX !"); } log(3, "\n"); } if (best) { best->evalflow = max; PAD3; log(3, "EVAL returning best [%s] eval=%d\n", best->val.str, max); } return best; } /** * eval() - eval possible moves from @flow_sorted list. * @_depth: recursivity depth (for debug only, TODO: remove). * @valve: &starting valve (where we are). * @depth: remaining depth (-1: full depth). * @pick: max position (in @flow_sorted) to pick moves from (-1 for all). * @time: remaining time. * @pressure: total pressure per time unit so far. * * Find the "best" next move by evaluating up to @depth moves, using only the * first @pick elements in @flow_sorted list, and within @time remaining time. * * @depth and @picked may be linked, for instance to fully explore the first N * possibilities in @flow_sorted with a N depth. * * @Return: the current position eval. */ static struct valve *eval2(int _depth, struct worker *worker, int pick, int pressure) { struct valve *cur, *best = NULL, *sub; struct list_head *list_flow, *tmp; int _pick = pick, val = 0, val1, max = 0; PAD3; log(3, "EVAL _depth=%d pos=%d[%s] depth=%d pick=%d time=%d pressure=%d\n", _depth, worker->pos->index, worker->pos->val.str, worker->depth, pick, worker->time, pressure); list_for_each_safe(list_flow, tmp, &graph.flow_sorted) { cur = list_entry(list_flow, struct valve, flow_sorted); int d = DIST(worker->pos->index, cur->index), remain = worker->time - (d + 1); PAD4; log(4, "dist(%s,%s) = %d\n", worker->pos->val.str, cur->val.str, d); if (!--_pick) { PAD4; log(4, "pick exhausted\n"); continue; } if (remain < 1) { PAD4; log(4, "time exhausted\n"); continue; } val = remain * cur->rate; PAD4; log(4, "val=%d\n", val); if (worker->depth > 0) { struct worker w = { .pos = cur, .depth = worker->depth - 1, .time = remain }; /* do not use list_del() here, to preserve prev/next pointers */ __list_del_entry(list_flow); sub = eval2(_depth + 1, &w, pick, pressure + worker->pos->rate); list_flow->prev->next = list_flow; list_flow->next->prev = list_flow; } else { sub = NULL; } val1 = sub? sub->evalflow: 0; PAD3; log(3, "eval2(%s->%s)= %5d = %d + %d", worker->pos->val.str, cur->val.str, val+val1, val, val1); if (val + val1 > max) { max = val + val1; best = cur; log(3, " NEW MAX !"); } log(3, "\n"); } if (best) { best->evalflow = max; best->worker = 0; /* FIXME */ PAD3; log(3, "EVAL returning best [%s] worker=%d eval=%d\n", best->val.str, best->worker, max); } return best; } /** * eval() - eval possible moves from @flow_sorted list. * @_depth: recursivity depth (for debug only, TODO: remove). * @valve: &starting valve (where we are). * @depth: remaining depth (-1: full depth). * @pick: max position (in @flow_sorted) to pick moves from (-1 for all). * @time: remaining time. * @pressure: total pressure per time unit so far. * * Find the "best" next move by evaluating up to @depth moves, using only the * first @pick elements in @flow_sorted list, and within @time remaining time. * * @depth and @picked may be linked, for instance to fully explore the first N * possibilities in @flow_sorted with a N depth. * * @Return: the current position eval. */ static struct valve *eval3(int _depth, struct worker *worker, int pick, int pressure) { struct valve *cur, *best = NULL, *sub; struct list_head *list_flow, *tmp; int _pick = pick, val = 0, val1, max = 0, bestworker; PAD3; log_f(3, "EVAL _depth=%d w0={ pos=%d[%s] depth=%d time=%d } w1={ pos=%d[%s] depth=%d time=%d } pick=%d pressure=%d \n", _depth, worker[0].pos->index, worker[0].pos->val.str, worker[0].depth, worker[0].time, worker[1].pos->index, worker[1].pos->val.str, worker[1].depth, worker[1].time, pick, pressure); list_for_each_safe(list_flow, tmp, &graph.flow_sorted) { cur = list_entry(list_flow, struct valve, flow_sorted); int nworkers = worker[0].pos->index == worker[1].pos->index? 1: 2; if (!--_pick) { PAD4; log(4, "pick exhausted\n"); break; } for (int _w = 0; _w < nworkers; ++_w) { struct worker *w = worker + _w; int d = DIST(w->pos->index, cur->index); int remain = w->time - (d + 1); PAD3; log(3, "worker %d/%d ", _w + 1, nworkers ); PAD3; log(3, "dist(%s,%s) = %d ", w->pos->val.str, cur->val.str, d); if (remain < 1) { PAD3; log(4, "time exhausted\n"); continue; } val = remain * cur->rate; PAD3; log(3, "--> val=%d\n", val); if (w->depth > 0) { struct worker _tmp = *w; w->pos = cur; w->depth--; w->time = remain; /* do not use list_del() here, to preserve prev/next pointers */ __list_del_entry(list_flow); sub = eval3(_depth + 1, worker, pick, pressure + w->pos->rate); list_flow->prev->next = list_flow; list_flow->next->prev = list_flow; *w = _tmp; } else { sub = NULL; } val1 = sub? sub->evalflow: 0; PAD3; log(3, "eval3(%s->%s)= %5d = %d + %d", w->pos->val.str, cur->val.str, val+val1, val, val1); if (val + val1 > max) { max = val + val1; best = cur; bestworker = _w; log(3, " NEW MAX !"); } log(3, "\n"); } } if (best) { best->evalflow = max; best->worker = bestworker; /* FIXME */ PAD3; log(3, "EVAL returning best [%s] worker=%d eval=%d\n", best->val.str, best->worker, max); } return best; } static struct valve *find_valve(union val val, int ntunnels, int rate) { struct valve *cur; log_f(3, "val=%s ntunnels=%d rate=%d\n", val.str, ntunnels, rate); list_for_each_entry(cur, &graph.index_sorted, index_sorted) { //log(3, "\tcomparing with found, addr=%p\n", cur); if (cur->val.val == val.val) { log(3, "\tfound, addr=%p\n", cur); if (ntunnels) goto init; goto end; } } cur = pool_get(pool_valve); cur->val.val = val.val; cur->ntunnels = 0; cur->state = CLOSED; cur->worker = -1; cur->evalflow = cur->playedflow = 0; cur->evaltime = cur->playedtime = 30; INIT_LIST_HEAD(&cur->index_sorted); INIT_LIST_HEAD(&cur->flow_sorted); INIT_LIST_HEAD(&cur->eval); INIT_LIST_HEAD(&cur->played); log(3, "\talloc new, addr=%p\n", cur); init: if (ntunnels) { cur->rate = rate; cur->tottunnels = ntunnels; cur->tunnels = calloc(ntunnels, sizeof(struct valve *)); } end: return cur; } /** * nthtok - get nth token fron string. * @buf: buffer to parse. * @sep: separators string. * @n: token number (0: first token). * * @Return: pointer to token. */ static char *nthtok(char *buf, const char *sep, int n) { char *ret; for (; n >= 0; n--) { ret = strtok(buf, sep); buf = NULL; } return ret; } #define SEP " ,;=" static struct graph *parse() { int index = 0, ntunnels; size_t alloc = 0; ssize_t buflen; char *buf = NULL, *tok; int rate; struct valve *v1, *v2; union val cur, link; while ((buflen = getline(&buf, &alloc, stdin)) > 0) { buf[--buflen] = 0; /* valve name */ strncpy(cur.str, nthtok(buf, SEP, 1), sizeof(cur.str)); //printf("valve=%s ", tok); rate = atoi(nthtok(NULL, SEP, 3)); //printf("rate=%s ", tok); tok = nthtok(NULL, SEP, 4); ntunnels = (buf + buflen - tok) / 4 + 1; v1 = find_valve(cur, ntunnels, rate); v1->index = index++; // TODO: remove this list ? list_add_tail(&v1->index_sorted, &graph.index_sorted); graph.nvalves++; //if (rate || v1->val.val == ('A' << 8 | 'A')) { if (rate) { struct valve *cur; graph.npositive++; /* keep this list sorted by flow decrasing */ list_for_each_entry(cur, &graph.flow_sorted, flow_sorted) { if (rate > cur->rate) { list_add_tail(&v1->flow_sorted, &cur->flow_sorted); goto inserted; } } list_add_tail(&v1->flow_sorted, &graph.flow_sorted); inserted: ; if (rate) { //printf("adjust openable(%d): %#lx", v1->index, graph.openable); graph.openable |= (1 << v1->index); //printf("->%#lx", graph.openable); } } //printf("lead=%s ntunnels=%d ", tok, ntunnels); do { link.val = *(u16 *)tok; v2 = find_valve(link, 0, 0); *(v1->tunnels + v1->ntunnels++) = v2; //printf(",%s", tok); } while ((tok = nthtok(NULL, SEP, 0))); //printf("\n"); } graph.aa = find_valve((union val) { .str="AA" }, 0, 0); /* build array of indexed valves */ graph.indexed_all = calloc(graph.nvalves, sizeof(struct valve *)); list_for_each_entry(v1, &graph.index_sorted, index_sorted) { graph.indexed_all[v1->index] = v1; } return &graph; } static int is_neighbour(int i, int j) { struct valve *v1 = graph.indexed_all[i], *v2 = graph.indexed_all[j]; for (int i = 0; i < v1->ntunnels; ++i) if (v1->tunnels[i]->val.val == v2->val.val) return 1; return 0; } static void build_distances() { int i, j, k; graph.dist = calloc(graph.nvalves * graph.nvalves, sizeof(int)); /* initialize values */ for (i = 0; i < graph.nvalves; ++i) { for (j = 1; j < graph.nvalves; ++j) { if (i != j) { if (is_neighbour(i, j)) DIST(i, j) = DIST(j, i) = 1; else DIST(i, j) = DIST(j, i) = 10000; } } } /* get all distances using Floyd-Warshall * see https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm * * Add all vertices one by one to the set of intermediate vertices. * ---> Before start of an iteration, we have shortest distances between all * pairs of vertices such that the shortest distances consider only the * vertices in set {0, 1, 2, .. k-1} as intermediate vertices. * ----> After the end of an iteration, vertex no. k is added to the set of * intermediate vertices and the set becomes {0, 1, 2, .. k} */ for (k = 0; k < graph.nvalves; k++) { /* Pick all vertices as source one by one */ for (i = 0; i < graph.nvalves; i++) { /* Pick all vertices as destination for the above picked source */ for (j = i + 1; j < graph.nvalves; j++) DIST(i, j) = DIST(j, i) = min(DIST(i, j), DIST(i, k) + DIST(k, j)); } } return; } static void print_played() { struct valve *p; int total = 0; for (int w = 0; w < 2; ++w) { int remain = 26, i = 1; struct valve *prev = graph.aa; i = 1; printf("played by %d:\n", w); list_for_each_entry(p, &graph.played[w], played) { printf("%2d: %s, ", i, p->val.str); remain -= DIST(p->index, prev->index) + 1; total += p->rate * remain; printf("dist=%d remain=%d total=%d", DIST(p->index, prev->index), remain, total); printf("\n"); i++; prev = p; } } } static void doit() { struct worker w[2]; w[0].pos = w[1].pos = graph.aa; w[0].depth = w[1].depth = 4; w[0].time = w[1].time = 26; /* struct worker w[2] = { { .pos = graph.aa, .depth = 5, .time = 30 }, { .pos = graph.aa, .depth = 5, .time = 30 }, }; */ struct valve *best; //list_add_tail(&w[0].pos->played, &graph.played[0]); //list_add_tail(&w[1].pos->played, &graph.played[1]); //while () while ((best = eval3(0, w, 12, 0))) { list_del(&best->flow_sorted); list_add_tail(&best->played, &graph.played[best->worker]); w[best->worker].time -= DIST(w[best->worker].pos->index, best->index) + 1; w[best->worker].pos = best; print_played(); } } static ulong part1() { ulong res = 1; struct worker w[2]; w[0].pos = w[1].pos = graph.aa; w[0].depth = w[1].depth = 5; w[0].time = w[1].time = 30; printf("part 1\n"); eval(0, graph.aa, 7, 7, 30, 0); eval2(0, &w[0], 4, 0); return res; } static ulong part2() { ulong res = 1; //struct worker w = { // .pos = graph.aa, // .depth = 4, // .time = 30 //}; printf("part 2\n"); //while () //eval2(0, &w, 7, 0); doit(); return res; } int main(int ac, char **av) { int part = parseargs(ac, av); pool_valve = pool_create("valve", 512, sizeof(struct valve)); parse(); build_distances(); print_valves(); printf("%s: res=%lu\n", *av, part == 1? part1(): part2()); exit(0); }