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.
Why do you use size_type for the columns? there is no dependence on string
Good point – it just seemed right to me back then when I did the tutorial. But then I could be wrong. Try a different way and see! (I was a total newbie of C++ when I did that tutorial back then!)
Very helpful explanations and thanks a lot. I’m just wondering if you actually designed the triangle algorithm yourself or you got the idea from somewhere? Coz I found it really hard to draw the triangle
Thank you @Salik – I’m glad it helps. Yes I did write the triangle algorithm myself – I recall spending about 30 minutes sitting at the desk, with a pen and a piece of paper. Did some simple drawings and attempted to figure out some sort of patterns, and wrote formulas that may fit the different types of scenarios. e.g. start with the smallest triange, then increase the triangle size a little bit, then a little bit more – what patterns do I see? e.g. what relationship do I see between the column-wise (x) and row-wise (y) direction? Whenever I need to “create” a new formula I tend to start with the simplest form, make observations, draw it out. Then make it slightly more complicated, repeat. At one point things are likely to just “click”. I can’t quite explain how I did it – I just “did it”. Maybe at some point in future (when I have time) I may come back to this triangle problem again, and properly document the thought process on how i started from drawing shapes, to deriving a formula! One day, one day, :-)
Thanks for replying. I just want to ask for advise. I am understanding the examples in the book completely but I’m not able to do the exercises. I am just a little scared to continue coz I’m not being able to solve these problems on my own. Should I continue with this book or get a different book that starts at a more beginner level. And THANKS again.
How about a specific example of where you get stuck. Maybe I can give you a hint or two.
Thanks for reply. Exercise 4-3,4-4,4-5, and most of others in chapters before. I’m not even being able to start to solve these problems. It seems there is no material in chapter that is needed to solve these problems. Maybe i’m not experienced enough programmer to think it out. Which leads me to stop reading the book because where I’m seeing authors solve complex problems very smartly I’m not being able to adapt them in my own code.
Im having the same problem as you…im understanding the programing but i dont know how to make the algorithms to solve problems
For people who want a more even triangle. Replace r in the if statement for the triangle with r * (numCols / numRows). You’ll get more accurate triangle when it’s being very short and wide.
This will keep a ratio between the longest line (the bottom line) and the current line.