day2: Use a LUT for pow10 operations for 50% performance improvement

This commit is contained in:
2025-12-02 13:38:24 -08:00
parent 4c1a7bf0be
commit fb24991102

View File

@@ -2,6 +2,19 @@ use aoc_runner_derive::{aoc, aoc_generator};
use itertools::Itertools; use itertools::Itertools;
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
const POW10MAX: usize = u64::MAX.ilog10() as usize;
const POW10: [u64; POW10MAX] = pow10_lut();
const fn pow10_lut<const N: usize>() -> [u64; N] {
let mut res = [0; N];
let mut i = 0;
while i < N {
res[i] = 10u64.pow(i as u32);
i += 1;
}
res
}
#[aoc_generator(day2)] #[aoc_generator(day2)]
fn parse(input: &str) -> Vec<RangeInclusive<u64>> { fn parse(input: &str) -> Vec<RangeInclusive<u64>> {
input input
@@ -62,12 +75,12 @@ fn part1_arithmetic_brute(input: &[RangeInclusive<u64>]) -> u64 {
for r in input { for r in input {
// println!("Range: {:?}", r); // println!("Range: {:?}", r);
for product in r.clone() { for product in r.clone() {
let n_digits = product.ilog10() + 1; let n_digits = (product.ilog10() + 1) as usize;
if n_digits % 2 != 0 { if n_digits % 2 != 0 {
continue; continue;
} }
let decade = 10u64.pow(n_digits / 2); let decade = POW10[n_digits / 2];
let lhs = product / decade; let lhs = product / decade;
let rhs = product % decade; let rhs = product % decade;
// println!("{product} {n_digits}: D:{decade} L:{lhs} R:{rhs}"); // println!("{product} {n_digits}: D:{decade} L:{lhs} R:{rhs}");
@@ -84,21 +97,19 @@ fn part2_arithmetic_brute(input: &[RangeInclusive<u64>]) -> u64 {
let mut invalid_sum = 0; let mut invalid_sum = 0;
for r in input { for r in input {
for product in r.clone() { for product in r.clone() {
let n_digits = product.ilog10() + 1; let n_digits = (product.ilog10() + 1) as usize;
for n in 1..=n_digits / 2 { for n in 1..=n_digits / 2 {
let repetitions = n_digits / n; let repetitions = n_digits / n;
if n_digits % n != 0 { if n_digits % n != 0 {
continue; continue;
} }
let decade = 10u64.pow(n_digits - n); let decade = POW10[n_digits - n];
let lhs = product / decade; let lhs = product / decade;
let remainder = product % decade; let remainder = product % decade;
// for each repetition we multiply by 10^(rep * n) // for each repetition we multiply by 10^(rep * n)
let expected_remainder = (0..repetitions - 1) let expected_remainder = (0..repetitions - 1).map(|rep| lhs * POW10[rep * n]).sum();
.map(|rep| lhs * 10u64.pow(rep * n))
.sum();
if remainder == expected_remainder { if remainder == expected_remainder {
invalid_sum += product; invalid_sum += product;
@@ -114,19 +125,19 @@ fn part2_arithmetic_brute(input: &[RangeInclusive<u64>]) -> u64 {
fn part1_clever(input: &[RangeInclusive<u64>]) -> u64 { fn part1_clever(input: &[RangeInclusive<u64>]) -> u64 {
let mut invalid_sum = 0; let mut invalid_sum = 0;
for r in input { for r in input {
let n_digits = r.start().ilog10() + 1; let n_digits = (r.start().ilog10() + 1) as usize;
let mut lhs = if n_digits % 2 != 0 { let mut lhs = if n_digits % 2 != 0 {
// If the number of digits is odd, we start our guess at the first possibility in the next decade // If the number of digits is odd, we start our guess at the first possibility in the next decade
10u64.pow(n_digits / 2) POW10[n_digits / 2]
} else { } else {
let start_decade = 10u64.pow(n_digits / 2); let start_decade = POW10[n_digits / 2];
r.start() / start_decade r.start() / start_decade
}; };
// we will guess the next repeater, and check if it's in range, if not we are done. // we will guess the next repeater, and check if it's in range, if not we are done.
loop { loop {
let repeater = lhs * 10u64.pow(lhs.ilog10() + 1) + lhs; let repeater = lhs * POW10[(lhs.ilog10() + 1) as usize] + lhs;
if repeater < *r.start() { if repeater < *r.start() {
lhs += 1 lhs += 1
} else if r.contains(&repeater) { } else if r.contains(&repeater) {