diff --git a/CPP/miscelanious/howManyValidISBNNumbersAreThere/11.cpp b/CPP/miscelanious/howManyValidISBNNumbersAreThere/11.cpp index 9cbd0c4..2fa8efb 100644 --- a/CPP/miscelanious/howManyValidISBNNumbersAreThere/11.cpp +++ b/CPP/miscelanious/howManyValidISBNNumbersAreThere/11.cpp @@ -3,6 +3,9 @@ #include #include #include +#ifdef _OPENMP +#include +#endif #ifndef CHECK_ISBN_CPP #define CHECK_ISBN_CPP @@ -68,35 +71,111 @@ bool checkISBN(const std::vector isbn) { return !(sum % CHECK_NUMBER); } -std::vector intToVector(unsigned long long int number) { - std::vector numbers; - while (number > 0) { - numbers.push_back(number % 10); - number /= 10; - } - std::reverse(numbers.begin(), numbers.end()); +// Optimisation 1 – no heap allocation: intToVector() removed; checkAll() +// uses only stack variables in the nested loops below. +// +// Optimisation 2 – algorithmic reduction (10^10 → 10^9 iterations): +// ISBN-10 validity: 10*d0 + 9*d1 + ... + 2*d8 + 1*d9 ≡ 0 (mod 11) +// Given a 9-digit prefix d0..d8 the check digit is determined: +// d9 = (11 - S%11) % 11 where S = weighted sum of prefix +// The number is valid iff d9 ≤ 9 (i.e. d9 ≠ 10). +// +// Optimisation 3 – incremental partial sums + OpenMP: +// Each loop level accumulates its contribution once, not inside the +// innermost body. The outermost loop is parallelised with OpenMP. +// +// Optimisation 4 – separated I/O: +// File writing is done in a second serial pass with a large buffer so +// it doesn't contend with (or race against) the parallel counting pass. +long long countISBNs() { + long long sum = 0; - return numbers; -} - -int checkAll() { - int sum = 0; - std::ofstream file; - file.open("ISBN.txt"); - for (unsigned long long int i = HIGHEST_ISBN; i >= 1; i--) { - // if(DEBUG) std::cout << i << std::endl; - if (checkISBN(intToVector(i))) { - ++sum; - file << std::to_string(i) << "\n"; +#ifdef _OPENMP +#pragma omp parallel for reduction(+ : sum) schedule(static) +#endif + for (int d0 = 0; d0 <= 9; d0++) { + int s0 = 10 * d0; + for (int d1 = 0; d1 <= 9; d1++) { + int s1 = s0 + 9 * d1; + for (int d2 = 0; d2 <= 9; d2++) { + int s2 = s1 + 8 * d2; + for (int d3 = 0; d3 <= 9; d3++) { + int s3 = s2 + 7 * d3; + for (int d4 = 0; d4 <= 9; d4++) { + int s4 = s3 + 6 * d4; + for (int d5 = 0; d5 <= 9; d5++) { + int s5 = s4 + 5 * d5; + for (int d6 = 0; d6 <= 9; d6++) { + int s6 = s5 + 4 * d6; + for (int d7 = 0; d7 <= 9; d7++) { + int s7 = s6 + 3 * d7; + for (int d8 = 0; d8 <= 9; d8++) { + int d9 = (11 - (s7 + 2 * d8) % 11) % 11; + if (d9 <= 9) + ++sum; + } + } + } + } + } + } + } } } - file << "There are " << sum << " valid ISBN numbers\n"; - file.close(); return sum; } +void writeISBNsToFile() { + static const int BUF = 1 << 20; // 1 MB write buffer + std::ofstream file; + file.rdbuf()->pubsetbuf(nullptr, BUF); + file.open("ISBN.txt"); + long long written = 0; + + for (int d0 = 0; d0 <= 9; d0++) { + int s0 = 10 * d0; + for (int d1 = 0; d1 <= 9; d1++) { + int s1 = s0 + 9 * d1; + for (int d2 = 0; d2 <= 9; d2++) { + int s2 = s1 + 8 * d2; + for (int d3 = 0; d3 <= 9; d3++) { + int s3 = s2 + 7 * d3; + for (int d4 = 0; d4 <= 9; d4++) { + int s4 = s3 + 6 * d4; + for (int d5 = 0; d5 <= 9; d5++) { + int s5 = s4 + 5 * d5; + for (int d6 = 0; d6 <= 9; d6++) { + int s6 = s5 + 4 * d6; + for (int d7 = 0; d7 <= 9; d7++) { + int s7 = s6 + 3 * d7; + for (int d8 = 0; d8 <= 9; d8++) { + int s = s7 + 2 * d8; + int d9 = (11 - s % 11) % 11; + if (d9 <= 9) { + long long isbn = (long long)d0 * 1000000000LL + + d1 * 100000000 + d2 * 10000000 + + d3 * 1000000 + d4 * 100000 + d5 * 10000 + + d6 * 1000 + d7 * 100 + d8 * 10 + d9; + file << isbn << '\n'; + ++written; + } + } + } + } + } + } + } + } + } + } + file << "There are " << written << " valid ISBN numbers\n"; + file.close(); +} + int main() { - checkAll(); + long long count = countISBNs(); + std::cout << "There are " << count << " valid ISBN numbers\n"; + writeISBNsToFile(); return 0; } diff --git a/CPP/miscelanious/howManyValidISBNNumbersAreThere/bench_opt b/CPP/miscelanious/howManyValidISBNNumbersAreThere/bench_opt new file mode 100755 index 0000000..222a6f9 Binary files /dev/null and b/CPP/miscelanious/howManyValidISBNNumbersAreThere/bench_opt differ diff --git a/CPP/miscelanious/howManyValidISBNNumbersAreThere/bench_optimized.cpp b/CPP/miscelanious/howManyValidISBNNumbersAreThere/bench_optimized.cpp new file mode 100644 index 0000000..05aaaa1 --- /dev/null +++ b/CPP/miscelanious/howManyValidISBNNumbersAreThere/bench_optimized.cpp @@ -0,0 +1,90 @@ +// Optimized ISBN-10 counter. +// +// Three improvements over the original: +// +// 1. NO HEAP ALLOCATION — original calls intToVector() on every iteration, +// which does a heap alloc+free (std::vector) for each of 10^10 numbers. +// Here we use only stack variables. +// +// 2. ALGORITHMIC REDUCTION — ISBN-10 validity condition: +// 10*d0 + 9*d1 + ... + 2*d8 + 1*d9 ≡ 0 (mod 11) +// Given any 9-digit prefix d0..d8 we can solve for d9 directly: +// d9 = (11 - S mod 11) mod 11 where S = sum of weighted prefix +// The number is valid iff 0 ≤ d9 ≤ 9 (i.e. d9 ≠ 10). +// → Only 10^9 outer iterations instead of 10^10. +// +// 3. INCREMENTAL WEIGHTED SUM — instead of multiplying each digit by its +// weight inside the innermost loop, partial sums are maintained +// incrementally as digits change, so the innermost body is almost free. +// The compiler can also vectorise the innermost digit loop. +// +// Compile: g++ -O2 -fopenmp bench_optimized.cpp -o bench_opt +// (OpenMP is optional; remove -fopenmp if not available – it just runs +// the outer loop on a single thread.) + +#include +#include + +#ifdef _OPENMP +#include +#endif + +int main() { + auto start = std::chrono::high_resolution_clock::now(); + + long long count = 0; + + // Nested loops over the 9 prefix digits. + // s_k = partial weighted sum up through digit k. + // weight for digit at position i (0-based, MSB first) = 10 - i + // so outer digit d0 has weight 10, d1 has weight 9, ..., d8 has + // weight 2. + +#ifdef _OPENMP +#pragma omp parallel for reduction(+ : count) schedule(static) +#endif + for (int d0 = 0; d0 <= 9; d0++) { + int s0 = 10 * d0; + for (int d1 = 0; d1 <= 9; d1++) { + int s1 = s0 + 9 * d1; + for (int d2 = 0; d2 <= 9; d2++) { + int s2 = s1 + 8 * d2; + for (int d3 = 0; d3 <= 9; d3++) { + int s3 = s2 + 7 * d3; + for (int d4 = 0; d4 <= 9; d4++) { + int s4 = s3 + 6 * d4; + for (int d5 = 0; d5 <= 9; d5++) { + int s5 = s4 + 5 * d5; + for (int d6 = 0; d6 <= 9; d6++) { + int s6 = s5 + 4 * d6; + for (int d7 = 0; d7 <= 9; d7++) { + int s7 = s6 + 3 * d7; + // Innermost: vary d8, weight 2. + for (int d8 = 0; d8 <= 9; d8++) { + int s = s7 + 2 * d8; + // Required check digit (weight 1): + int d9 = (11 - s % 11) % 11; + if (d9 <= 9) + ++count; + } + } + } + } + } + } + } + } + } + + auto end = std::chrono::high_resolution_clock::now(); + double elapsed = std::chrono::duration(end - start).count(); + + std::cout << "Valid ISBNs: " << count << "\n"; + std::cout << "Time: " << elapsed << " s\n"; + +#ifdef _OPENMP + std::cout << "Threads: " << omp_get_max_threads() << "\n"; +#endif + + return 0; +} diff --git a/CPP/miscelanious/howManyValidISBNNumbersAreThere/bench_orig b/CPP/miscelanious/howManyValidISBNNumbersAreThere/bench_orig new file mode 100755 index 0000000..31abc9c Binary files /dev/null and b/CPP/miscelanious/howManyValidISBNNumbersAreThere/bench_orig differ diff --git a/CPP/miscelanious/howManyValidISBNNumbersAreThere/bench_original.cpp b/CPP/miscelanious/howManyValidISBNNumbersAreThere/bench_original.cpp new file mode 100644 index 0000000..ac7f6e3 --- /dev/null +++ b/CPP/miscelanious/howManyValidISBNNumbersAreThere/bench_original.cpp @@ -0,0 +1,69 @@ +// Benchmark version of the original algorithm. +// File I/O removed so we measure pure computation time. +#include +#include +#include +#include + +const int ISBN_LENGTH = 10; +const int CHECK_NUMBER = 11; +const unsigned long long int HIGHEST_ISBN = 9999999999ULL; + +bool checkISBN(const std::vector isbn) { + int sum = 0, t = 0; + for (int i = 0; i < ISBN_LENGTH; i++) { + t += isbn[i]; + sum += t; + } + return !(sum % CHECK_NUMBER); +} + +std::vector intToVector(unsigned long long int number) { + std::vector numbers; + while (number > 0) { + numbers.push_back(number % 10); + number /= 10; + } + std::reverse(numbers.begin(), numbers.end()); + return numbers; +} + +// Run for at most SAMPLE seconds, then extrapolate total time. +static constexpr double SAMPLE_SECS = 20.0; + +long long checkAllTimed(double &elapsed_out) { + auto start = std::chrono::high_resolution_clock::now(); + auto limit = start + std::chrono::duration(SAMPLE_SECS); + long long sum = 0; + unsigned long long i; + for (i = HIGHEST_ISBN; i >= 1; i--) { + if (checkISBN(intToVector(i))) + ++sum; + + // Check wall-clock every 1 million iterations to keep overhead low. + if ((i & 0xFFFFF) == 0) { + if (std::chrono::high_resolution_clock::now() >= limit) + break; + } + } + auto end = std::chrono::high_resolution_clock::now(); + elapsed_out = std::chrono::duration(end - start).count(); + + unsigned long long done = HIGHEST_ISBN - i; + double rate = (double)done / elapsed_out; // numbers/s + double total_est = (double)HIGHEST_ISBN / rate; + + std::cout << "Iterated: " << done << " numbers in " << elapsed_out + << " s\n"; + std::cout << "Rate: " << (long long)rate << " numbers/s\n"; + std::cout << "Estimated total time for full range: " << (long long)total_est + << " s (" << total_est / 60.0 << " min)\n"; + return sum; +} + +int main() { + double elapsed = 0.0; + long long count = checkAllTimed(elapsed); + std::cout << "Valid ISBNs in sampled range: " << count << "\n"; + return 0; +}