8.3.4. palnumber.cpp

Review

Please review the following as needed:

The debugger examples build on previous examples. Please review the following as needed:

The following palnumber demonstrations follow the algorithms developed in the Palindrome-number problem section and parallel the cpalnumber.cpp programs presented previously in this chapter. However, the following demonstrations replace the C-strings used in the earlier versions with instances of the C++ string class. Presented first is a string version of the "finger" solution. The next example creates a function that converts integers into string objects, a function similar to the itoa function we wrote previously. The last demonstration presents a string version of the "reverse" solution.

Programming The "Finger" Solution

Version 1: Time: 00:05:35 | Download: Large Small | Streaming
#include <iostream>
#include <string>
using namespace std;

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

		//----------------------------------------------	   III.
		string	s = to_string(square);				// (c)

		//----------------------------------------------	   IV.
		if (s.length() < 6)					// (d/IV.i)
			continue;

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

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

		if (i == s.length() / 2)				// (i)
		{
			cout << number << " " << square << endl;	// (j)
			break;
		}
	}

	return 0;
}
The "finger" solution with the string class. The "finger" approach assumes that working with the string indexes is easier than altering or copying the string. Logically, the string solution is identical to the C-string solution presented earlier in the chapter. 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 (f) through (j).
  1. Creates a sequence of candidate numbers that might solve the problem; omitting the middle expression creates an infinite for-loop.
  2. Squares the number.
  3. to_string(square) converts a number to an instance of the string class. So, the statement converts the square to a string.
  4. Rejects strings that are less than six characters long (if the number is not at least six digits long, the continue skips all of the statements from there down, and the for-loop gets the next candidate number).
  5. Rejects strings that begin or end with a '0' (to_string will never put a leading 0 in the string, but the program includes the test to complete the demonstration). The string class provides alternate ways of doing this test:
    • if (s.at(0) == '0' || s.at(s.length() - 1) == '0')
    • if (s.find_first_of('0') == 0 || s.find_last_of('0') == s.length() - 1)
    • if (s.find('0') == 0 || s.rfind('0') == s.length() - 1)
    The at function behaves very much like the index operator, [], but includes a range check on the index value. The functions find and find_first behave the same way when the argument is a single character (see "Searching" for more detail) and return the index of the first occurrence of the argument character.
  6. Define the loop control variable outside the scope of the for-loop so that it is still in scope and usable at step (i).
  7. Compares the two characters indicated by the "left and right hands." i < s.length() / 2 divides the string in half, skipping the middle character if the number of characters is odd.
  8. s[i] represents the character pointed at by our left hand, which moves to the right as i increases. s[s.length() - 1 - i] represents the character pointed at by our right hand, which moves to the left as i increases and the indexing expression subtracts it from the string's length. The at function may be used in place of the index operator, [].
  9. Tests a string to see if it is a palindrome: if the fingers reach the middle of the string, it is a palindrome; if the loop ended early through the break statement, then the string is not a palindrome.
  10. 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 Strings

Visual Studio 2012 added the to_string function to C++. Before then, the palnumber example included its own version of to_string. Although writing our own to_string function is no longer necessary, doing so is still a good learning exercise. Internally, computers store textual data in a coded format. Java uses Unicode, a 2-byte integer, to encode text. C++ can represent text as a 1- or 2-byte (wide) character. We focus on 1-byte characters encoded with the older American Standard Code for Information Interchange (ASCII) scheme.

Examine the ASCII table, especially the middle column; look at the codes for the characters '0' through '9' (the red characters). The character '0' is represented by the numeric code 48 (base 10), and the character '9' is represented by the numeric code 57 (base 10). Also, notice that the characters between '0' and '9' are represented by consecutive code values.

Given an integer, our task is to convert that integer into a string that looks like the number. The solution involves working with an integer one digit at a time. If we work from left to right, the string will contain the digits in the correct order. However, working from left to right is difficult because we don't know how many digits the number has. Working backward (right to the left) is easier but converts the digits in reverse order. The string concatenation operator allows us to solve this problem elegantly: we append or concatenate the new characters on the right side of the string as we build it.

Before using our conversion function, we must modify the palnumber.cpp code in Figure 1 in two ways:

  1. Add a prototype at the top of the file: string my_to_string(int n);
  2. Replace the function call to_string(square); with my_to_string(square);
Writing my_to_string: Time: 00:04:40 | Download: Large Small | Streaming
my_to_string & the debugger: Time: 00:01:51 (no audio) | Download: Large Small | Streaming
string my_to_string(int n)			// (a)
{
	string	s;				// (b)
	string	sign;				// (c)

	if (n < 0)				// (d)
	{
		sign = "-";
		n *= -1;
		// n = -n;			// alternate way of making n positive
	}

	do					// (e)
	{
		s = (char)(n % 10 + '0') + s;	// (f)
		n /= 10;			// (g)
	} while (n);				// (h)

	return sign + s;			// (i)
}
The my_to_string function converts an integer to a string.
  1. The number to convert to a string is the function argument.
  2. string s; instantiates an empty string object named s.
  3. string sign; instantiates an empty string object named sign; this string will hold a "-" if n < 0.
  4. If n is negative, (1) set sign to "-" and (2) make n positive.
  5. The do-while loop continues until n becomes 0, implying that n must change in the body of the loop. Since n is non-negative following (d), it must decrease in the loop body for the loop to end. Most of the work done by my_to_string occurs in this loop.
  6. Convert the rightmost digit to a character and concatenate it to the left side of the string s:
    • n % 10 extracts the lowest digit of the number. Remember how the mod operator works: use the value 123 as an example, 123 % 10 = 3 (123 / 10 = 12 with a remainder of 3).
    • + '0' adds the ASCII code for a '0' (48) to the numeric value of the last digit. Continuing the example: 3 + 48 = 51.
    • (char) casts the numeric value represented by the expression inside the grouping parentheses into a character. The ASCII table indicates that 51 is the code for the character '3.' So, (char)51 converts the numeric 51 to a character '3.' Overall, (char)(n % 10 + '0') extracts the rightmost digit from a number and converts it to a character.
    • (char)(n % 10 + '0') + s concatenates the character to the left side of the string. The first time through the loop, s is empty, and the expression on the left of "+" evaluates to the character '3,' so string s becomes "3." The second time through the loop, the character is '2.' The concatenation operation, 2+3 yields the string "32"
  7. n /= 10 discards the one's digit (i.e., the rightmost digit). Returning to the example of 123, 123 / 10 = 12 (using integer division). The operation with assignment stores 12 back into n.
  8. while (n) loops until n becomes 0. Recall that C++ treats 0 as false and non-0 as true. When n becomes a single digit, then n / 10 is 0, ending the loop.
  9. return sign + s; concatenates sign (which is either an empty string or "-1") to the left of string s and returns the resulting string as the function's return value.

The Reverse Solution

The definition of a palindrome is a string that reads the same forwards and backward (without considering spaces or other punctuation characters). The program described below solves the palindrome-number problem based on the definition. It reverses the candidate string and then compares it to the original string. If the two strings are the same (i.e., equal), then the candidate string is a palindrome; if the two strings are not the same, then the candidate is not a palindrome.

Time: 00:02:26 | Download: Large Small | Streaming
int main()
{
	for (int number = 1; ; number++)				// (a)
	{
		int	square = number * number;			// (b)
		string	s = to_string(square);				// (c)

		if (s.length() < 6)					// (d)
			continue;

		if (s.find('0') == 0 || s.rfind('0') == s.length() - 1)	// (e)
			continue;

		if (s == reverse(s))					// (f)
		{
			cout << number << " " << square << endl;
			break;
		}
	}

	return 0;
}
Solving the palindrome-number problem by reversing a string. The only difference between this and the first version of the program is the palindrome test. The highlighted code calls the reverse function, detailed in the next figure, and uses the overloaded string operator == to test the two strings for equality.
  1. Creates a sequence of candidate numbers that might solve the problem; omitting the middle for-loop expression creates an infinite loop
  2. Squares the number
  3. Converts the square to a string
  4. Rejects strings that are less than six characters long
  5. Rejects strings that begin or end with a '0'
  6. Tests a string to see if it is a palindrome
"Traditional" For-Loop For-Range Loop
Time: 00:05:20 | Download: Large Small | Streaming
Time: 00:03:16 | Download: Large Small | Streaming
string reverse(string s)
{
	string r;

	for (int i = 0; i < s.length(); i++)	// (a)
		r += s[s.length() - 1 - i];	// (b)

	return r;
}
string reverse(string s)
{
	string r;

	for (char c : s)		// (c)
		r = c + r;		// (d)

	return r;
}
The string reverse function. The C++ string class provides many useful functions and operators but not a function to reverse a string, so we must write our own. Both versions of the reverse function share three common features: First, the string that it reverses is the function's argument; second, the function creates a local string, r, where it constructs the reversed string; and finally, both versions return the reversed string as the function's return value.
  1. A "traditional" for-loop accesses each character, from the beginning to the end, of the string.
  2. The expression s.length() - 1 - i indexes into the string beginning with the last character and moves backward (i.e., right to left) as the loop control variable increases. The += string operator concatenates the characters extracted from the original string to the right of any characters already in the reversed string.
  3. A for-range loop accesses the characters in the original string in the forward (i.e., left to right) direction one at a time.
  4. The expression c + r concatenates the character extracted from the original string to the left of the characters already added to the reversed string. The new string created by the concatenation operation replaces the contents of (i.e., is stored in) the reversed string, r.

Comparing the C-string and string class solutions for the palindrome-number problem, including the three different versions of the reverse function, suggests three elaborating questions that we should ask ourselves:

  1. The C-string solution copied the candidate string before reversing, but the string class solution does not. Why are the solutions different?
  2. The for-loop in the C-string version iterates for one-half the length of the string, but both string class versions iterate for the string's full length. Why is there a difference?
  3. The two string class versions use different concatenation operators to build the reversed string. The traditional for-loop uses the concatenation-with-assignment operator, +=, which concatenates the new character to the right of the reversed string. The for-range loop uses separate operators: + and =, and c+r concatenates the new character to the left of the reversed string. Can you figure out why there is a difference?

Downloadable Code