From f9dc41704fc4522b3b892af7f93576b62ad9d60e Mon Sep 17 00:00:00 2001 From: yut23 Date: Fri, 13 Dec 2024 21:33:33 -0500 Subject: [PATCH] 2024 day 11: don't use strings for counting digits Build an array of powers of 10 at compile-time, then use a binary search to get the number of digits. Full input on xrb, fast mode: 200ms -> 140ms --- 2024/src/day11.hpp | 79 +++++++++++++++++++------- 2024/src/test11.cpp | 107 ++++++++++++++++++++++++++++++++++++ tools/cpp/iwyu_mappings.imp | 2 + 3 files changed, 169 insertions(+), 19 deletions(-) create mode 100644 2024/src/test11.cpp diff --git a/2024/src/day11.hpp b/2024/src/day11.hpp index 565b9b6..806b09e 100644 --- a/2024/src/day11.hpp +++ b/2024/src/day11.hpp @@ -8,17 +8,17 @@ #ifndef DAY11_HPP_UOIOTEZD #define DAY11_HPP_UOIOTEZD -#include "lib.hpp" // for DEBUG -#include // for count_if -#include // for array -#include // for size_t -#include // for int64_t -#include // for istream, ostream -#include // for list -#include // for transform_reduce -#include // for string, to_string, stoi -#include // for variant, get, holds_alternative, visit -#include // for vector +#include "lib.hpp" // for DEBUG +#include // for count_if +#include // for array +#include // for size_t +#include // for int64_t +#include // for plus +#include // for istream, ostream, cerr +#include // for transform_reduce +#include // for string, to_string, stoi +#include // for variant, get, holds_alternative, visit +#include // for vector namespace aoc::day11 { @@ -162,8 +162,48 @@ Stone HistoryData::get_stone(stone_value_t value) const { void HistoryData::set_stone(Stone &stone, stone_value_t value) const { if (value >= 1 && value <= 9) { stone = HistoryRef{static_cast(value)}; + } else { + stone = value; + } +} + +template ::digits10> +constexpr std::array gen_powers_of_10() { + std::array arr; + IntegerT x = 1; + for (std::size_t i = 0; i < max_digits; ++i) { + x *= 10; + arr[i] = x; + } + return arr; +} + +template +int num_digits(IntegerT value) { + constexpr auto POWERS = gen_powers_of_10(); + return std::distance( + POWERS.begin(), + std::upper_bound(POWERS.begin(), POWERS.end(), value)) + + 1; +} + +template +IntegerT powi(IntegerT base, unsigned int exponent) { + if (exponent == 0) { + return 1; + } else if (exponent == 1) { + return base; + } else if (exponent == 2) { + return base * base; + } else { + IntegerT tmp = powi(base, exponent / 2); + tmp *= tmp; + if (exponent % 2 == 1) { + tmp *= base; + } + return tmp; } - stone = value; } // Main update routine. @@ -178,14 +218,15 @@ void StoneGroup::blink(const HistoryData &history) { stone_value_t value = std::get(stone); if (value == 0) { stone = history.get_stone(1); - } else if (std::string digits = std::to_string(value); - digits.size() % 2 == 0) { - int half_size = digits.size() / 2; - stone = history.get_stone(std::stoi(digits.substr(0, half_size))); - stones.push_back( - history.get_stone(std::stoi(digits.substr(half_size)))); } else { - stone = history.get_stone(value * 2024); + int ndigits = num_digits(value); + if (ndigits % 2 == 0) { + stone_value_t modulus = powi(10, ndigits / 2); + stone = history.get_stone(value / modulus); + stones.push_back(history.get_stone(value % modulus)); + } else { + stone = history.get_stone(value * 2024); + } } } } diff --git a/2024/src/test11.cpp b/2024/src/test11.cpp new file mode 100644 index 0000000..3db2c3c --- /dev/null +++ b/2024/src/test11.cpp @@ -0,0 +1,107 @@ +/****************************************************************************** + * File: test11.cpp + * + * Author: Eric T. Johnson (yut23) + * Created: 2024-12-11 + *****************************************************************************/ + +#include "day11.hpp" // IWYU pragma: associated +#include "unit_test/pretty_print.hpp" // for repr +#include "unit_test/unit_test.hpp" +#include "util/util.hpp" // for demangle + +#include // for size_t +#include // for string +// IWYU pragma: no_include // for type_info (util::demangle) + +namespace aoc::day11::test { + +template +std::size_t test_powers_of_10() { + const std::string type_name = util::demangle(typeid(IntegerT).name()); + unit_test::PureTest test( + "day11::test_powers_of_10<" + type_name + ">", + +[](IntegerT x) -> IntegerT { return x; }); + + constexpr auto POWERS_OF_10 = gen_powers_of_10(); + std::size_t size = POWERS_OF_10.size(); + IntegerT max = std::numeric_limits::max(); + int i = 0; + IntegerT x = 10; + while (x <= max / 10) { + test(POWERS_OF_10.at(i), x, + "incorrect element at index " + std::to_string(i)); + x *= 10; + ++i; + } + for (std::size_t i = 2; i < size; ++i) { + test(POWERS_OF_10[i], POWERS_OF_10[i - 1] * 10, + "ratio between elements at index " + std::to_string(i) + + " should be 10"); + } + test(POWERS_OF_10[size - 1] / 10, POWERS_OF_10[size - 2], + "ratio between elements at index " + std::to_string(size - 2) + + " should be 10"); + + test.done(); + if (test.num_failed() > 0) { + std::cout << "POWERS_OF_10: " << pretty_print::repr(POWERS_OF_10) + << "\n"; + } + return test.num_failed(); +} + +template +IntegerT powi(IntegerT base, unsigned int exponent) { + if (exponent == 0) { + return 1; + } else if (exponent == 1) { + return base; + } else if (exponent == 2) { + return base * base; + } else { + IntegerT tmp = powi(base, exponent / 2); + tmp *= tmp; + if (exponent % 2 == 1) { + tmp *= base; + } + return tmp; + } +} + +template +std::size_t test_num_digits() { + const std::string type_name = util::demangle(typeid(IntegerT).name()); + unit_test::PureTest test("day11::test_num_digits<" + type_name + ">", + &aoc::day11::num_digits); + + test(1, 1); + for (unsigned int i = 0; i <= std::numeric_limits::digits10; + ++i) { + IntegerT p10 = powi(static_cast(10), i); + if (p10 > 1) { + test(p10 - 1, i); + } + test(p10, i + 1); + test(p10 + 1, i + 1); + } + test(std::numeric_limits::max(), + std::numeric_limits::digits10 + 1); + + return test.done(), test.num_failed(); +} + +} // namespace aoc::day11::test + +int main() { + std::size_t failed_count = 0; + failed_count += aoc::day11::test::test_powers_of_10(); + failed_count += aoc::day11::test::test_powers_of_10(); + failed_count += aoc::day11::test::test_powers_of_10(); + failed_count += aoc::day11::test::test_powers_of_10(); + failed_count += aoc::day11::test::test_num_digits(); + failed_count += aoc::day11::test::test_num_digits(); + failed_count += aoc::day11::test::test_num_digits(); + failed_count += aoc::day11::test::test_num_digits(); + return unit_test::fix_exit_code(failed_count); +} diff --git a/tools/cpp/iwyu_mappings.imp b/tools/cpp/iwyu_mappings.imp index 0c75453..88128a1 100644 --- a/tools/cpp/iwyu_mappings.imp +++ b/tools/cpp/iwyu_mappings.imp @@ -23,4 +23,6 @@ { symbol: ["std::ostream", "private", "", "public" ] }, # this is internal { include: ["", "private", "", "public" ] }, +# IWYU thinks this is from if it's included from anywhere +{ symbol: ["std::tuple", "private", "", "public" ] }, ]