On this page:
Overview
4.1 Beyond Arithmetic and Concatenation
4.1.1 booleans and Comparing Numbers
4.1.2 Making Decisions
4.2 More Examples of Decision-Making Programs
4.2.1 More Conditions
4.2.2 More Comparisons
4.3 In Summary
6.8

Lecture 4: Programming, Decisively

There are no related files for lecture

Overview
4.1 Beyond Arithmetic and Concatenation

So far, we’ve used Java to do things a calculator could do, with the notable exception of Strings. We introduced methods mainly as a way to enhance our calculator-like behavior. In this lecture, we’ll go further, and start doing work that’s unique to computation – making decisions based on data.

4.1.1 booleans and Comparing Numbers

We’ve used +, -, /, and * to perform arithmetic on numbers (and in the case of +, append Strings). Mathematically, there’s a natural next set of operations to try on numbers – comparing them! Operators like < have meaning in math, and they also have meaning in Java. We can try them out:

class ExamplesLec {
boolean fourIsLessThanFive = 4 < 5;
boolean fiveIsLessThanFour = 5 < 4;
}

Here, we’re using the < operator to compare 5 and 4. If we run it, we get:

ExamplesLec:

---------------

 

 new ExamplesLec:1(

  this.fourIsLessThanFive = true,

  this.fiveIsLessThanFour = false)

The result says that fourIsLessThanFive has the value true, and fiveIsLessThanFour has the value false. This seems quite reasonable; the first is, after all, a true statement, while the second isn’t. These two values, true and false, have the type boolean; in fact, they are the only two values that have the type boolean. They represent computations that ask a question, where the answer can be yes (true) or no (false).

4.1.2 Making Decisions

To see how these new values are particularly useful in action, let’s go back to an example we’ve worked on before – calculating weekly pay with overtime. The past times we’ve addressed the problem, we’ve always made the assumption that the number of hours worked is greater than 40, because the calculation would produce nonsense for smaller numbers of hours. Armed with the ability to ask questions, along with one more piece we’ll add along the way, we’ll be able to make progress on this problem now. Let’s clearly state the problem:

Use the design recipe to write a program that, given an employee’s weekly hours worked and hourly wage, calculates their weekly pay. Any hours worked over 40 should count at double their hourly rate. An employee may work less than 40 hours a week.

First, we need to write the method header, which is the same as before:

int weekly(int hours, int rate)

We can add documentation:

// Calculate weekly pay at the given rate and number of hours worked. // If hours is above 40, pay at double the rate for hours beyond 40. Pay at // rate for the first 40 hours. If the number of hours is less than 40, don't // add any overtime. int weekly(int hours, int rate)

And it’s good to next think through some examples:

// Calculate weekly pay at the given rate and number of hours worked. // If hours is above 40, pay at double the rate for hours beyond 40. Pay at // rate for the first 40 hours. If the number of hours is less than 40, don't // add any overtime. int weekly(int hours, int rate) {
 
}
 
int exactly40 = this.weekly(40, 10); // Should be 400 int someOvertime = this.weekly(45, 10); // Should be 400 + 100, total 500 int lessThan40 = this.weekly(30, 20); // Should be 600

With that, we just need to work on the method body. We know now that we can compare numbers, but that on its own isn’t enough to solve the problem. We need to have the program make a decision based on the result of the comparison. To do this, we’re going to introduce a new kind of syntax that we can use in method bodies: the if statement. An if statement takes the form

if(someBooleanCalculation) {
... someResultIfTrue ...
}
else {
... someResultIfFalse ...
}

In our case, the boolean calculation we need to check is if the number of hours is greater or less than 40, which we get from the problem description. So we can start the method body with:

// Calculate weekly pay at the given rate and number of hours worked. // If hours is above 40, pay at double the rate for hours beyond 40. Pay at // rate for the first 40 hours. If the number of hours is less than 40, don't // add any overtime. int weekly(int hours, int rate) {
if(hours > 40) {
// ... answer if greater than 40 ... }
else {
// ... answer if less than or equal to 40 ... }
}
 
int exactly40 = this.weekly(40, 10); // Should be 400 int someOvertime = this.weekly(45, 10); // Should be 400 + 100, total 500 int lessThan40 = this.weekly(30, 20); // Should be 600

Now, we can use what we know about getting results from methods by using return to fill in the two comments above:

// Calculate weekly pay at the given rate and number of hours worked. // If hours is above 40, pay at double the rate for hours beyond 40. Pay at // rate for the first 40 hours. If the number of hours is less than 40, don't // add any overtime. int weekly(int hours, int rate) {
if(hours > 40) {
return 40 * rate + ((hours - 40) * (rate * 2));
}
else {
return hours * rate;
}
}
 
int lessThan40 = this.weekly(30, 25); // Should be 750 int exactly40 = this.weekly(40, 10); // Should be 400 int someOvertime = this.weekly(45, 10); // Should be 400 + 100, total 500

We’re going to introduce names for the different parts of the if statement. We call the expression in between the parentheses (in the above example, it’s hours > 40) the conditional part. The two parts between the curly braces we call the branches or cases of the if statement. The first is called the then branch, and the second is called the else branch. This helps us say what happens in English – The if statement evaluates the then branch if the condition is true, and the else branch if the condition is false.

Let’s see that definition in action. When we introduced methods, we said that to evaluate a method call we need to do the calculation in the body of the method with the parameters replaced with the argument values. So in the first example above, hours would be replaced with 30, and rate would be replaced with 25. That would look like:

if(30 > 40) {
return 30 * 25;
}
else {
return 40 * 25 + ((40 - 30) * 25);
}

The condition evaluates to false, because 30 isn’t greater than 40. That means Java will evaluate the else branch, and ignore the then branch. This makes the result (or return value) for this method call be 750, the result of 30 * 25.

4.2 More Examples of Decision-Making Programs

There are lots of useful programs that make decisions based on the values of numbers. For example, one handy function is max, which takes in two numbers and returns the larger one. It’s method header and documentation are:

// Returns the larger of num1 and num2 int max(int num1, int num2) {
// need to fill this in }
 
int maxLeft = this.max(5, 4); // should be 5 int maxRight = this.max(6, 7); // should be 7 int maxSame = this.max(3, 3); // should be 3

Here, the method body has a similar shape to the weekly pay method above, but instead of comparing the numbers to a constant like 40, we need to compare the two parameters to see which one is larger. So max looks like:

// Returns the larger of num1 and num2 int max(int num1, int num2) {
if(num1 > num2) {
return num1;
}
else {
return num2;
}
}

Exercise

Implement absolute, which takes a number and returns its absolute value.

4.2.1 More Conditions

Sometimes we can’t capture everything we need to know with a single comparison, like in max and weekly. For example, consider the program gradeForNumber, which takes a number representing a score out of 100, and returns a letter from "A" to "F" representing a grade. Let’s set it up using the design recipe:

// Returns A if the score is over 90, B if 80-89, C if 70-79, D if 60-69, F if below 60 String gradeForNumber(int score) {
// Fill in here }
String aGrade1 = this.gradeForNumber(95); // Should be "A" String aGrade2 = this.gradeForNumber(90); // Should be "A" String aGrade3 = this.gradeForNumber(100); // Should be "A" String bGrade1 = this.gradeForNumber(80); // Should be "B" String cGrade1 = this.gradeForNumber(72); // Should be "C" String fGrade1 = this.gradeForNumber(55); // Should be "F"

If we try to use the structure above, we can easily figure out how to return "A", but it’s less clear what to do in the else branch:

String gradeForNumber(int score) {
if(score >= 90) {
return "A";
}
else {
// What to do here? }
}

It turns out we can write more than just two branches for an if statement. Instead of just having an else branch, we can add more branches, each with its own condition:

String gradeForNumber(int score) {
if(score >= 90) {
return "A";
}
else if(score >= 80) {
return "B";
}
else if(score >= 70) {
return "C";
}
else if(score >= 60) {
return "D";
}
else {
return "F";
}
}

There are a few things to note here:

Exercise

Change the program so that the condition and branch for the "D" case comes before the condition and branch for the "C" case. With this change, is there any argument that could be supplied for score that would make the method return "C"?

Exercise

Write this method in the “opposite” order, where the check for the "F" case comes first, and the check for the "A" case comes last?

4.2.2 More Comparisons

Sometimes, we need to combine more than one comparison result. For example, in this class (and many classes) you need a passing average and over half the credit on the final. To write a function that calculates whether a student passed or not, we need to consider both of these criteria. There are two operators we can use for combining booleans, && and ||. The && operator, pronounced “and”, evaluates to true if both operands are true, and false otherwise. In contrast, ||, pronounced “or”, evaluates to true if either or both operand(s) is true, and false otherwise. Here are examples of their use:

boolean andExample1 = true && true; // evaluates to true boolean andExample2 = true && false; // evaluates to false boolean andExample3 = false && true; // evaluates to false boolean andExample4 = false && false; // evaluates to false boolean orExample1 = true || true; // evaluates to true boolean orExample2 = true || false; // evaluates to true boolean orExample3 = false || true; // evaluates to true boolean orExample4 = false || false; // evaluates to false

With this in mind, we can implement a method passing that takes two numbers, one representing an overall average and another representing the score on the final, and returns whether or not they make up a passing performance.

// Returns true if the grades are sufficient to pass, false otherwise boolean passing(int overallAverage, int finalScore) {
return (overallAverage >= 60) && (finalScore >= 50);
}
boolean goodGradeBadFinal = this.passing(80, 40); // should be false boolean goodGradeGoodFinal = this.passing(80, 60); // should be true boolean badGradeGoodFinal = this.passing(40, 80); // should be false boolean badGradeBadFinal = this.passing(40, 40); // should be false

Note here that the method is returning a boolean value. This can be useful for representing programs that give the answer to a yes/no question. This method doesn’t use if, since the yes/no answer is represented directly by the boolean value returned.

4.3 In Summary

We introduced several new ideas in this lecture: