8.2.7. cpalnumber.cpp

Review

The introduction to the palindrome-number problem outlined an overall solution, and designed two algorithms for identifying general palindromes. The examples in this section implement two C-string-based programs that follow the outline and implement the designed palindrome algorithms. Generating and squaring candidate numbers are inherently arithmetic operations. Although it is possible to isolate and examine the individual digits in a number (as illustrated in a subsequent example), it is both easier and in line with the subject of the current chapter to convert the number to a string, allowing the program to use string functions to validate the puzzle's requirements.

The algorithms rely on and develop two additional functions: itoa and reverse. When designing algorithms, developers often encounter sub-problems that, independent of the problem's complexity, they logically treat as a single, atomic step. This approach simplifies the design, reading, understanding, and programming of algorithms. We can choose to visualize function calls as a single operation or as a gateway to a set of complex statements.

Identifying C-String Palindromes With The Finger Method

The first solution converts the squared candidate number to a C-string with some variant of the "ASCII to integer" function: atoi. The ANSI standard doesn't require this function, so the example creates one following the initial puzzle solution. Once the solution squares the candidate number, it converts it to a C-string. This solution calculates index values for the left- and right-hand fingers to select characters for comparison.

#define _CRT_SECURE_NO_WARNINGS						// Microsoft only
#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 - begin case analysis
		if (strlen(s) < 6)					// (f/IV.i)
			continue;

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

		//----------------------------------------------	   IV.iii - palindrome detection
		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;
			break;
		}
	}

	return 0;
}
cpalnumber1.cpp: The C-string pal-number solution using the finger method. The Roman numerals in the comments correspond to the solution outline steps. s[0] and s[i] represent the left-hand and s[strlen(s) - 1] the right. Step IV.iii implements algorithm 1.
  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 using one version of itoa. The text presents a simple version of this function below.
  5. Microsoft's safe or secure version.
  6. If the squared number has fewer 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.
  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.
  10. Compares the characters indicated by the left hand and right hands; end the loop at the first mismatch. break applies to the inner for-loop.
  11. The string is a palindrome only if "the fingers meet in the middle of the string," satisfying all puzzle requirements. break applies to the outer for-loop, ending the program.

Converting Numbers To C-Strings

Typecasting works well when the original and converted datatypes are similar. For example, a program can cast one integer type to another, or one floating-point type to another, but it can't cast a number to a string. When available, the itoa function can perform the conversion. Although various versions are readily available on the Internet, creating a simple version offers valuable insight into a common task. Given an integer, convert it into a string that looks like the number. The function can process the digits in the number from left to right or right to left. Each approach offers advantages and disadvantages, but both must isolate each digit and convert it into an ASCII character. This task is more abstract and less familiar than detecting a palindrome, so the examples present their solutions with little introduction but with detailed explanations.

Left To RightRight To Left
Advantages
Digits are processed in final string order
Allows using C-string functions
Uses little temporary storage
Disadvantages
Digit conversion requires complex mathematical operations
Advantages
Digit conversion uses familiar arithmetic operations
Disadvantages
Requires large temporary storage, must assume its size
Digits are processed and stored in reverse string order
Stored digits must be reversed
Advantages and disadvantages of itoa processing order. The relative numbers of advantages and disadvantages for each order seem to favor left-to-right processing. Nevertheless, new programmers generally find the right-to-left order easier to understand. Both orders rely on the mod operator, %, Integer division, and typecasting. Introduced in chapter 2, these are the only "unusual" operations right-to-left processing requires, and they should now be familiar.

 

char* itoa(int n, char* s)				// convert n to a string, store it in s
{
	bool	sign = n < 0;				// capture the sign if n is negative
	char	temp[100];				// stores chars in reverse order
	int	index = 0;				// index into temp

	if (sign)					// captures the sign if n is negative
		n = -n;					// make n positive for processing

	// converts the number to characters right to left - the function's heart
	do
	{
		temp[index++] = (char)(n % 10 + '0');	// isolates and converts the rightmost digit
		n /= 10;				// discards lowest (right-most) digit
	} while (n);					// ends when n is 0

	// prepares the output
	int i = 0;					// processing index into s
	if (sign) s[i++] = '-';				// insert '-' into s if n was negative

	// reverses the digits
	while(index > 0)
		s[i++] = temp[--index];			// copy digits from temp to s backwards

	s[i] = '\0';					// null-terminate s, making it a C-string

	return s;					// return s as a convenience
}
itoa function (1): Right to left. A simple but general function that converts integers (n) to C-strings (s). The function's heart is the do-loop that isolates the number's digits and converts them to characters. The loop works right-to-left, isolating the one's or rightmost digit, and converts it to a character:
  1. n % 10 isolates the one's digit. For example, if n = 123, 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 step 1. Continuing the previous example, 123 % 10 = 3, and the ASCII code for '0' is 48; so, 3 + 48 = 51. Locate 51 in an ASCII table and verify that 51 is the code for a character '3.'
  3. (char)(n % 10 + '0') casts the numeric result to a character. For example, (char)51 casts 51 to a character '3.'
  4. temp[index++] = ... stores the character in the temporary array. index points to the next available array element, and is incremented after each insertion.
  5. n /= 10; discards the one's digit with truncating integer division; this operation shifts the number right by one decimal place. For example, if n = 123, 123 / 10 = 12
  6. Discarding the last digit makes n 0, ending the loop.
The while-loop reverses the temp C-string. A reverse function, implemented later in the section, could also work well for this purpose.

Processing the number from the left to the right allows the function to append characters at the end of the string, reducing the temporary storage and eliminating the need to reverse the processed string. However, two aspects of this approach complicate the function. First, the function uses the C-string concat function to append the converted characters. The function's prototype, char* strcat(char* dest, const char* source );, underscores the problem: source is a C-string, not a single character. Second, the process of isolating each digit assumes an understanding of the positional number system (aka a place-value notation). While the positional system is familiar to most software developers, the isolation process uses some mathematics that are possibly less familiar.

Example (n = 123): 123
Digit Positions: 210
Number Evaluation: 1 × 102 + 2 × 101 + 3 × 100
⌊log10 n⌋ + 1 2
n / 10p, p = 2: 123 / 102 = 123 / 100 = 1
p = 1: 123 / 101 = 123 / 10 = 12
p = 0: 123 / 100 = 123 / 1 = 123
Digit isolation concepts. The value of a number expressed in a positional number system is the sum of the products of the digits and the base raised to the power of the position. For example, in a base-10 number system, a digit d in position p has the value p × 10p. The positions are numbered right to left, beginning with 0.

The number of digits in a base-10 number n, is ⌊log10 n⌋ + 1, where the L-shaped symbols are the floor function, which evaluates to the largest integer not greater than the argument. While the math library has a floor function, casting also works, and is familiar and efficient. The expression for discarding the rightmost digits is n / 10p (using integer division), where p is the digit's position, with the one's digit in position 0. The following itoa function uses the pow and log10 functions from the <cmath> header file.

 

char* itoa(int n, char* s)						// convert n to a string, store it in s
{
	char	temp[2];						// converts a character to a C-string
	s[0] = temp[1] = '\0';						// null-terminates s and temp

	if (n == 0)							// returns '0' if n is 0
	{
		strcat(s, "0");						// appends '0' to s
		return s;
	}

	if (n < 0)							// captures the sign if n is negative
	{
		strcat(s, "-");						// appends '-' to s
		n = -n;							// makes n positive for processing
	}

	// converts the number to characters from left to right - the function's heart
	for (int digit = (int)log10(n); digit >= 0; digit--)		// iterates through the number's digits
	{
		temp[0] = char((n / (int)pow(10, digit)) % 10 + '0');	// isolates and converts the leftmost digit
		strcat(s, temp);					// appends the character digit to s
	}

	return s;							// return s as a convenience
}
itoa function (2): Left to right. The function solves the concatenation problem by creating a two-character array named temp. Storing a null-terminator in temp[1] makes it a C-string. As the function isolates and converts the digits, it stores them in temp[0], allowing it to use strcat to append them to s, one by one.
  1. digit = (int)log10(n) initializes the for-loop at the leftmost digit. Number positions follow the same rules as array indexes: 0 to the size minus 1. So the leftmost digit is in position ⌊log10 n⌋ + 1 - 1, and the plus and minus 1s cancel. For example, if n = 123, log10(123) = 2.089905111, which truncates to 2. The loop includes 0 in the count with digit >= 0.
  2. n / (int)pow(10, digit) isolates the leftmost digit. Continuing the example with n = 123, 123 / 102 = 123 / 100 = 1, using integer division. For the second digit, the example becomes 123 / 101 = 123 / 10 = 12.
  3. ... % 10 + '0' isolates the rightmost digit and converts it to the corresponding ASCII code. The % 10 operation is necessary when step 2 produces a number with two or more digits.
  4. char( ... ) casts the ASCII code to a character.
  5. temp[0] = ... stores the converted character in the C-string temp, allowing concatenation.
  6. strcat(s, temp); appends the converted character to the end of C-string s.

Identifying C-String Palindromes With The Reverse Method

A palindrome is a string that reads the same forwards and backward, suggesting the next solution of the palindrome-number puzzle. The solution copies the candidate string, reverses it, and then compares it to the original string. If the two strings are the same (i.e., if they are equal), then the candidate string is a palindrome, but it's not a palindrome if the strings are not the same. The code for the reverse function follows the second palindrome program.

#define _CRT_SECURE_NO_WARNINGS						// Microsoft only
#include <iostream>
#include <cstring>
using namespace std;

void reverse(char* s);

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

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

		//----------------------------------------------	   IV - begin case analysis
		if (strlen(s) < 6)					// (IV.i)
			continue;

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

		//----------------------------------------------	   IV.iii - palindrome detection
		char	r[100];						// (a)

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

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

	return 0;
}
cpalnumber2.cpp: The C-string pal-number solution using the reverse method. An examination of the logic diagram of the puzzle's solution suggests that the finger and reverse methods are identical until step IV.iii. Consequently, this example begins its elaboration at that point in the program where it copies the candidate string, reverses the copy, and compares the two strings, identifying a palindrome if they 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 with the strcpy function.
  3. Calls the reverse function, defined below, to reverse C-string r.
  4. Tests the two C-strings for equality with the strcmp function, which returns a zero if the strings are the same, implying s is a palindrome.
Caution

C++ manipulates C-strings as character pointers. Consequently, operators operate on the string's address and not its contents:

The reverse Function

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 copying 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. The reverse function draws elements from two previously solved problems. First, the text addressed the problem of exchanging the contents of two variables in The Swapping Problem example, where it demonstrated the need for an intermediate temporary variable. The first version of the cpalnumber program above (Figure 1) is the second source contributing to the reverse function. The for-loop driving the reverse operation is functionally identical to Figure 1(i), and the operations used to access the two swapped variables are identical to Figure 1(j).
  1. C-string s is a null-terminated character array that the program passes by pointer, making it an INOUT passing mechanism.
  2. The for-loop operates for half the length of string s, swapping pairs of characters. Unlike cpalindrome1.cpp, where going past the string's middle is merely an inefficiency, in this situation, it is an error. Try to explain why?
  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., overwriting the character currently saved in s[i] - "freeing" the array element at location s[strlen(s) - 1 - i].
  5. To complete the swap operation, the function copies the character saved in temp to the location of the rightmost character, s[strlen(s) - 1 - i].
Although the finger and reverse methods approach the palindrome test in different ways, their code is surprisingly similar.

General Palindromes

The definition of a palindrome allows phrases, which may include spaces, punctuation, and mixed-case letters. Palindromes typically don't consider these elements. However, if the previous palindrome-detection algorithms filter out these characters and convert alphabetic characters to the same case, either algorithm can detect a general palindrome.

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

	int j = 0;					// filters out spaces, punctuation, and 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 validation function. The first part of the function filters out space and punctuation characters with isalnum, which only returns true if its argument is an alphabetic or digit character. tolower converts upper case letters to lower case but does not affect other characters. Equivalently, the function can replace tolower with toupper. The function copies and reverses the input string. Finally, it compares the strings using the strcmp function, returning true if s is a palindrome and false if it is not.

Downloadable C-string palindrome programs and functions

ViewDownloadComments
cpalnumber1.cpp cpalnumber1.cpp C-string solution of the palindrome-number puzzle using the finger method
itoa1.cpp itoa1.cpp itoa right-to-left processing order. Includes a validating driver
itoa2.cpp itoa2.cpp itoa left-to-right processing order. Includes a validating driver
cpalnumber2.cpp cpalnumber2.cpp C-string solution of the palindrome-number puzzle using the reverse method
creverse.cpp creverse.cpp Function to reverse a C-string
is_palindrome.cpp is_palindrome.cpp The compleat is_palindrome function with the necessary include directives