diff options
Diffstat (limited to '2024/src/day24.cpp')
| -rw-r--r-- | 2024/src/day24.cpp | 234 |
1 files changed, 231 insertions, 3 deletions
diff --git a/2024/src/day24.cpp b/2024/src/day24.cpp index bf439c7..3f252a1 100644 --- a/2024/src/day24.cpp +++ b/2024/src/day24.cpp | |||
| @@ -1,3 +1,4 @@ | |||
| 1 | #include <array> | ||
| 1 | #include <fstream> | 2 | #include <fstream> |
| 2 | #include <iostream> | 3 | #include <iostream> |
| 3 | #include <map> | 4 | #include <map> |
| @@ -7,6 +8,8 @@ | |||
| 7 | #include <vector> | 8 | #include <vector> |
| 8 | using namespace std; | 9 | using namespace std; |
| 9 | 10 | ||
| 11 | static const auto MAX_BITS = 45UL; | ||
| 12 | |||
| 10 | vector<string> | 13 | vector<string> |
| 11 | split(string_view line, string_view delimiter) | 14 | split(string_view line, string_view delimiter) |
| 12 | { | 15 | { |
| @@ -26,6 +29,23 @@ split(string_view line, string_view delimiter) | |||
| 26 | return res; | 29 | return res; |
| 27 | } | 30 | } |
| 28 | 31 | ||
| 32 | template<typename Container> | ||
| 33 | string | ||
| 34 | join(const Container& container, const string& delimiter) | ||
| 35 | { | ||
| 36 | ostringstream oss; | ||
| 37 | auto iter = container.begin(); | ||
| 38 | if ( iter != container.end() ) { | ||
| 39 | oss << *iter; | ||
| 40 | ++iter; | ||
| 41 | } | ||
| 42 | while ( iter != container.end() ) { | ||
| 43 | oss << delimiter << *iter; | ||
| 44 | ++iter; | ||
| 45 | } | ||
| 46 | return oss.str(); | ||
| 47 | } | ||
| 48 | |||
| 29 | tuple<map<string, unsigned long>, map<string, tuple<string, string, string>>> | 49 | tuple<map<string, unsigned long>, map<string, tuple<string, string, string>>> |
| 30 | read_file(string_view filename) | 50 | read_file(string_view filename) |
| 31 | { | 51 | { |
| @@ -76,9 +96,6 @@ part1(const tuple<map<string, unsigned long>, map<string, tuple<string, string, | |||
| 76 | else if ( op == "OR" ) { | 96 | else if ( op == "OR" ) { |
| 77 | value = eval(lhs) | eval(rhs); | 97 | value = eval(lhs) | eval(rhs); |
| 78 | } | 98 | } |
| 79 | else { | ||
| 80 | cerr << "Das darf nicht passieren! Input: " << input << endl; | ||
| 81 | } | ||
| 82 | 99 | ||
| 83 | inputs[input] = value; | 100 | inputs[input] = value; |
| 84 | 101 | ||
| @@ -97,9 +114,220 @@ part1(const tuple<map<string, unsigned long>, map<string, tuple<string, string, | |||
| 97 | cout << value << endl; | 114 | cout << value << endl; |
| 98 | } | 115 | } |
| 99 | 116 | ||
| 117 | string | ||
| 118 | make_wire(char chr, unsigned long num) | ||
| 119 | { | ||
| 120 | static const size_t MAX_LEN = 10; | ||
| 121 | array<char, MAX_LEN> tmp{}; | ||
| 122 | snprintf(tmp.data(), tmp.size(), "%c%02lu", chr, num); // NOLINT | ||
| 123 | return tmp.data(); | ||
| 124 | } | ||
| 125 | |||
| 126 | class RippleCarryAdder { | ||
| 127 | public: | ||
| 128 | explicit RippleCarryAdder(const tuple<map<string, unsigned long>, map<string, tuple<string, string, string>>>& data) | ||
| 129 | : inputs_(get<0>(data)) | ||
| 130 | , deps_(get<1>(data)) | ||
| 131 | { | ||
| 132 | } | ||
| 133 | |||
| 134 | RippleCarryAdder(const map<string, tuple<string, string, string>>& deps, unsigned long x_value, unsigned long y_value) | ||
| 135 | : deps_(deps) | ||
| 136 | { | ||
| 137 | build_inputs(x_value, y_value); | ||
| 138 | } | ||
| 139 | |||
| 140 | void swap_wire(const string& lhs, const string& rhs) | ||
| 141 | { | ||
| 142 | swap(deps_.at(lhs), deps_.at(rhs)); | ||
| 143 | } | ||
| 144 | |||
| 145 | bool eval_z(unsigned long x_value, unsigned long y_value) | ||
| 146 | { | ||
| 147 | build_inputs(x_value, y_value); | ||
| 148 | |||
| 149 | bool overflow = false; | ||
| 150 | for ( auto bit = 0UL; bit != MAX_BITS + 1; ++bit ) { | ||
| 151 | static const int MAX_RECURSION_DEPTH = 99; | ||
| 152 | eval(make_wire('z', bit), MAX_RECURSION_DEPTH, overflow); | ||
| 153 | } | ||
| 154 | return !overflow; | ||
| 155 | } | ||
| 156 | |||
| 157 | [[nodiscard]] vector<string> test(unsigned long x_value, unsigned long y_value) const | ||
| 158 | { | ||
| 159 | const auto z_value = x_value + y_value; | ||
| 160 | |||
| 161 | vector<string> failed_bits; | ||
| 162 | auto bit = 0UL; | ||
| 163 | for ( ; bit != MAX_BITS + 1; ++bit ) { | ||
| 164 | const auto z_bit = (z_value & (1UL << bit)) != 0 ? 1UL : 0UL; | ||
| 165 | const auto wire = make_wire('z', bit); | ||
| 166 | if ( inputs_.at(wire) != z_bit ) { | ||
| 167 | failed_bits.push_back(wire); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | return failed_bits; | ||
| 171 | }; | ||
| 172 | |||
| 173 | void print(unsigned long x_value, unsigned long y_value, ostream& out) const | ||
| 174 | { | ||
| 175 | const auto z_value = x_value + y_value; | ||
| 176 | |||
| 177 | out << " x: " << bitset<MAX_BITS>{ x_value } << '\n' | ||
| 178 | << "+ y: " << bitset<MAX_BITS>{ y_value } << '\n' | ||
| 179 | << "= z: " << bitset<MAX_BITS + 1>{ z_value } << '\n'; | ||
| 180 | |||
| 181 | out << "===: "; | ||
| 182 | for ( auto bit = MAX_BITS + 1; bit-- > 0; ) { | ||
| 183 | const auto wire = make_wire('z', bit); | ||
| 184 | if ( inputs_.contains(wire) ) { | ||
| 185 | out << inputs_.at(wire); | ||
| 186 | } | ||
| 187 | else { | ||
| 188 | out << '_'; | ||
| 189 | } | ||
| 190 | } | ||
| 191 | out << endl; | ||
| 192 | }; | ||
| 193 | |||
| 194 | void collect(const string& input, set<string>& possible_fails) const | ||
| 195 | { | ||
| 196 | collect(input, 2, possible_fails); | ||
| 197 | } | ||
| 198 | |||
| 199 | private: | ||
| 200 | void build_inputs(unsigned long x_value, unsigned long y_value) | ||
| 201 | { | ||
| 202 | inputs_.clear(); | ||
| 203 | |||
| 204 | for ( auto bit = 0UL; bit != MAX_BITS; ++bit ) { | ||
| 205 | inputs_.emplace(make_wire('x', bit), (x_value & (1UL << bit)) != 0 ? 1 : 0); | ||
| 206 | inputs_.emplace(make_wire('y', bit), (y_value & (1UL << bit)) != 0 ? 1 : 0); | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | unsigned long eval(const string& input, size_t depth, bool& overflow) | ||
| 211 | { | ||
| 212 | if ( depth == 0 ) { | ||
| 213 | overflow = true; | ||
| 214 | return 0; | ||
| 215 | } | ||
| 216 | |||
| 217 | if ( inputs_.contains(input) ) { | ||
| 218 | return inputs_.at(input); | ||
| 219 | } | ||
| 220 | |||
| 221 | const auto& [lhs, op, rhs] = deps_.at(input); | ||
| 222 | |||
| 223 | unsigned long value = 0; | ||
| 224 | |||
| 225 | if ( op == "XOR" ) { | ||
| 226 | value = eval(lhs, depth - 1, overflow) ^ eval(rhs, depth - 1, overflow); | ||
| 227 | } | ||
| 228 | else if ( op == "AND" ) { | ||
| 229 | value = eval(lhs, depth - 1, overflow) & eval(rhs, depth - 1, overflow); | ||
| 230 | } | ||
| 231 | else if ( op == "OR" ) { | ||
| 232 | value = eval(lhs, depth - 1, overflow) | eval(rhs, depth - 1, overflow); | ||
| 233 | } | ||
| 234 | |||
| 235 | inputs_[input] = value; | ||
| 236 | |||
| 237 | return value; | ||
| 238 | } | ||
| 239 | |||
| 240 | void collect(const string& input, size_t depth, set<string>& possible_fails) const | ||
| 241 | { | ||
| 242 | if ( !deps_.contains(input) ) { | ||
| 243 | return; | ||
| 244 | } | ||
| 245 | |||
| 246 | possible_fails.insert(input); | ||
| 247 | |||
| 248 | if ( depth == 0 ) { | ||
| 249 | return; | ||
| 250 | } | ||
| 251 | |||
| 252 | const auto& [lhs, op, rhs] = deps_.at(input); | ||
| 253 | |||
| 254 | collect(lhs, depth - 1, possible_fails); | ||
| 255 | collect(rhs, depth - 1, possible_fails); | ||
| 256 | }; | ||
| 257 | |||
| 258 | map<string, unsigned long> inputs_; | ||
| 259 | |||
| 260 | // register => { reg1 operator reg2 } | ||
| 261 | map<string, tuple<string, string, string>> deps_; | ||
| 262 | }; | ||
| 263 | |||
| 264 | void | ||
| 265 | part2(const tuple<map<string, unsigned long>, map<string, tuple<string, string, string>>>& data) | ||
| 266 | { | ||
| 267 | RippleCarryAdder rca(data); | ||
| 268 | |||
| 269 | set<string> solution; | ||
| 270 | |||
| 271 | for ( auto bit = 0UL; bit != MAX_BITS; ++bit ) { | ||
| 272 | const auto x_value = 1UL << bit; // (1UL << (bit+1)) - 1; | ||
| 273 | const auto y_value = 0UL << bit; | ||
| 274 | |||
| 275 | if ( !rca.eval_z(x_value, y_value) ) { | ||
| 276 | return; | ||
| 277 | } | ||
| 278 | |||
| 279 | const auto failed_bits = rca.test(x_value, y_value); | ||
| 280 | if ( !failed_bits.empty() ) { | ||
| 281 | set<string> candidates; | ||
| 282 | for ( const auto& wire: failed_bits ) { | ||
| 283 | rca.collect(wire, candidates); | ||
| 284 | } | ||
| 285 | |||
| 286 | const vector<string> foo{ candidates.begin(), candidates.end() }; | ||
| 287 | |||
| 288 | for ( auto i = foo.begin(); i != foo.end(); ++i ) { | ||
| 289 | for ( auto j = i + 1; j != foo.end(); ++j ) { | ||
| 290 | auto do_test = [&](unsigned long lhs, unsigned long rhs) -> bool { | ||
| 291 | RippleCarryAdder rca_tester{ data }; | ||
| 292 | |||
| 293 | rca_tester.swap_wire(*i, *j); | ||
| 294 | |||
| 295 | if ( !rca_tester.eval_z(lhs, rhs) ) { | ||
| 296 | return false; | ||
| 297 | } | ||
| 298 | return rca_tester.test(lhs, rhs).empty(); | ||
| 299 | }; | ||
| 300 | |||
| 301 | if ( do_test(x_value, y_value) ) { | ||
| 302 | const auto x_value2 = 7UL << (bit - 1); | ||
| 303 | const auto y_value2 = 3UL << (bit - 1); | ||
| 304 | |||
| 305 | if ( do_test(x_value2, y_value2) ) { | ||
| 306 | const auto x_value3 = 1UL << bit; | ||
| 307 | const auto y_value3 = 1UL << bit; | ||
| 308 | |||
| 309 | if ( do_test(x_value3, y_value3) ) { | ||
| 310 | const auto x_value4 = 5UL << (bit - 1); | ||
| 311 | const auto y_value4 = 3UL << (bit - 1); | ||
| 312 | |||
| 313 | if ( do_test(x_value4, y_value4) ) { | ||
| 314 | solution.insert(*i); | ||
| 315 | solution.insert(*j); | ||
| 316 | } | ||
| 317 | } | ||
| 318 | } | ||
| 319 | } | ||
| 320 | } | ||
| 321 | } | ||
| 322 | } | ||
| 323 | } | ||
| 324 | cout << join(solution, ",") << endl; | ||
| 325 | } | ||
| 326 | |||
| 100 | int | 327 | int |
| 101 | main() | 328 | main() |
| 102 | { | 329 | { |
| 103 | const auto data = read_file("data/day24.txt"); | 330 | const auto data = read_file("data/day24.txt"); |
| 104 | part1(data); | 331 | part1(data); |
| 332 | part2(data); | ||
| 105 | } | 333 | } |
