(See also How to develop your first Java program.)
One way to solve a problem is to break it down into simpler steps, simple enough to implement directly. This technique was illustrated previously. Another problem solving method is to simplify a task by looking for patterns. Find the right way to look at a problem and the design becomes straight forward. (By the same token, if you can't see how to implement some program, try to look at it differently.) By generalizing you may come up with a simpler solution than if you had tried to solve the problem directly.
diamondas in the following output:
(not including the row and column numbers, shown for your convenience):
12345678901234567890
* *** ***** ******* ***** *** *
You might quickly create a design similar to this:
Write the first line of output Write the second line of output Write the third line of output Write the fourth line of output Write the fifth line of output Write the sixth line of output Write the seventh line of output
This is hardly an elegant design! Although it is simple and fast, it is very inflexible. In the real world project requirements are often vague, ambiguous, and change throughout the life of the project. A better design should be more flexible. That is it should be general enough to accommodate the likely potential changes.
When designing a program, whatever you do it will be wrong.
— Vonder Haar's Law of software design
In this case you should be asking yourself
what might change in the requirements
?
You might consider the customer saying the diamond is
too big
,
the diamond is too small
, or the diamond is
just right, but needs to be moved a few more columns to
the right
.
Try to find the pattern here, a generalization of the problem that can easily be changed to accommodate the changes you can anticipate. I think my program should be general enough to draw a diamond of any given size, and any given indent from the left margin.
It is also a mistake to make the requirements too general.
For example you might decide that the customer may want a different
shape and try to design a program that can draw any shape.
Finding the right level of generalization requires software
design and programming experience, as well as knowledge of
the big picture
, that is, the customer's line of business.
Hmm, This looks like a diamond, or a tilted square. Is it easy to draw a diamond? If so I can't think of how. I should ask the instructor if there is a built-in Java method or class that will draw a diamond... The answer is not for a non-GUI program. Oh well. Is there another way to look at this? Well, the number of the stars seems to be increasing for each row, and then reducing at the same rate until the last line looks the same as the first one. Also the indent from the left decreases as the rows get longer, and then decreases.
Outputting a certain number of blanks, then a certain number of
stars, then start a new line and repeat.
Ah-ha!
This pattern sounds like something familiar.
I can do this with a bunch of for
loops!
Let's see: the first row has four spaces and then one star. The next row has three spaces (one less) and three stars (2 more). The next row has two spaces (one less) and five stars (2 more). The next row has one space (one less) and seven stars). Then we draw the same lines, but in reverse.
So one pattern I can use to generalize is that I draw rows
starting with 1 star up to 7 stars, and increase the number of
stars by 2 each time.
And this is followed by another loop, starting with 5 stars
and decreasing the number of stars
each time by 2, and ending with 1 star.
I'll use numStars
for the variable name:
for ( int numStars = 1; numStars <= 7; numStars += 2 ) print correct number of leading spaces print numStars number of stars print a newline for ( int numStars = 5; numStars >= 1; numStars -= 2 ) print correct number of leading spaces print numStars number of stars print a newline
This program could be generalized if I used variables
for the minimum
number of stars,
the maximum
number of stars,
and the amount of increase to the number of stars (commonly
known as a STEP
or INCREMENT
value).
(To make a good-looking diamond the STEP
should always
be 2.
This really should be a constant!)
But how do we actually print the correct number of stars
each time?
One way would be another loop (this would be a nested loop).
The harder part is to print the correct number of leading
spaces, given a specific indent such as 4
.
Actually printing a certain number of spaces can be done the
same way as printing a certain number of stars, but how many
spaces should be printed for each row?
In the requirements use case (the provided picture of the output)
that starts as 4 spaces and decreases by 1 for each row
until the middle row (with seven stars) is printed, then
increases by one for each remaining row.
This sounds like I'll need another variable, let's call it
numSpaces
.
Let's try a design with another set of loops:
for ( int numStars = 1; numStars <= 7; numStars += 2 ) for ( int numSpaces = 4; numSpaces >= 1; --numSpaces ) print numSpaces number of leading spaces print numStars number of stars print a newline for ( int numStars = 5; numStars >= 1; numStars -= 2 ) for ( int numSpaces = 2; numSpaces <= 4; ++numSpaces ) print numSpaces number of leading spaces print numStars number of stars print a newline
This so-called elegant
solution is turning quite
messy.
Look at all the for
loops, and I didn't even show them
all!
Nasty, messy code like this may mean I didn't get it right.
I'll try to simulate this code...
Oops!
This doesn't work at all!
For the first row numStars
is 1.
But then the next for
loop
prints 4, then 3, then 2, then 1 space, a total of 10 spaces!
Clearly I have one too many for
loops!
What I really want is a single loop that runs once
for each row
, setting both the numSpaces
and
numStars
variables each pass through the loop.
Let's see if that works:
set numSpaces to 4; for ( int numStars = 1; numStars <= 7; numStars += 2, --numSpaces ) print numSpaces number of leading spaces print numStars number of stars print a newline for ( int numStars = 5; numStars >= 1; numStars -= 2, ++numSpaces ) print numSpaces number of leading spaces print numStars number of stars print a newline
This seems more elegant; numSpaces
goes from 4,
decreases by one each pass of the first loop, then increases each
pass of the second loop.
Hey, I wonder if I can simplify the loops to deal with
numStars
that same way?
It would be nice to get rid of the magic number
5 in there!
The design becomes:
set numSpaces to 4, numStars to 1; for ( ; numStars <= 7; numStars += 2, --numSpaces ) print numSpaces number of leading spaces print numStars number of stars print a newline for ( ; numStars >= 1; numStars -= 2, ++numSpaces ) print numSpaces number of leading spaces print numStars number of stars print a newline
Not bad!
This should work for any diamond as long as I get
the initial values for the variables right.
I will use constants INDENT
(set to 4),
MINIMUM
(set to 1, for one star to start),
MAXIMUM
(set to 7), and STEP
(set to 2).
This design not only draws the diamond required, but accommodates
the potential changes we identified:
To change the size or indent, I need only change one constant!
The only question I have left is, should the code to
print numSpaces
spaces and numStars
stars
be for
loops, and if so should they be separate
methods?
Hmm, I'm not sure what is the best way to go.
The program is complex but not very long, so maybe
a single method for everything will be good enough.
(Another good question for the instructor, before I spend
a lot of time writing the code — I don't want to
have to change the design later!)
The answer is, to look for more patterns.
(What a typical instructor answer! :-)
But I can see I use the same code, in two places each.
That's a clue to use methods.
But hold on a minute!
I see another pattern here.
All those statements to print a number of stars or spaces
are doing the same thing except for the number and the
character to show.
Maybe a single method to draw a run of some given character, of
a some given length, can be used in all four cases?
Now that would be elegant!
Let's see...
Such a method will need two arguments, the character to draw
and the number of times to draw it.
I'll call the method
.
drawChar
Should the method return a String
for the
main
method to draw, or should it return nothing
and do the drawing itself?
I suppose it would be more flexible for the future to have it
return a String
, but that seems like it would make
the program even more complex so I won't bother for now.
(If the program needs changing, or stays useful long enough to
have a version 2, I can consider the change then.)
The first draft of the code (with line numbers added):
// Non-GUI program to display a diamond pattern of stars. // Written 2/2008 by Wayne Pollock, Tampa Florida USA. class Diamond { static final int MINIMUM = 1; // How many stars in first and last rows. static final int MAXIMUM = 7; // How many stars in middle row. static final int STEP = 2; // How many stars to add to each row. static final int INDENT = 4; // How many leading spaces for first row. public static void main ( String [] args ) { int numSpaces = INDENT, numStars = MINIMUM; for ( ; numStars <= MAXIMUM; numStars += STEP, --numSpaces ) { drawChar( ' ', numSpaces ); // Draw leading spaces. drawChar( '*', numStars ); // Draw the row of stars. System.out.println(); // add a newline. } for ( ; numStars >= MINIMUM; numStars -= STEP, ++numSpaces ) { drawChar( ' ', numSpaces ); // Draw leading spaces. drawChar( '*', numStars ); // Draw the row of stars. System.out.println(); // add a newline. } } // Implementation method to display a "run" of some character: private static void drawChar ( char ch, int numToDraw ) { for ( int i = 0; i < numToDraw; ++i ) System.out.print( ch ); // Not println! } }
And here's the output:
* *** ***** ******* ********* ******* ***** *** *
Not bad!
I think I'll change INDENT
to 6, so the output matches
the requirements (two leading spaces on the middle row).
I could stop here, but the design with two for
loops
seems awkward and messy.
Could there be another way to look at this task, that results
in a single loop?
The figure looks symmetrical both horizontally and vertically.
Is there a way to exploit that to come up with a simpler design?
Hmm.
(Many hours and false starts later:)
Suppose we think of the vertical center column to be
the zero
column, and you draw a line of stars centered at
that column.
Then my loop goes from -3 to +3, and I just add that number to
INDENT
to determine the column of the first star on a
line, or how many leading spaces to draw.
Also I add that number to MAXIMUM
to determine how
many stars to draw.
The two for
loops then becomes a single, shorter loop:
... int range = MAXIMUM / 2; for ( int i = -range; i <= range; ++i ) { drawChar( ' ', INDENT + i ); // Draw leading spaces. drawChar( '*', MAXIMUM + i ); // Draw a row of stars. System.out.println(); // Add a newline. } ...
The next draft looks like this:
// Non-GUI program to display a diamond pattern of stars. // Written 2/2008 by Wayne Pollock, Tampa Florida USA. class Diamond { static final int MAXIMUM = 7; // How many stars in middle row. static final int INDENT = 6; // How many leading spaces for first row. public static void main ( String [] args ) { final int range = MAXIMUM / 2; for ( int i = -range; i <= range; ++i ) { drawChar( ' ', INDENT + i ); // Draw leading spaces. drawChar( '*', MAXIMUM + i ); // Draw a row of stars. System.out.println(); // Add a newline. } } // Implementation method to display a "run" of some character: private static void drawChar ( char ch, int numToDraw ) { for ( int i = 0; i < numToDraw; ++i ) System.out.print( ch ); // Not println! } }
And here's the output:
**** ***** ****** ******* ******** ********* **********
Well that's not right!
Perhaps I should have paid more attention in math class.
Let's see...
I think the answer is to use Math.abs
method,
and I think I added when I should have subtracted.
The number of stars in each row is also twice the correct
value, I should divide by two.
And the number of leading spaces is completely wrong, it should
be the index of the center column minus one-half the width
of the line of stars.
After making these changes (plus renaming some
variables), the new code becomes:
// Non-GUI program to display a diamond pattern of stars. // Written 2/2008 by Wayne Pollock, Tampa Florida USA. import static java.lang.Math.abs; class Diamond { static final int WIDTH = 7; // How many stars in middle (fattest) row. static final int CENTER = 5; // Column index for vertical center axis. public static void main ( String [] args ) { final int range = WIDTH / 2; for ( int i = -range; i <= range; ++i ) { int numStars = WIDTH - abs(i*2); drawChar( ' ', CENTER - numStars/2 ); // Draw leading spaces. drawChar( '*', numStars ); // Draw a row of stars. System.out.println(); // Add a newline. } } // Implementation method to display a "run" of some character: private static void drawChar ( char ch, int numToDraw ) { for ( int i = 0; i < numToDraw; ++i ) System.out.print( ch ); // Not println! } }
And here's the output:
* *** ***** ******* ***** *** *
You can view/download Diamond.java to see the final version. Whew! This was hard. Thank goodness I wasn't shy about asking the instructor for help!
(What do you suppose happens when WIDTH
is
an even number, say eight?
The first and last rows will have zero stars.
Not a problem for now, but we will need to run this design
by the customer if the requirements for the size of the
diamond changes.)
The code becomes even shorter if drawChar
returns
a String
;
the body of the loop becomes a single println
statement (with the two drawChar
method calls as
arguments).
Now that's what I call elegant: simple, short, and flexible!
(The method drawChar
can be replaced with standard
Java methods which makes the code even shorter,
but not simpler!
In general your design should not depend on clever hacks
that are hard to read.)
It is sometimes difficult to know when good enough is reached and it is time to stop working on a project (and begin another). Before stopping, reflect on the purpose: have I fulfilled that? In real world code (as opposed to toy code like this), think about safety and security, and testing your code before showing it to a customer (or instructor). Think about the clarity of your code and whether or not you have the right amount and type of comments. Think about deployment of the application/service. (Modern development tools and workflows can help tremendously with all these issues, but such tools take time to master.)
So you've written your code carefully or copied it from a book,
and compiled it without any error messages.
But it doesn't run, or
it does run but does something you didn't intend or expect.
You've looked, but just can't see what is wrong with it.
You need to
debug it.
See Patricia Shanahan's Debug Strategy for some good advice.