Exercise 2-5
Write the set of “*” characters so that they form a square, a rectangle, and a triangle.
Solution
This is a fun one! To make this an even more interesting problem to solve, I’m going to add one more requirement – hollow and solid shapes. This diagram outlines my solution strategy:
In this post I will:
- Walk through the solution strategy step-by-step, with explanation of the code snippets that I have created using logics.
- Put all the snippets together and form one complete program.
- Run the complete program and record the results – seeing is believing!
I will start from step one of my solution strategy.
1 – Choose Shape Type
The program first asks the user to select the int inputShapeType 1 (Square), 2 (Rectangle), or 3 (Triangle). This is stored as the const int shapeType which has global scope. I’ve also added a for loop for the extra effect that, in the case that the user type in a number outside the selected range (e.g. 9), the program would keep re-asking the question until the user eventually type in the valid range of 1, 2, or 3. Note that if we really want we could add extra controls such as preventing the user from entering letters (instead of numbers). But for the sake of this exercise.
// ask for the Shape Type
int inputShapeType;
for (int keepAsking = 1; keepAsking == 1; )
{
cout << "Select the Shape Type." << endl;
cout << " (1) - Square " << endl;
cout << " (2) - Rectangle " << endl;
cout << " (3) - Triangle " << endl;
cout << "Shape Type (enter 1/2/3): ";
cin >> inputShapeType;
// Make sure the user input is within range. If not, ask again!
if (inputShapeType == 1 || inputShapeType == 2 || inputShapeType == 3)
{keepAsking = 0;}
}
const int shapeType = inputShapeType;
2 – Define Shape Height
We ask the user to supply the desirable height (in number of columns). This integer height is stored as a int const numRows and has global scope.
// ask for the shape height
cout << "Enter Shape Height: ";
int shapeHeight;
cin >> shapeHeight;
// set the total number of rows to write (as a constant)
int const numRows = shapeHeight;
3 – Define or Compute Shape Width
- If the shape is a square, the width is computed as the same as height.
- If the shape is a rectangle, the user will need to explicitly supply the width (in number of columns).
- If the shape is a triangle, the width is computed as: width = (2*numRows)-1.
We store the width as const string:size_type numCols.
// Only need to ask for a width if the shape is a a rectangle
// For square and triangle, the width is computed instead.
int shapeWidth;
if (shapeType == 2)
{
// ask for the shape width
cout << "Enter Shape Width: ";
cin >> shapeWidth;
}
// compute the number of columns to write (as a constant) based on shape type
int finalShapeWidth;
if (shapeType == 1)
{finalShapeWidth = shapeHeight; } // Compute square width
else if (shapeType == 2)
{finalShapeWidth = shapeWidth; } // Define rectangle width
else
{finalShapeWidth = (2*numRows)-1; } // Compute triangle width
const string::size_type numCols = finalShapeWidth; // define constant here
4 – Choose Fill Style (Hollow / Solid)
To make the problem more interesting I have added the option to either output a hollow or solid shape. We ask user to choose option 1 (Hollow), or 2 (Solid) and store the value as int inputFillStyle. We eventually store this value as a const int fillSyle. Depending on the shapeType and fillStyle we may apply different algorithm accordingly later on.
// ask for the Shape Fill Style
int inputFillStyle;
for (int keepAsking = 1; keepAsking == 1; )
{
cout << "Select Fill Style." << endl;
cout << " (1) - Hollow " << endl;
cout << " (2) - Solid " << endl;
cout << "Select Fill Style (enter 1/2): ";
cin >> inputFillStyle;
// Make sure the user input is within range. If not, ask again!
if (inputFillStyle == 1 || inputFillStyle == 2)
{keepAsking = 0;}
}
const int fillStyle = inputFillStyle;
5 – Apply Algorithms
All algorithms follow the same “looping” procedure. We have two loops (like in the previous exercises):
- a for loop with the invariant r (increment of 1), and an asymmetric range of [0, numRows). Denote this as r[0,numRows].
- a nested while loop with the invariant c, and an asymmetric range of [0, numCols). Denote this as c[0,numCols].
Just to refresh memory, asymmetric range of [0,numRows) means the range is betwen 0 and numRows, and excludeding numRows from the range. The looping procedure looks like this:
// invariant: we have written r rows so far
for ( int r = 0; r != numRows; ++r )
{
string::size_type c = 0;
// invariant: we have written c characters so far in the current row
while ( c != numCols)
{
// Algorithms go here (where the invariant c gets incremented accordingly)
}
}
Rectangle (and Square) Algorithm
If rectangle and square, use the solid/hollow rectangle algorithm (as square is a form of rectangle).
// If Square or Rectangle Option
if (shapeType == 1 or shapeType == 2)
{
// hollow option
if (fillStyle == 1)
{
// Are we at the exact position to output asterisk (border)?
if (
( c == 0 ) // the left edge
|| ( c == numCols-1) // the right edge
|| ( r == 0 ) // the top edge
|| ( r == numRows-1) // or the bottom edge
)
{ cout << "*"; ++c; }
else
{ cout << ' '; ++c; }
}
// If solid option
else if (fillStyle == 2)
{ cout << "*"; ++c; }
}
Triangle Algorithm
If triangle, use the solid/hollow triangle algorithm.
// If Triangle Option
if (shapeType == 3)
{
// If Triangle hollow option
if (fillStyle == 1)
{
// Are we at the exact position to output asterisk (border)?
if (
( c == ((numCols-1)/2)-r ) // the triangle left edge
|| ( c == ((numCols-1)/2)+r ) // or the triangle right edge
|| ( r == numRows-1) // or the triangle bottom edge
)
{ cout << "*"; ++c; }
else
{ cout << ' '; ++c; }
}
// If triangle solid option
else if (fillStyle == 2)
{
// Are we at the exact position to output asterisk (solid fill)?
if (
( c >= ((numCols-1)/2)-r ) // between the triangle left edge
&& ( c <= ((numCols-1)/2)+r ) // and the triangle right edge
)
{ cout << "*"; ++c; }
else
{ cout << ' '; ++c; }
}
}
6 – Extras
To make the problem more robust I have also added the following features:
A banner at the top to make the console output prettier.
cout << endl;
cout << "*********************************" << endl;
cout << "*** The Shape Making Program ***" << endl;
cout << "*********************************" << endl;
cout << endl;
Display the height and width values for clarification.
// display the shape specification
cout << endl;
cout << "Height = " << numRows << " rows." << endl;
cout << "Width = " << numCols << " columns." << endl;
cout << endl;
At the end of the program, offer the user an option to re-start the program (from the top) by entering the y button on the keyboard (whereas entering any other keys will cause the program to terminate peacefully). To do this, we wrap the entire main program with a while loop.
int main()
{
string tryAgain = "y";
while (tryAgain == "y")
{
// The core program components go here
// at the end of the program we ask user if he would like to re-run program
// depending on the user input then this program may either re-run or terminate
cout << "Again? (y/n): ";
cin >> tryAgain;
}
return 0;
}
}
[/code]
The Complete Program
Putting this all together, we now have our complete program.
#include <iostream>
#include <string>
// say what standard-library names we use
using std::cin; using std::endl;
using std::cout; using std::string;
int main()
{
// at the end of the program we ask user if he would like to re-run program
// depending on the user input then this program may either re-run or terminate
string tryAgain = "y";
while (tryAgain == "y")
{
cout << endl;
cout << "*********************************" << endl;
cout << "*** The Shape Making Program ***" << endl;
cout << "*********************************" << endl;
cout << endl;
// ask for the Shape Type
int shapeType;
for (int keepAsking = 1; keepAsking == 1; )
{
cout << "Select the Shape Type." << endl;
cout << " (1) - Square " << endl;
cout << " (2) - Rectangle " << endl;
cout << " (3) - Triangle " << endl;
cout << "Shape Type (enter 1/2/3): ";
cin >> shapeType;
// Make sure the user input is within range. If not, ask again!
if (shapeType == 1 || shapeType == 2 || shapeType == 3)
{keepAsking = 0;}
}
// ask for the shape height
cout << "Enter Shape Height: ";
int shapeHeight;
cin >> shapeHeight;
// set the total number of rows to write (as a constant)
int const numRows = shapeHeight;
// Only need to ask for a width if the shape is a a rectangle
// For square and triangle, the width is computed instead.
int shapeWidth;
if (shapeType == 2)
{
// ask for the shape width
cout << "Enter Shape Width: ";
cin >> shapeWidth;
}
// compute the number of columns to write (as a constant) based on shape type
int finalShapeWidth;
if (shapeType == 1)
{finalShapeWidth = shapeHeight; } // Compute square width
else if (shapeType == 2)
{finalShapeWidth = shapeWidth; } // Define rectangle width
else
{finalShapeWidth = (2*numRows)-1; } // Compute triangle width
const string::size_type numCols = finalShapeWidth; // define constant here
// ask for the Shape Fill Style
int fillStyle;
for (int keepAsking = 1; keepAsking == 1; )
{
cout << "Select Fill Style." << endl;
cout << " (1) - Hollow " << endl;
cout << " (2) - Solid " << endl;
cout << "Select Fill Style (enter 1/2): ";
cin >> fillStyle;
// Make sure the user input is within range. If not, ask again!
if (fillStyle == 1 || fillStyle == 2)
{keepAsking = 0;}
}
// display the shape specification
cout << endl;
cout << "Height = " << numRows << " rows." << endl;
cout << "Width = " << numCols << " columns." << endl;
cout << endl;
// invariant: we have written r rows so far
for ( int r = 0; r != numRows; ++r )
{
string::size_type c = 0;
// invariant: we have written c characters so far in the current row
while ( c != numCols)
{
// If Square or Rectangle Option
if (shapeType == 1 or shapeType == 2)
{
// hollow option
if (fillStyle == 1)
{
// Are we at the exact position to output asterisk (border)?
if (
( c == 0 ) // the left edge
|| ( c == numCols-1) // the right edge
|| ( r == 0 ) // the top edge
|| ( r == numRows-1) // or the bottom edge
)
{ cout << "*"; ++c; }
else
{ cout << ' '; ++c; }
}
// If solid option
else if (fillStyle == 2)
{ cout << "*"; ++c; }
}
// If Triangle Option
if (shapeType == 3)
{
// Triangle hollow option
if (fillStyle == 1)
{
// Are we at the exact position to output asterisk (border)?
if (
( c == ((numCols-1)/2)-r ) // the triangle left edge
|| ( c == ((numCols-1)/2)+r ) // or the triangle right edge
|| ( r == numRows-1) // or the triangle bottom edge
)
{ cout << "*"; ++c; }
else
{ cout << ' '; ++c; }
}
// If triangle solid option
else if (fillStyle == 2)
{
// Are we at the exact position to output asterisk (solid fill)?
if (
( c >= ((numCols-1)/2)-r ) // between the triangle left edge
&& ( c <= ((numCols-1)/2)+r ) // and the triangle right edge
)
{ cout << "*"; ++c; }
else
{ cout << ' '; ++c; }
}
}
}
cout << endl;
}
// ask user if he would like to start the program again
cout << "Again? (y/n): ";
cin >> tryAgain;
}
return 0;
}
Result
*********************************
*** The Shape Making Program ***
*********************************
Select the Shape Type.
(1) - Square
(2) - Rectangle
(3) - Triangle
Shape Type (enter 1/2/3): 1
Enter Shape Height: 5
Select Fill Style.
(1) - Hollow
(2) - Solid
Select Fill Style (enter 1/2): 1
Height = 5 rows.
Width = 5 columns.
*****
* *
* *
* *
*****
Again? (y/n): y
*********************************
*** The Shape Making Program ***
*********************************
Select the Shape Type.
(1) - Square
(2) - Rectangle
(3) - Triangle
Shape Type (enter 1/2/3): 1
Enter Shape Height: 5
Select Fill Style.
(1) - Hollow
(2) - Solid
Select Fill Style (enter 1/2): 2
Height = 5 rows.
Width = 5 columns.
*****
*****
*****
*****
*****
Again? (y/n): y
*********************************
*** The Shape Making Program ***
*********************************
Select the Shape Type.
(1) - Square
(2) - Rectangle
(3) - Triangle
Shape Type (enter 1/2/3): 2
Enter Shape Height: 5
Enter Shape Width: 10
Select Fill Style.
(1) - Hollow
(2) - Solid
Select Fill Style (enter 1/2): 1
Height = 5 rows.
Width = 10 columns.
**********
* *
* *
* *
**********
Again? (y/n): y
*********************************
*** The Shape Making Program ***
*********************************
Select the Shape Type.
(1) - Square
(2) - Rectangle
(3) - Triangle
Shape Type (enter 1/2/3): 2
Enter Shape Height: 5
Enter Shape Width: 10
Select Fill Style.
(1) - Hollow
(2) - Solid
Select Fill Style (enter 1/2): 2
Height = 5 rows.
Width = 10 columns.
**********
**********
**********
**********
**********
Again? (y/n): y
*********************************
*** The Shape Making Program ***
*********************************
Select the Shape Type.
(1) - Square
(2) - Rectangle
(3) - Triangle
Shape Type (enter 1/2/3): 3
Enter Shape Height: 5
Select Fill Style.
(1) - Hollow
(2) - Solid
Select Fill Style (enter 1/2): 1
Height = 5 rows.
Width = 9 columns.
*
* *
* *
* *
*********
Again? (y/n): y
*********************************
*** The Shape Making Program ***
*********************************
Select the Shape Type.
(1) - Square
(2) - Rectangle
(3) - Triangle
Shape Type (enter 1/2/3): 3
Enter Shape Height: 5
Select Fill Style.
(1) - Hollow
(2) - Solid
Select Fill Style (enter 1/2): 2
Height = 5 rows.
Width = 9 columns.
*
***
*****
*******
*********
Again? (y/n): n
Process returned 0 (0x0) execution time : 245.384 s
Press any key to continue.
Remarks
This (procedural) program works fine when the problem is fairly simple. When things get more complicated (e.g. if we are asked to produce hundreds or even thousands of different types of shapes, a procedural code might become harder and harder to write and maintain. In that case, I wonder if an object-orientated approach may help simplifying this? Something to come back to in future I guess. i.e. to rewrite the above program in an object oriented manner.
Reference
Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000