Toward Developing Good Programming Style
Pascal version, August 1997
(McCann)
Every program you write that you intend to keep around for more
than a couple of hours ought to have documentation in it. Don't
talk yourself into putting off the documentation. A program that
is perfectly clear today is clear only because you just wrote
it. Put it away for a few months, and it will most like take
you a while to figure out what it does and how it does it. If
it takes you a while to figure it out, how long would it take
someone else to figure it out?
Programming style is a term used to describe the effort a programmer
should take to make his or her code easy to read and easy to understand.
Good organization of the code and meaningful variable names help
readability, and liberal use of comments can help the reader understand
what the program does and why.
Probably the best way to demonstrate the value of good style is
with a simple example. (Even if you don't know Pascal very well
yet, keep reading; you can benefit from this example even if you
can't understand it.) Take a look at this program:
program foo(input,output);
var seg : string; d1, d2, d3, d4, m, td, ts : integer;
begin
seg := '6255456376'; m := 0;
for d1 := 0 to 1 do
for d2 := 0 to 9 do
for d3 := 0 to 5 do
for d4 := 0 to 9 do begin
if (not ((d1 = 0) and (d2 = 0))) and
(not ((d1 = 1) and (d2 > 2)))
then begin
if (d1 = 0) then
begin
ts := (ord(seg[d2+1]) - ord('0'))
+ (ord(seg[d3+1]) -
ord('0')) + (ord(seg[d4+1]) - ord('0'));
td := d2 + d3 + d4;
if ts = td then begin m := m + 1;
writeln(' ',d2:1,':',d3:1,d4:1); end;
end else begin
ts :=
(ord(seg[d1+1]) -
ord('0')) + (ord(seg[d2+1]) - ord('0'))
+ (ord(seg[d3+1]) - ord('0'))
+ (ord(seg[d4+1]) - ord('0'));
td := d1 + d2 + d3 + d4;
if ts = td then begin m := m + 1;
writeln(d1:1,d2:1,':',d3:1,d4:1);
end; end; end; end;
end.
Do you have the faintest idea of what it accomplishes? How long
would it take you to explain how it works? More to the point:
If you were assigned to fix a problem with this code, how frustrating
would the task be to you and how often would you curse the programmer
who wrote it? Now imagine that you wrote it and some other programmer
was cursing you! (Worse: Imagine you wrote this, it didn't work,
and you had to ask your instructor for help in finding the problem.
If I were your instructor, I'd flat-out refuse to look at this
until you cleaned it up.)
Style Principle
Structure and document your program the way you wish other programmers
would.
INDENTATION
One immediate problem that this program has is that it does not
adhere to a consistent indentation pattern. There are dozens
of indentation styles that you could adopt, and some general ideas
are common to most of them:
- The style is applied consistently throughout the program.
- Code within a block (e.g., inside a loop, or in the body of
a subprogram) should be indented.
- If a block is nested within another block the inner block's
body should be indented relative to the enclosing block.
- Avoid excessive "stairstep" indentation (such as
you often see with groups of nested IF statements) because this
will force you to attempt to squeeze code to fit on just the right
half of the screen/page. If stairstepping becomes a problem,
reduce the number of spaces per indentation (from 8 to 4, for
example) or switch to a vertical style temporarily.
Here's the code from above with a consistent indentation applied.
I also took the liberty of adding a little "white space"
(blank lines) to help set off sections of the program. I think
you'll agree that this is an improvement, but not yet acceptable:
program foo(input,output);
var seg : string;
d1, d2, d3, d4, m, td, ts : integer;
begin
seg := '6255456376';
m := 0;
for d1 := 0 to 1 do
for d2 := 0 to 9 do
for d3 := 0 to 5 do
for d4 := 0 to 9 do
if (not ((d1 = 0) and (d2 = 0))) and
(not ((d1 = 1) and (d2 > 2))) then
begin
if (d1 = 0) then
begin
ts := (ord(seg[d2+1])
- ord('0'))
+ (ord(seg[d3+1])
- ord('0'))
+ (ord(seg[d4+1])
- ord('0'));
td := d2 + d3 + d4;
if ts = td then
begin
m := m + 1;
writeln(' ',
d2:1,':',
d3:1,d4:1);
end;
end
else
begin
ts := (ord(seg[d1+1])
- ord('0'))
+ (ord(seg[d2+1])
- ord('0'))
+ (ord(seg[d3+1])
- ord('0'))
+ (ord(seg[d4+1])
- ord('0'));
td := d1+d2+d3+d4;
if ts = td then
begin
m := m + 1;
writeln(d1:1,
d2:1,':',
d3:1,d4:1);
end;
end;
end;
end.
Do you see the stairstep effect caused by the indentation of the
nested FOR loops? (Heck, how could you miss it?) Add the IF
statements to the code and there isn't much space left for code
on each line if you're trying to stay within the 80 column limit
of most screens. Here's the program with a vertical style applied
to the loops (note that a similar procedure can be applied to
closely nested IF statements as well):
program foo(input,output);
var seg : string;
d1, d2, d3, d4, m, td, ts : integer;
begin
seg := '6255456376';
m := 0;
for d1 := 0 to 1 do
for d2 := 0 to 9 do
for d3 := 0 to 5 do
for d4 := 0 to 9 do
if (not ((d1 = 0) and (d2 = 0))) and
(not ((d1 = 1) and (d2 > 2))) then
begin
if (d1 = 0) then
begin
ts := (ord(seg[d2+1]) - ord('0'))
+ (ord(seg[d3+1]) - ord('0'))
+ (ord(seg[d4+1]) - ord('0'));
td := d2 + d3 + d4;
if ts = td then
begin
m := m + 1;
writeln(' ',d2:1,':',d3:1,d4:1);
end;
end
else
begin
ts := (ord(seg[d1+1]) - ord('0'))
+ (ord(seg[d2+1]) - ord('0'))
+ (ord(seg[d3+1]) - ord('0'))
+ (ord(seg[d4+1]) - ord('0'));
td := d1 + d2 + d3 + d4;
if ts = td then
begin
m := m + 1;
writeln(d1:1,d2:1,':',d3:1,d4:1);
end;
end;
end;
end.
This looks much better because there's now room to indent the
nested IF statements with enough room left over to do a decent
job with the statements inside the IFs. Not everyone likes this
approach, however. Some people prefer that programmers move the
body of the loops to a subprogram instead. In my view this is
acceptable only if that section of code can logically stand on
its own. Even if this code-relocation idea doesn't sit right with
you, play along; having a subprogram will be handy as an example
later on. Here's the complete program with this philosophy applied:
program foo(input,output);
var d1, d2, d3, d4, m : integer;
procedure count (d1, d2, d3, d4 : integer; VAR m : integer);
var ts, td : integer;
seg : string;
begin
seg := '6255456376';
if (not ((d1 = 0) and (d2 = 0))) and
(not ((d1 = 1) and (d2 > 2))) then
begin
if (d1 = 0) then
begin
ts := (ord(seg[d2+1]) - ord('0'))
+ (ord(seg[d3+1]) - ord('0'))
+ (ord(seg[d4+1]) - ord('0'));
td := d2 + d3 + d4;
if ts = td then
begin
m := m + 1;
writeln(' ',d2:1,':',d3:1,d4:1);
end;
end
else
begin
ts := (ord(seg[d1+1]) - ord('0'))
+ (ord(seg[d2+1]) - ord('0'))
+ (ord(seg[d3+1]) - ord('0'))
+ (ord(seg[d4+1]) - ord('0'));
td := d1 + d2 + d3 + d4;
if ts = td then
begin
m := m + 1;
writeln(d1:1,d2:1,':',d3:1,d4:1);
end;
end;
end;
end;
begin
m := 0;
for d1 := 0 to 1 do
for d2 := 0 to 9 do
for d3 := 0 to 5 do
for d4 := 0 to 9 do
count(d1,d2,d3,d4,m);
end.
Please note that the 'seg' variable was moved into the subprogram
because it is not used by the main routine at all.
MEANINGFUL VARIABLE NAMES
Another impediment to program readability is that the program's
identifiers (variable names, subprogram names, etc.) are mostly
meaningless. You should strive to give each object a name that
gives the reader a strong hint as to the object's purpose within
the program. Many early languages limited the size of the allowable
names, and that forced programmers to use short, cryptic names.
Modern languages permit identifier names to be quite lengthy,
so there's no excuse not to create good names. As with indentation,
there are some principles that apply to naming:
- Use good, meaningful names, but don't go overboard. If you
have a variable in your program that holds the number of hours
an employee works in a week, you might call it HOURS, although
that name still leaves a lot of doubt as to the exact contents
of the variable. On the other hand, a name such as HOURS_WORKED_IN_A_WEEK
is much more descriptive but contains 22 symbols; a simple increment
of the variable might fill an entire line! A compromise such
as HOURS_PER_WEEK is a good solution, though there are others
(see below).
- Many languages now permit you to use underscores as part of
names, as shown above. If you can't use them, you can still improve
name readability by mixing the case of the letters in the name.
For example, 'HoursPerWeek' is much easier to read than 'hoursperweek'.
- Always place a comment with each variable name declaration.
The comment should give a brief phrase or sentence that explains
the purpose of the variable. If the variable name itself isn't
enough to make the purpose of the variable clear to the reader,
the comment should clear up any confusion. (At the same time,
you still want to select good names; the reader doesn't want to
have to keep referring to the declaration comment to refresh his
or her memory of a variable's purpose.
- Common abbreviations are often acceptable in variable names.
For example: HRS_PER_WEEK. But don't get carried away; HRS_P_WK
simply is not a good name.
- Although this isn't really a point about naming, it is related:
Don't reuse variables in a subprogram. For example, you may
need a loop control integer variable at the top of the block,
and you might also need a place to store a value for a while at
the bottom of the block. Resist the temptation to reuse that
loop control variable as the temporary holder. Such reuses can
lead to a reduction in readability of the code as well as to confusion
in program debugging.
Here's our program with better variable names and declaration
comments:
program segment_counting(input,output);
var hour1, { the first (leftmost) digit in the hour two-digit pair }
hour2, { the second (rightmost) digit in the hour }
tens, { the ten's digit in the minute two-digit pair }
ones, { the one's digit in the minute }
matches { the count of the times the sums match }
: integer;
procedure count_segments (hour1, hour2, tens, ones : integer;
VAR matches : integer);
var total_segments, { number of segments used in the time }
total_digits { sum of the digits in the time value }
: integer;
segments { 'array' of the number of segments
needed to display each digit }
: string;
begin
segments := '6255456376';
if (not ((hour1 = 0) and (hour2 = 0))) and
(not ((hour1 = 1) and (hour2 > 2))) then
begin
if (hour1 = 0) then
begin
total_segments :=
(ord(segments[hour2+1]) - ord('0'))
+ (ord(segments[tens+1]) - ord('0'))
+ (ord(segments[ones+1]) - ord('0'));
total_digits := hour2 + tens + ones;
if total_segments = total_digits then
begin
matches := matches + 1;
writeln(' ',hour2:1,':',tens:1,ones:1);
end;
end
else
begin
total_segments :=
(ord(segments[hour1+1]) - ord('0'))
+ (ord(segments[hour2+1]) - ord('0'))
+ (ord(segments[tens+1]) - ord('0'))
+ (ord(segments[ones+1]) - ord('0'));
total_digits := hour1 + hour2 + tens + ones;
if total_segments = total_digits then
begin
matches := matches + 1;
writeln(hour1:1,hour2:1,':',
tens:1,ones:1);
end;
end;
end;
end;
begin
matches := 0;
for hour1 := 0 to 1 do
for hour2 := 0 to 9 do
for tens := 0 to 5 do
for ones := 0 to 9 do
count_segments(hour1,hour2,
tens,ones,matches);
end.
Notice that the variable names are a little bit on the cryptic
side, and the declarations are commented to provide clarification.
In my view this is a reasonable compromise. As we'll see, some
variables are difficult to name, and a short but slightly cryptic
name with a good declaration comment is cleaner than a descriptive
but lengthy and awkward name. Also notice that the longer variable
names forced us to split some lines and reorganize others to get
them to fit in the 80 column lines. Readability does have its
price.
One place where short, cryptic variable names are often used:
Loop control variables. These are often difficult to name, and
most programmers will simply use single letter names such as I
and J for them. (Why I and J? Those letters are frequently used
for integer subscripts in mathematics. The early scientific language
FORTRAN was designed so that variables whose names started with
I and J (and a few others) were of type INTEGER by default, which
made them easy to use as integer loop control variables. The
habit may never fade.)
INTERNAL DOCUMENTATION
The variable declaration comments are one part of good internal
documentation. Internal documentation is the set of comments
that are included within the code to help clarify algorithms.
Some students take internal documentation to mean that they should
comment each line of code! This is obviously an example of overdoing
a good idea. Any programmer knows how to increment a value in
a variable; there's no reason to explain trivial operations such
as that. The value of some good internal documentation should
be clear by looking at the latest version of our sample program.
Even with the good code organization and variable names, the
function of this program is still not obvious.
Here's a list of items that should be included in your internal
documentation:
- "Block comments" (comments that are several lines
long) should be placed at the head of every subprogram. These
will include the subprogram name; the purpose of the subprogram;
and a list of all parameters, including direction of information
transfer (into this routine, out from the routine back to the
calling routine, or both), and their purposes.
- Meaningful variable names. In a nod to tradition, simple
loop variables may have single letter variable names, but all
others should be meaningful. Never use nonstandard abbreviations.
- Each variable and constant must have a brief comment next
to its declaration that explains its purpose. This applies to
all variables, as well as to fields of record declarations.
- Complex sections of code and any other parts of the program
that need some explanation should have comments just ahead of
them or embedded in them.
A critical point: Documentation, and internal documentation in
particular, should be written and included in the program as the
code is being written. Students tend to get in the habit of writing
the code and then tossing in some documentation only if they have
time before the due date. This makes documentation seem even
more boring and tedious that it already is, and students who rush
the documentation at the last minute usually do a very mediocre
job. Documentation should be written when the code is being
written, and should be typed in as the code is typed in.
To demonstrate some of these points, here's yet another version
of our program, this time containing some acceptable internal
documentation:
program segment_counting(input,output);
var hour1, { the first (leftmost) digit in the hour two-digit pair }
hour2, { the second (rightmost) digit in the hour }
tens, { the ten's digit in the minute two-digit pair }
ones, { the one's digit in the minute }
matches { the count of the times the sums match }
: integer;
{+------------------------------------------------ COUNT_SEGMENTS -----
| Procedure COUNT_SEGMENTS
|
| Purpose: COUNT_SEGMENTS computes the number of segments
| a digital clock will need to display the time given by
| the parameters. It then computes the sum of the digits
| and compares the two totals. If they match, the success
| is recorded by incrementing the sum 'matches' and by
| displaying the time.
| The number of segments a digital clock uses to
| display any of the ten numbers 0-9 is stored in the string
| 'segments'. In AIX Pascal, the first character in a string
| is at position 1. Here, the number of segments needed to
| display a '0' is in position 1 of the string, and '1' is in
| position 2, etc.
|
| Parameters:
| hour1 (IN) - In a two-digit hour value, this is the leftmost
| digit. Ex: In the time 12:34, hour1 would hold 1.
| hour2 (IN) - In a two-digit hour value, this is the rightmost
| digit. Ex: In the time 12:34, hour2 would hold 2.
| tens (IN) - In a two-digit minute value, this is the leftmost
| digit. Ex: In the time 12:34, tens would hold 3.
| ones (IN) - In a two-digit minute value, this is the rightmost
| digit. Ex: In the time 12:34, ones would hold 4.
| matches (IN/OUT) - The sum of the times the number of segments
| equals the sum of the digits.
+--------------------------------------------------------------------}
procedure count_segments (hour1, hour2, tens, ones : integer;
VAR matches : integer);
var total_segments, { number of segments used in the time }
total_digits { sum of the digits in the time value }
: integer;
segments { 'array' of the number of segments
needed to display each digit }
: string;
begin
segments := '6255456376'; { explained above }
{ We don't want to consider times that start with
{ '00' (like 00:35) or anything with hours over
{ '12' (like 16:14). }
if (not ((hour1 = 0) and (hour2 = 0))) and
(not ((hour1 = 1) and (hour2 > 2))) then
begin
{ If the time is between 12:59 and 10:00, the
{ leftmost hour digit is 0. A clock doesn't
{ display that 0, so we shouldn't count its
{ segments. }
if (hour1 = 0) then
begin
total_segments :=
(ord(segments[hour2+1]) - ord('0'))
+ (ord(segments[tens+1]) - ord('0'))
+ (ord(segments[ones+1]) - ord('0'));
total_digits := hour2 + tens + ones;
if total_segments = total_digits then
begin
matches := matches + 1;
writeln(' ',hour2:1,':',tens:1,ones:1);
end;
end
else { here we do count the leftmost hour digit }
begin
total_segments :=
(ord(segments[hour1+1]) - ord('0'))
+ (ord(segments[hour2+1]) - ord('0'))
+ (ord(segments[tens+1]) - ord('0'))
+ (ord(segments[ones+1]) - ord('0'));
total_digits := hour1 + hour2 + tens + ones;
if total_segments = total_digits then
begin
matches := matches + 1;
writeln(hour1:1,hour2:1,':',
tens:1,ones:1);
end;
end;
end;
end; { procedure COUNT_SEGMENTS }
begin
matches := 0;
for hour1 := 0 to 1 do
for hour2 := 0 to 9 do
for tens := 0 to 5 do
for ones := 0 to 9 do
count_segments(hour1,hour2,
tens,ones,matches);
end.
The trick with internal documentation is to make it easy to find
while at the same time ensuring that it's not making the code
hard to read. Block comments can be partially boxed (as shown)
to separate them from the code. The use of the '{' at the start
of each line of the shorter clarifying comments in the code serves
a similar purpose. There's no one right way to do this, but it
does need to be done. Experiment with some styles and pick one
you like. One piece of advice: Don't fall in love with the "complete
box" style. Lots of students like to completely enclose
the block comments within a box. This looks great, but the right-hand
wall of the box is very hard to keep lined up as you make adjustments
and additions to the comments. The "three wall" style
shown above is much easier to deal with and looks almost as good.
EXTERNAL DOCUMENTATION
In a professional programmer's shop, large projects are documented
in great detail, not only with comments in the code but with descriptions
that are maintained separately from the code. In such an environment,
programmers are often asked to fix problems in code that they
didn't write. Many times, the author of the code isn't even with
the company any longer. The documentation may be all the programmer
has as reference material to help him or her make the necessary
modifications.
External documentation doesn't deal with details of the code.
Instead, it serves as a general description of the project, including
such information as what the code does, who wrote it and when,
which common algorithms it uses, upon which other programs or
libraries it is dependent, which systems it was designed to work
with, what form and source of input it requires, the format of
the output it produces, etc. Often the external documentation
will include structure charts of the outline of the program that
were produced when the program was being designed. All of this
information is necessary to help other programmers understand
the program. One seemingly innocent change in a program can have
unpredictable consequences on other parts of the system. Good
documentation can help prevent such problems.
In most programming classes, it is impractical for instructors
to require large amounts of external documentation for programs
that are only a few hundred lines long. Instead, it is common
for instructors to require that a small amount of external documentation
be included at the top of
the program in the form of a large block comment. This condensed
version should include at least the following pieces of information:
- Your name, the course name, assignment name/number, instructor's
name, and due date.
- Description of the problem the program was written to solve.
- Approach used to solve the problem. This should always include
a brief description of the major algorithms used, or their names
if they are common algorithms.
- The program's operational requirements: Which language system
you used, special compilation information, where the input can
be located on disk, etc.
- Required features of the assignment that you were not able
to include.
- Known bugs should be reported here as well. If a particular
feature does not work correctly, it is in your best interest to
be honest and complete about your program's shortcomings.
The final version of the program is given at the end of this document.
Look it over carefully. Do you understand what the program does?
More importantly for this discussion, do you understand how it
does it? If the indentation, identifier names, and documentation
helped, then they were well worth the time it took the programmer
to put them in. Hopefully, you'll now see the value of putting
such documentation in your programs as well.
Take the time to ask yourself if you think the design of the comments
is a good one; are the comments easy to find and to read? Do
they distract from the code excessively? Are there too many of
them to suit you, or too few? By asking and answering questions
such as these, you will begin to develop a style of your own.
When you see documentation styles that you like, consider adopting
them into your own style. Soon you'll have one you like, and
as a result you'll be more likely to use it.
There are plenty of decisions that were made in the design and
documentation of this program that can be questioned and improved
on. As you gain more experience in programming, consider revisiting
this program and trying to rewrite it from scratch. Perhaps you
can think of a better way to generate the times, for example.
There isn't a program in existence that can't be improved, and
this one is certainly no exception.
MISCELLANEOUS COMMENTS
In a programming class, instructors don't want you to write the
most efficient programs; they'd much rather you learn the material
well and learn good programming style at the same time. Never
pursue efficiency at the expense of clarity. An efficient program
is better than an inefficient one, of course, but it is also true
that a slow, correct program is better than a fast, buggy one.
Clear, well-designed programs are more likely to be correctly
functioning programs. Get the program working before you worry
too much about making it work quickly.
A style topic that this document didn't cover is Top-Down Design.
In TDD, the idea is to design a program by first identifying
the major tasks of the program. For each task, break it down
into smaller subtasks. Continue this process until it is clear
how each task is to be accomplished. Each task that you have
identified is a candidate to be a subprogram, with the main program
consisting mostly of calls to the top-level subtasks. This approach
requires that you have the discipline to plan the structure of
your program before you write any code. If you can do it, the
process of planning the program will help minimize the number
of logical errors in your program. Typically, you'll spend less
time planning than you would have spent debugging the code you
didn't plan. This is a lesson most programmers can only learn
by experiencing a long, late-night debugging marathon firsthand.
For examples of Top-Down Design, refer to an introductory programming
text. Most of them cover it in detail.
Finally, no document on style would be complete without a mention
of the GOTO problem. The unconditional branch (GOTO) operation
is provided in nearly all languages, and its use is frequently
discouraged, particularly by instructors. When you're learning
to program, it's important that you learn to avoid using a GOTO.
Programs with several GOTOs can quickly become hard to understand
and thus hard to repair or modify. However, in some isolated
situations, a nice unconditional branch can do the job of a lot
of convoluted but well-structured logic. If you ever feel the
need to use a GOTO, be sure to ask your instructor if he or she
will sanction its use in that situation. They may be able to
show you a more structured solution to the problem.
{*=============================================================================
| Assignment: Program #0 -- Digital Clock Digit and Segment Sums
|
| Author: [Student's Name Here]
| Language: AIX Pascal (the xlp compiler)
| To Compile: xlp segment.pas
|
| Class: COMSC 0000
| Instructor: Dr. Staff
| Due Date: December 32nd, 2024, at the beginning of class
|
+-----------------------------------------------------------------------------
|
| Description: A common digital display clock, such as an alarm clock or
| a digital wristwatch, creates numbers by lighting segments in a
| standard 7-segment display. This program assumes that the digits
| look like this:
| _ _ _ _ _ _ _ _
| | | | _| _| |_| |_ |_ | |_| |_|
| |_| | |_ _| | _| |_| | |_| _|
|
| Thus, 6 segments are needed to display the digit 0, for example.
| The program creates all legal twelve-hour times (plus some illegal
| ones that are logically eliminated from consideration) and for each
| determines if the number of segments in the displayed time equals
| the sum of the digits in the display. For example, 7:21 requires
| 10 segments and the sum of the digits is also 10 (7+2+1). All such
| times are output by the program and the total number of such
| times is also determined.
|
| Input: No input (either from the keyboard or from a file) is
| required by this program.
|
| Output: The times are displayed one per line to the standard output.
| The number of times displayed is output at the end.
|
| Algorithm: The times are generated by a set of 4 nested FOR loops.
| Times are composed of 4 digits, one loop per digit. Legal
| times can start with a 0 or a 1, but the 0 is never shown
| on a clock. Hours go from 01 through 12, so the second
| digit of the hour value can range from 0 through 9, as can
| the rightmost digit of the minute. The left digit in the
| minute value ranges from 0 through 5 only. For each time
| generated by the loops, the COUNT_SEGMENTS subprogram
| determines if the segment sum equals the digit sum; see
| the internal documentation of COUNT_SEGMENTS for details
| on its operation.
|
| Required Features Not Included: The program adheres to all
| requirements stated in the program assignment, and all
| required features are included.
|
| Known Bugs: There are no known bugs remaining in this program.
|
*============================================================================}
program segment_counting(input,output);
var hour1, { the first (leftmost) digit in the hour two-digit pair }
hour2, { the second (rightmost) digit in the hour }
tens, { the ten's digit in the minute two-digit pair }
ones, { the one's digit in the minute }
matches { the count of the times the sums match }
: integer;
{*-------------------------------------------------COUNT_SEGMENTS -----
| Procedure COUNT_SEGMENTS
|
| Purpose: COUNT_SEGMENTS computes the number of segments
| a digital clock will need to display the time given by
| the parameters. It then computes the sum of the digits
| and compares the two totals. If they match, the success
| is recorded by incrementing the sum 'matches' and by
| displaying the time.
| The number of segments a digital clock uses to
| display any of the ten numbers 0-9 is stored in the string
| 'segments'. In AIX Pascal, the first character in a string
| is at position 1. Here, the number of segments needed to
| display a '0' is in position 1 of the string, and '1' is in
| position 2, etc.
|
| Parameters:
| hour1 (IN) - In a two-digit hour value, this is the leftmost
| digit. Ex: In the time 12:34, hour1 would hold 1.
| hour2 (IN) - In a two-digit hour value, this is the rightmost
| digit. Ex: In the time 12:34, hour2 would hold 2.
| tens (IN) - In a two-digit minute value, this is the leftmost
| digit. Ex: In the time 12:34, tens would hold 3.
| ones (IN) - In a two-digit minute value, this is the rightmost
| digit. Ex: In the time 12:34, ones would hold 4.
| matches (IN/OUT) - The sum of the times the number of segments
| equals the sum of the digits.
*--------------------------------------------------------------------}
procedure count_segments (hour1, hour2, tens, ones : integer;
VAR matches : integer);
var total_segments, { number of segments used in the time }
total_digits { sum of the digits in the time value }
: integer;
segments { 'array' of the number of segments
needed to display each digit }
: string;
begin
segments := '6255456376'; { explained above }
{ We don't want to consider times that start with
{ '00' (like 00:35) or anything with hours over
{ '12' (like 16:14). }
if (not ((hour1 = 0) and (hour2 = 0))) and
(not ((hour1 = 1) and (hour2 > 2))) then
begin
{ If the time is between 12:59 and 10:00, the
{ leftmost hour digit is 0. A clock doesn't
{ display that 0, so we shouldn't count its
{ segments. }
if (hour1 = 0) then
begin
total_segments :=
(ord(segments[hour2+1]) - ord('0'))
+ (ord(segments[tens+1]) - ord('0'))
+ (ord(segments[ones+1]) - ord('0'));
total_digits := hour2 + tens + ones;
if total_segments = total_digits then
begin
matches := matches + 1;
writeln(' ',hour2:1,':',tens:1,ones:1);
end;
end
else { here we do count the leftmost hour digit }
begin
total_segments :=
(ord(segments[hour1+1]) - ord('0'))
+ (ord(segments[hour2+1]) - ord('0'))
+ (ord(segments[tens+1]) - ord('0'))
+ (ord(segments[ones+1]) - ord('0'));
total_digits := hour1 + hour2 + tens + ones;
if total_segments = total_digits then
begin
matches := matches + 1;
writeln(hour1:1,hour2:1,':',
tens:1,ones:1);
end;
end;
end;
end; { procedure COUNT_SEGMENTS }
begin
matches := 0;
for hour1 := 0 to 1 do
for hour2 := 0 to 9 do
for tens := 0 to 5 do
for ones := 0 to 9 do
count_segments(hour1,hour2,
tens,ones,matches);
writeln;
writeln('The number of times displayed is ',matches:1);
end.