Laboratory Notebook

Chapter 5 - Functions

Geof Pawlicki

Your name: | |

Your email address: | |

Your student ID number: |

Once this form has been customized for your
institution, you can use this button

To gain experience in

- relating a function's parameter/return value interface to its purpose
- tracing the flow of a function's execution
- function naming and commenting
- recognizing when to use value and reference parameters
- determining the scope of variables
- decomposing complex tasks into simpler ones
- designing functions that solve practical problems
- programming recursive functions

Predictable input will result in predictable output. A function maps
element(s) from a problem domain, called *parameter(s)*, into an
element from a range of solutions, called the *return value*.

To treat a function as a "black box", one simply uses it.

For instance, here's a function to compute the volume of a cylinder, say, a beer can, given the height and diameter in millimeters.

/** * computes the volume of a cylinder * @param height the height in millimeters * @param diameter the diameter in millimeters * @return volume - in cubic milliliters */ public static double cylinder_volume(double height, double diameter) { . . . }

To use this function to measure the volume of a can of Blatz Lite, just supply parameters and get its return.

double v = cylinder_volume(2 * can_depth, 1.5 * can_diameter);

Notice that you can use the function without knowing what's inside. Actually, this function is quite simple

public static double cylinder_volume(double height, double diameter) { double volume = Math.PI * diameter * diameter * height / 4; return volume; }

Suppose, instead of milliliters, an answer in fluid ounces is needed,
say for a recipe. Use the following functions, together with `cylinder_volume
` to implement a US measurement version `public static double
cylinder_volume_oz(double height, double diameter) ` like this:

import ccj.*; public class Cylinder { /** * convert inches to millimeters * Note: 1 inch = 25.4 millimeters * @param inches value in inches to convert to millimeters * @return the converted value */ public static double inch_to_mm(double inches) { return inches * MM_PER_INCH; } /** * convert cubic millimeters to U.S. ounces * Note: 1 fluid U.S. ounce = 29.586 milliliters * @param mm3 - volume in cubic millimeters * @return volume in ounces */ public static double mm_cubed_to_oz(double mm3) { return value_to_convert / MM_CUBED_PER_OZ; } /** * computes the volume of a cylinder * @param height the height in millimeters * @param diameter the diameter in millimeters * @return volume - in cubic milliliters */ public static double cylinder_volume(double height, double diameter) { double volume = Math.PI * diameter * diameter * height / 4; return volume; } /** * computes the volume of a cylinder * @param height the height in inches * @param diameter the diameter in inches * @return volume - in fl. oz. */ public static double cylinder_volume_oz(double height, double diameter) {

} public static void main(String[] args) { System.out.println("Please enter the height (in inches)"); double height = Console.in.readDouble(); System.out.println("Please enter the diameter (in inches)"); double diameter = Console.in.readDouble(); double volume = cylinder_volume_oz(height, diameter); System.out.println("The volume is " + volume + " ounces"); } public static final double MM_PER_INCH = 25.4; public static final double MM_CUBED_PER_OZ = 29.586; }

Productivity Hint 5.1 in the text suggests several enhancements to the future value function. Here is the function from the text.

import ccj.*; public class Futval { /** * computes the value of an investment with compound interest, compounded monthly * @param initial_balance the initial value of the investment * @param p the interest rate as a percent * @param nyear the number of years the investment is held * @return the balance after nyears years */ public static double futureValue(double initial_balance, double p, int nyear) { double b = initial_balance * Math.pow(1 + p / (12 * 100), 12 * nyear); return b; } public static void main(String[] args) { System.out.print("Please enter the initial investment: "); double initial_balance = Console.in.readDouble(); System.out.print("Please enter the interest rate in percent: "; double rate = Console.in.readDouble(); System.out.print("Please enter the number of years: "; double nyears = Console.in.readInt(); double balance = futureValue(initial_balance, rate, nyears); System.out.println("After " + nyears + ", the initial investment of " + initial_balance + " grows to " + balance); } }

Change the `futureValue` function to compute the value of the
investment when there are `npayments` regular interest payments
per year (instead of 12 monthly payments). Supply a `main`
function that calls your changed function.

What is the value of a $2000 investment after 6 years at 12 percent if interest is compounded quarterly?

Change the `futureValue` function to accept holding an
investment for a fractional number of years, `fyears`. Supply a
`main` function that calls your changed function.

What is the value of a $2000 investment after 6.5 years at 12 percent if
interest is compounded weekly (52 weeks/year)?

The following code can be used to test a text mode function's interface.
It's called a test harness, because any function can be used in it, simply
by replacing ` foo() `with the new function call and the desired
parameters and returns.

import ccj.*; public class TestHarness { /** * Test harness for calls to console programs * Used to test parameter and return passing. * @param n programmer specified parameter * @return programmer specified return value */ public static double foo(int n) { System.out.println("Got to " + "foo" + " with parameter " + n); return n * 0.1; /* to simulate function activity */ } public static void main() { int give = 3; /* any test value can be entered here */ double get = foo(give); System.out.println("Returned from " + "foo" + " with " + get); } }

Why are the following functions badly named? Try using them in the test harness. What happened? What names would be better?

`double 2toTheN(int n)`

/* compute a power of 2 */`double(int max_value)`

/* generate a random floating point number between 0 and max_value */`double rt(double x)`

/* computes the square root */

Sooner or later, you'll encounter cryptically named and uncommented work like the following.

public static bool cn(Employee e1, Employee e2) { return e1.getName() == e2.getName(); } public static bool cs(Employee e1, Employee f) { return e1.getSalary() == f.getSalary(); } public static bool c4dup(Employee joe, Employee mary) { return cn(joe, mary) && cs(joe, mary); } public static void main(String[] args) { Employee john; Employee jane; . . . if(c4dup(john, jane)) System.out.println("Same"); else System.out.println("Different"); }

What do these functions do ?

Rewrite them with comments and more descriptive function and parameter names. Use:

/** *purpose* @param * @return */

In functions with more complicated branching of control, one way to
insure a reasonable return value is to gather together all the
possibilities and issue only one `return` statement from the very
end of the block statement. Rewrite the `pointsOfCompass` function
as follows:

- Introduce an additional variable
`string directionString` - Be more clever about the logic--first compute the major direction (north, east, south, west), then append an east or west if necessary
- Return the
`directionString`from the end of the function only

import ccj.*; public class Compass { /** * Convert a numeric compass position to it's verbal equivalent * @param degrees the compass needle angle in degrees * @return the value as a compass direction ("N", "NE", ...) */ String pointsOfCompass(int degrees) { double octant = (degrees % 360) / 45.0 - 0.5; if (octant >= 7) return "North West"; else if (octant >= 6) return "West"; else if (octant >= 5) return "South West"; else if (octant >= 4) return "South"; else if (octant >= 3) return "South East"; else if (octant >= 2) return "East"; else if (octant >= 1) return "North East"; else if (octant >= 0) return "North"; else return "North West"; } public static void main(String[] args) { System.out.print("Please enter the compass heading (in degrees): "); int degrees = Console.in.readInt(); String direction = pointsOfCompass(degrees); System.out.println("You are heading " + direction); } }

Consider the following functions:

public static String replace(String s, String a, String b) public static double canVolume(double h, double r)

and these variables:

String greeting = Hello!" int a; int b;

What is wrong with each of the following function calls ?

`greeting = replace("Hello?", "?");`

`greeting = replace("Goodaye", a, b)`

`greeting = canVolume(a,b)`

In Java, a function cannot modify contents of any variables that are
passed into it. For example, consider this call to the procedure `f`:

Employee harry = . . .; int n = . . .; . . . f(harry, n)

After the call, `harry` still refers to the same employee object
as before the call, and `n` still contains the same number as
before the call.

The procedure `f` can modify the state of the employee object,
for example, raise it's salary. But it can't replace the object with a
different one.

The procedure cannot modify the value of `n` at all.

- In Java, it is not possible to write a procedure
`increment`in Java that achieves the following. Explain.int a = 4; int b = 2; increment(a, b); /* now a is 6, b is 2 */

- Can you solve this problem by writing a function instead? How?
- Is it possible to write a procedure
`increment`in Java that achieves the following? Explain.Employee a = new Employee("Aaronsen, Adam", 34000); int b = 1000; increment(a, b); /* now a's salary is 35000, b is 1000 */

- Is it possible to write a procedure
`swap`in Java that achieves the following? Explain.Employee a = new Employee("Aaronsen, Adam", 34000); Employee b = new Employee("Birnbaum, Bruce"); swap(a, b); /* now a is Bruce, b is Adam */

Generally, we want to encourage you to define a variable when you first need it, but you have to pay attention to the scope. Find what's wrong with this function's variable scoping, then fix it.

/** * Select the maximum of three integer values * @param i an integer * @param j an integer * @param k an integer */ public static int maximum(int i, int j, int k) { if (i > j) { int a; a = i; } else { a = j; } if (k > a) { return k; } else { return a; } }

Global (or `static`) variables may "work", but the
advantages they offer are outweighed by the confusion they can cause.
Since all functions can set a global variable, it is often difficult to
find the guilty party if the global variable is set to the wrong value.

import ccj.*; public class Max { /** * Updates maximum if parameter is larger * @param a the value to compare against maximum * @remark Uses static int maximum */ public static void set_max(int a) { if (maximum < a) { maximum = a; } } public static int max3(int i, int j, int k) { maximum = i; set_max(j); set_max(k); return maximum; } public static void main(String[] args) { System.out.print("Please enter the first integer: "); int i = Console.in.readInt(); System.out.print("Please enter the second integer: "); int j = Console.in.readInt(); System.out.print("Please enter the third integer: "); int k = Console.in.readInt(); int maximum = max3(i, j, k); System.out.println("The maximum is " + maximum); } public static int maximum; }

Re-write ` max3() ` to avoid the use of global variables, and to
preserve the logic of the function.

A call-tree diagram can help organize a large number of related functions. Consider drawing a house like this one using the code library's graphics functions.

It can be done by organizing calls like these:

drawHouse() drawWindow() drawFront() drawRoof() drawDoor()

into a call tree like this

drawHouse | +---------- drawFront | | | +----------- drawWindow (3 times) | | | +----------- drawDoor | +---------- drawRoof

Write a program that implements these functions to draw a house. (Hint:
`draw_window` needs a parameter to specify the location of the
window.)

Modify the `intName` function of the text book to print numbers
in German (or, if you prefer, in another language). Here is how German
numbers are formed.

The digit names are as follows:

1 eins 2 zwei 3 drei 4 view 5 fuenf 6 sechs 7 sieben 8 acht 9 neun

The tens are named as follows:

10 zehn 20 zwanzig 30 dreissig 40 vierzig 50 fuenfzig 60 sechzig 70 siebzig 80 achtzig 90 neunzig

As in English, the names of the numbers between 11 and 19 are special.

11 elf 12 zwoelf 13 dreizehn

You can obtain 14 ... 19 from 40 ... 90 by replacing `"zig"`
with `"zehn"`.

14 vierzehn 15 fuenfzehn 16 sechzehn 17 siebzehn 18 achtzehn 19 neunzehn

You also need to know

100 hundert 1000 tausend

So far, this is just as in English, just with different names. However,
there is one crazy complication. When forming a number between 21 and 99,
the ones come *before* the tens. For example

23 dreiundzwanzig

is "three and twenty". That is, you first convert the ones,
then append the word `"und"`, then convert the tens.
This being German, you do not add any spaces at all. For example,

23456 dreiundzwanzigtausendvierhundertsechsundfuenfzig

Write the `intName` function (and the auxiliary functions that
it calls) to produce German number names.

Perform a walkthrough of the German `intName` with `n = 416`

Consider a function `int digits(int)` which finds the number of
digits needed to represent an integer. For example, `digits(125)`
is 3 because 125 has three digits (1, 2, and 5). The algorithm is defined
as:

if n < 10, then digits(n) equals 1. Else, digits(n) equals digits(n / 10) + 1.

(Why? If n is less than 10, one digit is required. Otherwise, n requires one more digit than n/10.)

For example, if called as `int num_digits = digits(1457)`, the
following trace results:

digits(1457) = digits(145) + 1 = digits(14) + 1 + 1 = digits(1) + 1 + 1 + 1 = 1 + 1 + 1 + 1

Do a trace of `digits(32767)`

Write `int digits(int n)` to be called by the following `main()`:

public static void main(String[] args) { System.out.print("Please enter a number: "); int testValue = Console.in.readInt(); int ndigits = digits(testValue); System.out.println("You need " + ndigits + " digits to represent " + testValue + " in decimal"); }

Don't forget to send your answers when you're finished.