4

I have seen previous solutions to this problem, but it is all complete search with a check function and not fast enough for me.

I am working on a C++ program trying to generate all prime palindromes highly efficiently in a given range of integers. For the prime part, I have created a fast primality tester by cutting down on divisors I test by eliminating all multiples of 2 and 3, though suggestions for improvement here would also be appreciated (function pasted below).

My main issue is that I need to generate palindromes fast enough without using the conventional complete search and palindrome test that slowly increments the tested integer. My current search code and primality test are pasted below.

I have tried to increment the digits of a number in the middle then the outer ones, but because over time more digits are added over time I couldn't even piece together an algorithm.

Primality Test:

bool CheckPrime(int n){
    switch (n) {
    case 1: return false;  break;
    case 2: return true;  break;
    case 3: return true;  break;
    default: break;
    }
    if (n % 2 == 0 || n % 3 == 0) {
        return false;
    }
    for (int i = 5; i * i <= n; i = i + 6) {
        if (n % i == 0 || n % (i + 2) == 0) {
            return false;
        }
    }
    return true;
}

Palindrome Test + Main Function

bool CheckPalindrome (int n) {
    string temp = to_string(n);
    reverse(temp.begin(), temp.end());
    if (temp.compare((to_string(n))) == 0) {
        return true;
    }
    else {
        return false;
    }
}


int main() {
int L, R; cin >> L >> R;
vector <int> Palindromes;
for (int i = L; i <= R; i++) {
    if (CheckPalindrome(i)) {
        Palindromes.push_back(i);
    }
}
}
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Hudson
  • 312
  • 2
  • 18
  • for a start you convert n to a string twice, thats going to be expensive. Plus allocate a fixed size buffer for the 2 strings rather than asking for heap all the time. – pm100 Dec 20 '22 at 02:28
  • Your generation of palindromes is horribly inefficient - you essentially check EVERY value in the range to see if it is a palindrome. There are plenty of ways to reduce the burden of that. For example, if your range is `100` to `200`, only the values `1x1` where `x` represents a digit are palindromes. There are only 10 such values, but you're checking `101` values - so at least an order of magnitude more computation. – Peter Dec 20 '22 at 02:32
  • Peter - this is why I asked this question: inefficiency. Thanks for your idea of 1x1, but what if my range span includes more digits i.e. 100 - 10000? How do you establish something the 1x1 for each digit number? – Hudson Dec 20 '22 at 02:35
  • This question's code/phrasing suggests that it came from one of many countless coding challenge/puzzle scam sites. They take advantage of people who want to learn C++ by offering arcane coding puzzles, promising that you don't need to study and learn C++ with a good textbook, just do a bunch of random coding puzzles. Everyone eventually realizes that these pointless coding puzzles are a waste of time, and there's nothing to be learned from them. But only after spending a lot of time doing them. And there's nothing to show for it. – Sam Varshavchik Dec 20 '22 at 02:35
  • I'm sorry Sam, but I'm afraid your comment does not apply to me. I am participating in official coding competitions and came across a problem involving my question. Thanks for your prudent insight though. I will make sure not to fall into those unofficial scams. – Hudson Dec 20 '22 at 02:38
  • `std::string` is not needed. When you have the value, the digits should be known. Then you may mod the digits into a vector, then compare the data instead of using string. Also I recall `std::to_string()` is slower than `std::format`. – Louis Go Dec 20 '22 at 02:38
  • If compile time doesn't count, using template creating a bunch of palindrome then returning them according to the range might be "fastest" way in runtime. – Louis Go Dec 20 '22 at 02:41
  • @Hudson - The approach I used can be extended quite easily. Instead of doing a brute-force check on whether every value is palindrome, work out patterns that allow you to generate ALL palindromes in a range (and not even considering the rest) rather than checking every value. You might need more than one pattern (e.g. for generating palindromes between 100 and 1000) but the idea is the same. – Peter Dec 20 '22 at 03:05
  • @Hudson: "*official coding competitions*" There's no such thing. There are no "official" organizations that have coding competitions. – Nicol Bolas Dec 20 '22 at 04:18
  • *'I have created a fast primality tester'* -- Look up some prime generation algorithms. The bottleneck of your program is likely the prime generation algorithm, not the palindrome check (especially if you have a larger range). – Ranoiaetep Dec 20 '22 at 04:22
  • ACM hosts a mostly credible tournament, though as a software engineering student when I participated 20-some years ago, I was horrified by the that won. IEEE also sponsors one, but I've never been involved in it and hope they better monitor the quality of the submissions. – user4581301 Dec 20 '22 at 04:47
  • A multi-digit prime can only end in the digits 1, 3, 7, 9. Hence, for a palindromic prime, you can immediately reject any multi-digit number starting with digits 0, 2, 4, 5, 6, 8, since its palindrome cannot be prime. – rossum Dec 20 '22 at 09:19

1 Answers1

2

String conversions are not only slow, but put pressure on the memory allocator. This is a big issue if you are performing lots of queries.

Instead, you can use math to reverse the digits. All you do is pull each digit off the right-hand side of your number and add it to the right-hand side of another number:

e.g.

Step      Value     Digit     Reversed
0         12345     -         0
1         1234      5         5
2         123       4         54
3         12        3         543
4         1         2         5432
5         0         1         54321

Notice that by step 3 we can already determine that the number will not be a palindrome, because the "reversed" value has become greater than the remaining value.

The code might look something like this:

bool CheckPalindrome(unsigned int value)
{
    unsigned int reverse = 0;
    while (value > reverse)
    {
        reverse = reverse * 10 + value % 10;
        if (value == reverse) return true;
        value /= 10;
    }
    return value == reverse;
}

Note that the check in the middle of the loop catches the case where the number of digits in the palindrome is odd.

This will help improve performance. And if you really want a speed boost then you should use something like the Sieve of Eratosthenes. Provided you know the maximum value required for prime testing, you can generate the prime table extremely fast.

Here's a quick and dirty implementation. It's not very memory efficient, but it's simple to write. You're welcome to make it use less memory by condensing the data to bits and removing all even entries. That will reduce the memory consumption by a factor of 16.

class PrimeSieve
{
public:
    PrimeSieve(unsigned int maxval)
    {
        isPrime.resize(maxval, 1);
        isPrime[0] = isPrime[1] = 0;
        for(unsigned int i = 2; i < maxval; i++)
        {
            if (!isPrime[i]) continue;
            for(int x = i *2; x < maxval; x += i) isPrime[x] = 0;
        }
    }

    bool operator[](unsigned int value) const
    {
        return isPrime[value] != 0;
    }

    unsigned int size() const {
        return isPrime.size();
    }

private:
    std::vector<char> isPrime;
};

Now, if you have a sieve already computed, then your prime test actually becomes way faster than your palindrome test. So you only need to call it as many times as there are prime numbers. With that approach, you can generate lots of palindromic primes rather quickly:

int main()
{
    PrimeSieve sieve(100000000);
    for (unsigned int i = 1; i < sieve.size(); i++)
    {
        if (sieve[i] && CheckPalindrome(i))
        {
            std::cout << i << " is a palindromic prime.\n";
        }
    }
}

Results, after some experiments

I did a little bit of basic testing, and found to my surprise that using the sieve for prime-testing is actually overkill here, and leads to worse performance -- at least when searching the entire space once.

Because the actual number of palindromic primes is very small compared to the number of primes, it seems there's better efficiency by first using the fast palindrome test, and then performing the naive prime-test if required.

On my computer, searching the first 1 billion integers (to find 5953 total palindromic primes), I get the following results:

Using sieve approach:

Generating prime table: 14.374s
Searching palindromic primes: 1.138s
Total search using sieve: 15.513s

Using naive approach:

Searching palindromic primes: 9.206s
Total search with naive prime calculation: 9.206s
paddy
  • 60,864
  • 6
  • 61
  • 103