Books / Patterns for Beginning Programmers / Chapter 9
Indicator Methods
Sometimes the value needed to perform a calculation is known, and other times it too must be calculated. This is true of both “traditional” values and indicators (of the kind discussed in Chapter 8). This chapter considers problems in which an indicator’s value must be calculated before it can be used in another expression.
Motivation
There’s a famous saying among performers, “the show must go on”, which
means that there must be a show whenever there’s an audience. In the
context of Chapter
8
on indicators, this means that the number of seats sold for a particular
showtime must be used to calculate an indicator, setting it to 0
when
no tickets have been sold and setting it to 1
otherwise. This is an
example of a threshold indicator in which the threshold is 1.
So, given that some number of tickets has been sold for a particular
show time, you must determine the number of shows that must be offered
at that time (i.e., either 0
or 1
). Letting shows
denote the
number of shows and sold
denote the number of tickets sold, you want
to calculate shows
from sold
in such a way that it takes on the
value 0
when sold
is 0 and it takes on the value 1
for all
positive values of sold
.
Review
You could, of course, calculate the value of the variable shows
as
follows:
if (sold >= 1) {
shows = 1;
} else {
shows = 0;
}
However, you should also know that this is a bad practice, since it is likely that you will need to use this simple algorithm in more than one place. Hence, to avoid code duplication, you should instead write a method like the following:
public static int shows(int sold) {
if (sold >= 1) {
return 1;
} else {
return 0;
}
}
and use it as needed.
The Pattern
The entertainment example is, obviously, a very particular problem.
However, it can easily be generalized to uncover a pattern. In
particular, the entertainment problem is a particular example of a
general problem in which you need to determine whether a particular
value exceeds a particular threshold. Further, there is an even more
general problem in which you need a method that returns a value of 0
or 1
based on the value of the parameters it is passed.
For threshold indicators, the solution to the problem is the following method:
public static int indicator(int value, int threshold) {
if (value >= threshold) {
return 1;
} else {
return 0;
}
}
For other indicators, the solution is a method with parameters that vary with the specifics of the problem.
Examples
It’s useful to consider examples of both threshold indicators and other indicators.
Threshold Indicators
Continuing with the entertainment example, the threshold is 1
(i.e.,
if the size of the audience is 1 or more then there must be a show), so
given the size of the audience (represented by the variable size
), the
number of shows is given by the following:
shows = indicator(sold, 1);
Returning to the parking ticket example used in Chapter
8,
letting the number of prior tickets be given by priors
, the fine can
be calculated as follows:
baseFine = 10.00;
repeatOffenderPenalty = 35.00;
totalFine = baseFine + (indicator(priors, 1) * repeatOffenderPenalty);
It is also instructive to return to the car rental example from Chapter 8. For this example, you can initialize the necessary variables as follows:
baseRate = 19.95;
ageSurcharge = 10.00;
ageThreshold = 25;
multiSurcharge = 5.00;
multiThreshold = 1;
Then, you can calculate the daily rental rate as follows:
rate = baseRate
+ (1 - indicator(minimumAge, ageThreshold)) * ageSurcharge
+ indicator(extraDrivers, multiThreshold) * multiSurcharge;
Notice that, the age surcharge uses a converse threshold indicator while the multi-driver surcharge uses an ordinary threshold indicator.
Other Indicators
As an example of a more general indicator, consider the following method for calculating a person’s basic metabolic rate (BMR):
\[b = 5.00 + 10.00 m + 6.25 h - 5.00 a - 161.00 \delta_f\]where \(b\) denotes the BMR (in kilocalories per day), \(m\) denotes the person’s mass (in kilograms), \(h\) denotes the person’s height (in centimeters), \(a\) denotes the person’s age (in years), and \(\delta_f\) is \(1\) if the person is female and \(0\) otherwise.
Now, suppose the double
variables m
, h
, and a
contain the values
of the person’s mass, height, and age (respectively), and the String
variable s
contains the person’s sex. Then, you would create the
following indicator method:
public static int indicator(char sex) {
if ((sex == 'F') || (sex == 'f')) {
return 1;
} else {
return 0;
}
}
and use it as follows:
b = 5.00 + 10.00 * m + 6.35 * h - 5.00 * a - 161.00 * indicator(s);
Some Warnings
As in the discussion of indicator variables in Chapter
8,
you might think that you should use boolean
variables, if
statements, and the updating pattern from Chapter
2
instead of indicator methods. The trade-offs here are the same as the
trade-offs there.
It is also important to realize that you shouldn’t over-generalize the
code that is used in this pattern. Suppose, for example, you needed to
assign the value true
to the boolean
variable isLegal
if and only
if the value in the variable age
is greater than or equal to the value
in the “constant” DRINKING_AGE
. Given the code used to implement the
pattern, you might be tempted to do something like the following.
if (age >= DRINKING_AGE) {
isLegal = true;
} else {
isLegal = false;
}
However, most people think this is completely inappropriate (and
demonstrates a lack of understanding of relational operators and
boolean
variables). Instead, it should be implemented as follows:
isLegal = (age >= DRINKING_AGE);
That is, the expression (age >= DRINKING_AGE)
evaluates to a boolean
that should be directly assigned to the variable isLegal
— the if
statement is superfluous. In other words, a pattern that is appropriate
for assigning values to numeric variables or returning numeric values
(like those in the previous section) may not be appropriate for
boolean
variables.
Looking Ahead
On some hardware architectures, if
statements (including the boolean
expressions that must be evaluated) take more CPU time to execute than
arithmetic operations. Hence, on such architectures it is important to
replace if
statements with arithmetic expressions. Threshold
indicators are an example of this that might arise in a course on
computer architecture.
If you’ve read Chapter
5
on arithmetic on the circle, you’re may be thinking about how you can
use the integer division operator and/or the remainder operator to
eliminate the if
statements. While it is, indeed, possible to do so,
the solution is not obvious. Consider the following candidates:
(value / (max + 1))
is0
whenvalue
is0
, it is also0
for every other possibleint
.(value / max)
is slightly better since it is0
whenvalue
is0
, and1
whenvalue
ismax
, but it is still0
for every other possibleint
.value % (max + 1)
is0
whenvalue
is0
, and is1
whenvalue
is1
, it isvalue
(not1
) otherwise. (Usingmax
rather than(max + 1)
in the denominator does not improve things.)
The easiest way to think about a solution that does work is to consider
the problem in three stages. First, consider the case when the threshold
is less than twice the value. Then, consider the special case of a
non-zero threshold indicator (i.e., when the threshold is 1
). Finally,
consider the general case (i.e., the threshold can be anything).
A Threshold Less Than Twice the Value
When value
is strictly less than (2 * threshold)
, it follows that
(value / threshold)
is going to be either 0
or 1
(because, using
real division, the result will always be less than 2
). Hence, in this
case, you can find the indicator
as follows:
indicator = (value / threshold);
Unfortunately, this won’t work in general because we don’t always know
value
.
A Threshold of 1
One way to get to the correct solution to the special case when the
threshold is 1
is to find max
consecutive integers that have the
property that each divided by some value is 1
.
To move in the right direction, observe that for value
in the set
\(\\{\texttt{1}, \texttt{2}, \ldots, \texttt{max}\\}\) it
follows that (value + max)
is an element of the set
\(\\{\texttt{(1+max)}, \texttt{(2+max)}, \ldots,
\texttt{(value+max)}\\}\). Since value
is less than or equal
to max
, it also follows that each element of this set is less than or
equal to (2*max)
. Hence, the result of dividing each element by
(max+1)
(using integer division) is 1
(because using real division
the result would be strictly between 1
and 2
).
To finalize the pattern when the threshold is 1
, observe that
(0 + max) / (max + 1)
is 0
(using integer division), since the
numerator is strictly less than the denominator. What this means is that
the pattern when the threshold is 1
can be expressed as the following
non-zero indicator:
indicatorNZ = (value + max) / (max + 1);
The General Case
In the general case, you need to create an indicator that is 0
when a
non-negative value
is less some threshold
and 1
otherwise. The
easiest way to create such an indicator is to first calculate an
intermediate value that is 0
when the value
is less than the
threshold
and positive otherwise, and then use a non-zero indicator.
To do so, observe that, since both value
and threshold
are in the
interval \(\[\texttt{0}, \texttt{max}\]\), it follows
that, as required, (value / threshold)
is 0
when value
is less
than threshold
and that it is in \(\[\texttt{1},
\texttt{max}\]\) otherwise. Hence, you can calculate
intermediate
as follows:
intermediate = value / threshold;
Then, you can use intermediate
and the expression for a non-zero
indicator to calculate a threshold indicator as follows:
indicatorT = (intermediate + max) / (max + 1);
Putting it all together yields the following general expression for a threshold indicator:
indicatorT = ((value / threshold) + max) / (max + 1);
To see that this is, indeed, a generalization of the special case, you
need only substitute 1
for threshold
in this expression.
This leads to a general-purpose method like the following for
calculating the threshold indicator without an if
statement:
public static int aindicator(int value, int threshold, int max) {
return ((value / threshold) + max) / (max + 1);
}