diff --git a/2017/10.cpp b/2017/10.cpp new file mode 100644 index 0000000..ed22575 --- /dev/null +++ b/2017/10.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#include + +#include + +#include +#include +#include + +auto main(int argc, char** argv) -> int { + std::string line; + std::getline(*aocpp::Startup(argc, argv), line); + + std::vector lengths1; + for (auto && x : aocpp::SplitOn(line, ",")) { + lengths1.push_back(std::stoul(x)); + } + + auto result = knothash::hash_ex<256>(1, lengths1.begin(), lengths1.end()); + std::cout << "Part 1: " << long{result[0]} * long{result[1]} << std::endl; + + std::cout << "Part 2: " << knothash::render(knothash::hash(line)) << std::endl; +} \ No newline at end of file diff --git a/2017/14.cpp b/2017/14.cpp new file mode 100644 index 0000000..7e2b427 --- /dev/null +++ b/2017/14.cpp @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace { + +using Grid = std::vector>; + +auto MakeRows(std::string input) -> Grid { + Grid rows; + rows.reserve(128); + + input += "-"; + for (int i = 0; i < 128; i++) { + rows.emplace_back(knothash::hash(input + std::to_string(i))); + } + + return rows; +} + +auto CountBits(Grid const& grid) -> std::size_t { + std::size_t bits = 0; + for (auto const& row : grid) { + for (auto const x : row) { + bits += std::popcount(x); + } + } + return bits; +} + +auto Connect(std::vector & leaders, std::uint16_t who, std::uint16_t leader) { + auto who_ = leaders[who]; + if (who_ == std::uint16_t(-1)) { return; } // there wasn't a node here, abort + for (;;) { + leaders[who] = leader; + if (who_ == who) { return; } + who = who_; + who_ = leaders[who]; + } +} + +auto CountComponents(Grid const& grid) -> std::uint16_t { + + std::vector leaders(128 * 128); + + // Visit all pairs of neighbors linking them if both are set + for (std::uint16_t r = 0; r < 128; r++) { + bool prev = false; + for (std::uint16_t c = 0; c < 128; c++) { + std::uint16_t me = r*128+c; + + // first time we've visited this node, if it's in the graph it's a leader! + if (grid[r][c/8] & (1<<(7-(c%8)))) { + leaders[me] = me; + + // If previous cell in row is a node, link it to me immediately + // It will have just been set as its own group leader. + if (prev) { leaders[me-1] = me; } + + // Connect up's leader _after_ updating left's leader in case they were connected + if (0 < r) { Connect(leaders, me - 128, me); } + prev = true; + } else { + prev = false; + leaders[me] = -1; + } + } + } + + // count component leaders + std::uint16_t n = 0; + for (std::size_t i = 0; i < leaders.size(); i++) { + if (leaders[i] == i) { n++; } + } + + return n; +} + +} // namespace + +TEST_CASE("documented example") { + auto rows = MakeRows("flqrgnkx"); + CHECK(CountBits(rows) == 8108); + CHECK(CountComponents(rows) == 1242); +} + +auto main(int argc, char** argv) -> int { + std::string input; + std::getline(*aocpp::Startup(argc, argv), input); + auto rows = MakeRows(std::move(input)); + std::cout << "Part 1: " << CountBits(rows) << std::endl; + std::cout << "Part 2: " << CountComponents(rows) << std::endl; +} \ No newline at end of file diff --git a/2017/CMakeLists.txt b/2017/CMakeLists.txt index 0bac4c1..50c7b8c 100644 --- a/2017/CMakeLists.txt +++ b/2017/CMakeLists.txt @@ -1,2 +1,8 @@ +add_executable(2017_10 10.cpp) +target_link_libraries(2017_10 aocpp knothash) + +add_executable(2017_14 14.cpp) +target_link_libraries(2017_14 aocpp knothash) + add_executable(2017_18 18.cpp) target_link_libraries(2017_18 aocpp) diff --git a/CMakeLists.txt b/CMakeLists.txt index b896419..34a8a59 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ pkg_check_modules(GMP REQUIRED IMPORTED_TARGET gmpxx) add_subdirectory(lib) add_subdirectory(dlx) +add_subdirectory(knothash) add_subdirectory(zmod) add_subdirectory(intcode) add_subdirectory(2017) diff --git a/knothash/CMakeLists.txt b/knothash/CMakeLists.txt new file mode 100644 index 0000000..b568181 --- /dev/null +++ b/knothash/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(knothash src/knothash.cpp) +target_include_directories(knothash PUBLIC include) +target_link_libraries(knothash PRIVATE aocpp) \ No newline at end of file diff --git a/knothash/include/knothash.hpp b/knothash/include/knothash.hpp new file mode 100644 index 0000000..6c6e3e8 --- /dev/null +++ b/knothash/include/knothash.hpp @@ -0,0 +1,55 @@ +#ifndef KNOTHASH_HPP_ +#define KNOTHASH_HPP_ + +#include +#include +#include +#include + +namespace knothash { + +template +auto reverser(std::array & result, std::size_t cursor, std::size_t len) -> void { + auto leftside = N - cursor; + auto start = result.begin() + cursor; + if (len <= leftside) { + std::reverse(start, start + len); + } else { + auto rightside = len - leftside; + if (leftside < rightside) { + std::swap_ranges(start, result.end(), result.rend() - rightside); + std::reverse(result.begin(), result.begin() + (rightside-leftside)); + } else { + std::swap_ranges(result.rend() - rightside, result.rend(), start); + std::reverse(result.end() - (leftside-rightside), result.end()); + } + } +} + +auto hash(std::string key) -> std::array; +auto render(std::array const& hash) -> std::string; + +template +auto hash_ex(std::size_t cycles, It begin, It end) + -> std::array +{ + std::size_t cursor = 0; + std::size_t skip = 0; + + std::array result; + for (std::size_t i = 0; i < N; i++) { result[i] = i; } + + for (std::size_t i = 0; i < cycles; i++) { + std::for_each(begin, end, [&](std::size_t len) { + reverser(result, cursor, len); + cursor += skip + len; + cursor %= N; + skip++; + }); + } + return result; +} + +} // namespace + +#endif diff --git a/knothash/src/knothash.cpp b/knothash/src/knothash.cpp new file mode 100644 index 0000000..ac9193d --- /dev/null +++ b/knothash/src/knothash.cpp @@ -0,0 +1,79 @@ +#include + +#include +#include +#include + +#include + +namespace knothash { + +auto hash(std::string key) -> std::array +{ + key += 17; + key += 31; + key += 73; + key += 47; + key += 23; + auto sparse = hash_ex<256>(64, key.begin(), key.end()); + + std::size_t offset = 0; + std::array dense; + for (auto & x : dense) { + x = std::reduce(sparse.begin()+offset, sparse.begin()+offset+16, 0, std::bit_xor()); + offset += 16; + } + + return dense; +} + +auto render(std::array const& hash) -> std::string { + std::ostringstream out; + out << std::hex << std::setfill('0'); + for (int const x : hash) { + out << std::setw(2) << int(x); + } + return out.str(); +} + +TEST_SUITE("knot hash") { + +TEST_CASE("reverser") { + std::array v {0,1,2,3,4,5,6,7,8}; + + SUBCASE("more left") { + std::array expect {1,0,8,7,4,5,6,3,2}; + reverser(v, 7, 6); + REQUIRE(v == expect); + } + + SUBCASE("more right") { + std::array expect {6,5,2,3,4,1,0,8,7}; + reverser(v, 5, 6); + REQUIRE(v == expect); + } + + SUBCASE("middle") { + std::array expect {0,1,5,4,3,2,6,7,8}; + reverser(v, 2, 4); + CHECK(v == expect); + } +} + +TEST_CASE("knot example") { + std::array lengths {3, 4, 1, 5}; + auto result = hash_ex<5>(1, lengths.begin(), lengths.end()); + std::array expected {3, 4, 2, 1, 0}; + CHECK(result == expected); +} + +TEST_CASE("hash examples") { + CHECK(render(hash("")) == "a2582a3a0e66e6e86e3812dcb672a272"); + CHECK(render(hash("AoC 2017")) == "33efeb34ea91902bb2f59c9920caa6cd"); + CHECK(render(hash("1,2,3")) == "3efbe78a8d82f29979031a4aa0b16a9d"); + CHECK(render(hash("1,2,4")) == "63960835bcdc130f0b66d7ff4f6a5a8e"); +} + +} + +} // namespace diff --git a/lib/src/Startup.cpp b/lib/src/Startup.cpp index 8b63eea..fbc249b 100644 --- a/lib/src/Startup.cpp +++ b/lib/src/Startup.cpp @@ -13,6 +13,10 @@ namespace aocpp { auto Startup(int argc, char ** argv) -> std::unique_ptr { + if (std::getenv("DOCTEST")) { + exit(doctest::Context{argc, argv}.run()); + } + bool should_delete; std::istream* result_ptr;