#include #include #include #include #include #include #include #include using i8 = int8_t; using i16 = int16_t; using i32 = int32_t; using i64 = int64_t; using isize = ssize_t; using u8 = uint8_t; using u16 = uint16_t; using u32 = uint32_t; using u64 = uint64_t; using usize = size_t; static u32 name_to_u32(BAN::StringView name) { ASSERT(name.size() == 3); return ((u32)name[0] << 16) | ((u32)name[1] << 8) | ((u32)name[2] << 0); } static BAN::String u32_to_name(u32 val) { BAN::String result; MUST(result.push_back(val >> 16)); MUST(result.push_back(val >> 8)); MUST(result.push_back(val >> 0)); return result; } struct Op { enum { OR, AND, XOR } type { OR }; u32 src1 { 0 }; u32 src2 { 0 }; u32 dst { 0 }; }; struct ParseInputResult { BAN::HashMap wires; BAN::Vector ops; }; static ParseInputResult parse_input(FILE* fp) { char buffer[128]; BAN::HashMap wires; while (fgets(buffer, sizeof(buffer), fp)) { if (buffer[0] == '\n') break; MUST(wires.insert(name_to_u32(BAN::StringView(buffer).substring(0, 3)), buffer[5] - '0')); } BAN::Vector ops; while (fgets(buffer, sizeof(buffer), fp)) { auto parts = MUST(BAN::StringView(buffer).split([](char c) -> bool { return isspace(c); })); MUST(ops.emplace_back( (parts[1] == "OR"_sv) ? Op::OR : (parts[1] == "AND"_sv) ? Op::AND : Op::XOR, name_to_u32(parts[0]), name_to_u32(parts[2]), name_to_u32(parts[4]) )); } return { .wires = BAN::move(wires), .ops = BAN::move(ops), }; } i64 part1(FILE* fp) { auto [wires, ops] = parse_input(fp); BAN::HashSet defined_wires; for (const auto [wire, _] : wires) MUST(defined_wires.insert(wire)); usize ops_done_count = 0; BAN::Vector ops_done_map; MUST(ops_done_map.resize((ops.size() + 7) / 8, 0)); while (ops_done_count < ops.size()) { for (usize i = 0; i < ops.size(); i++) { if (ops_done_map[i / 8] & (1 << (i % 8))) continue; if (!defined_wires.contains(ops[i].src1) || !defined_wires.contains(ops[i].src2)) continue; const u8 src1 = wires[ops[i].src1]; const u8 src2 = wires[ops[i].src2]; u8 val = 0; switch (ops[i].type) { case Op::OR: val = src1 | src2; break; case Op::AND: val = src1 & src2; break; case Op::XOR: val = src1 ^ src2; break; } MUST(wires.insert_or_assign(ops[i].dst, val)); MUST(defined_wires.insert(ops[i].dst)); ops_done_count++; ops_done_map[i / 8] |= (1 << (i % 8)); } } u64 result = 0; for (usize bit = 0; bit < 64; bit++) { char name[4]; name[0] = 'z'; name[1] = (bit / 10) + '0'; name[2] = (bit % 10) + '0'; name[3] = '\0'; auto it = wires.find(name_to_u32(name)); if (it == wires.end()) break; result |= (u64)it->value << bit; } return result; } static bool has_sources(Op op, u32 src1, u32 src2) { if (op.src1 == src1 && op.src2 == src2) return true; if (op.src1 == src2 && op.src2 == src1) return true; return false; } struct u32Triplet { u32 op_and, op_xor, op_or; }; static u32Triplet find_ops(const BAN::Vector& ops, u32 src1, u32 src2) { u32 op_and = 0, op_xor = 0, op_or = 0; for (auto op : ops) { if (!has_sources(op, src1, src2)) continue; switch (op.type) { case Op::AND: op_and = op.dst; break; case Op::XOR: op_xor = op.dst; break; case Op::OR: op_or = op.dst; break; } } return { op_and, op_xor, op_or }; } BAN::String part2(FILE* fp) { auto [wires, ops] = parse_input(fp); BAN::Vector swapped; u32 carry = 0; for (usize bit = 0; bit < 64; bit++) { dprintln("bit {}", bit); char name[4]; name[1] = (bit / 10) + '0'; name[2] = (bit % 10) + '0'; name[3] = '\0'; name[0] = 'x'; const u32 src1 = name_to_u32(name); name[0] = 'y'; const u32 src2 = name_to_u32(name); name[0] = 'z'; const u32 dst = name_to_u32(name); auto [src_and, src_xor, _1] = find_ops(ops, src1, src2); if (!src_and) break; ASSERT(src_xor); if (bit == 0) { carry = src_and; continue; } auto [dst_and, dst_xor, _2] = find_ops(ops, carry, src_xor); if (dst_xor == 0 && dst_and == 0) { dwarnln("swapped src xor, src and"); MUST(swapped.push_back(src_xor)); MUST(swapped.push_back(src_and)); BAN::swap(src_xor, src_and); auto [tmp_and, tmp_xor, _] = find_ops(ops, carry, src_xor); dst_and = tmp_and; dst_xor = tmp_xor; } else if (dst_xor != dst) { if (dst_and == dst) { dwarnln("swapped dst xor, dst and"); MUST(swapped.push_back(dst_xor)); MUST(swapped.push_back(dst_and)); BAN::swap(dst_xor, dst_and); } else if (src_and == dst) { dwarnln("swapped src and, dst xor"); MUST(swapped.push_back(src_and)); MUST(swapped.push_back(dst_xor)); BAN::swap(src_and, dst_xor); } else if (src_xor == dst) { dwarnln("swapped src xor, dst xor"); MUST(swapped.push_back(src_xor)); MUST(swapped.push_back(dst_xor)); BAN::swap(src_and, dst_xor); } else { auto [_1, _2, tmp_or] = find_ops(ops, src_and, dst_and); if (tmp_or == dst) { dwarnln("swapped carry, dst xor"); MUST(swapped.push_back(dst_xor)); MUST(swapped.push_back(tmp_or)); carry = dst_xor; continue; } else { dwarnln("invalid ({}, {}, {})", u32_to_name(dst_xor), u32_to_name(dst_and), u32_to_name(dst)); ASSERT_NOT_REACHED(); } } } auto [_3, _4, tmp_or] = find_ops(ops, src_and, dst_and); carry = tmp_or; } ASSERT(swapped.size() == 8); BAN::sort::sort(swapped.begin(), swapped.end()); BAN::String result; for (const auto& swap : swapped) MUST(result.append(MUST(BAN::String::formatted("{},", u32_to_name(swap))))); result.pop_back(); return result; } int main(int argc, char** argv) { const char* file_path = "/usr/share/aoc2024/day24_input.txt"; if (argc >= 2) file_path = argv[1]; FILE* fp = fopen(file_path, "r"); if (fp == nullptr) { perror("fopen"); return 1; } printf("part1: %" PRId64 "\n", part1(fp)); fseek(fp, 0, SEEK_SET); printf("part2: %s\n", part2(fp).data()); fclose(fp); }