8.2.7. cpalnumber.cpp

Review

The demonstrations appearing in this section implement the algorithms developed in the previous section and rely on previously introduced concepts. Please review the following as needed:

cpalnumber.cpp presents two C-string-based programs that solve the Palindrome-Number Problem. Both programs follow the solution outline formed in that section, and each program implements one of the algorithms developed there. The first program (Figure 1) uses a single C-string and compares pairs of characters in it. The second program (Figure 3) copies the C-string that contains a potential palindrome, reverses it, and then compares the original and the reversed C-strings for equality.

Programming The "Finger" Solution

Time: 00:09:27 | Download: Large Small | Streaming
#include <iostream>
#include <cstring>
using namespace std;

int main()
{
	for (int number = 1; ; number++)				// (a/I.i and I.ii)
	{
		int	square = number * number;			// (b/II)

		//----------------------------------------------	   III
		char	s[100];						// (c)
		//_itoa(square, s, 10);					// (d)
		_itoa_s(square, s, 100, 10);				// (e)

		//----------------------------------------------	   IV
		if (strlen(s) < 6)					// (f/IV.i)
			continue;

		if (s[0] == '0' || s[strlen(s) - 1] == '0')		// (g/IV.ii)
			continue;

		//----------------------------------------------	   IV.iii
		int	i;						// (h)
		for (i = 0; i < strlen(s) / 2; i++)			// (i)
			if (s[i] != s[strlen(s) - 1 - i])		// (j)
				break;

		if (i == strlen(s) / 2)					// (k)
		{
			cout << number << " " << square << endl;	// (l)
			break;
		}
	}

	return 0;
}
The "finger" version of cpalnumber.cpp. The Roman numerals appearing in the comments correspond to the steps given in the solution outline for the palindrome-number problem. Step IV.iii of the solution outline is the palindrome verification test, which follows algorithm 1 and is implemented by the statements (h) through (l).
  1. Generates a list of candidate numbers. Omitting the middle expression makes this an infinite loop.
  2. Squares the candidate number.
  3. A character array that will hold the squared number when converted to a C-string.
  4. Converts square to a C-string - common version of itoa.
  5. Microsoft's safe or secure version.
  6. If the squared number has less than six digits, skip the other tests and get the next candidate.
  7. If the squared number begins or ends with a 0, skip the last test and get the next candidate. None of the itoa functions place a leading 0 in the converted string, but the example includes the test for the demonstration.
  8. Define the loop control variable outside the scope of the for-loop so that it is still in scope and usable at step (k).
  9. Move the fingers toward the center of the string; i < strlen(s) / 2 divides the string in half, skipping the middle character if the string length is odd.
  10. Compares the character pointed to by the left hand, s[i], to the character pointed to by the right hand, s[strlen(s)-1-i]. The loop continues while each pair of characters matches, but the string is not a palindrome if a pair of characters is different, which ends the palindrome test.
  11. The string is a palindrome only if "our fingers made it to the middle of the string" without finding a mismatched pair of characters.
  12. When a candidate number satisfies all puzzle requirements, print the number and its square, and then break out of the outer for loop.

Converting Numbers To C-Strings

So, what happens if you use a compiler that doesn't support any version of itoa? You can find its source code on the internet, but we can also write a simple version. Given an integer, our task is to convert that integer into a string that looks like the number. We must convert each digit of the number into the ASCII character that represents that digit and then sequence the characters together to form a string.

The solution involves working with an integer one digit of the number at a time. If we work from left to right, the resulting string will contain the digits in the correct order. However, working from left to right is more difficult because we don't know how many digits are in the number, making it difficult to isolate them. Working from right to left is easier, but doing so converts the digits to characters in the reverse order. So, we'll use a stack to reverse the digits. (Download links to the stack code used here are found at the bottom of the Automatic Stack Implementation page.)

Before we can use our itoa function, we must modify the cpalnumber.cpp code in Figure 1 in three ways:

  1. Include the stack header file: #include "stack.h"
  2. Add a prototype at the top of the file: void itoa(int n, char* s);
  3. Replace the function call _itoa_s(square, s, 100, 10); with a call to our function: itoa(square, s);
Time: 00:08:20 | Download: Large Small | Streaming
void itoa(int n, char* s)				// (a)
{
	bool	sign = n < 0;				// (b)
	stack	st;					// (c)

	init_stack(&st);				// (d)

	if (sign)					// (e)
		n = -n;

	// converts the number to characters
	do						// (f)
	{
		push(&st, (char)(n % 10 + '0'));	// (g)
		n /= 10;				// (h)
	} while (n);					// (i)

	if (sign)					// (j)
		push(&st, '-');

	// reverses the characters & makes the C-string
	int	index = 0;				// (k)
	while(size(&st) > 0)				// (l)
		s[index++] = pop(&st);			// (m)
	s[index] = '\0';				// (n)
}
C++ itoa function. The function converts the number n into a string of digits stored in C-string s. Although all candidate numbers generated by the palindrome-number problem are positive, we'll generalize our itoa function to handle negative numbers correctly. If n is 0, the function returns an empty string.
  1. n is the number to convert to a string, s is the C-string where the converted string is stored
  2. Detect and "remembers" if n is negative
  3. The stack used to reverse the order of the converted characters
  4. Initializes the stack (see Figure 2)
  5. Insure that n is non-negative for the following operations
  6. Working from right to left, the do-while-loop converts each digit of n into a character, removes the digit from n, and stops after all digits are converted
  7. Converts the one's digit (i.e., the rightmost digit) into a character
    1. n % 10 isolates the one's digit. The key is remembering how the mod operator works. If we use 123 as an example, 123 % 10 = 3 (10 divides 123 12 times with a remainder of 3)
    2. + '0' adds the ASCII code for a character '0' to the numeric value produced by the previous expression. Continuing the previous example, 123 % 10 = 3 and the ASCII code for '0' (from an ASCII table) is 48; so, 3 + 48 = 51
    3. Find 51 in an ASCII table and notice that 51 is the code for a character '3'
    4. (char)51 casts the numeric 51 to a character '3'
    5. push(&st, . . .); pushes the character onto the stack defined at step (c)
  8. Discards the one's digit (i.e., the rightmost digit). If we return to our example of 123, 123 / 10 = 12 (using integer division), and the operation with assignment stores 12 back into n
  9. Loops while n > 0. Recall that C++ treats 0 as false and non-0 as true. n decreases with each iteration of the loop, and the loop ends when n becomes 0. Continuing the example, 123 % 10 = 12, 12 % 10 = 1, and finally 1 % 10 = 0
  10. Pushes a '-' character on the stack if the original number was negative
  11. Defines and initializes an index into the C-string s
  12. The pop operation removes characters from the stack; so, keep looping while there are still characters on the stack
  13. Removes characters from the stack and appends them to the right side of the string
    1. pop(&st) pops one character off the stack and returns it
    2. s[index++] stores a character at the current index location in s and then increments index
  14. Adds a null termination character to the end of s, which truly makes s a C-string

The Reverse Solution

A palindrome is a string that reads the same forwards and backward. The program described below relies on that definition to solve the palindrome-number problem. The solution copies the candidate string, reverses it, and then compares it to the original string. If the two are the same (i.e. if they are equal), then the candidate string is a palindrome; if the two are not the same, then the candidate is not a palindrome.

This version of the program leaves in place the itoa function written above, but you may switch back to _itoa_s if you wish. The reverse solution (Figure 3) has a great deal in common with the "finger" solution (Figure 1), so only the changed code is described. The code for reverse function is presented in Figure 4.

Time: 00:04:14 | Download: Large Small | Streaming
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;

void itoa(int n, char* s);
void reverse(char* s);

int main()
{
	for (int number = 1; ; number++)
	{
		int	square = number * number;

		char	s[100];
		itoa(square, s);

		if (strlen(s) < 6)
			continue;

		if (s[0] == '0' || s[strlen(s) - 1] == '0')
			continue;

		char	r[100];						// (a)

		strcpy(r, s);						// (b)
		reverse(r);						// (c)

		if (strcmp(s, r) == 0)					// (d)
		{
			cout << number << " " << square << endl;
			break;
		}
	}

	return 0;
}
The reverse version of cplanumber.cpp. Copies the candidate string, reverses the copy, and compares the two strings, identifying a palindrome if the strings are equal.
  1. Defines a new C-string to contain the reversed copy of the candidate palindrome string
  2. Copies the original string, s, to string r; note that the assignment operator, r = s;, will copy the address stored in s to r but will not copy the contents or characters
  3. Calls the reverse function (defined in Figure 4) to reverse C-string r
  4. Tests the two C-strings for equality. Unfortunately, there is no C-string "equals" function. strcmp is a string comparison function that returns a negative value if s comes before r (e.g., like "apple" comes before "zebra"), returns 0 if the C-strings are the same, and returns a positive value if s comes after r (like "zebra" comes after "apple." If strcmp returns a 0, the strings are the same, implying s is a palindrome and ending the for-loop

Caution

if (r == s) will compile but does NOT test the contents of the two C-strings for equality. Both r and s are character arrays, and (like all arrays) the array names are addresses, so r == s returns true if and only if r and s refer to the same C-string. So, r == s will always be false in this program!

The reverse Function

You should carefully compare the body of the reverse function with the "finger" loop in the first version of the program. Although the two versions of the program seem to approach testing for a palindrome in quite different ways, their code is quite similar! The key to understanding this function is to notice that the loop only goes to the middle of the string. What would happen if we allowed the loop to go to the end of the string?

void reverse(char* s)					// (a)
{
	for (size_t i = 0; i < strlen(s) / 2; i++)	// (b)
	{
		char	temp = s[i];			// (c)
		s[i] = s[strlen(s) - 1 - i];		// (d)
		s[strlen(s) - 1 - i] = temp;		// (e)
	}
}
An illustration of the steps taken to swap the positions of two characters in the C-string named s. For the sake of the discussion, imagine that the two elements are s[x] and s[y]. First, save s[x] by coping it to the variable temp; this is step (c) in the figure caption. Second, copy s[y] to s[x]; this is step (d). Last, copy temp to s[y], which is step (e).
A C-string reverse function.
  1. C-string s is a null-terminated character array, but, like all arrays, the program passes it by-pointer. Pass by pointer is an INOUT passing mechanism (see Data Flow Summary)
  2. The for-loop operates for half the length of string s, swapping pairs of characters
  3. Swapping array elements requires a temporary variable. The swap operation saves the leftmost character, s[i], in temp, "freeing" or making available the location s[i]
  4. The operation copies the rightmost character, s[strlen(s) - 1 - i], into the now "free" location of the leftmost character, s[i] - i.e., it overwrites the character currently saved in s[i] - "freeing" the array element at location s[strlen(s) - 1 - i]
  5. Next, the swap operation copies the character saved in temp to the location of the rightmost character, s[strlen(s) - 1 - i]

General Palindromes

"A palindrome is a word, number, phrase, or other sequence of characters which reads the same backward as forward...Sentence-length palindromes ignore capitalization, punctuation, and word boundaries." Our previous palindrome detection algorithms are not robust enough to detect these general palindromes. Fortunately, we only need to filter out the spaces and punction characters and convert all the alphabetic characters to the same case. Once these steps are complete, either of our palindrome algorithms will work.

bool is_palindrome(const char* s)
{
	// filter
	char	temp[1000];				// store the filtered C-string

	int j = 0;					// filters punctuation, converts case
	for (size_t i = 0; i < strlen(s); i++)
		if (isalnum(s[i]))
			temp[j++] = tolower(s[i]);
	temp[j] = '\0';					// null terminate temp

	// check for palindrome
	char	r[1000];				// an array to hold the reversed C-string
	strcpy(r, temp);				// copy the candidate temp to r

	reverse(r);					// reverse C-string r

	return strcmp(r, temp) == 0;			// s is a palindrome if r and temp are equal
}
A general palindrome checker. The first part of the function filters out space and punctuation characters. isalnum returns true if s is an alphabetic or digit character. tolower converts upper case letters to lower case but does not affect other characters. You can also replace tolower with toupper. The "reverse" palindrome algorithm tests the filtered string.

Downloadable Code

All source code files are formatted with 8-character tab stops.