Skip to content

Commit

Permalink
2024 day 11: don't use strings for counting digits
Browse files Browse the repository at this point in the history
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
  • Loading branch information
yut23 committed Dec 14, 2024
1 parent bd863f3 commit f9dc417
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 19 deletions.
79 changes: 60 additions & 19 deletions 2024/src/day11.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@
#ifndef DAY11_HPP_UOIOTEZD
#define DAY11_HPP_UOIOTEZD

#include "lib.hpp" // for DEBUG
#include <algorithm> // for count_if
#include <array> // for array
#include <cstddef> // for size_t
#include <cstdint> // for int64_t
#include <iostream> // for istream, ostream
#include <list> // for list
#include <numeric> // for transform_reduce
#include <string> // for string, to_string, stoi
#include <variant> // for variant, get, holds_alternative, visit
#include <vector> // for vector
#include "lib.hpp" // for DEBUG
#include <algorithm> // for count_if
#include <array> // for array
#include <cstddef> // for size_t
#include <cstdint> // for int64_t
#include <functional> // for plus
#include <iostream> // for istream, ostream, cerr
#include <numeric> // for transform_reduce
#include <string> // for string, to_string, stoi
#include <variant> // for variant, get, holds_alternative, visit
#include <vector> // for vector

namespace aoc::day11 {

Expand Down Expand Up @@ -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<int>(value)};
} else {
stone = value;
}
}

template <typename IntegerT,
int max_digits = std::numeric_limits<IntegerT>::digits10>
constexpr std::array<IntegerT, max_digits> gen_powers_of_10() {
std::array<IntegerT, max_digits> arr;
IntegerT x = 1;
for (std::size_t i = 0; i < max_digits; ++i) {
x *= 10;
arr[i] = x;
}
return arr;
}

template <std::integral IntegerT>
int num_digits(IntegerT value) {
constexpr auto POWERS = gen_powers_of_10<IntegerT>();
return std::distance(
POWERS.begin(),
std::upper_bound(POWERS.begin(), POWERS.end(), value)) +
1;
}

template <typename IntegerT>
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.
Expand All @@ -178,14 +218,15 @@ void StoneGroup::blink(const HistoryData &history) {
stone_value_t value = std::get<stone_value_t>(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<stone_value_t>(10, ndigits / 2);
stone = history.get_stone(value / modulus);
stones.push_back(history.get_stone(value % modulus));
} else {
stone = history.get_stone(value * 2024);
}
}
}
}
Expand Down
107 changes: 107 additions & 0 deletions 2024/src/test11.cpp
Original file line number Diff line number Diff line change
@@ -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 <cstddef> // for size_t
#include <string> // for string
// IWYU pragma: no_include <typeinfo> // for type_info (util::demangle)

namespace aoc::day11::test {

template <typename IntegerT>
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<IntegerT>();
std::size_t size = POWERS_OF_10.size();
IntegerT max = std::numeric_limits<IntegerT>::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 <typename IntegerT>
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 <class IntegerT>
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<IntegerT>);

test(1, 1);
for (unsigned int i = 0; i <= std::numeric_limits<IntegerT>::digits10;
++i) {
IntegerT p10 = powi(static_cast<IntegerT>(10), i);
if (p10 > 1) {
test(p10 - 1, i);
}
test(p10, i + 1);
test(p10 + 1, i + 1);
}
test(std::numeric_limits<IntegerT>::max(),
std::numeric_limits<IntegerT>::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<int>();
failed_count += aoc::day11::test::test_powers_of_10<unsigned int>();
failed_count += aoc::day11::test::test_powers_of_10<std::int64_t>();
failed_count += aoc::day11::test::test_powers_of_10<std::uint64_t>();
failed_count += aoc::day11::test::test_num_digits<int>();
failed_count += aoc::day11::test::test_num_digits<unsigned int>();
failed_count += aoc::day11::test::test_num_digits<std::int64_t>();
failed_count += aoc::day11::test::test_num_digits<std::uint64_t>();
return unit_test::fix_exit_code(failed_count);
}
2 changes: 2 additions & 0 deletions tools/cpp/iwyu_mappings.imp
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@
{ symbol: ["std::ostream", "private", "<iostream>", "public" ] },
# this is internal
{ include: ["<bits/std_abs.h>", "private", "<cstdlib>", "public" ] },
# IWYU thinks this is from <variant> if it's included from anywhere
{ symbol: ["std::tuple", "private", "<tuple>", "public" ] },
]

0 comments on commit f9dc417

Please sign in to comment.