diff --git a/2024/src/day22.cpp b/2024/src/day22.cpp index 23ff7d9..f352c20 100644 --- a/2024/src/day22.cpp +++ b/2024/src/day22.cpp @@ -14,9 +14,7 @@ int main(int argc, char **argv) { auto args = aoc::parse_args(argc, argv); auto market = aoc::day22::MonkeyMarket::read(args.infile); - for (int i = 0; i < 2000; ++i) { - market.evolve(i); - } + market.evolve(2000); std::cout << market.get_sum() << "\n"; int part_2 = market.find_best_sell_sequence(); std::cout << part_2 << "\n"; diff --git a/2024/src/day22.hpp b/2024/src/day22.hpp index 8f8ebf0..83979bd 100644 --- a/2024/src/day22.hpp +++ b/2024/src/day22.hpp @@ -8,42 +8,49 @@ #ifndef DAY22_HPP_A3Y597BB #define DAY22_HPP_A3Y597BB -#include "lib.hpp" // for as_number, DEBUG -#include "unit_test/pretty_print.hpp" // for repr -#include "util/hash.hpp" // for make_hash - -#include // for ranges::max_element -#include // for array -#include // for size_t -#include // for int8_t, uint32_t, uint64_t -#include // for plus, hash -#include // for setw -#include // for cerr, istream -#include // for forward_as_tuple -#include // for unordered_map -#include // for pair (unordered_map), piecewise_construct -#include // for vector +#include "lib.hpp" // for as_number, DEBUG +#include "util/math.hpp" // for powi + +#include // for max_element +#include // for strong_ordering +#include // for size_t +#include // for int8_t, uint32_t, uint64_t +#include // for cerr, istream, ostream +#include // for distance +#include // for vector namespace aoc::day22 { -struct ChangeSequence : public std::array {}; -} // namespace aoc::day22 -template <> -struct std::hash { - std::size_t - operator()(const aoc::day22::ChangeSequence &changes) const noexcept { - // random number (hexdump -n8 -e '"0x" 8/1 "%02x" "ull\n"'((value / aoc::math::powi(BASE, 3 - index)) % + BASE) - + 9; +} -ChangeSequence add_price_change(const ChangeSequence &changes, - std::int8_t new_change) { - return ChangeSequence{changes[1], changes[2], changes[3], new_change}; +void ChangeSequence::add_price_change(std::int8_t new_change) { + value *= BASE; + value += static_cast(new_change + 9); + value %= MODULUS; +} + +std::ostream &operator<<(std::ostream &os, const ChangeSequence &changes) { + os << aoc::as_number(changes[0]) << ',' << aoc::as_number(changes[1]) << ',' + << aoc::as_number(changes[2]) << ',' << aoc::as_number(changes[3]); + return os; } class Buyer { @@ -78,13 +85,27 @@ void Buyer::evolve() { xorshift(-5); xorshift(+11); std::int8_t new_price = secret % 10; - curr_changes = add_price_change(curr_changes, new_price - old_price); + curr_changes.add_price_change(new_price - old_price); } class MonkeyMarket { std::vector buyers; - std::unordered_map> - sequence_prices; + + struct PriceEntry { + // running total for each change sequence + std::uint32_t total_bananas = 0; + + private: + // last monkey to encounter this sequence + std::uint32_t last_monkey_plus_1 = 0; + + public: + void update(std::int8_t price, std::size_t monkey_index); + + std::strong_ordering + operator<=>(const PriceEntry &other) const = default; + }; + std::vector price_lookup{ChangeSequence::MODULUS}; public: void evolve(int iter); @@ -94,23 +115,25 @@ class MonkeyMarket { static MonkeyMarket read(std::istream &); }; -void MonkeyMarket::evolve(int iter) { +void MonkeyMarket::PriceEntry::update(std::int8_t price, + std::size_t monkey_index) { + // this assumes we simulate the buyers individually (i.e. not interleaving + // evolve calls) + if (last_monkey_plus_1 != monkey_index + 1) { + total_bananas += price; + last_monkey_plus_1 = monkey_index + 1; + } +} + +void MonkeyMarket::evolve(int num_iters) { for (std::size_t i = 0; i < buyers.size(); ++i) { Buyer &buyer = buyers[i]; buyer.evolve(); - if (iter >= 3) { - auto it = sequence_prices.find(buyer.curr_changes); - if (it == sequence_prices.end()) { - // fill price list with -1 - it = sequence_prices - .emplace(std::piecewise_construct, - std::forward_as_tuple(buyer.curr_changes), - std::forward_as_tuple(buyers.size(), -1)) - .first; - it->second[i] = buyer.price(); - } else if (it->second[i] == -1) { - it->second[i] = buyer.price(); - } + buyer.evolve(); + buyer.evolve(); + for (int iter = 3; iter < num_iters; ++iter) { + buyer.evolve(); + price_lookup[buyer.curr_changes].update(buyer.price(), i); } } } @@ -125,33 +148,13 @@ std::uint64_t MonkeyMarket::get_sum() const { } int MonkeyMarket::find_best_sell_sequence() const { - const auto sell_sequence_value = [](const auto &entry) -> int { - int total = 0; - for (int price : entry.second) { - if (price != -1) { - // cppcheck-suppress useStlAlgorithm - total += price; - } - } - return total; - }; - auto it = - std::ranges::max_element(sequence_prices, {}, sell_sequence_value); + auto it = std::max_element(price_lookup.begin(), price_lookup.end()); if constexpr (aoc::DEBUG) { - std::cerr << "best sequence: " - << pretty_print::repr>( - it->first, {.char_as_number = true}) - << "\n"; - std::cerr << "prices:\n"; - for (int monkey = 0; const auto price : it->second) { - if (price != -1) { - std::cerr << std::setw(5) << monkey << ": " - << aoc::as_number(price) << "\n"; - } - ++monkey; - } + ChangeSequence changes{static_cast( + std::distance(price_lookup.begin(), it))}; + std::cerr << "best sequence: " << changes << "\n"; } - return sell_sequence_value(*it); + return it->total_bananas; } MonkeyMarket MonkeyMarket::read(std::istream &is) { diff --git a/2024/src/test22.cpp b/2024/src/test22.cpp new file mode 100644 index 0000000..25d64df --- /dev/null +++ b/2024/src/test22.cpp @@ -0,0 +1,119 @@ +/****************************************************************************** + * File: test22.hpp + * + * Author: Eric T. Johnson (yut23) + * Created: 2025-01-07 + *****************************************************************************/ + +#include "day22.hpp" // IWYU pragma: associated + +#include "unit_test/unit_test.hpp" + +#include // for size_t +#include // for source_location + +namespace aoc::day22::test { +std::size_t test_ChangeSequence() { + unit_test::TestSuite suite("aoc::day22::ChangeSequence"); + + using namespace unit_test::checks; + constexpr auto check_valid = [](const ChangeSequence &changes, + std::source_location loc = + std::source_location::current()) { + check( + changes[0] >= -9 && changes[0] <= 9, + [&](auto &os) { + os << "first value out of range: " + << aoc::as_number(changes[0]); + }, + loc); + check( + changes[1] >= -9 && changes[1] <= 9, + [&](auto &os) { + os << "second value out of range: " + << aoc::as_number(changes[1]); + }, + loc); + check( + changes[2] >= -9 && changes[2] <= 9, + [&](auto &os) { + os << "third value out of range: " + << aoc::as_number(changes[2]); + }, + loc); + check( + changes[3] >= -9 && changes[3] <= 9, + [&](auto &os) { + os << "fourth value out of range: " + << aoc::as_number(changes[3]); + }, + loc); + }; + + constexpr auto check_values = [](const ChangeSequence &changes, int first, + int second, int third, int fourth, + std::source_location loc = + std::source_location::current()) { + check_equal( + changes[0], first, + [&](auto &os) { + os << "first value incorrect (" << changes.value << ")"; + }, + loc); + check_equal( + changes[1], second, + [&](auto &os) { + os << "second value incorrect (" << changes.value << ')'; + }, + loc); + check_equal( + changes[2], third, + [&](auto &os) { + os << "third value incorrect (" << changes.value << ')'; + }, + loc); + check_equal( + changes[3], fourth, + [&](auto &os) { + os << "fourth value incorrect (" << changes.value << ')'; + }, + loc); + check(changes.value < aoc::math::powi(changes.BASE, 4), [&](auto &os) { + os << "raw value is greater than BASE**4 (" << changes.value << ')'; + }); + }; + + suite.test("add_price_change", [&]() { + ChangeSequence changes{}; + check_valid(changes); + changes.add_price_change(-1); + check_valid(changes); + changes.add_price_change(1); + check_valid(changes); + changes.add_price_change(9); + check_valid(changes); + changes.add_price_change(-9); + check_valid(changes); + check_values(changes, -1, 1, 9, -9); + + changes.add_price_change(2); + check_values(changes, 1, 9, -9, 2); + + changes.add_price_change(3); + check_values(changes, 9, -9, 2, 3); + + changes.add_price_change(4); + check_values(changes, -9, 2, 3, 4); + + changes.add_price_change(8); + check_values(changes, 2, 3, 4, 8); + }); + return suite.done(), suite.num_failed(); +} +} // namespace aoc::day22::test + +int main() { + std::size_t num_failed = 0; + num_failed += aoc::day22::test::test_ChangeSequence(); + return unit_test::fix_exit_code(num_failed); +} diff --git a/aoc_lib/src/lib.hpp b/aoc_lib/src/lib.hpp index 2b8a076..6eb72ba 100644 --- a/aoc_lib/src/lib.hpp +++ b/aoc_lib/src/lib.hpp @@ -439,6 +439,9 @@ class as_number { explicit as_number(T &dest) : dest(dest) {} }; +template +as_number(const T &) -> as_number; + /** * Advent of Code problem part number. */ @@ -518,7 +521,7 @@ struct std::hash> { // instantiate templates in an anonymous namespace, so static analyzers will // check these functions namespace { -[[maybe_unused]] void _lint_helper(std::istream &is) { +[[maybe_unused]] void _lint_helper(std::istream &is, std::ostream &os) { is >> aoc::skip(2); is >> aoc::skip(1); is >> aoc::skip(3); @@ -528,10 +531,14 @@ namespace { is >> aoc::expect_input("foo"); is >> aoc::expect_input(std::string{"bar"}); - char ch; - unsigned char uch; + char ch{}; + unsigned char uch{}; is >> aoc::as_number(ch); is >> aoc::as_number(uch); + + os << aoc::as_number(ch); + os << aoc::as_number(uch); + os << aoc::as_number('a'); } } // namespace diff --git a/aoc_lib/src/unit_test/unit_test.hpp b/aoc_lib/src/unit_test/unit_test.hpp index 8d632d7..8e5d672 100644 --- a/aoc_lib/src/unit_test/unit_test.hpp +++ b/aoc_lib/src/unit_test/unit_test.hpp @@ -714,14 +714,16 @@ void check(auto &&condition, } } -void check_equal(const auto &actual, const auto &expected, +template +void check_equal(const std::type_identity_t &actual, const T &expected, const std::string &info = "", const SL loc = SL::current()) { if (actual != expected) { throw detail::equal_failure_helper(actual, expected, loc, info); } } -void check_equal(const auto &actual, const auto &expected, +template +void check_equal(const std::type_identity_t &actual, const T &expected, std::invocable auto &&info, const SL loc = SL::current()) { if (actual != expected) { @@ -761,6 +763,19 @@ namespace { test("banana", {{'a', 3}, {'b', 1}, {'n', 2}}); test.done(); } + { + unit_test::TestSuite suite("foo"); + suite.test("asdf", []() { + using namespace unit_test::checks; + check(true); + check(true, "info"); + check(true, [](auto &os) { os << "lazy info"; }); + check_equal(1, 1); + check_equal("a", "a", "info"); + std::vector expected{1, 2, 3}; + check_equal({1, 2, 3}, expected, "info"); + }); + } } } // namespace diff --git a/aoc_lib/src/util/math.hpp b/aoc_lib/src/util/math.hpp index a7281c0..55cf34b 100644 --- a/aoc_lib/src/util/math.hpp +++ b/aoc_lib/src/util/math.hpp @@ -30,7 +30,7 @@ constexpr std::array gen_powers_of_10() { } template -int num_digits(IntegerT value) { +constexpr int num_digits(IntegerT value) { constexpr auto POWERS = gen_powers_of_10(); int i; for (i = 0; i < static_cast(POWERS.size()); ++i) { @@ -55,7 +55,7 @@ IntegerT next_power_of_10(IntegerT value) { } template -IntegerT powi(IntegerT base, unsigned int exponent) { +constexpr IntegerT powi(IntegerT base, unsigned int exponent) { if (exponent == 0) { return 1; } else if (exponent == 1) {