## Are you sure?

This action might not be possible to undo. Are you sure you want to continue?

Lecture Notes Spring 2006 c 2006, 2005, 2004, 2003, 2002, 2001, Jason Zych

ii

Part 1 : Programming Basics Lecture 1 : Computer Science and Software Design . . . . . . . . . . . Lecture 2 : Architecture and Program Development . . . . . . . . . . Lecture 3 : Types, Variables, and Expressions . . . . . . . . . . . . . . Lecture 4 : Type Checking, Input/Output, and Programming Style . . Lecture 5 : Boolean Expressions, Simple Conditionals, and Statements Lecture 6 : Compound Statements, Scope, and Advanced Conditionals Lecture 7 : Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lecture 8 : One-Dimensional Arrays . . . . . . . . . . . . . . . . . . . Lecture 9 : Multi-Dimensional Arrays . . . . . . . . . . . . . . . . . . Lecture 10 : Processing Data Collections . . . . . . . . . . . . . . . . . Part 2 : Programming Methodologies Lecture 11 : Procedural Composition and Abstraction . . . . . . . . . Lecture 12 : Method Syntax . . . . . . . . . . . . . . . . . . . . . . . . Lecture 13 : Reference Variables and Objects . . . . . . . . . . . . . . Lecture 14 : Objects and Methods . . . . . . . . . . . . . . . . . . . . Lecture 15 : Data Composition and Abstraction: Classes and Instance Lecture 16 : Classes, Reference Variables, and null . . . . . . . . . . Lecture 17 : Instance Methods . . . . . . . . . . . . . . . . . . . . . . Lecture 18 : static versus non-static, and Constructors . . . . . . . Lecture 19 : Access Permissions and Encapsulation . . . . . . . . . . . Lecture 20 : Copying and Mutability . . . . . . . . . . . . . . . . . . . Part 3 : Algorithm Design and Recursion Lecture 21 : Introduction to Algorithm Design and Recursion Lecture 22 : Subproblems . . . . . . . . . . . . . . . . . . . . Lecture 23 : Picture Recursion . . . . . . . . . . . . . . . . . Lecture 24 : Recursive Counting . . . . . . . . . . . . . . . . Lecture 25 : Subarrays – Recursion on Data Collections . . . Lecture 26 : Searching . . . . . . . . . . . . . . . . . . . . . . Lecture 27 : Sorting . . . . . . . . . . . . . . . . . . . . . . . Lecture 28 : Tail Recursion and Loop Conversion . . . . . . . Lecture 29 : Accumulator Recursion . . . . . . . . . . . . . . Lecture 30 : More Accumulator Recursion . . . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

1 2 9 20 31 48 56 65 75 82 101 109 110 128 142 156 170 176 196 210 217 223

. . . . . . . . . . . . . . . . . . . . . . . . Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

231 . 232 . 256 . 278 . 288 . 292 . 301 . 312 . 334 . 354 . 366 379 . 380 . 386 . 392

Part 4 : Algorithm Analysis Lectures 31 and 32 : Introduction to Algorithm Analysis . . . . . . . . . . . . . . . . . . Lecture 33 : Selection Sort Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lecture 34 : Insertion Sort Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iii

iv Lecture 35 : Exponentiation and Searching Lectures 36 and 37 : Mergesort . . . . . . . Lectures 38 and 39 : Quicksort . . . . . . . Lecture 40 : Advanced Sorting Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393 402 410 437

**Part 1 : Programming Basics
**

The study of programming begins with some of the very basic tools needed by any program. Any computer program is simply a set of instructions that manipulates data, so we need some way to store data and some way of manipulating it. To begin with, our data values will be very simple – integers and ﬂoating point numbers, for example — and so we want some way to store simple values like that, and simple operations to manipulate those values (such as arithmetic operations). We of course want to be able to input data from the user, and print data to the screen as well, so we will also need to learn how to do those things. But in addition to simple data storage and arithmetic manipulation, we will also ﬁnd that to write meaningful programs, we need a few more tools as well. Our programs will need some way of making choices (for example, should a program report that a student has passed, or report that a student has failed), repeating work (if you want to calculate the homework average for one student, you might want to repeat that same calculation for 299 other students as well), and storing large amounts of data in a way that makes it easy to manipulate (for example, if you have a roster of the names of 300 students in a class, you might want to simply say “print all the names on the roster”, rather than individually saying to print the ﬁrst name, print the second name, print the third name, and so on). Once you can store and manipulate data, and can use the above sorts of tools as well, you will be on your way to writing very interesting and useful programs. However, prior to learning how to do any programming, it is useful to understand what computer science is, and where programming ﬁts into that deﬁnition. It is also useful to have a vague idea of how the actual circuitry of a computer stores and manipulates your data, and how the process of developing a program converts code that you understand into code the computer understands. So, we will start with those discussions, and armed with that as background, we will then begin to learn how to write programs in Java.

1

2

**Lecture 1 : Computer Science and Software Design
**

An introductory discussion Imagine you walk into a room and there are 100 small boxes laid out in a single-ﬁle row on the ﬂoor. The lids of the boxes are closed. You are told that either exactly one of the boxes contains a penny, or else all the boxes are empty. Your task is to determine which box, if any, holds the penny. Now, suppose that there isn’t a penny inside any of the 100 boxes. However, you don’t know that yet; you’ve only just now walked into the room, and you are asked to ﬁgure out which box – if any – holds a penny. How many of the boxes would you have to open before you could be certain there was no penny in any box? For example, if you open just one box, and it was empty, could you then be certain there was no penny in any of the 100 boxes? If not, how many boxes would you have to open before you could be certain that none of the 100 boxes held a penny? (Assume you can only tell if a penny is in a box, by opening the box and looking inside – i.e. you don’t have metal detectors or X-ray machines or any such thing.) Well, since you have not been given any extra information about the boxes, it will be necessary to look inside every box to see whether or not that box holds a penny. If you only check half the boxes, the penny could be in the boxes you didn’t check. If you check all but one of the boxes, the penny could be in that last box you didn’t check. You can only say for certain that no box contains a penny if you’ve checked every box. Certainly, if you knew something extra about the boxes, that might help. For example, perhaps half the boxes are green, and half are red. If you were told that the penny – if there is one – is in a green box, then you would not need to check the red boxes, since you’ve been told the penny would not be in a red box. However, you would need to look at every box, since you at least need to determine if a box is green or red before deciding whether or not you need to bother opening it to look inside. On the other hand, if you are told that if there is a penny, it would only be in one of the three boxes closest to the door, then in that case, not only would you not need to look in most of the boxes, but you wouldn’t even need to look at most of the boxes – you could just look inside the three boxes closest to the door, and ignore every other box in the room. But if you are given no such additional information – if it’s possible for the penny to be in any of the boxes – then you would need to check all of the boxes in order to be sure that there was no penny in any box. Now, imagine a second scenario. What if I printed out the name of every single student at the University of Illinois, in a list that is not alphabetized, and gave that printout to you? Imagine that I then ask you to search that list to see if there is any student named “Abraham Lincoln” on the list. Perhaps there is such a student on the list, and perhaps there is not. However, if you only look at the ﬁrst page of names, ﬁnd no one named “Abraham Lincoln”, and then quit, you can’t be sure there is no such student at the University – maybe there’s a student with that name on one of the other pages you didn’t check. If you check all the pages except the last one, or if you check every name on every page except for the last name on the last page, you face a similar problem. As long as there are names you haven’t looked at yet, one of those names could have been “Abraham Lincoln” and you’d have no way of knowing. You can only be sure there is no student with that name if you’ve checked every name. Now, certainly, if the list is ordered in some manner, that would help. If the names are sorted alphabetically by last name, for example, you’d only need to check a certain part of the list – namely, where “Lincoln” would occur in alphabetical order. But if there is no order to the list for you to take advantage of, then you’d be forced to check every name if you wanted to be sure that

3 no student named “Abraham Lincoln” existed. Note that the problem of searching boxes for a penny, and the problem of searching a list of names for a student with a particular name, are very similar problems. In both problems, you have a number of similar items, in no particular order, and you are searching the collection of items for one which matches a description exactly. In the ﬁrst case, you are looking at boxes one by one to ﬁnd the box that matches the description “there’s a penny inside”; in the second case, you are looking at names one by one to ﬁnd the name that is “Abraham Lincoln”. And in both problems, if the only information you have is the description you are trying to match, you need to examine all the items before you can be certain that no item matches the description. However, it is also true that in both problems, if you had additional information that you could use to rule out some items without examining them in detail, then it might have saved you some work. This is computer science!! Now, that might sound strange, since we have not even discussed computers yet. However, what we are doing here is conveying an important principle: computer science is not about computers.

Computer Science Computer Science is the study of computation. (The discipline really should have been called “Computation Science” from the start, but “Computer Science” got chosen way back when, and now we’re stuck with that name, even though it’s a little bit misleading.) The study of computation encompasses a great deal of material. Among the more theoretical aspects of this material is the study of algorithms. An algorithm is a step-by-step procedure (with a ﬁnite number of steps) that, when followed from beginning to end, solves a particular problem – i.e., performs a speciﬁc computation. Note that these steps should be well-deﬁned steps that can be easily and correctly followed every time the algorithm is run; for example, you can’t say “Step 5: meditate until the answer pops into your head” and count that as a legitimate step. Note that we have basically come up with an algorithm for solving each of our earlier problems, even though we didn’t state the algorithm in any sort of formal way. Let’s do that now, for the box-and-penny problem:

4 To determine which of a finite number of boxes, if any, holds a penny: step 1) Start at the first box in the collection of boxes; call this your ‘‘current box’’; go to step 2. step 2) Open the ‘‘current box’’ to see if it holds a penny; if it does, go to step 6; otherwise go to step 3. step 3) If your ‘‘current box’’ is the last box in your collection of boxes, go to step 5, otherwise, go to step 4. step 4) Consider the next box after your ‘‘current box’’ to be the new ‘‘current box’’, and go back to step 2. step 5) It is confirmed that no box in the collection of boxes has a penny inside it, since we have now checked every box in the collection. Stop. step 6) You have found the box that contains a penny. Stop. Note that as you run through this algorithm, step 4 takes you back to step 2. So, if the box does NOT hold a penny in step 2 (thus leading you to step 3), and if that is NOT your last box in step 3 (thus leading you to step 4), you end up at step 2 again, and might run through the series of steps 2, 3, and 4 over and over again. You only stop running steps 2, 3, and 4 repeatedly, when you either ﬁnd a box with a penny in step 2, or you run out of boxes in step 3. And that should make sense, given our problem – we only stop looking for a penny, when we’ve found the penny, or run out of boxes to look at. Note that this algorithm is very similar to the one we might have used to ﬁnd “Abraham Lincoln” on our list of unalphabetized names:

To determine which of a finite number of student names, if any, is ‘‘Abraham Lincoln’’: step 1) Start at the first name in the list of names; call this your ‘‘current name’’; go to step 2. step 2) Examine the ‘‘current name’’ to see if it is ‘‘Abraham Lincoln’’; if it is, go to step 6; otherwise go to step 3. step 3) If your ‘‘current name’’ is the last name in your list of names, go to step 5, otherwise, go to step 4. step 4) Consider the next name after your ‘‘current name’’ to be the new ‘‘current name’’, and go back to step 2. step 5) It is confirmed that no name in the list of names is ‘‘Abraham Lincoln’’, since we have now checked every name in the list. Stop. step 6) You have found the name that is ‘‘Abraham Lincoln’’. Stop. In fact, you could generalize this, and have a procedure that work no matter what collection you were searching and no matter what particular value you were searching for:

one by one. from the ﬁrst item to the last item. Even if you search through the collection of integers using a computer. step 3) If your ‘‘current value’’ is the last value in your collection. when we said “Computer science is not about computers”. step 5) It is confirmed that no value in the collection matches the description. and go back to step 2. For now. based on our earlier discussion. It can be applied to many other kinds of speciﬁc problems as well – any time we have a collection and need to search it for a particular item. A faster computer can only perform each individual step of an algorithm faster. step 2) Examine the ‘‘current value’’ to see if it matches your description. otherwise. when we discuss algorithms. the computer program you write will still have to look through all one hundred diﬀerent integers. trying to ﬁnd one that matches a particular description. If in the future. Stop. go to step 6. matches some description D: step 1) Start at the first value in the collection. the design of computers. even if you don’t know that yet – you would have to look at all one hundred integers to be certain that none of them are equal to 47. go to step 4.5 To determine which of a finite number of values in a collection C. in this very course. we simply wanted to present it as one example of an algorithm – a ﬁnite. For example. was to point out that we can discuss what work needs to be done. it cannot eliminate any of the steps of the algorithm. later CS courses will introduce you to the design of computers as well. or searching a list of names for a particular name. then – assuming that none of the integers are equal to 47. step-by-step procedure designed to solve a particular problem. that computer will still need to look at all one hundred integers. In fact. too Now. We will explore linear search and other kinds of searching. So. The important idea behind the earlier discussion. if it does. step 4) Consider the next value after your ‘‘current value’’ to be the new ‘‘current value’’. go to step 5. perhaps that was a little bit inaccurate. Stop. without discussing how that work needs to be done. you will begin your study of the programming of computers. later in the semester. we know that if you are searching through one hundred diﬀerent integers. As we have seen. that doesn’t mean you suddenly only need to look at ﬁfty integers – because looking at every single integer is an inherent property of the computation itself. it can be applied to searching boxes for a penny. Usually. step 6) You have found the value that matches your description. The above is an algorithm known as “linear search” – and the purpose of the algorithm is to search a collection of items. This is an inherent property of the problem. and the programming of computers. since we have now checked every value in the collection. you buy a computer 100 times as fast as the one you use today. for one that equals 47. not people. our intention is to eventually have computers. otherwise go to step 3. go to step 2. call this your ‘‘current value’’. though. We’ll talk about computers. are also aspects of computer science. run through the algorithms step by step. If you buy a computer tomorrow that is twice as fast. since every one of those steps still needs to . and has nothing at all to do with the speed of the computer or person doing that computation.

In that respect. However. and some program design techniques. However. basic operations such as simple arithmetic. For example. here. If your fast computer only looks at the ﬁrst ninety-nine integers. all programs basically consist of (1) obtaining data and (2) running instructions that manipulate that data. and the particular symbols which represent those ideas in Java. algorithm design and analysis. we will use Java to express our ideas. just as the idea of “water” is the same whether the word you use to represent the idea is the English word “water”. and though we will not touch on every aspect of computer science in this course. the computational ideas we will learn will will be the same. We will be touching on some more general computer science issues as well – among them. are understood by the language. as well as giving you some beginning instruction in how to program computers and design software. won’t necessarily be the same as the particular symbols that represent those ideas in other languages. We are learning general programming ideas which you will ﬁnd in many diﬀerent programming languages. (The concept is similar to that of an “atom” in chemistry or physics class. they can’t really be broken down into smaller parts without losing their meaning. you will learn about various computational ideas. Conceptual Tools In the coming lectures. is that – though programming is part of computer science – a proper exposure to computer science involves more than just programming. Primitives are not things you need to deﬁne. or multiplying two numbers. and if this is the ﬁrst time you’ve done any programming. similarly. Other programming languages might express those ideas with diﬀerent syntax than Java uses. characters such as a lowercase ‘h’ or the dollar sign ‘$’. Similarly. the last integer could have been 47 and neither you or the program would have any way of knowing for sure – except by continuing onward and inspecting that ﬁnal integer out of the one hundred integers. or the Japanese word for that concept. there are three kinds of conceptual ideas we can introduce right now.) • When talking about data. would be considered . just like diﬀerent spoken languages would have diﬀerent words for the concept of “water” or “computer”. we will indeed be doing more than just teaching you how to program in the Java language. The important thing to realize. though. There are many other sorts of advanced theoretical issues that can be discussed as well – and you will discuss many of them in later courses! CS125 is meant to introduce you to some introductory theoretical issues. and the particular Java syntax that you use to express those ideas in a Java program. it might be easy to think we are learning Java ideas instead of general programming ideas. regardless of what language we happen to be using to express those ideas.6 directly into your Java program. you can type numbers such as 5 or -2 or 38. which you will make heavy use of as you design programs throughout the semester: 1. since they are built right into the language. That is not the case. In this course. primitives – these are the lowest-level data values and procedures provided by a programming language. the primitives are small. Adding two numbers. likewise. or the Spanish word for that concept. the language is designed to understand numerical values like that automatically.6 be performed in order for the algorithm to be run correctly. All of these things are part of the discipline of computer science. the primitives are the built-in data values that your program can use without any further deﬁnition. • When talking about procedures.

you might know that the phone is encoding sound waves as electrical signals. and the names of the classes you took and the grades you got in those classes. So let’s apply that ﬁrst procedure (addition) twice. and so on. when you take your name. and then apply the second procedure (division by 3) after that. it is possible to add two numbers together. we mean building larger pieces of information by collecting together smaller pieces of information. and certainly you don’t perform that encoding on your own! You simply trust that the phone will perform the encoding properly. primitive procedures – addition and division. smaller procedures that together form a more larger.7 primitive operations – they are built right into the language and you can use them without any further deﬁnition. composition – this is the idea of taking smaller ideas. You worry about the overall purpose of the phone. such as your name. to the third number (c) divide the sum from the previous step. . forget about it. GPA. but you don’t really worry too much about that when you speak into a phone. more complex piece of data. • When we talk about procedural composition. the result of the last step will be the average of the three numbers we were given. someone or something has to handle those details. but that someone or something doesn’t have to be you. social security number. When you speak into a telephone. Given three numbers: (a) add the ﬁrst two numbers together (b) add the sum from the previous step. 2. and putting them together to build a larger idea which is eﬀectively just the collection of the smaller ideas • When we talk about data composition. in that case. we mean building larger procedures out of smaller ones. For example. and it is possible to divide a number by 3. and put all that information together. which together form a larger. However. you have your personal record in the student database. abstraction – this is the idea of focusing on the “big picture” of an idea. is in reality composed of many smaller pieces of data. and just pick up the phone and speak. Certainly. by 3 Now. The student record is a composition of other. GPA. 3. By combining some primitive mathematical operations – addition and division – we were able to produce a procedure that is slightly more complex. smaller data values. rather than the details of how that purpose is achieved. and ignoring the details that implement that big picture. address. we’ve applied some small. the chunk of data known as a “student record”. more complex procedure. what we have really done above is to create a procedure for ﬁnding the average of three numbers. This more complex procedure is a composition of other. So. The advantage to doing this is that worrying about those details often bogs us down in a lot of little issues that we really don’t need to worry about. For example. In this case.

Sometimes. and with procedural composition and abstraction. if you break any procedure down into small enough detail. We can send the three values to our procedure. we are referring to the idea of viewing a data composition in terms of what the overall collection of data is trying to represent. once we have written a procedure to take the average of three values. will be primitives. Sometimes. we don’t need to worry about sending a lot of little pieces of data to those procedures. though. you will ﬁnd that it is composed of the same limited set of data primitives that the language (and processor) supports. instead of always peeking into a procedural composition to see the smaller procedures from which it is made. we can make use of it whenever we have three values we want the average of. instead of always peeking into a data composition to see the smaller pieces of data from which it is made. we mean viewing a composition of smaller procedures in terms of what the overall goal is. All these ideas ﬁt together to aid us in program design. In addition. For example. . For example. if you break any piece of data down into small enough detail. we will often compose a larger piece of data out of smaller pieces of data. you will ﬁnd that it is composed of the same limited set of procedural primitives that the language (and processor) supports. on the larger data concept rather than worrying about what smaller pieces of data form the larger one. and then focus from then on. on the larger procedure and what the overall task is that it accomplishes. quite often. we simply send one piece of data – a student record – and don’t need to worry that a student record is “really” lots of smaller pieces of data if you look at it closely. We just choose to focus on the “big picture” when we can. Similarly. those smaller pieces of data that form the larger piece of data. rather than focusing on the details of what pieces of data make up the composition.8 We make use of this idea when writing computer programs. and you will also learn about the syntax available in Java for dealing with data composition and abstraction. though. We just choose to focus on the “big picture” when we can. and we don’t have to worry about the details of how the average is calculated. Over the ﬁrst half of CS125. as well: • When we talk about data abstraction. rather than worrying about all the smaller little procedures that form it. rather than viewing it in terms of the smaller procedures that make up the larger one. • When we talk about procedural abstraction. will be primitives. Ultimately. and then focus from then on. the smaller procedures we use to form the larger procedure. you will learn about many of the primitives available to you in the Java programming language. Quite often. we might have procedures that “print a student record” or “copy a student record”. and sometimes they will instead be other data compositions we already created earlier. and sometimes they will instead be other procedural compositions we already created earlier. Ultimately. and get the average back. you will see the concept of abstraction in use in other ways as well. in those cases. we will compose a larger procedure out of smaller ones.

and translate that information into an entirely diﬀerent form. assuming there is a phone on the other end of the line to decode the electrical signals back into sounds. though wires. The reason is that a computer is nothing more than a big piece of electronic circuitry – eﬀectively. and each of them has only two settings or positions – “on”. no matter how loud you yell. Since most of the time. changes this. electrical signals can be transported long distances. we don’t want to have to think about the particular circuitry layout on a computer chip. and thus we want the information in “sound wave” form. we instead represent the idea of a single transistor or the ﬂow of electricity on a single wire. then your friend can hear your voice in California. And though sound cannot easily be transported long distances. the person in California is not going to hear you. We can freely convert between the “sound wave” form and the “electrical signal” form (using the phone hardware). but that sound cannot be carried by the air all the way to California.9 Lecture 2 : Architecture and Program Development Data Encoding The idea of data encoding is to take information in one form. Likewise. any of the wires in the computer either has electrical current ﬂowing through it at that particular moment. with the intention being that we can translate back to the ﬁrst form eventually. so we encoded the sounds (our voices) into a form that was more convenient to us – electrical signals – and then decoded the information back to the ﬁrst form – sound waves – once the transport was done. and “oﬀ”. or doesn’t (in the case of a 0). If you did not have a phone. have electrical current ﬂowing through it. the sound generated by your voice can be carried through the air to the people standing near you. however. Imagine you are in Champaign-Urbana and you are talking on the phone to someone in California. We presumably want to translate information to the second form because the second form is easier to deal with in some way – otherwise. respectively. then when our goal is creating or hearing that information with our own body. or else it doesn’t have electrical current ﬂowing through it at that particular moment. We implement a bit in hardware with a transistor set to either “on” or “oﬀ”. and thus we want the information in “electrical signal” form. A bit is a single digit that is always either 1 or 0. thinking . with the idea of a bit. and what position switches are in and where current is ﬂowing. Encoding data as bits The concept of data encoding is of tremendous importance in the design of a computer. They are just too far away. a pile of electrical switches hooked together by wires. since your friend cannot understand electrical signals! But. But when our goal is transporting the information long distances quickly. the telephone you are holding encodes the sounds of your voice into electrical signals. The concept here was that sounds were not convenient for sending across long distances. Each of the electrical switches is called a transistor. depending on which form of the information is more convenient at the time. then sound waves are the more useful form. Having a telephone. then electrical signals are the more useful form. we would just keep the information in the ﬁrst form to begin with! Talking on the phone is a good example of this. this idea is useless unless your friend in California also has a working phone. But. If we think of vocal sounds as information. When you speak. and just stood in the middle of the Quad and yelled. Of course. or with an electrical wire that either does (in the case of a 1).

You’ll learn more details in other courses. The more bits we have. but the simpliﬁed view of things we are presenting is good enough for our purposes. For example.10 in terms of transistors and wires is more detail than we really care about in many situations. we will instead view it as a collection of bits. the larger the set of integers we can encode. both with current). The processor is where the circuitry for performing additions. or two wires. set up as follows (with CUR meaning there is current on that wire. we have 2N diﬀerent bit strings of that length. resides. The example on the right below shows how we might encode 8 integers using 3 bits. and so we can encode 2N diﬀerent values (N is 2 in the example on the left below. the following: 1111100010111001 is a bit string that is 16 bits long. The two components of the computer that we are concerned with understanding are the processor and memory. set as follows: on on on on on off off off on off on on on off off on or by a row of wires. or a row of wires. subtractions. two transistors. so commonly. If we want to represent the number 3 in that case. integer ------0 1 2 3 bit string encoding ------------------<--> 00 <--> 01 <--> 10 <--> 11 integer bit string encoding ------------------------0 <--> 000 1 <--> 001 2 <--> 010 3 <--> 011 4 <--> 100 5 <--> 101 6 <--> 110 7 <--> 111 Basic computer architecture Computer architecture is much more complicated than we are describing here. everything gets encoded using bit strings. The example on the left below shows how we might encode a set of 4 integers using 2 bits. In the actual computer hardware. given a bit string length of N . when thinking about having a collection of transistors. and so on. it would be represented by 16 transistors in a row. we can do it using two bits. . and 3 in the example on the right below). both set to 1 (or in other words. and NCUR meaning there is no current on that wire): CUR CUR CUR CUR CUR NCUR NCUR NCUR CUR NCUR CUR CUR CUR NCUR NCUR CUR When dealing with computers. both set to “on”. A bit sequence or bit string is then a collection of bits.

.| | operation ---|___________________________________| || | ||. starting with zero at the top: . if you want to add 2 and 3. we need to be able to tell the processor what operation it should be doing (for example.11 _____________________________________ | | | processor (lots of circuitry | | here that we are not drawing in) | |___________________________________| We need the ability to send data into the processor.. that’s where memory comes in. this is a somewhat-more-accurate model for a processor: many wires to encode one input value many wires to encode a second input value || | || | ||. The processor cannot store information.| _||___|__________________||___|______ | | many wires ---| processor (lots of circuitry | to encode ---| here that we are not drawing in) | an ..| || | many wires to encode an output value That’s basically all a processor is – a bunch of wires carrrying input. a bunch of wires carrying output. to produce the desired output signals. on encoded data values. if we tell the processor to add 2 and 3. though – it can only process the information it is given. the encoded value of 5).. subtraction.. and circuitry between the input and output wires which manipulates the input signals in the desired way. how to design a processor is beyond the scope of this course. although you will learn the beginning ideas in CS 231 and CS 232 (or ECE 290 and ECE 291). Imagine there is a room with some shelving in it. Where does that information come from.. you need a way to send the encoded values of 2 and 3 into the processor. and each each shelf is numbered. Therefore. plus you need a way to receive the result. and receive data out of it (for example. etc. In addition. The interesting thing about processors is how you design that circuitry – how it is that you can wire transistors together to perform addition.. that we ultimately give to the processor? Well. we get a diﬀerent result than if we tell the processor to multiply 2 and 3). You can imagine memory as a shelving unit..| ||. Unfortunately.

top shelf I could tell you to do things such as “take the item from shelf 3 and move it to shelf 6”. or “take the item from shelf 4 and throw it away”. The “shelves” are often called memory cells or memory locations. the address is represented as a bit string in the machine. I ﬁrst tell you what shelf to go to by telling you the shelf number. It would not do you any good for me to say. The addresses of our memory locations start at 0. In reality. just as the shelves in our above example did. | . with . except the “items” we store on these “shelves” are bit strings – one per “shelf”. | . “take the item from the shelf and throw it away”. You can think of computer memory as if it were a shelving unit. since you wouldn’t know if I was talking about some item on shelf 0. or shelf 6. we’ll use the convention of putting a letter a in front of the number.12 ___________________________ | top shelf |__________________________ | second shelf from top |__________________________ | third shelf from top |__________________________ | fourth shelf from top |__________________________ | fifth shelf from top |__________________________ | sixth shelf from top |__________________________ | seventh shelf from top |__________________________ 0 1 2 3 4 5 6 <--. or shelf 4. (When talking about memory. just to remind ourselves that it’s an address. Anytime I want you to do something to one of the items on a shelf.) a0 a1 a2 a3 ___________________________ | row of 8 switches |__________________________ | row of 8 switches |__________________________ | row of 8 switches |__________________________ | row of 8 switches |__________________________ | .. 4096 numbered shelves). and the numerical labels on these memory locations are usually called memory addresses.e. we have 4096 memory locations (i. just as the rest of our data is. |__________________________ | row of 8 switches |__________________________ | row of 8 switches |__________________________ a4094 a4095 In our example above. or some other shelf.

which collectively store our larger bit string. write the given 8-bit bit string into the memory cell at that given address – meaning that the 8-bit bit string is now stored at that address for as long as we need it to be It may be that some data value we are trying to store takes up more than 8 bits. The assorted input signals to the processor come (mostly) from the memory itself. Those four consecutive memory cells. in such cases. If we did that. we might decide to encode 232 diﬀerent integers as 32-bit bit strings. we could not ﬁt our 32-bit bit string into one memory cell. So. would together hold the 32 bits that our bit string takes up. since 32 bits will take up four 8-bit cells. For example. | |___________________________________| | . For example. then you need 4 memory cells to store the information. Each memory location holds a row of 8 switches – i. and the storage of this information started at the memory cell with address a104. since each memory cell only holds 8 bits. and a107. obtain the 8-bit bit string stored at that given address.e. a106. memory basically supports two operations: 1. | | . a106. given an address and an 8-bit bit string. | . a105. | |------|-------------------| |a4095 | row of 8 switches | |______|___________________| The stored-program computer Since bit strings are all a computer understands. In general. And. So. | . it can be read or written by the processor. address bit string ____________________________ | a0 | row of 8 switches | |------|-------------------| _____________________________________ | a1 | row of 8 switches | | |-----|------|-------------------| | |-----| a2 | row of 8 switches | | processor (lots of circuitry |-----|------|-------------------| | here that we are not drawing in) |-----| . we break our larger bit string into 8-bit pieces and store them in consecutive memory cells. but will also take up the memory cells at a105. At that time. since the information storage starts at a104. That is.13 addresses 0 through 4095. if your piece of information needed 32 bits to represent.. we need to make sure the following two things are true in order for computers to be able to accomplish anything: . your information not only takes up the memory cell at a104. it holds one 8-bit-long bit string. and the output signals get written back into the memory. located at addresses a104. and send it back to whatever part of the hardware requested that information 2. | . the image below is the model of a computer that we will deal with in this class. The purpose of memory – much like the purpose of a shelving unit – is to be a storage unit where data can be stored until it is directly needed. and a107 as well. that is how we store larger chunks of information – we break it up into many consecutive 8-bit memory cells. given an address.

So there are two key concepts here. Our large sequence of bits. are encoded as bit strings and stored in memory That is. one related to instructions. Larger bit strings get broken up into 8-bit pieces and stored in consecutive memory cells. not only is our data stored in bit string form. by supplying it with some huge sequence of bits that encodes our instructions to the machine. If our data value is only 8 bits long. all our data is encoded as bit strings and stored in memory 2. That means that writing a program is a matter of coming up with some proper series of instructions that the processor should run through. The second important idea is that any given data value is also stored as a sequence of bits. all our instructions to the processor. and one related to data. could also be used to store our programs instead. it can be stored in one memory cell (since each cell can hold up to 8 bits). . specifying our program.14 1. The ﬁrst important idea is that whatever task we want a computer to do. because our programs are encoded into bits just as our data is. That is the essential idea behind a stored-program computer – that the same hardware that is used to store our data. is broken into 8-bit pieces and stored in consecutive memory cells. and then encoding those instructions into a form such that the program can be stored in the computer’s memory. just like larger bit strings representing programs. were broken up into 8-bit pieces and stored in consecutive memory cells. we need to tell it to do that task. but our instructions to the computer – our “computer programs” – are also stored in bit string form.

if you have a typo in a computer program. run program through tests to see how well it works 4. and accidentally type “the” as “teh”.out. one important diﬀerence between writing a computer program and writing a term paper.println("Hello World!").) Preciseness is very important when writing computer programs. but in addition. However. For example. debug – ﬁnd errors in the test results and deduce their cause We will look at each of those four steps in detail now. If you write a term paper. is that the text of the computer program must be exact. or change an existing program) 2. in the following text. and can continue reading your paper. in this case it renders the program not even legitimate at all. . but many will. } } The program above is just composed of text characters.15 Developing a Java program The following is one of the simplest possible Java programs – all it does is print one line of text to the screen. } } The program above is not even an actual Java program! The loss of one letter not only means it’s not the same program as the ﬁrst one. Developing a program is a four-step cycle: 1. (Not every typo will have that eﬀect. compile (encode program in bit string from) 3.out. the paper will still be readable – a human reading the paper can ﬁgure out that that is just a typo. However. it renders the program completely incorrect.println("Hello World!"). just like those you might type into a word processor if you were writing a term paper. public class HelloWorld { public static void main(String[] args) { System. we have forgotten the ‘c’ in “static”: public class HelloWorld { public stati void main(String[] args) { System. edit (either write a new program.

and the text inside that ﬁle is known as source code. For example. or to learn a diﬀerent text editor instead. bit strings. such as automatically indenting in complex ways. At any rate. checking to make sure you have a closeparenthesis for every open parenthesis. That is. Once you type in your program text and save the ﬁle.java. etc. This is in contrast to using a word processor. color-coding certain syntax features. so it is worth your while to become proﬁcient in the use of a good text editor! Xemacs is a text editor that we’ll give you some beginning guidance in how to use. and so either you have to use that particular word processor to view your ﬁle later. TextEdit and BBEdit on Mac OS X.java as input to the Java compiler. vi. is available on all three platforms. In addition.) Step 2 of writing any program is to compile the source code for the program. some of those editors are actually cross-platform. and vim on Unix. if you sent the ﬁle HelloWorld. and so you need to convert your information (the program you’ve written) into a form that the hardware can make sense of – namely.java suﬃx. for example. the encoded ﬁle has a . • provide some features very useful for programming. the . As we discussed before. but provides the sort of features important for program development. Xemacs. there’s all sorts of fancy extra formatting added to the ﬁle.16 Step 1 of writing a program is to type the program into a ﬁle using a text editor. we will name our source code ﬁles with the . (We’ll talk about the issue of ﬁle names in the next lecture packet. If you want to get through all your computer science courses using only the minimum amount of information we give you about text editors during the ﬁrst few weeks of this course. • save the program as plain text – that is to say. in that you are allowed to type and edit text. This step is necessary because the source code is meaningless to the actual hardware. the ability to save and open ﬁles. text editors generally: • provide some of the same features as word processors. Our ﬁrst program above would go into a ﬁle named HelloWorld. because the name on the ﬁrst line after public class is HelloWorld. A good text editor can save you a great deal of time in developing your programs. The process of encoding your source code into bit string form is known as compiling and the software that does this encoding for you is called a compiler.java or Spacing. Some examples of text editors are pico. In Java. your computer hardware is nothing but a collection of transistors and wires. such as allowing the entering and editing of text. the next step is to encode your source code into bit string form so that the machine can understand it. learning how to use a text editor well is one of the many situations where your courses will point you in the right direction. but most of the work will be left up to you. A text editor is a program that is similar to a word processor. you can – but you’ll ﬁnd that spending a bit more time now and again learning more features of your chosen text editor can save you a great deal of time in the future. so our ﬁles will have names such as Foo. but you are free to both learn more about Xemacs. xemacs. rather than the sort of features important for writing term papers. the ﬁle a text editor saves contains nothing but the text you typed in. etc. or else someone needs to do a lot of work to enable some other piece of software to read your ﬁle. that ﬁle is known as a source ﬁle or source code ﬁle.java. In particular. In Java. and NotePad and TextPad on Windows. and thus should be conveniently viewable using a diﬀerent text editor. emacs.java or Test. support for cut-and-paste and search-and-replace. where generally if you save a ﬁle in a word processor.class suﬃx and otherwise has the same name as the source code ﬁle.

what the compiler will do for you in this case. and furthermore. Spanish. You can imagine a spectrum of possibilities: <---------------------------------------------------------> | | | natural high-level bit strings languages languages The far right end of the spectrum is where we have instructions encoded as bit strings – the only language the machine actually understands (for this reason. However. If you sent the compiler the following SDFSDF SWEasasdf 9745972348963qq anczdciero283 2je9 23 83e 123 q then the compiler can’t produce an encoded version of that. since it isn’t a Java program to begin with – it’s just a bunch of random characters. the language of bit strings that have meaning to the machine is also known as machine language). and so on. the tone of voice it is spoken in.class. The far left end of the spectrum is the collection of languages we speak. than bit strings would be. but not quite so far to the left. which are very understandable to us but not precise enough for usage as programming languages. Japanese. These languages are closer to our level of speech. There are particular rules about what is and what isn’t a Java program – our ﬁrst “hello world” example earlier is indeed a Java program.. would tell you that the word “stati” on that line is not something the compiler can make any sense of. are the languages in which most modern program development is done. so that it is very clear what is a legal program in that language and what isn’t a legal program. and the random garbage above is clearly NOT a legal program.but if you send a ﬁle of source code that is NOT a legal program for that language. then the compiler cannot produce any encoded version of your program. So. if you send a ﬁle of legal source code to the compiler. we don’t want the computer to assume a diﬀerent meaning than we intended. but unfortunately. (For example. Whereas when we specify a set of instructions to the computer. is to tell you what areas of your program violate the syntax rules of the language – that is. and what it thinks those errors are. although it is closer to being a legal program than the above nonsense is. the one with the typo.. This is because the compiler can only encode actual Java programs. the compiler will tell you what lines had errors. and that makes it much easier to develop programs in a high level language than it would be to develop programs by writing out bit string after bit string from scratch. etc. The compiler’s job is actually more than just encoding your program. we have very exact speciﬁcations for programming languages. getting back to the compiler.) . Therefore. the Java compiler might tell us that there is an error on line 3. so you might think they’d be ideal for writing programs in... and it is this ﬁle that would contain the encoded version of your program. Natural languages are languages that people speak – languages such as English. We as people understand natural languages. and these programming languages are called high-level languages because of how much their syntax resembles natural languages instead of bit strings. since you haven’t actually given the compiler a real program to begin with. What we are getting at here is that programming languages are diﬀerent than natural languages. as well as exactly what each legal program should do when it is run.but the second “hello world” example. natural languages are not precise enough – the same sentence can have multiple meanings depending on context. Close to natural languages. the compiler can produce the encoded version of that source code.17 output would be a ﬁle named HelloWorld. if the earlier “hello world” program with the one typo was sent in as input to the Java compiler. isn’t a legal program either.

and it doesn’t understand the encoding for a Pentium. and would only have a small amount of work to have to do – they’d need to translate your encoding. to produce an encoded version of your source code. but a PowerPC processor will not be able to make sense of the Pentium translation. as well – it is possible you could make up a machine – imagine a machine that nobody has built yet – and then translate your source code into the machine code for that machine. and the way you ﬁx them is by changing your source code so that what you have written does conform to the speciﬁcation of the language. cannot read the story. but if all you’ve done is the Pentium translation. the encoding you use for one machine is diﬀerent than the encoding you use for the other machine. However. Since the translation from one machine code to another is not hard. there are many diﬀerent kinds of machines! Some of these machines are similar to each other – for example. On the other hand. and vice-versa. the diﬃcult part about performing an encoding. to some machine language. nor will this person understand the Spanish translation. if you want. translating that to a diﬀerent machine language. People who want your program. since it doesn’t understand the original source code. If I were to write a story in English. In that case. It is designed to run on this other processor which you dreamed up but which no one has built yet. and. very very similar. a computer built around the newest Pentium processor (which is the processor most commonly used in Windows machines). So. you could download my encoding for an imaginary machine.class ﬁle into a form that can run on the actual machine you are on. your task is not hard. Once you have a machine language version of a program. The advantage to . Similarly. and then further translate that for your Pentium machine. is somewhat more signiﬁcant. and a computer built around a PowerPC processor (which is the processor used in Macintosh machines). Step 3 of program development is to run your program. and translate it to Spanish.18 These kinds of errors – where a program is written that does not conform to the speciﬁcation of the language – are called syntax errors. This is the Java model. To run a Java program. Now. you’ve translated your source code so that it is in a form the machine can understand. is going from the high level language. You can run a second translation. or virtual machine – and then put the code out on the internet. you will have to run a separate program – the Java virtual machine – which is a piece of software that (among other things) translates the encoded . nor would it run on a PowerPC. if your computer had a Pentium processor. you can have the compiler translate your source code to run on a Pentium processor. you have an encoded version of your source code – that is. There’s another option. if you don’t have any syntax errors. and translate your source code to the PowerPC machine code. the diﬀerence between a computer built around a Pentium processor. The compiler’s job is to tell you what syntax errors you have in your source code. probably doesn’t run on a Macintosh. This is a big part of why software you can buy at the store for a Windows machine. then anyone who can’t understand the original English version and who also can’t understand the Spanish translation. to the encoding for their own processor. could then download it. Why would you do this? Well. is relatively easy by comparison. I could go back to my original story and create a second translation if I wanted – a translation to Japanese – but if I’ve only done the one translation. At this point. the machine code you end up with. For example. probably isn’t all that diﬀerent from a computer built around last year’s Pentium processor. someone who only understands Japanese isn’t going to understand my original story. to Spanish. would not run on a Pentium. or at least. you could do the hard work of translating your code for a machine – your imaginary machine. then a PowerPC processor cannot run your program. and so the encoding for the two machines is probably the same. Because those processors are so diﬀerent from each other.

That is. you make what you think are the correct changes to your program to get it to work correctly. They change any random thing in the hopes that it will work. since the virtual machine is performing the additional translation as your program runs. This is generally not easy to do – it’s the main diﬃculty in program development. If you write a program in Java. and they should run on any processor that has a virtual machine available. and then try again.) Step 4 of program development is to debug your program (if necessary) and then return to step 1. (This entire discussion has been simpliﬁed a bit.but if portability is a higher priority. These are known as logic errors – when the actual program you’ve written doesn’t actually do what you intended the program to do. you don’t need to release one version for the Pentium processor..class ﬁles.. but if you’ve speciﬁed the wrong sequence of instructions in your program. Look carefully at the output your program is producing. You can simply provide the . Certainly. the machine might not do what you want. you will then know what needs to be done to ﬁx the program. you need to ﬁgure out where you made an error in your chosen sequence of instructions. as long as that computer has virtual machine software installed. Java might end up not being the best language to use. Look carefully at your program code (the high-level language instructions you have written).class ﬁles somewhat portable – they can be run on any computer. When software you use crashes. as we just discussed. and so you therefore have to change the program to one that does do what you want it to do. it’s because the programmer of that software made some logic errors that were not found and ﬁxed before the program was made publicly available for people to use. until you believe it works correctly. Once you’ve ﬁgured out why the program is doing what it is doing. then of course the “wrong” things will be done.19 this process is that it makes the . . However. and then correct that error. You repeat this cycle until the program works correctly – or at least. therefore. and then you re-translate using the compiler and try to run the program again. The downside is that running your program can take a bit longer. Beginning programmers often take a haphazard approach to this cycle. That is. but that’s the basic idea. You want to reason through your errors. the machine will do what your program tells it to do. and you can aﬀord to have things slow down a little. And try and understand why the program is producing that incorrect output instead of the output you want it to produce. and another version for the PowerPC processor. Once you have a program and have translated it to machine language. you then can run the program. then the Java model might be a useful one for you. If speed is your top priority. Don’t do this!. or makes some other sort of mistake. The debugging cycle – the cycle for ﬁxing logic errors – is basically the same as the cycle for ﬁxing syntax errors.

we saw this simple Java program: public class HelloWorld { public static void main(String[] args) { System.20 Lecture 3 : Types. you want to make sure you have the following “program skeleton” when you write a program: public class ProgramName { public static void main(String[] args) { (the stuff you learn these next few weeks would go here) } } Basically. However. in every program you write. and then in between the inner set of curly braces.println("Hello World!").out. . Variables. you can add the stuﬀ we talk during the next few lectures. and Expressions The “program skeleton” During the last lecture. the only thing in the “program skeleton” above that will ever change is the ProgramName. } } We’re not going to go into any detail right now on what most of the above code means. you do want to have everything but the line System. Until we start talking about methods in lecture 11.out.println("Hello World!"). That is. you can copy those six lines – four of them are curly braces – into your ﬁle. The rest of it stays exactly the same. It’s not important (yet). and all you will do is write diﬀerent code within those inner curly braces. So don’t worry about trying to understand that code.

. except that in addition to one-character names like x or y. our variable names can also be longer names such as totalProfits. but sometimes that variable is very simple (such as a single character) and sometimes it is more complex (such as a word. exam2. such as an integer: _________________ | | exam1-----| 86 | |_______________| or a ﬂoating-point value: _________________ | | exam1-----| 80. If we have a variable named exam1.3 | |_______________| or an individual character: _________________ | | exam1-----| B | |_______________| or an entire word: _________________ | | exam1-----| passed | |_______________| A variable in a computer program can only hold one value at a time. It’s the same sort of idea in computer programs. One useful way to think of a variable is to imagine it as a box with a label. made up of many characters). then think of it like a box labelled “exam1”: _________________ | | exam1-----| | |_______________| and then various values could be stored in that box. where you would be given equations such as y = 3x + 5 and then various values could be substituted for x or y.21 Variables A variable is a name which refers to a value. You’ve seen this concept in math class before. exam1. or numStudents.

For example. would not correctly compile until you ﬁxed this problem. to prevent this sort of problem from happening. before we go any further. we will design our own. how on earth do you multiply “hello” by 3 and then add 5 to it? The idea is silly. • A byte takes up 8 bits of memory. we will basically stick with int. and you were told that x was the word “hello”. If x is the word “hello”. A type is the “kind” of data that variable is allowed to hold. • A short takes up 16 bits. you must sacriﬁce by using more memory. int (by far the most common) takes up 32 bits. short.. The types you are about to learn are speciﬁc to Java. but those types will use the eight primitive types as building blocks.) The actual range is -128 through 127. more complicated types. (216 = 65536. Java is such a language. that would not make any sense! You are expecting x and y to stand for numbers. therefore. well.called the 8 “primitive” types. and long is 64 bits. • But in this course. int. The compiler is then responsible for making you stick to that decision. There are 8 essential types in all. if you were using the equation y = 3x + 5 in math class. some computer languages require the programmer to associate a type with each variable. If you want to save memory.22 Types One problem with the concept of variables is that the variable might hold a value that doesn’t make sense. you must sacriﬁce by allowing yourself a smaller range of values. and long • These four types all refer to integers. The programmer will have to decide in advance. given how we are trying to use that variable. or might be diﬀerent. For example. “this variable will hold only integers” or “this variable will hold only ﬂoating-point numbers” or “this variable will hold only words”.) Integral types • Four types: byte. (Later in the semester. the larger the range of values it represents • Trade-oﬀ here! – If you want a larger range of values. and thus can be one of exactly 65536 diﬀerent values. we need to learn what types of data we can have our variables store. (28 = 256. but have diﬀerent size restrictions.) The actual range is -32768 through 32767.. In Java. and then tried to store a word in that variable. . and you must decide what that type will be before you can use the variable in the rest of your program. • Likewise. So. And the larger the type gets. every variable has a particular type. Your program. So. not words. and as a result can be one of exactly 256 diﬀerent values. the compiler would complain that your variable type and your value type don’t match (this is sometimes referred to as a “type mismatch”). but other languages have their own types that might be similar or identical. if you decided a variable would hold only integers.

you need to round oﬀ somewhere.6.000004556 – are literals of type double. • A float uses up 32 bits of memory. rather than short.) As a result. • If you were to type 2 + 3 into your program’s code. or byte. So. • Floating-point literals that you type into your program’s code – symbols such as 98. – They can have greater precision (i. such as the four diﬀerent integral types had diﬀerent size limits. the larger the range of values it represents. then the 2 and 3 you typed into your program are literals in this case.23 The concept of a “literal” for a type • We’ll use the word literal to refer to the symbols you type directly into your program’s code in order to indicate certain values.e.00000000004 of each other (or within some other distance of each other). • One note of caution about ﬂoating point numbers – any numbers you get as a result of calculations are generally slighly imprecise. -88420. Floating-point types • Two types: float and double • These two types both refer to ﬂoating-point values. say. or long. instead. (For example. you should NEVER compare two ﬂoating point numbers directly to see if they are equal.14159.e. The syntax you learn in lecture packets 5 and 6 can help you with this. number of signiﬁcant digits). Floating-point values. and 0. • Again. and saving memory results in having a smaller value range available. 0. 3. -56. • In this course. since computers need to round calculations just as you do. say. . For that reason. a double uses up 64 bits of memory. size of value). often a ﬂoatingpoint result could be diﬀerent than you expect in. • Examples include 2. we have the same same trade-oﬀ here as we did with integers! – a gain in value range results in using up more memory. the square root of two is an irrational number and thus has an inﬁnite number of digits. 3451. can have a larger range in two diﬀerent ways: – They can have a larger magnitude (i.22491. partly because the fact that literals are automatically of type double makes that type easier to deal with than float would be. Values of type double can have both greater magnitude and greater precision than values of type float. • Integers you type directly into your program’s code are literals of type int. however. That is one reason we tend to use int in this class rather than the other three integral types – it makes your life easier for now. and so on. just as with integers. -957. but of diﬀerent size limits. the larger the type gets. the 12th signiﬁcant digit. you should be concerned with whether they are within. we will basically stick with double.

using a “international character to 16-bit bit string” translation standard known as Unicode.) • Needs exactly one bit of space – because we can simply say that the bit equalling 1 (i. • A value of type char takes up 16 bits. boolean • There are only two values of the boolean type. • Literals of type char are single characters that appear within single quotes. ’)’. Just worry about putting single characters in single quotes as above. ’c’. not c. to indicate the literal character. the switch being on) inside the machine is equivalent to the boolean value true. • Useful in situations where there are only two possibile results (ex: a certain part of the program either has completed its work or has not completed its work.e. don’t forget the single quotes! You want to type ’c’. For example: ’A’. don’t worry about Unicode right now. That gives us the ability to store one of 65536 diﬀerent characters. when your keyboard only has a hundred or so characters? • The answer to that is: Java’s character type is designed for international use – it can hold characters of many diﬀerent languages. The literals representing those values are true and false. • But. Why would need the ability to represent so many diﬀerent characters.24 char • The values of type char are single characters. . ’$’. If you leave oﬀ the single quotes it means something diﬀerent (which we will explain shortly). • Warning!! When you are dealing with character literals in a program. a certain student either graduates or doesn’t.e. and the bit being 0 (i. the switch being oﬀ) is equivalent to the boolean value false.

1 -2^31 to (2^31) . thinking of how data is stored in the machine is not generally something you’ll have to do – you can simply use the abstraction provided by variables.1 -2^(63) to (2^63) . then the next 8 bits are at address a49. For that reason.79769313486231570E308 short 16 int long float 32 64 32 double 64 Values in the machine As we have previously discussed.79769313486231570E308 to 1.40292347E38 to 3.e.) handle for you the particulars of where a variable’s value should be stored in memory. all our data is really stored as bit strings in the computer’s memory cells. . In addition. the next 8 bits are at address a50. say. For example.40292347E38 -1. If the ﬁrst 8 of those bits are stored at. Variables are abstractions of this idea – they allow us to think of our value as an item in a “labelled box”. we mentioned that a value that needs more than one cell will use consecutive cells.1 -3. etc. -(2^15) to (2^15) .e.25 Summary of Type Information type ----boolean char byte size in bits -------1 16 8 values --------true or false ’\u0000’ to ’\uFFFF’ -128 to 127 i. and let the environment (compiler. which means it needs four 8-bit cells to hold all 32 bits. rather than thinking of it as a bit string spread across one or more consecutive memory cells. address a48. operating system. a value of type int is 32 bits. and the last 8 bits are in address a51. -(2^7) to (2^7) -1 -32768 to 32767 i.

the 7 is replaced by 3. myVal = 3. • Some examples. boolean isCompletedYet. using the variables declared on the previous slide: x = 2. • Some examples: int x. myVal = 7. isCompletedYet = false. • For example: int myVal. selectionLetter = ’c’. char selectionLetter. Variable Assignment Statements • Writing a value to a variable is known as assignment. • Note that we put single quotes around a characater literal – such as ’x’ – to distinguish it from a variable with that character as a name (such as x above). For example: int exam1 = 93. temperature = 98. . and so on. • Format of the statement is: variableName = value. so before you can use a variable. • The very ﬁrst assignment to a variable is known as initialization because it is the initial assignment. this assignment is NOT an initialization • You can declare and initalize on the same line.26 Variable Declaration Statements • In Java. each variable can only hold one particular type of value. you must announce which type the variable is supposed to hold. • The statement we use to do this is known as a variable declaration statement. • Now x can only hold int values.this assigment is an initializaton <--. double temperature. • Format is: typeName variableName. <--. selectionLetter can only hold char values.6.

In fact. and then storing that new sum back into i. As in mathematics. division. then the addition comes ﬁrst. Eﬀectively. because they help us perform arithmetic. and decrement operators (all the operators in the above list except the assignment operator and the parenthesis) as arithmetic operators. For example. and the addition is in parenthesis. For example. again from left to right. it performs an addition and an assignment together – adding one to the value inside i. increment. These are usually very simple actions. a + b is compiled to a diﬀerent collection of machine language instructions depending on whether a and b are ﬂoating-point types. then addition and subtraction were performed.: decrement • = : assignment • () : parenthesis One symbol can mean diﬀerent things in diﬀerent contexts. even though the multiplication would be done ﬁrst if the parenthesis were not there. such as: • + : addition • . . then the expression i++ would raise the value of i to 6. For example. Similarly. The ++ operator performs a very common operation – it adds 1 to the value of a variable. our course web page has a link to a website with that information.: subtraction • * : multiplication • / : division • % : modulus • ++ : increment • -. This context depends on the type of the operands. even though the expression 5 * 2 + 3 equals 13 and the expression 3 + 2 * 5 equals 13. the -. and once all the multiplications and divisions were completed. Operators have precedence – certain operators get evaluated before other operators. subtraction. a and b could be diﬀerent types. and any Java reference book probably has a full list of the operators and their precedence rules. if the variable i held the value 5. We will refer collectively to the addition. so if you have an addition and a multiplication.and the language needs to have a rule about what happens in that case as well. the parenthesis have the highest precedence. Also. multiplication. This is similar to algebra. modulus. where multiplication and division were performed left to right ﬁrst.operator subtracts 1 from the value of a variable..27 Operators – (some of) the “verbs” of a computer language Operators are symbols that indicate some kind of action is to be performed. or integer types. We will be exploring more operators in lecture packet 5.. the expression 5 * (2 + 3) equals 25.

e. smaller expressions.and so on. both count as expressions. if we make a list of what sorts of things constitute an expression.. we could end up with a list much like the following: • a literal • a variable • (some arithmetic expression) + (some other arithmetic expression) • (some arithmetic expression) . to an int or double (or any of the other four numerical types that we won’t use in CS125).. “arithmetic expression” refers to any expression that evaluates to a numerical type – i.28 Expressions – the “phrases” of a computer language. since as the above list indicates. For example. they are used in code segments that are complete units of work – and those code segments are generally trying to use the value that the expression evaluates to.. In the list above. An expression is a code segment that can be evaluated to produce a single value. that means 5 + 7 is also an expression.(some other arithmetic expression) • (some arithmetic expression) * (some other arithmetic expression) • (some arithmetic expression) / (some other arithmetic expression) • . And so on – you can use the fact that some expressions on the list above are deﬁned in terms of other. to create large expressions of aribtrary complexity. and 7 being the second one) separated by a + operator. And since 3 is also a literal of type int and thus also an expression. literals are expressions (they evaluate to single values). since you have two arithmetic expressions (5 being the ﬁrst one. since 5 is a literal of type int.e The language itself deﬁnes many kinds of expressions in terms of other expressions. Expression examples: • 6 • 2 + 2 • exam1 • slope * x + intercept • 5280 * numberOfMiles • total/numScores • a + b . and 3 is an arithmetic expresssion. Expressions are not always complete units of work – in the cases where they are not. And each of them reduces to a single value – that’s what makes them expressions! . Meaning.c + d . 5 + 7 .. then since 5 + 7 is an arithmetic expression. However. and so is 7.3 is also an expression.

the concept will become clearer. is composed of smaller expressions (which are possibly primitive expressions – variables or literals) – and yet is itself still an expression. • numberOfFeet = 5280 * numberOfMiles. And then.) We have already seen variable declaration statements and assignment statements. the literals and variables are primitives. and thus is something that can be reduced to a single value. that composition is itself an expression. • int b.you list them one after the other. as we learn more Java syntax. and primitives to start those rules oﬀ. and they are run one by one until the program has completed. where expr is some expression that evaluates to a single value of the appropriate type. that value is actually an expression – and thus we could describe the syntax of an assignment statement as. Then. Statement examples: • c = 2 + 2. variableName = expr. We will be looking at many other kinds of statements besides declaration and assignment statements. since those are very simple expressions that can’t be broken down further. Every larger expression we create in this manner. and can be included in still-larger compositions. • y = slope * x + intercept. . It only needs to provide the building blocks to let you create your own expressions. In our list of possible expressions.29 A computer programming language (such as Java) doesn’t need to have a rule for every single expression you might come up with.. • x = 5. Statements are the building blocks of a computer program . That value then gets stored in the variable on the left-hand side of the assignment statement. (I know that is a rather vague deﬁnition. Statements – the “sentences” of a computer language A statement is a code segment that produces a complete unit of work. a rule such as (some arithmetic expression) + (some other arithmetic expression) allows us to create a composition – we can create a larger expression out of smaller pieces. We said earlier that an assignment statement will have a value on its right-hand side.

double temperature = 98. and we know how such statements should ﬁt inside a Java program. z. note the program below. y = x * 2. int w = (2 * x) + (3 * y) + (y * z * 4). } } . we now know what types are. y. x = x + 3.6. So. z = (x + y)/2. x = 0.5 + temperature. isCompletedYet = false. isCompletedYet = true. x = 5. Granted. and we can declare variables of particular types and assign values to those variables. to use in your calculations. in which we have taken some variable declarations and variable assignments and expressions involving the arithmetic operators. as the ﬁnal example for this packet. boolean isCompletedYet. and we will give you the ability to print values (such as the results of your calculations) to the screen. the program below doesn’t do too much that is interesting. In the next lecture packet. char selectionLetter. temperature = 44. and placed them within a new program skeleton.30 So. selectionLetter = ’c’. x = 2. we will give you the abilty to input values from the user into your program. but it will compile and it will run (producing no output whatsoever). public class Example { public static void main(String[] args) { int x.

values. However. NOT by the compiler when your program is compiled. Input/Output. often. Example 1 – integer calculations public class Example1 { public static void main(String[] args) { int weightedQuantity. We can double-check that we are using types correctly by working through these type expressions to see what type of values our code expressions produce. int exam1. // weighedQuantity is 6800 when program runs } } • 20 * exam1 is int * int which is an int • 80 * exam2 is int * int which is an int • The addition is int + int which is an int • The assignment is int = int. so the next ﬁve examples below serve two purposes – one is to explain how you could reason through your own code to see what it does. the compiler makes sure that the way in which we use our variables and values is legal – in part. conﬁrming that variable types match value types whenever they are supposed to. you’ve only learned about arithmetic operations. which is okay. We can do this type checking ourselves by replacing our variables. weightedQuantity = 20 * exam1 + 80 * exam2. the compiler does generate the machine code that will tell the processor to perform those calculations.31 Lecture 4 : Type Checking. as we mentioned earlier. Part of the reason for this is that. and Programming Style Type Checking The various operations in your program (up to now. exam2. so you can imagine we are only talking about numerical calculations for now. . The compiler does essentially the same thing when type-checking your code during compilation. and expressions with their types. exam2 = 70. but the same idea applies to other operations you will learn later) are performed by the machine when your program runs. and the second is to explain a bit about how the compiler does its job. exam1 = 60. those operations would involve values inputted by the user. and the compiler would therefore not know those input values since the user would not enter values as input until the program is being run. In addition to that task. by performing type checking. thus obtaining what we will call type expressions (as seen in the explanations that accompany the code in the next ﬁve examples).

sum = exam1 + exam2. which is okay. average is 82.a slight problem with division public class Example2 { public static void main(String[] args) { int sum. average. exam2 = 90. which is okay. // When program runs. Result: 82 . average = sum/2.32 Example 2 . • The division is int/int which is an int – result is truncated!! • The assignment is int = int. not 82.5 } } • exam1 + exam2 is int + int which is an int • The assignment to sum is int = int. exam1 = 75. exam2. int exam1.

exam2. average = sum/2. not 82. • The division is int/int which is an int – result is truncated!! • The assignment is double = int.change average to double public class Example3 { public static void main(String[] args) { int sum. } } • exam1 + exam2 is int + int which is an int • The assignment to sum is int = int. exam2 = 90. // When program runs. The int is automatically converted to a double. double average. exam1 = 75.which is why we get 82... which is okay. average is 82.0 and not 82. .5. sum = exam1 + exam2.0. int exam1.33 Example 3 . which is okay.

but rather. exam1 = 75.0. result is not truncated!! • The assignment is double = double. which is okay. The result: 82. exam2 = 90. double average. sum = exam1 + exam2.5 when program runs } } • exam1 + exam2 is int + int which is an int • The assignment to sum is double = int. 165. exam2. int exam1. // average is 82.5 .34 Example 4 – ﬁx #1: change sum to double public class Example4 { public static void main(String[] args) { double sum. • The division is double/int which is a double – ﬁnally. average = sum/2. sum now holds not 165. which is okay.

or division. int exam1.0. average = sum/2. that means that the fractional portion is truncated. • The division is int/double which is a double – again. exam2 = 90. subtraction. . result is not truncated!! • The assignment is double = double. are of type int.5 when program runs } } • exam1 + exam2 is int + int which is an int • The assignment to sum is int = int.35 Example 5 – ﬁx #2: change int literal to double literal public class Example5 { public static void main(String[] args) { int sum. exam1 = 75. sum = exam1 + exam2. However.5 The rule is that. if one or both of the operands is a double instead of an int. then the result will be a double (and thus division would not be truncated). then the result will be of type int – and as we have seen. which is okay. multiplication. for division. double average. // average is 82. which is okay. The result: 82. if both operands of an addition. exam2.

The expression is evaluated. exam2. } } • The code "Hello World!" in the code above is a literal of type String – the ﬁrst nonprimitive type we have encountered. • If you have only double-quoted text inside the parenthesis. sum = exam1 + exam2. Output of variables In the previous example. it is also possible to print out the values of our variables. exam1 = 60. the item we were sending to the screen was literal text.out.out.36 Outputting text using System. where expr is some expression.println Recall our HelloWorld program: public class HelloWorld { public static void main(String[] args) { System.out. . and the result is then printed to the screen. and so on. There are many diﬀerent pieces of pre-written code – one to print a String. public class Example6 { public static void main(String[] args) { int exam1. the expression in parenthesis is of type String. System.println("Hello World!").println(sum). We won’t talk about variables of type String for a while yet. However. The compiler picks the right one based on the type of the expression in the parenthesis. the expression in parenthesis is of type int. • The general form is System. then since that is a String literal. one to print an int.out. one to print a double.println(expr). } } In the code above. sum. exam2 = 70.println activates some pre-written code already present in the virtual machine. Any text that appears in between double quotes is a literal of type String. The use of System.out. but we will use literals of type String.

The operator + in an int + int expression has a diﬀerent meaning than the operator + in a String + int expression.out. in the expression: System. System. The type of the expression is String + String --> String • One example: "Levia" + "than" evaluates to the String value "Leviathan" • If one of the operands is a variable of a primitive type instead of a String. An example using concatenation public class Example7 { public static void main(String[] args) { int exam1.println("Sum is: " + 20).println(exam1). just as in our earlier type expression examples.out.out. exam2. exam1 = 60. • For example. the value of an int variable got automatically converted to a double value if we used an assignment statement to write an int value into a double variable. the type of the expression in parenthesis is String. System.println(exam2). and that means that the + is a concatentation. sum = exam1 + exam2. and that expression evaluates to the String value Sum is 130 (which is the String value that gets printed to the screen). all that is printed is a blank line. System. and that means the value of the int should be converted to a String automatically and then the concatenation done.println("Sum is " + sum). sum. exam2 = 70.out. the meaning of an operator depends on the type of the operand(s). • In the last line.out. • "Sum is: " + 20 -> "Sum is: " + "20" -> "Sum is: 20" • So. again. System.println().37 String concatenation • Concatenation is the merging of two Strings into one by placing them one after the other. . we have a String + int operation. that operand’s value is automatically converted to a String. } } • When there is nothing at all in the parenthesis.

out. the ﬁrst output line: String + int + int -----------| | \. A few other operators can’t be used multiple times in a row to begin with. System.println("Sum is " + (v1 + v2)). } } The + operator is read left-to-right.println("Sum is " + v1 + v2). the leftmost + is done ﬁrst. int v2 = 7. where d is assigned the value 2 ﬁrst. as with the statement a = b = c = d = 2. System. then c is assigned the value of d. So. the parenthesis take higher precedence and so the rightmost + is done ﬁrst./ int + String -------------| | \.. But most operators are read left-to-right./ String + int -------------| | \.38 A bit more messing with types Consider the following code: public class Example8 { public static void main(String[] args) { int v1 = 5./ String And the second output line: int + int + String --------| | \. System./ String 5 + 7 + " is the sum" "Sum is " + 5 + 7 "Sum is 5" + 7 "Sum is 57" 12 + " is the sum" "12 is the sum" .out. so in the ﬁrst two output statements above. are read right-to-left. such as assignment. Most operators are read left-to-right. A few.println(v1 + v2 + " is the sum"). and so on. In the third output statement.out.

).print(). ").println("world!").out..out. except that the latter doesn’t start a new line after the printing./ String + int -----------------| | \./ String "Sum is " + (5 + 7) "Sum is " + 12 "Sum is 12" One last note.out.print("world!"). This means that the statement System. Everything that is true for System. is meaningless.out.print("Hello. . it’s just an indicator in our two examples on this page. But if you don’t start a new line and you don’t print anything. System.print(.print(). world! ^ and the next thing to be printed goes where the ^ is.out.out.println(..println().out. world!^ and the next thing to be printed goes where the ^ is.out. command..) System. is also true for System. ").. will print: Hello.out. and the former does. there is at least a new line started even if no other text is printed... (The ^ is not actually printed. will print: Hello. So there’s no reason to have a System. System. With System. System. then you did no work whatsoever.39 And the third output line: String + (int + int) -----------| | \.println("Hello.).

char. and double.readChar() – makes your program pause until the user types in an character and hits return. there are rumors that such useful tools are being added to the language in a future revision). that builds the kind of nice facilities we need out of the more primitive facilities that Java provides us. does not have nice. (As we learn more about Java in the ﬁrst half of the course. the value of the expression is that integer that the user typed in. respectively. then.java ﬁle might start to make a little bit more sense to you than it would right now.java source code.) The Keyboard.readString() • Keyboard. . you can take a look at the Keyboard. • Keyboard. the value of the expression is that ﬂoating-point value that the user typed in. easy-to-use facilities for reading input from the keyboard in a command-line application (at least as of this writing.40 Input using Keyboard. we also need to see how to input data from the keyboard. the source code of the Keyboard. you don’t ever really have to do this. Java.java ﬁle in the same directory as your MP!!! Forgetting to do this is the single most common mistake made by students on the ﬁrst MP. so you’ll know where to get a copy of the ﬁle when you need it. as part of an output-testing tutorial. then. • Keyboard.endOfFile() We won’t be dealing with the last two right now – we’ll only be using Keyboard. So the authors of one of your recommended textbooks have written a Java ﬁle. Keyboard.java So. • Keyboard.readChar().readChar() • Keyboard.readInt() • Keyboard.java ﬁle if the ﬁle isn’t there! Your ﬁrst MP will direct you to where to obtain a copy of this ﬁle.java ﬁle provides us with ﬁve tools for our use: • Keyboard. You can just type into your own code the kinds of expressions we’ll discuss below and that will be enough to make input from the keyboard work for you. are very low-level tools that a beginning student would not understand how to use. the value of the expression is that character that the user typed in. and Keyboard. you will need to put the Keyboard.readInt() – makes your program pause until the user types in an integer and hits return. oddly enough. then. Each of those three expressions evaluates to a particular type. But. In order to use the input tools we describe below.readDouble().java. Keyboard.readDouble() – makes your program pause until the user types in an ﬂoatingpoint value and hits return.readDouble() • Keyboard.readInt(). The compiler can’t use the Keyboard. If you happen to be curious. The only tools Java provides for reading input from the keyboard. int. namely. now that we have seen how to output data to the screen.

the closing double-quotes must be on the same line. • When you are writing your ﬁrst MP. you should always print a prompt before you ask for input. is what the virtual machine looks for when you start the virtual machine to run your program.41 Input example Read in an integer and print it out again. can be used by other programs.out. Otherwise the program seems to just halt suddenly. .out. try running the following two lines: javac Keyboard.readInt(). and that is why ﬁles like Keyboard. Remember the word main that was part of our “program skeleton”? Well. in order to let the user know what they are supposed to input.println("Your value was: " + myValue). myValue = Keyboard. the second line will NOT work – you will get an error message telling you in some way that your ﬁle (Keyboard. System. And.java ﬁle. If your ﬁle has no main.readInt(). System.java java Keyboard The ﬁrst line will work ﬁne. Keyboard. like any other expression.class) is missing information about main(). • The ﬁrst output line is called a prompt. if you start a value with double-quotes. indeed. } } • Keyboard.java. it turns out that every program you ever run in Java. but are not complete programs in and of themselves.class which – as we described earlier – will contain the machine code the compiler produced from the source code in the Keyboard. is on the right of the assignment operator. • Note that the last line is broken into two lines so as not to run oﬀ the end of the slide. That “program skeleton” we talked about. needs a main somewhere. and will produce a ﬁle Keyboard. Just don’t put a split in the middle of a double-quoted String literal.println("Enter new value:"). However.java does not contain the code for a program you can run. which do not have that “program skeleton”. Likewise you can use that technique to avoid running past 70 or 80 characters on a line in your code. What it contains are additional utilities which can be used by a program such as the ones you are going to write. then your ﬁle is not a program you can run. public class Example9 { public static void main(String[] args) { int myValue.

println("Hello World!"). and tab characters (collectively known as “whitespace”) are discarded by the compiler. Think of the text of a program as being a bunch of characters one after another in a pipe. and make names as long as you want. Imagine if our “Hello World” program was stored in a ﬁle named Foo. which is very important. So. (This will be even more important when we starting adding other methods besides main to a class. as we have stated. and becomes even more important as your programs get larger. you can use all the whitespace you want. There are various “indentation standards” which vary slightly. but the general rule of thumb they are all based on is that stuﬀ inside curly braces should be moved to the right a few spaces (three to ﬁve is a good choice). all the names you use get traded in by the compiler for actual memory locations. blank lines.42 Programming Style While certainly it is most important to write a program that actually works.println("Hello World!"). and was written as follows: public class Foo { public static void main(String[] x) { System. Wrong: public class HelloWorld { public static void main(String[] args) { System. Those things only serve to help you (and others) read your program. The compiler reads these characters in one by one. Style component #1: Indentation Indenting your source code properly is very important. it is also important to write your code using good programming style.out. Doing this makes your code more readable and understandable. }} That is legal! The compiler can understand this! But you cannot. Likewise. and all spaces.) .java.out. } } The ﬁrst thing you need to do is realize that it would be easier to tell what code was inside the class if that code was indented three spaces.

Again. You can use whichever variation you prefer – just be consistent and keep your line lengths at about 80 characters or so. you still have the same kind of visual structure. since the code is crushed together a bit more. Correct: public class HelloWorld { public static void main(String[] args) { System. To give another example. } } The second thing to realize is that. Some people think this tradeoﬀ is worth it and others (myself among them) don’t.out.println("Hello World!"). but still not ideal: public class HelloWorld { public static void main(String[] args) { System.out. it is very easy to visually pick out with one glance where the class begins and ends. That is what was meant by “indentation standards” – there are slightly diﬀerent ways of doing things. but on the other hand. you can ﬁt more code in the viewing window at once. A slight variation on this is that some people like to put the open brace on the same line as the Java syntax tool it is starting: Also correct: public class HelloWorld { public static void main(String[] args) { System.43 Better. You should indent at least three but you don’t want to indent so many spaces that your code runs oﬀ the end of the page all the time. it will be easier to read the code inside main if that code is indented three spaces from the curly braces that begin and end main.println("Hello World!"). likewise. the more important this becomes. You can make your own choice. the more code you have. some prefer ﬁve. } } Since the word public lines up with the closing brace. and where main begins and ends. } } Now. . and what code is inside it.out. some people indent three spaces. what code is inside it. It’s a bit harder to see it.println("Hello World!").

method. Style component #3: Commenting Indentation makes it easy to read your code. So. We use a special Java syntax to notate these remarks as comments. . as you start to write other methods besides main. Much better: totalScore = exam1 + exam2. and variable names As we have already discussed. we can add as many comments as we want without aﬀecting the resultant machine code at all. it is a lot easier to quickly ﬁgure out what a given chunk of code is doing if the variables in that code have been given names that describe what data they hold. With relatively few exceptions. Java provides syntax for commenting our code. In addition. Any names you choose should in some way describe the purpose of what is being named. as a result. Thus. The comments are there for the code reader’s beneﬁt only. not every collection of statements has an immediatelydecipherable purpose even if you know what each of the variables holds. However. Likewise.or 5-character abbreviation of a longer word is more descriptive than a or G or q. it often helps to explain the general purpose of a particular ﬁle. and as you start to write other classes. a quick summary of the intent behind a particular section of code. Unclear: c = a + b.44 Style component #2: Descriptive class. Even a short 4. or whatever else. Comments are basically just text descriptions of whatever it is you want to describe or explain – the purpose of the ﬁle. the compiler ignores them just as it ignores extra whitespace. descriptive variable names make it easy to tell what those variables are for. using single letters for variable names tends to be a bad idea. you should choose descriptive names for those methods and classes as well. or the individual major divisions of code within that ﬁle. for the purposes of documentation.

Written August 1999 by Jason Zych public class HelloWorld { // Method: main // . the commenting syntax you will want to use is the use of the “double slash”: //.out.45 One syntax for commenting Most of the time.println("Hello World!"). you can also surround the area as follows: /* <---.in control of greeting generations // . Example: // Class: HelloWorld // .controls main execution of program public static void main(String[] args) { // line prints out a greeting System. You can place that two-character symbol anywhere in your code.slash-asterisk to open comment whatever you want goes in here */ <---.asterisk-slash to close comment . and anything from that point to the end of the line of text will be ignored by the compiler. } } Another commenting syntax If you want to quickly comment out a large chunk of text or code.

Written August 1999 by Jason Zych */ public class HelloWorld { // Method: main // .Written August 1999 by Jason Zych */ public class HelloWorld { public static void main(String[] args) { System.out. } } Example mixing the two kinds of comments: /* Class: HelloWorld .46 Example 2: /* Class: HelloWorld .in control of greeting generations .out.controls main execution of program public static void main(String[] args) { // line prints out a greeting System.println("Hello World!").in control of greeting generations .println("Hello World!"). } } .

Comments like this are generally a bad idea. Usually.47 New programmers sometimes take this to an extreme and place a comment on every single line. the code’s purpose is mostly clear at ﬁrst glance. when you write your MPs – and when you write other code as well – it will be important to keep in mind the elements of good coding style: 1. that’s another comment you might not have to bother writing. . Remember – if you choose descriptive variable names. then often your code becomes mostly “self-documenting” – i. Descriptive variable names 3.e. you will gain a sense of the right balance between “not enough commenting” and “too much commenting”. So. even the lines whose purpose is obvious. Comments where needed Part of your grade will depend on writing your code in a good style. the purpose of a comment is just to remark on a line whose purpose isn’t immediately obvious. Indentation 2. Any time a variable name helps to document the variable’s purpose. then the comments start to clutter up the code. For example: // adds 1 to the number of CDs numCDs = numCDs + 1. If you put comments even at lines where the purpose of the code is clear. With experience. or to remark on the general purpose of an entire section of code. due to your choice of variable names. fewer comments are necessary because.

the expressions we wrote that used those operators. We had the arithmetic operators to help us create more complex arithmetic expressions. but the resultant expression evaluates to a boolean value • logical operators – the operands need to be of type boolean. rather than a value of some other type. most of the expressions we have seen have evaluated to type int or type double. as in the examples we just saw. and the idea that every expression evaluates to a single value of a particular type. Simple Conditionals. and variables of type boolean. To form boolean expressions that are more complicated than literals or single variable names. For example. and Statements Boolean Expressions We have previously discussed the idea of an expression. if we perform the following declaration and initialization: boolean exampleVariable. we will need operators that help produce boolean values. then after the above code is run. rather than the arithmetic operators. the following is another expression of type boolean (which again happens to be a literal of the boolean type): true Finally. . which help produce numerical values. but none of those operators helped to produce boolean values – all of those operators helped perform arithmetic and. Boolean expressions of greater complexity Up to this point. exampleVariable = false. the following is an expression – speciﬁcally. and the resultant expression evaluates to a boolean value We will examine both categories of operators. the only boolean expressions we have been able to put into our programs are literals of type boolean. There are many possible expressions you might write. evaluated to numerical values. These operators that produce boolean values fall into two groups: • relational operators – two operands can be any type.48 Lecture 5 : Boolean Expressions. and not all of them evaluate to values of type int or double. however. Up to this point. it’s a literal – of type boolean: false And. the following is also an expression of type boolean: exampleVariable We call those expressions boolean expressions because they are expressions that evaluate to a value of type boolean. as a result.

and evaluates to false otherwise) • grade != ’A’ (evaluates to true when the variable grade of type char holds a character other than the capital letter ’A’. then the expression evaluates to false. it is possible to compare them to each other using the relational operators (for example. and evaluates to false otherwise) • statusFlag == true (evaluates to true when the variable statusFlag of type boolean holds the value true. 5 <= 6. are binary operators.1 or 23. we question whether or not two values are related to each other in a particular way. since both integers and ﬂoating-point values are numbers. If they are indeed related to each other in that way. The double equals sign means “compare for equality”. and evaluates to false otherwise) • examScore <= 91 (evaluates to true when the variable examScore of type int holds a value less than or equal to 91. the two operands of a relational operator tend to be of the same type. like the arithmetic operators. meaning they have two operands.00002 == 23). evaluates to true when the value stored in time1 is less than the value stored in time2. we are trying to see how two values relate to each other. Speciﬁcally. it asks a question. “are the two operands equal. Do not confuse = and == when you write your code!!! This is a very common mistake. For example: • heightOfPerson > 6.3. Using these operators.49 Relational Operators The relational operators are operators that perform comparisons. The single equals sign means assignment.3 (evaluates to true when the variable heightOfPerson of type double holds a value greater than 6. and evaluates to false otherwise) • time1 < time2 (assuming time1 and time2 are both of type int. and if the two values are not related to each other in that way. . However. or not?”. The relational operators are: • < (less than) • > (greater than) • <= (less than or equal to) • >= (greater than or equal to) • == (are equal) • != (are not equal) These. it is an action. the expression evaluates to true. and evaluates to false otherwise In general. we can create boolean expressions that perform comparisons for us.

true only when X is true and Y is false. and if the ﬁrst two variables held the value true and the third held the value false. • The following four operators are the logical operators: || && ! ^ (or) (and) (not) (exclusive or. val2. however. relational operators have assorted types as operands. which is the opposite of the operand) If we had three variables. and produce values of type boolean. we can create more complex boolean expressions out of simpler boolean expressions such as boolean literals or boolean variables. logical operators also must have operands of type boolean.true if either X is true. false if X and Y are the same Using these operators. since AT LEAST ONE operand is true) • true ^ false (evaluates to true. • The logical operators also produce values of type boolean. • Logical operators are designed to create complex boolean expressions out of simple boolean expressions. or both X and Y -. also called xor) • The concepts these operators implement are very common all over computer science. since EXACTLY ONE operand is true) • !false (evaluates to true. val1. and val3. X or Y -.returns the opposite of X X xor Y -. or when Y is true and X is false.true only if both X and Y are true not X -. or Y is true. each of type boolean. unlike relational operators. For example: • true && false (evaluates to false.50 Logical Operators • As we stated earlier. since it is NOT the case that both operands are true • true || false (evaluates to true. then: • val1 && val2 evaluates to true • val2 && val3 evaluates to false • val1 || val3 evaluates to true • val1 ^ val2 evaluates to false • val1 ^ val3 evaluates to true • !val3 evaluates to true .

5 < 6. For example. As another example. but the operands themselves do NOT have to be boolean values. and then all the small boolean expressions are merged into one large boolean expression by using the logical operators. inclusive. each using a relational operator to generate a boolean value. the following expression: (x == 5) || ((x > 10) && (x < 0)) evaluates to true only when x is 5. the expression: (x >= 1) && (x <= 100) && (x % 2 == 0) evaluates to true whenever x is an even integer between 1 and 100.1 is a perfectly legal boolean expression. often we have many small boolean expressions. the operands are not boolean values even though the result is • the logical operators not only produce boolean values.51 Using relational and logical operators together It’s important to keep in mind the diﬀerence between the relational and logical operators: • the relational operators produce boolean values. since the second operand of the logical OR above can never evaluate to true. For example. . but must have boolean values as operands as well As a result.

So. our program is safe! So in addition to saving time. that is. then that means your program runs a little bit faster since there is less work to do. for the OR operator. In that case. On the other hand. Note that XOR has no short-circuiting ability since you always need to know the values of both operands in order to evaluate the XOR. if the ﬁrst operand evaluates to true. they don’t evaluate the second operand of the operator if the answer to the overall expression is already known after evaluating the ﬁrst operand. .2) to know for sure what the result of the AND expression is. we know the entire AND expression must evaluate to false regardless of the value of the second operand. And in that case. that is assuming that x is zero. the short-circuiting feature can be used as we used it in the example above. Again. Also. This feature might be helpful for two reasons. x != 0 – evaluates to false. is time we save – so if the machine can avoid having to evaluate the second operand. because of that. we know what the result of the entire expression will be! Since an AND expression is only true if both operands are true. First of all. depending on the result of the evaluation of the second operand. in the expression above. any work that we don’t have to do. if so. But since the short-circuit property of the && operator prevents that second expression from being evaluated when x is zero. the AND expression could still evaluate to true or to false. Assume x is zero. to keep us from evaluting a particular expression such as y/x if it would be dangerous to do so. and since we know the value of the ﬁrst operand is false. we must evaluate the second operand (y/x > 70. if x held a non-zero value. we don’t want to evaluate y/x if x is zero. Example: (x != 0) && (y/x < 70. the second operand won’t be evaluated. and NOT has no short-circuiting ability since there is only one operand to begin with.52 The short-circuiting behavior of logical AND and logical OR The && and || operators are short-circuiting. since the subsequent division-by-zero operation would crash our program. since the ﬁrst operand being true means the entire expression must be true regardless of the value of the second operand. then the ﬁrst expression – the x != 0 expression – would evaluate to true.2) In the boolean expression above. this expression we are trying to evaluate – namely. Likewise. the ﬁrst thing to be evaluated is the truth or falsehood of the left side of the && operator.

But. print out that the student has passed Otherwise examScore must be <= 60.out.println("Done!"). If student’s grade is greater than 60. print out whether each has passed (better than 60) or failed (60 or worse).) . then both lines get printed.out. we don’t always print “passed” and don’t always print “failed”. If the condition evaluates to false instead. and the next line of code that executes is whatever is after the if statement. the statement is skipped over. we perform exactly one of those – we will always print either “passed” or “failed”. each time we read an exam score. call it examScore If examScore is greater than 60. The line that prints "Done! has nothing to do with the if statement. • condition must be a boolean expression • statement is a statement of some kind • The statement is only executed if the condition evaluates to true. The form for this statement is as follows: if (condition) statement. System. ex.: Read a student score. than only "Done!" gets printed. (Note that we indent the statement that is run conditionally by the if statement – that is the proper style. but never both. we’ve only listed instructions one after the other. In fact. which will run code “conditionally”. Read score. In this case.53 Basing results on comparisons – the conditional Up to this point.println("Passed!"). In this case. Example: if (grade > 60) System. so it gets run regardless of whether the condition is true or false. just listing instructions is not enough! What we do might depend on certain conditions being true. If student’s grade is not greater than 60. The if statement Today we are introducing a new kind of statement – the if statement. then. print out that the student has failed. How can we make this work? What we need is a new kind of language statement – the conditional.

Keyboard." into your program.54 Statements Up to this point.) .println statement • a System. and Keyboard. but what is a statement? Well. doesn’t make a difference } } You can’t just toss 2 + 3 + " is the sum.println statement) – that we’ve made meaningful use of the expression’s value. but what do we do with those values? It is not until we put the values inside statements – perhaps by writing the value to a variable (via an assignment statement) or perhaps by printing the value to the screen (via a System.out. So.out. } } but the following two examples will not compile: public class Example2 { public static void main(String[] args) { 2 + 3 + " is the sum. But we can put them in the “statement” category. whether we do something with the returned value or not.readDouble() expressions are also statements. Those statements evaluate to values. // <-. so they are also expressions.".print statement Furthermore. you need to do something with the result of that expression. as we discussed before.println(2 + 3 + " is the sum. simply calculates the value and then ignores it.readInt(). For example. the expressions 5 . we have used the term “statement” to refer to any one of the following: • a variable declaration statement • an assignment statement • a System.out.readChar(). even if you don’t use the value you’ve inputted – you’ve taken in an input value from the user so that that input value won’t be re-entered the next time your program wants input. is why the following will compile: public class Example1 { public static void main(String[] args) { System. it’s all ﬁne and good to make a list of things we call “statements”. so in that case. on its own.adding a . if you want to get technical. The expression. (If you run Keyboard. too. The fact that an expression is not automatically a statement.readInt(). you have indeed done something.3 * 2 or "Sum is : " + 120 evaluate to single values. the Keyboard. since they accomplish work on their own." } } public class Example2 { public static void main(String[] args) { 2 + 3 + " is the sum.out. one vague way to deﬁne the term is to say that a statement is one complete unit of work. you obtain a piece of data from the user.").

but they were all on the list. the grammar of an if-statement was: if (condition) statement. likewise we can make use of statements-that-hold-other-statements. so they were all statements.55 However. . we have said this is a statement: if (grade > 60) System. and print statements. Instead. since any statement can go inside an if statement? That is. and the condition is daysInAttendance > 200.out. to create statements of whatever complexity we need. we can come up with a more precise deﬁnition of “statement” than “one complete unit of work”. This widely-ranging deﬁnition of “statement” is a good thing! It means that in any code construct where we say a “statement” should go. that can contain other statements within itself. as long as it is called a “statement”. This is not so diﬀerent from expressions.println("Passed!"). i. while more complex expressions were built from simpler expressions. we can put any statement we want there – even another conditional! In a sense. That is legal code! Since the grammar of a conditional had a statement within it. We started our list at the top of the previous page. so if the statement is the conditional we listed above. declarations. all those statements accomplished diﬀerent sorts of things.out.e. it is as if the simple statements. So. are primitives – and then statements such as the if statement. then we get the following: if (daysInAttendance > 200) if (grade > 60) System. anything that is on our list of things we call statements. For example.println("Passed!"). if it is a statement. our deﬁnition of “statement” will be: a statement is anything we refer to as a statement. And just as we made use of those expression rules to build expressions that were as complex as we needed. we can substitute anything we want. are the means by which we create compositions. and the way literals and variables were primitive expressions. then why not put it inside an if statement. such as assignments. We will explore this idea further in the next lecture.

we would like a way to group statements together. then it is being evaluated three times as well. we had to write the same condition three diﬀerent times. if (x > 5) b = b + 3. conditionally run many statements. if (x > 5) c = c + 1. Scope. For both of these reasons. . as we did above with our “DO ALL OF THESE/OK NOW YOU ARE DONE” remarks. rather than just three statements that depend on that condition like in the above example.56 Lecture 6 : Compound Statements. we might be able to do something like this: if (x > 5) DO ALL OF THESE: a = a + 2. To accomplish this. if we wanted three diﬀerent statements to run based on the truth or falsehood of the same condition. rather than just one. If we had some means of collecting those three assignment statements together. then none of the assignment statements will be run. if x is indeed greater than 5. we would need three diﬀerent conditionals: if (x > 5) a = a + 2. then all three assignment statements will be run. then we’d save even more work by being able to group all those statements into one conditional statement under one copy of the condition. we are being wasteful in two ways here. and if x is NOT greater than 5. If there were 1000 statements that should all run depending on whether or not x was greater than 5. we limit the number of times it has to be evaluated – thus reducing the work our program is requiring the processor to do. instead of typing the same condition 1000 times (once in each of 1000 diﬀerent conditional statements). if we’ve listed the condition three times in our code. c = c + 1. we will introduce another kind of statement – the compound statement. By limiting the number of times we list the same condition. and Advanced Conditionals Multiple statements based on the same condition Given what we have seen so far. First of all. In addition. OK NOW YOU ARE DONE and thus only write out the condition once. b = b + 3. We would like a way to make a conditional statement. However. In the above code.

we can use anything appropriate from that list. we can add the “compound statement” to that list of things that we can call “a statement”. and thus we can use a “compound statement” anywhere we might need a statement in our program. in sequence. you just run each of the smaller statements within it. they are all included within a pair of curly braces. } We can consider the above ﬁve lines of code to be one statement. is that having a list of diﬀerent things that we call a statement is very useful. but even that is a vague deﬁnition. and we’ll talk about that later in this packet. since it means whenever we need a statement. In places where we need a single statement. a = 2. instead of only one smaller statement. since in both cases. we just make a list of everything that is called a statement. third. we will run the declaration. . and writing three separate statements on their own: int a. a = 2. is a helpful thing to do. The other situation is the one we were just talking about at the start of this packet – by treating a number of statements as one complete unit. System. compound statement. and fourth lines of the example above). It’s a good deﬁnition because there really isn’t any similarity between the diﬀerent things that we call a statement. Now. Each one basically represents a single “chunk” of work. the above qualiﬁes – it is considered only one statement. (We’ll see an example in just a moment. To run this single statement. System. There are only two situations where converting a series of individual statements into a single. is a statement”. because the curly braces have the eﬀect of gathering all the smaller statements together into one logical unit. there is no diﬀerence at all between writing a program with the above code in it. we can then include that one complete unit as the one statement inside a conditional. we are seeing here that a good deﬁnition of “statement” is.out. in sequence. and thus enable a single conditional statement to conditionally execute many smaller statements together. and the print statement. and then enclosing the entire collection of statements within a set of curly braces.out. one after the other. “Anything that we call a statement. the assignment.57 Compound Statements The compound statement is created by taking any collection of other statements.) Once again. So rather than trying to deﬁne “statement”.println(a). though. One of these situations involves scope. The fact that it happens to be composed of many smaller statements is irrelevant. For example: { int a. What we have found already. one after the other. since even though there are three smaller statements (on the second. So. listing them one after the other in the order you want. in most situations.println(a).

we simply need to enclose each of the three assignment statements. a = 2. System. For example. and if the condition is false. then those 1000 lines together would either all get run. to solve the problem we introduced at the start of this packet. For example. then we might get the following: if (daysInAttendance > 200) { int a. Similarly. all the smaller statements within the compound statement are run. we can choose any statement from our “list of statements” to go in that position. if there were 1000 smaller statements within a compound statement.out. if we chose our example compound statement from the previous page. likewise the if-statement has another statement nested within it. } In both cases. if the condition is true. together inside a compound statement: if (x > 5) { a = a + 2. c = c + 1.58 Conditionals with Compound Statements Just as the compound statement had other statements nested within it. as the result of evaluating a single condition. . or else none of those 1000 lines would run – in either case. as with the compund statement. b = b + 3. and if the condition is false. And. all three lines inside the curly braces are run. to be the statement nested within an if-statement. if the condition is true. } In the above example. none of the three lines inside the curly braces are run. By using this technique. we can make a single condition aﬀect whether or not an enormous amount of code is run. none of the smaller statements within the compound statement are run.println(a). and we then put that compound statement within a conditional statement.

out. So. System. Part of what determines the scope of a variable is what block it is declared in. } // System. But now.e. . x = 1. The outermost block is the main() method itself – all the code inside the open.print(i). } A variable such as i is declared in the outermost block – inside main() but not inside any other block. i++. { int x. put another block inside the main() method: public static void main(String[] args) { int i = 0. down to the end of main() at the ﬁnal closing brace. since now you’ve got the assorted smaller statements grouped within a set of curly braces. In the absence of any additional blocks inside main(). For example. a block can be thought of as the collection of code within a set of curly braces. Variables declared in a block are usable only until the end of that block. Things will get more complex when we start discussing methods. a variable declared inside main() is usable from the point of its declaration. the section of the code for which that particular variable is deﬁned and accessible.59 Scope The scope of a variable is the part of the program in which that variable can be used. For now.out. to the end of the block it was declared in – i. but for now – as we deal with code where everything is inside the main() method – we can think of our program as a bunch of blocks nested within other blocks.and closebraces of the main() method. creating a compound statement creates a block. the end of the main() method. it is usable everywhere in main() from the point of its declaration.println(x).

permanently aﬀects i. At that point in the code. if a variable is out of scope. And so. since the increment that happpened to the variable i within the compound statement. the variable x is declared inside a block that is inside main(). As a result. or. are still in eﬀect when the nested block ends and we return to the outer block. x is declared inside the nested block and thus goes out of scope as soon as the closing brace of the nested block is reached.) • Variables declared inside an inner block are not accesssible to the outer block – i.60 But. the variable i is usable anywhere inside that compound statement. The rule is as follows: • As always. its memory is now free for some other variable to use. and any changes made to the variable inside that inner. once you leave the inner block and you are back in the outer block inside which the inner block was nested. • Variables declared in outer blocks can be used inside any block nested inside that outer block. The rule is that a variable declared inside a block goes out of scope at the end of the block. variables cannot be used before they are declared. more formally. the variable i holds the value 1. The value of i does NOT reset to 0 when the compound statement ends. the variable x is destroyed at the close of the compound statement and does not exist past that point. involving properly organizing what variable names we are using – those advantages will become a bit clearer as we learn additional Java syntax and as our programs become more complex.e. So x and any value it was holding eﬀectively vanish once the end of the nested block is reached. which is why the commented-out print statement would not compile if we uncommented it – it is the same as if we tried printing the value of the expression x when the variable x had never been declared at all. and after the compound statement is over. There are other advantages as well. inside a block that is nested in main(). the program would not compile if we uncommented the output line that is currently commented out. nested block. . there isn’t a variable x. (In our example. because that variable went out of scope when the nested block ended and thus no longer exists. the variable you declared inside the inner block has gone out of scope and cannot be used by the outer block. (In our example.) One advantage of this is that.

println(x). and thus the scope of the variable i above will end at the end of the closing curly brace.x declared inside block System.will print 12. <-. then the scope rules work exactly as we said they did for compound statements earlier.y declared outside block if (y < 10) { int x = 9. <--. it is still treated as if there were a set of curly braces around it. if (grade > 60) { int i = 5. the variable i is NOT in scope once the if-statement has concluded.println(y). Or in other words.x can be used here y = y + 7.out. <--. anything declared within the if-statement is out of scope once the if-statement has concluded. If you actually have a compound statement within your if-statement. whether you actually put the curly braces into your code or not. } That is. in the above left example.y can be used here } System. For example: // same with an if statement as with a loop int y = 5. if you only have a single-line statement nested within the if-statement. not only can y be used here but whatever alterations made to y inside the block are still in effect here System. we can consider the following two statements to be equivalent: if (grade > 60) int i = 5.61 Scope in conditionals From the standpoint of scope.ERROR! x went out of scope with the close brace of the if statement and can’t be used here . <--. <--.print(x).out.out. <--.

the condition is a boolean expression • When the condition is true.println("Done!"). statement1 is skipped over and statement2 is executed.. System.out. • Again. as before.out. Only one of those conditions can be true.println("Passed!"). just once. "Done!" gets printed no matter what). statement1 is executed and statement2 is skipped over.. • When the condition is false. which is as follows: if (condition) statement1. if the grade is not greater than 60. and we didn’t need to evaluate conditions twice. then the output will be: Passed! Done! On the other hand.62 The if-else statement Imagine we had exactly one of two statements we wanted to execute.println("Failed!").out. if (grade <= 60) System. Example: if (grade > 60) System. There is a more convenient form for this. else statement2.println("Failed!").out. else System.println("Done!").only now we needed only one statement to do it. then the output will be: Failed! Done! This is exactly like the example on the top of the previous slide. .println("Passed!"). depending on whether a condition was true or false: if (grade > 60) System. System. so only one of "Passed!" or "Failed!" will be printed (though.out. If the grade is greater than 60.out.

println("gets a B.out.out. and another after the else keyword.63 As we stated before.println("gets a C. else if (grade >= 70) // but < 80 System.")."). else if (grade >= 80) // but < 90 System. else if (grade >= 70) // but < 80 System.").out. The above can instead be written as: // the above: if (grade >= 90) System. else if (grade >= 80) // but < 90 System."). else System.out.println("gets a C.")."). // compactly: if (grade >= 90) System.println("gets a B.out.println("below C.println("gets an A. else if (grade >= 80) // but < 90 System.such as another if-else. if (grade >= 90) System.out. else if (grade >= 70) // but < 80 System."). you can put very complex statements in those spots.out.println("gets an A.out.out.out. for example. you can substitute as simple a statement or as complex a statement as you like.out. whenver the form of these constructs requires a statement somewhere. that since the if-else statement needs a statement after the condition. It is possible to list these statements very compactly if there are many nested else cases.println("below C.println("gets a B."). else // grade < 70 System.println("below C...println("gets an A.")."). else System."). This means.out.println("gets a C."). .

").").. be careful! To correct this."). .").").out.out.out. } else System. if (grade >= 80) { if (grade >= 90) System. So.. a semicolon by itself.out.println("gets an A. .println("lower than B. . else if (grade >= 90) System.println("gets an A.println("gets an A."). else . if (grade >= 80) if (grade >= 90) System. you can either put in an “empty statement” – i. The else is paired with the nearest unmatched if.println("lower than B.out. This is known as a dangling else and is an error that can be very hard to track down.. else System.out.").e.out.out. So.println("lower than B."). else System.out.println("lower than B. else System..").out... the computer reads it as: if (grade >= 80) if (grade >= 90) System.or else (best of all) rearrange the logic.or (better!) remove ambiguity and make the nested if a clearer statement by using braces.").println("gets an A.64 What happens here? if (grade >= 80) if (grade >= 90) System.println("gets an A. if (grade < 80) System.println("lower than B...

sometimes they run. and move on to whatever statement comes next. depending on whether certain conditions are true or not. instead. ex. Read score. we added the ability to have certain sections of code run conditionally – i. and then repeat all this code again.e. print out that the student has failed. print out whether each has passed (better than 60) or failed (60 or worse). . In this case. print out that the student has passed. Otherwise. call it examScore If examScore is greater than 60. and sometimes they don’t. This is not a real exam score. print out that the student has failed. so it must instead be a signal to stop reading in exam scores.: Read a student score. Otherwise examScore must be <= 60. and then repeat all this code again. • We solved problems like that above with the if statement and the if-else statement. In this case. So do NOT repeat all this code. examScore must be < 0. • What if we want to do this for many students.65 Lecture 7 : Loops Repeating our code statements many times Last time. If it is also >=0. call it examScore If examScore is greater than 60. how can we stop? How can we tell the computer that there are no more student scores? For example: Read score. and not just one? How can we repeat that code over and over again? • And once we start repeating that code. stop running any of this code. then it is a real exam score. print out that the student has passed Otherwise examScore must be <= 60.

System. • There are diﬀerent kinds of loops. We only proceed as long as condition is true. and then check the condition again. If it is false. check condition again • Each time you re-check condition. and instead run the next line of code after the loop. and is executed only once. • The loop is controlled via a condition. The print statement is not part of the loop. at which point the loop will stop. the particular syntax of that kind of loop is more convenient for some situations and less convenient for other situations. but for each one.println("Done!"). • If condition is false to begin with – never run statement • If condition is true to begin with – run statement once.out. The form is very similar to the if statement: while (condition) statement. . The while loop statement The ﬁrst loop statement we will look at is the while loop. if it is true. run statement one more time. just as the if statement was. The value of i will get smaller and smaller until ﬁnally it is less than 0.66 Loops • Loops are designed to run a particular piece of code – called the body of the loop – many times in succession. leave loop without running the loop statement again. after you leave the loop. For example: while (i >= 0) i--. The body of the loop will continue to be repeated over and over until the condition becomes false. All of them basically do the same thing.

. • Symptom 2: your program appears to freeze and do nothing..but never all). each of the statements is run once. you will not be able to tell that until run-time • Symptom 1: your program starts printing the same thing over and over and over. the loop ends immediately and none of the statements is run. Once you evaluate the condition and it is false.67 The while loop with a compound statement Just as with the if statement. the condition is evaluated again. • Every time you re-evaluate the condition and it is still true. } • If the condition is false. and so if the combination of your code and your data produces an inﬁnite loop. A compiler cannot ﬁnd your inﬁnite loops (it could perhaps ﬁnd some of them. because it is running the same internal calculations over and over and thus is not printing anything out to you.. and move on to whatever statement is after the while loop. though! The program might just be waiting for input! A good reason to use prompts. all the statements get executed again.) . what happens if the condition is always true? • Answer: you get an inﬁnite loop (the loop repeats endlessly). while (condition) { statement1. and then. . or printing a lot of “junk” very rapidly for a long time. statementN. (Careful. .. A common error • Whether the internal statement is a single statement or a compound statement. • This is a common run-time error. any kind of statement can go into the statement segment of a while loop. exit the while-loop statement. • If the condition is true. . then you stop executing the statements. including a compound statement. statement2.

println(i).out. } . while (i <= 10) { System. we would print it using the following statement: System.out.68 A counting example We wish to print the integers from 1 through 10. i++.out. int i = 1. let’s put that in a loop: while (condition) System.out. This is the same as repeating the “print an integer” action 10 separate times. we want to increase i after each print statement. we need to have i start out at 1. and the loop should stop after i has passed 10 in value. However. So.println(i).println(i). i++. } And ﬁnally.println(i). so that needs to be in the loop too: while (condition) { System. If some variable i held our integer.

and the condition is false. grade = Keyboard. boolean notDone = true. grade = Keyboard. } System.println("Enter grade:").out. . Usually.println("Enter grade:"). Why not just directly check if grade is negative instead? int grade = 0.println("Passed!").out. int grade. we want to read in grades one by one.println("Done!"). printing for each grade whether it is a passing or failing grade. else if (grade >=0) // we know grade <= 60 System.println("Failed!").readInt(). at which point we stop. we change the value of the boolean variable once grade is negative. how can we write the program we discussed earlier? Remember. in practice.69 An input-based example So.println("Done!"). else if (grade > 60) System.readInt(). nothing is printed.println("Passed!").out. while (notDone) { System. else // grade >=0 and grade <= 60 System.out. if (grade < 0) notDone = false.out. // else if grade < 0 we do nothing } System. You could also have used grade itself in the condition. the previous version is slightly easier to read. if (grade > 60) System. until a negative number is ﬁnally entered. while (grade >= 0) { System.println("Failed!").out. we go back to check the condition. • It could be argued that while this is slightly faster.out.out. both versions should be okay. so we exit the loop and print Done!. Right now. • If a negative grade is entered.

before statement2 } . statement2) statement3. but anything you can express using a for loop. This type of loop is called a for-loop. Loop equivalence In a language. the while loop is really all you need. The for loop is simply a more convenient form for many loops. condition to continue. for (statement1. ‘‘alteration statement’’) body-of-loop. statement2) statement3. <-. is equivalent to the code: statement1.70 The for loop statement There is another type of loop which separates some more of the loop control code from the body of the loop. while (condition) { statement3. you can express using a while loop. condition.note body comes statement2. The loop: for (statement1. Most commonly the above statements take the following roles: for (initialization statement. condition.

Otherwise. • Next. we have a series of three steps: 1. Move on to step 3. . That is the only time that statement gets run. the ﬁrst statement inside the parenthesis gets run. and just as before.71 A counting example Once again. 3. that initialization statement only gets run once. then move on to step 1. Note that you can also declare the integer as part of the initialization statement: for (int i = 1.out. 2.println(i). We will discuss that in just a moment. the declaration and assignment together are only run once. we wish to print the integers from 1 through 10. Run the body of the loop. into a for loop that does the same thing: int i. i<=10. the assignment was only run once. Exit the loop if the condition is false. which runs over and over until step 1 ﬁnally causes the loop to exit. Using the equivalence discussed on the previous slide. Run the last statement in the parenthesis. In the previous example. Check condition. and now in this example. This is slightly diﬀerent from the previous example in that the scope of the variable i is now somewhat diﬀerent. for (i = 1. we can convert our earlier while loop that did this printing. i++) System. i<=10.println(i). • As the loop begins. i++) System.out. continue with step 2.

That is. <--.out.x declared inside the loop i++. then you treat that as if it had curly braces around it – just as we mentioned with the if-statement. do statement.println(x). <--.println(x).i usable later. That is because the do-while loop will always run the body of the loop once before checking the condition.72 The do-while loop This is another loop form that. but want to make sure the body of the loop gets run at least once.println(i). like the for loop. while (grade < 60) { int i = 6. } And. <--. scope with compound statements within loops works just as we’ve described before. <---.this line is wrong.i usable inside the loop System.x usable inside the loop } System. is sometimes more convenient to use than the while loop and is sometimes not more convenient. for the purposes of scope: while (grade < 60) int i = 6. x is out of scope! . outside the loop System. <--. while (condition). the scope rules for loops are nothing new.out. The do-while loop is generally used when you would have a while loop. Scope for loops For the most part.out. <---. just as with if-statements. For example: int i = 7. If you have a one-line statement inside your loop.i declared outside the loop while (i < 10) { int x = 5. while (condition) statement. is equivalent to: statement. the following two code segments are considered equivalent.

and i went out of scope when the loop ended (i. we must decide exactly what the scope of that variable would be.i declared { *inside* the loop int x = 9. <-. i++) <---. You could think of this as follows. // and so this line // won’t compile Example 4: for (int i = 1.out. // // // // // <-.out.73 A special situation that needs addressing Because you can declare a variable inside the parenthesis of a for loop.println(i).println(i).println(i).and used inside the loop } System.ERROR! System.won’t compile. for (int i = 1.println(i). the variable i is no longer accessible at this point in the code. i++) System. i is gone System.out.println(x). <-. <--. <--.println(i).out. System.e.out. i++) System. } // stand-alone block ends. x went out of scope every time the compound statement ended.x declared System.out. i<=10. when the loop condition was evaluated to false) . And it turns out that the scope of the parenthesis of a for loop ends when the loop itself ends. if it will help you get a clearer idea of the scope involved: { // start a stand-alone block int i.ERROR! Neither variable is accessible here. i <= 10.out. i<=10. for (i = 1.println(x).

y is 35. But none of it is actually necessary in the sense of “needing to be built into the hardware in order for programs to work”. y = y . y will not remain at 35. System. .2.out. // this will indeed alter y } y = y .1.print(z + " even"). The rest of what we will look at are constructs designed to make programming more organized and programs easier to write and maintain. // this will indeed alter y // neither z NOR w are in scope here } // neither z NOR w NOR x are in scope here Every alteration to y is permanent.print(w + " odd"). There are no more “necessary” control ﬂow constructs. Control Flow Fact Any control ﬂow you might want through your program can be created using a combination of: 1. Sequential execution 2. if (x % 2 == 0) { int z = x. while (y >= 0) { int x = y. but due to subtractions inside the loop. y = y .out.1. When we ﬁrst enter the while loop. System. // this will indeed alter y } else { // z is NOT in scope here int w = x.74 Example 5: multiple nestings int y = 35. since y is in scope for every block. Conditionals 3. Loops That’s all you need!! And so that’s all we’ll look at.

out. we need to try something else. we didn’t need to save the exam scores. score9. System. we could just add them to the same variable one by one: int total = 0.print("Enter score #10: "). score10.println("Score #1 is " + score1 + ". Since the above doesn’t work. we’d have to save them in 10 separate variables. total.” Now. code for scores 4-9 #10 is " + score10 + "..print("Enter score #1: "). System. score2. score3. but there is no way to obtain each of the ten individual exam scores from that. score5.readInt(). One thing we could do is to save the exam scores as we read them.println("Score System.println("Score System. i++) { System.out..out.out. // similar System. score7. score4. given our knowledge so far: “For a given student. System. } System.readInt(). System."). score10 = Keyboard.print("Enter score #2: ").").").out.readInt().readInt(). #3 is " + score3 + ". System. and then print each exam score as well.out. read 10 exam scores.readInt()."). for (int i = 1.println("Score . i<=10. and then later.out.out.out. score6. . total = total + Keyboard. // similar code for scores 4-9 System.print("Enter score #3: ").” In this case.out. read 10 exam scores and print out the total of those exam scores.75 Lecture 8 : One-Dimensional Arrays The need for arrays An example we can do. the above will not do! We haven’t saved the values so we have no way to print them out. score2 = Keyboard.println("Total is " + total + ". int score1. print their total.println("Total is " + total + ". print those 10 variables out one by one."). score3 = Keyboard.out. New problem: “For a given student. score1 = Keyboard."). We know their total. . score8... #2 is " + score2 + ". total = score1 + score2 + score3 + score4 + score5 + score6 + score7 + score8 + score9 + score10.print("Enter score #" + i + ": "). Since there are ten exam scores.

The indices of our six cells are 0. Above. respectively. An array is essentially a collection of variables – called cells in this context – which are all of the same type and are all part of one collection with a single name. 1. since each print statement is printing a slightly diﬀerent variable name.76 but that has two problems: • We have to write out the prompt and input statments 10 separate times. the form of such a statement is: SomeType[] arrayName = new SomeType[ExpressionForNumberOfCells]. our integer literal 6 evaluates to a non-negative integer. and always (in Java) starts at 0. since each input requires a slightly diﬀerent variable name. 6. or less commonly. each with an associated integer called an index. 4. What if there were 100? 1000? What if we didn’t know in advance how many we would need? What we would like to have is a way of writing the previous code. • This repetitive work is bad enough for only 10 variables. This line tells the computer to create an array named arr. Every cell will have an index. In general. What if we had some way to make the computer recognize this? What if we could simply use the variable name “score” along with some number. a subscript. and array with 148 cells would have them indexed 0 through 147. whose name is arr. but without the two problems we describe above. and to have those cells be of type int. so that is ﬁne as well). and so on. namely. an array with 10 cells would have them indexed 0 through 9. 3. such as score1 or score4 or score8? Arrays Our solution will be very similar to that idea. and ExpressionForNumberOfCells is some expression that evaluates to a non-negative integer (in the example above. An array is created with a line such as the following: int[] arr = new int[6]. Likewise. and 5. we need 10 print statements – we cannot use a loop. and to have that new array contain 6 cells (so the indices are therefore 0 through 5). where SomeType is whatever type you want (int in the example above). our array of six cells has them indexed 0 through 5. There are six cells in this array. There is no way to use a loop. the set of indices is always a range of consecutive integers. What we will discuss today is a computer language tool known as an array. and the computer could easily put the name “score” and the number together. So. So. For example: _______________________________ | | | | | | | | | | | | | | |____|____|____|____|____|____| 0 1 2 3 4 5 arr Above we see an array. another example of an array creation would be the third line in the following code snippet: . arrayName is a variable name of your choosing (arr in the example above). to get the appropriate full variable name. Note that all the variables score1 through score10 are very similar in name – the only thing diﬀerent is the number at the end. 2.

You have the following form: arrayName[indexExpression] where arrayName is the name of your array. int b = 5. We will explain a bit more about this syntax – including why it looks like an assignment statement – in the coming lectures.println(a). you can’t have two arrays both named arr. the combination of the two – name and index together – uniquely identiﬁes an array cell. but for now.println(arr[4]). // assigning the array cell System. A name will identify exactly one particular array. to either write to or read from. and so those cells would have indices 0 through 14. just as you can’t have two int variables both named theValue. the array cells are of type double.77 int a = 10. // printing the array cell The ﬁrst line will create the array: . you at least know what line you would type to create an array. So. the array name is results. arr[4] = 17. You refer to a particular cell in a particular array via a combination of the name and an index. double[] results = new double[a+b]. this expression gives you cell 4 of the array in the above picture this expression will make your program crash when it runs. // array created In that example. just as you could write statements using single variables such as the following: int a. You indicate this combination of name and index using square brackets ( [ ] ).out. // assigning the variable // printing the variable you can write similar statements such as: int[] arr = new int[6]. a = 2. and there are 15 cells total. And then the index uniquely identiﬁes a cell within that array. System. So.out. Each name can only refer to one array – that is. since 7 is not an index in the array named ‘‘arr’’ You can use this name-index combination as a variable name. and indexExpression is some expression that evaluates to an index of the array. For example: arr[0] // // arr[1] // // arr[4] // // arr[2+3] // // arr[2*(x-r)] // // // arr[7] // // // this expression gives you cell 0 of the array in the above picture this expression gives you cell 1 of the array in the above picture this expression gives you cell 4 of the array in the above picture this expression gives you cell 5 of the array in the above picture if x holds 3 and r holds 1.

0.78 _______________________________ | | | | | | | | | | | | | | |____|____|____|____|____|____| 0 1 2 3 4 5 arr The second line will write the value 17 into cell 4 of that array: _______________________________ | | | | | | | arr | | | | | 17 | | |____|____|____|____|____|____| 0 1 2 3 4 5 and the third line will read the value 17 from cell 4 of that array and print that value 17 to the screen. _______________________________ | | | | | | | arr | 13 |128 |-10 | 99 | 17 | 0 | |____|____|____|____|____|____| 0 1 2 3 4 5 . Likewise. 128. the following statements: arr[0] arr[1] arr[2] arr[3] arr[5] = = = = = 13. would then write values into the remaining cells of the array. -10. 99.

readInt(). System."). for (int i = 1. // indices 0 through 9 Our code for the prompting and inputting would now look like this: System. i <= 10. we can use a loop to have a variable i incremement each time through the loop.print("Enter score #3: ").out. • If we wanted to change this to 100. scores[i-1] = Keyboard. i <= 10.print("Enter score #" + i + ": ").79 Solving our earlier problem So now.println("Score #" + (i+1) + " is " + scores[i] + ". all we would need to do is change the “10”s above to a diﬀerent number. rather than just 10. i++) total = total + scores[i-1]. Note that the loops can either run from 1 through 10. instead..readInt().print("Enter score #" + i + ": "). scores[i-1] = Keyboard.out. . and use an expression involving i for the array index. scores[0] = Keyboard. for (int i = 0.println("Total is " + total + ". } for (int i = 1.out. i++) { System. i < 10.readInt().out.print("Enter score #2: "). i++) System. we only need to take care of totalling and printing and we’re all set. The ﬁnished code snippet is as follows: int[] scores = new int[10]. i <= 10.. System. or 1000 scores. . i++) { System. let’s solve that earlier problem. System.readInt(). since the index can be an expression. we can use a loop! for (int i = 1.out. scores[1] = Keyboard. } Then.print("Enter score #1: "). // and so on for cells 3 through 9 except now. The two problems we had earlier have now gone away: • We no longer need to repeat the same snippet of code 10 times.readInt()."). or from 0 through 9 – you only need to alter the index expression in the body of the loop to take the particular loop range into account.out.out. int total = 0. Instead of having 10 diﬀerent integer variables. scores[2] = Keyboard. let’s simply create one array with 10 cells: int[] scores = new int[10].

i < numberOfScores. int[] scores = new int[numberOfScores].out. i++) total = total + scores[i-1]. for (int i = 1. i <= numberOfScores. for (int i = 0. it is possible to have read in the size as user input. System.").out. } for (int i = 1.out.").print("How many scores are there?: ").out. scores[i-1] = Keyboard.println("Total is " + total + ". int total = 0. System. i++) System.readInt().print("Enter score #" + i + ": "). i++) { System. The following program will work for any number of scores. since the size of the array can also be an expression.80 In fact. public class ScorePrinting { public static void main(String[] args) { int numberOfScores. numberOfScores = Keyboard. i <= numberOfScores.println("Score #" + (i+1) + " is " + scores[i] + ". } } .readInt().

readInt(). i <= scores. We could have written the previous code slide as follows: public class ScorePrinting { public static void main(String[] args) { int numberOfScores.out.81 The length variable Every array has a built-in length variable that tells you how many cells the array has.length.length. since that is how many cells the array has.print("How many scores are there?: "). the expression scores. int[] scores = new int[numberOfScores].print("Enter score #" + i + ": ").length. int total = 0. } } .out. System.println("Score #" + (i+1) + " is " + scores[i] + ". scores[i-1] = Keyboard.length This length value is initialized automatically whenever we create an array. then from that point on.out. So. System. i++) System. for (int i = 1. i++) total = total + scores[i-1]. You access this length variable by using the array name.println("Total is " + total + ". followed by a period."). } for (int i = 1.").length will always evaluate to 10. i++) { System.readInt(). i <= scores. followed by the name length: arr. i < scores. as soon as we executed a statement such as: int[] scores = new int[10]. for (int i = 0.out. numberOfScores = Keyboard.

if you would ﬁnd it useful to have an array with both rows and columns (i. with two dimensions): ____________________________________ | | | | | | | | | | | | |______|______|______|______|______| | | | | | | | | | | | | |______|______|______|______|______| | | | | | | | | | | | | |______|______|______|______|______| | | | | | | | | | | | | |______|______|______|______|______| 0 1 2 3 4 0 1 2 3 or an array with three dimensions: ___________________________________ / / / / / /| 1 / / / / / / | / / / / / / | /_____ /______/______/______/______/ | / / / / / /| /| 0 / / / / / / | / | / / / / / / |/ | /______/______/______/______/______/ / /| | | | | | | /| / | | | | | | | / |/ | |______|______|______|______|______|/ / / | | | | | | /| / | | | | | | / |/ |______|______|______|______|______|/ / | | | | | | / | | | | | | / |______|______|______|______|______|/ 0 1 2 3 4 0 1 2 then you can create such an array in Java! . However. it is possible to have arrays of multiple dimensions as well. These arrays are by far the most common type of array. single-dimensional arrays. and so often we just say “array” when we mean this kind of array. For example.82 Lecture 9 : Multi-Dimensional Arrays Multi-dimensional arrays Up to now we have been talking about one-dimensional arrays (or alternatively.e.

1. since there is only one dimension. Note that the syntax for one-dimensional arrays – which you learned in the last notes packet – is just the above syntax. In general. __ _______ 1 of these 1 of these . such as the two pictures you saw earlier. which has space for twenty integers.[] varname = new Type[size1][size2][size3]. are the sizes of the diﬀerent dimensions in your array (for our purposes. and 2) to be the ﬁrst dimension. In the picture of the two-dimensional array that we had earlier in this notes packet. the ﬁrst dimension has indices 0 through size1 . all the way up to dimension D.1. the indices are a consecutive range of integers beginning at 0 and ending at size . and we wanted the dimension with two cells (indexed with 0 and 1) to be the third dimension. you decide which will be the ﬁrst dimension. 3. we simplify the general syntax to two sets of brackets: Type[][] varname = new Type[sizeOfFirstDimension][sizeOfSecondDimension].83 The array creation syntax we discussed in the last notes packet – for one-dimensional arrays – was a special case of the more general array creation syntax. So. 2. For example.. which has indices from 0 through sizeD . where size is the size of that dimension. then we would write a statement such as the following one. with a variable name theMatrix. expressions in the square brackets above. 1. and wanted the array to hold values of type char.1. with D being equal to 1. ___________ _______________________________ D of these D of these The size1. we would use the following statement: char[][][] threeDarray = new char[3][5][2]. For each dimension. and so on (if there are more than two dimensions). those expressions will evaluate to positive integers. So. for a two-dimensional array. size2. and we wanted the dimension with three cells (indexed with 0.1. for the array created by the above syntax. and one pair of square brackets with a size in it after the assignment operator – exactly as you saw in the previous notes packet: Type[] varname = new Type[size1]. For multi-dimensional arrays.. and we have now created a two-dimensional array. if we wanted the number of rows in our picture to be the ﬁrst dimension in our array (note there are four rows) and we wanted the number of columns in our picture to be the second dimension in our array (note there are ﬁve columns). the second dimension has indices 0 through size2 . you would have exactly one pair of square brackets before the variable name.[sizeD]. If we wanted to create an array to match the three-dimensional example from above. and then use the general syntax above.. etc. and 4) to be the second dimension.. 1. and we wanted to hold values of type int.1. and which will the second dimension. the syntax for creating an array of D dimensions is as follows: Type[][][]. the third dimension would have indices 0 through size3 . in order to create that array: int[][] theMatrix = new int[4][5]. we will only allow the size to be 0 when dealing with one-dimensional arrays). then in order to create such an array. and we wanted the dimension with ﬁve cells (indexed with 0.

column 3 of our two-dimensional array from the earlier examples (counting row 0 as the ﬁrst row and column 0 as the ﬁrst column. if we want to access row 1.84 Accessing cells of multi-dimensional arrays Just as the syntax for creating a one-dimensional array was a special case of the more general syntax for creating a D-dimensional array. you would have two sets of brackets: varname[index1][index2] In the case of a three-dimensional array.. Accessing a D-dimensional array is done with the following expression: varname[index1][index2]. we would use the following expression: theMatrix[1][3] So. In the case of a one-dimensional array. since all index ranges always start with 0). that just simpliﬁes to what you learned earlier. the syntax for accessing a one-dimensional array is a special case of the more general syntax for accessing a D-dimensional array.. we could write the value 17 into that cell using the following assignment statement: theMatrix[1][3] = 17. you would have three sets of brackets: varname[index1][index2][index3] and so on.[indexD] ___________________________ D of these In the case of a two-dimensional array. and that would give us the following picture: ____________________________________ | | | | | | | | | | | | |______|______|______|______|______| | | | | | | | | | | 17 | | |______|______|______|______|______| | | | | | | | | | | | | |______|______|______|______|______| | | | | | | | | | | | | |______|______|______|______|______| 0 1 2 3 4 0 1 2 3 . since there would be only one set of brackets: varname[index1] ________ 1 of these To give an example.

and then the indices of the second dimension area used to select a column within that row.println(theMatrix[1][3]). Making sure you only use an index from 0 through 3 for the ﬁrst dimension. it is important to point out that. and making sure you only use an index from 0 through 4 for the second dimension. it is not necessary to think of things that way. However. if you are viewing pictures of two-dimensional arrays. For example. is that the ﬁrst dimension is of size 4 (and thus has indices 0 through 3) and the second dimension is of size 5 (and thus has indices 0 through 4). go back to the earlier statement that created a two-dimensional array: int[][] theMatrix = new int[4][5]. then you’d get this picture (which you saw earlier): ____________________________________ | | | | | | | | | | | | |______|______|______|______|______| | | | | | | | | | | | | |______|______|______|______|______| | | | | | | | | | | | | |______|______|______|______|______| | | | | | | | | | | | | |______|______|______|______|______| 0 1 2 3 4 0 1 2 3 .out. is what is important. we would use the following statement” System. Typically. even though that is the “typical” way you tend to see examples.85 If we then wanted to print out the value of that cell to the screen. If you want to picture the ﬁrst dimension as indexing the rows. All that matters is that you are consistent. What is important about that statement. you see the indices of the ﬁrst dimension used to select a row.

whatever dimension – rows or columns – you decide will be your ﬁrst dimension. once you decide what “row” or “column” means to you. That is what is important. if you decided that the ﬁrst dimension would index your rows. and thus that you have four rows. then the cell you are accessing has index 1 in the ﬁrst dimension. then that assignment would select row 1 and column 3 and write a 17 into that cell. The code is only deﬁned in terms of “ﬁrst dimension” and “second dimension”. For example.86 But if you instead wanted to picture the ﬁrst dimension as indexing the columns. and ﬁve columns. and that would be ﬁne too: _____________________________ | | | | | | | | | | |______|______|______|______| | | | | | | | | | | |______|______|______|______| | | | | | | | | | | |______|______|______|______| | | | | | | | | | | |______|______|______|______| | | | | | | | | | | |______|______|______|______| 0 1 2 3 0 1 2 3 4 The important thing is that. So just as with any other deﬁnition. for as long as the array exists. or the “columns”. and index 3 in the second dimension. you need to be consistent. Whatever you chose for the ﬁrst dimension – rows or columns – you are accessing index 1 in that dimension. if you try to make the following assignment: theMatrix[1][3] = 17. and you have decided that your ﬁrst dimension indexes the “rows”: . makes more sense to you. that is always what must be your ﬁrst dimension. given the two-dimensional array you just created. and relevant. and whatever you chose for the second dimension – rows or columns – you are accessing index 3 in that dimension. So. It is you that decides whether deﬁning the ﬁrst dimension as indexing the “rows”. you’d have this picture instead. because the assignment selects index 1 from your ﬁrst dimension.

since the language only deals in concepts like “ﬁrst dimension” and “second dimension”. if you decided that the ﬁrst dimension would index your columns. the index of the ﬁrst dimension is 1. because the assignment selects index 1 from your ﬁrst dimension. then that assignment would select column 1 and row 3. and you have decided that your ﬁrst dimension indexes the “columns”: _____________________________ | | | | | | | | | | |______|______|______|______| | | | | | | | | | | |______|______|______|______| | | | | | | | | | | |______|______|______|______| | | | | | | | 17 | | | |______|______|______|______| | | | | | | | | | | |______|______|______|______| 0 1 2 3 0 1 2 3 4 In both examples. Whether that means “row 1” or “column 1” depends entirely on whether you decided the ﬁrst dimension should index “rows” or “columns”. The language itself is only accessing index 1 in the ﬁrst dimension. and thus that you have four columns and ﬁve rows. and write a 17 into that cell.87 ____________________________________ | | | | | | | | | | | | |______|______|______|______|______| | | | | | | | | | | 17 | | |______|______|______|______|______| | | | | | | | | | | | | |______|______|______|______|______| | | | | | | | | | | | | |______|______|______|______|______| 0 1 2 3 4 0 1 2 3 On the other hand. It has nothing to do with the language itself. and does NOT deal in concepts like “rows” and “columns”. .

and then look at the picture on the next page. to see how we can view each row above as a one-dimensional array of size 5. and the second dimension indexes the columns. It’s a good way to think about a two-dimensional array. in which the ﬁrst dimension indexes the rows. Imagine it as an array of arrays – where each row is a one-dimensional array holding the items in that row. or 3 of an array of size 4. as follows: ____________________________________ | | | | | | | 3 | 6 | 9 | 12 | 15 | |______|______|______|______|______| | | | | | | | 18 | 21 | 24 | 27 | 30 | |______|______|______|______|______| | | | | | | | 33 | 36 | 39 | 42 | 45 | |______|______|______|______|______| | | | | | | | 48 | 51 | 54 | 57 | 60 | |______|______|______|______|______| 0 1 2 3 4 0 1 2 3 What value will the expression: theMatrix. Furthermore. and then there is another array holding all of those rows as its items.length in multi-dimensional arrays Imagine we created a two-dimensional array as in our earlier examples: int[][] theMatrix = new int[4][5]. 2.88 The use of . . imagine we have written values into all twenty cells. being stored in either cell 0.length} produce? How do we obtain the length of the ﬁrst dimension (which is 4)? What code would we write to obtain the length of the ﬁrst dimension (which is 4)? What code would we write to obtain the length of the second dimension (which is 5)? The best way to answer these questions is with the picture on the following page. 1. Look at the above picture.

The ﬁrst part is selecting index 1 from the ﬁrst dimension – i. If that is the case. theMatrix.89 ________ | __|_________________________________ | | | | | | | | | 3 | 6 | 9 | 12 | 15 | | |______|______|______|______|______| | | | | 0 1 2 3 4 |______| | __|_________________________________ | | | | | | | | | 18 | 21 | 24 | 27 | 30 | | |______|______|______|______|______| | | | | 0 1 2 3 4 |______| | __|_________________________________ | | | | | | | | | 33 | 36 | 39 | 42 | 45 | | |______|______|______|______|______| | | | | 0 1 2 3 4 |______| | __|_________________________________ | | | | | | | | | 48 | 51 | 54 | 57 | 60 | | |______|______|______|______|______| | | | | 0 1 2 3 4 |______| 0 1 2 3 Given the above picture. then we can consider an array access such as: theMatrix[1][3] to be composed of two parts. this expression will return the item at cell 1 of the array drawn vertically in the picture above. what is at cell 1 of that array? Another array! . is instead the name of the one-dimensional array of size 4 that is drawn vertically in the above picture.e. imagine for a moment that the name of the two-dimensional array. accessing index 1 of the one-dimensional array of size 4 that is drawn vertically in the picture above: theMatrix[1] As we would expect for any one-dimensional array. And.

That is.length evaluates to – the value 4. So how can we get the length of the rows themselves? – i. the one-dimensional array we have obtained in this matter. and so that is exactly what the expression theMatrix. has its cell with index 3 accessed. the ﬁrst dimension of the array. hold the actual rows representing the second dimension of the array. That would mean that the expression theMatrix. And in fact. Think of the name of the two-dimensional array as the name of the vertical array in the previous pictures.90 |______| | __|_________________________________ | | | | | | | | | 18 | 21 | 24 | 27 | 30 | 1 | |______|______|______|______|______| | | | | 0 1 2 3 4 |______| | | <---. that is exactly what it does.length should evaluate to the length of that array that the name theMatrix refers to – namely.e. and then the cells of that ﬁrst-dimension array. The expression theMatrix. In our examples above.array returned by theMatrix[1] And then. the number of columns? – i. the vertical array in our pictures above. the length of the ﬁrst dimension is 4. the name of the array is the “name of the ﬁrst dimension”. remember that the expression: .length will return the length of the ﬁrst dimension of the array. the length of the second dimension? Well.e. theMatrix[1][3] ____________ /|\ gives you the | 1-D array of | size 5 above |______gives cell 3 of that 1-D array giving you the cell that currently holds 27: |______| | __|_________________________________ | | | | | | | | | 18 | 21 | 24 | 27 | 30 | 1 | |______|______|______|______|______| <---.array returned | | by theMatrix[1] | | 0 1 2 3 4 |______| | | /|\ | | | array cell returned by theMatrix[1][3] That is. it sometimes helps to think of a two-dimensional array as an array of arrays.

that is what we do! Since the following expressions: theMatrix[0] theMatrix[1] theMatrix[2] theMatrix[3] would return the one-dimensional arrays representing the rows at index 0.length Well. . so that we could then access cell three of that array by adding a [3] to the end of that expression: theMatrix[1][3] So. respectively. respectively. we’d use: arr[3] and if we instead wanted the length of the array.length above.length theMatrix[2]. index 0. and index 3. evaluates to 5.91 theMatrix[1] in a sense gave us the particular row at index 1. instead of arr being the name of an array. so why not use: theMatrix[1]. all those rows have a length of 5. and we wanted to access cell 3.length theMatrix[3]. index 1.length theMatrix[1]. instead of accessing the cell at index 3 of that row. and index 3.length will return the lengths of the one-dimensional arrays representing the rows at index 0. index 1.length to get the length of that row? And. index 0.. so each of the four expressions involving . of course. And. why not try and obtain the length of the row instead? If we had the variable arr which was the name of an array..then the expressions: theMatrix[0]. we’d use: arr. we are now using: theMatrix[1] to access the one-dimensional array representing the row with index 1. in fact.

length threeDarray[0].length would give you the lengths of the ﬁrst. where in place of 0 you had some other index that was within the legal index range for the ﬁrst dimension (an index between 0 and 3. in the four row. respectively – due to much the same reasoning as we used earlier to explain the two-dimensional array example. that is how you obtain the length of the ﬁrst dimension and second dimension of some two-dimensional array twoDarray.length or by any similar expression. second. The length of the ﬁrst dimension is given by: twoDarray. And then likewise.92 So.length and the length of the second dimension would be given by twoDarray[0]. inclusive. the following expressions: threeDarray. for a three-dimensional array threeDarray. ﬁve column array of our earlier example). .length threeDarray[0][0]. and third dimensions.

write 37 into every cell in that row for all columns c from 0 through 4 write 37 into the cell with row r and column c or. And. once converted to a for-loop: // assuming you know the index r of your row for (int c = 0. that will involve writing 37 into every array cell in row 0. the key to this problem will be to use loops. . r <= 3. c <= 4. We are writing 37 into every cell in a particular row. There are many columns in that row. // array from previous examples Furthermore.. if we want to write 37 into every cell of row r. i <= 4. writing 37 into every array cell in row 2. that itself is a repetitive process. To begin with. r++) write 37 into every cell of row r Now. as follows: for each row from 0 through 3 write 37 into every cell of that row which we can then translate into a for-loop: for (int r = 0.93 An example – initializing a two-dimensional array Assume we have created a two-dimensional array. How could we do it? Well. where rows are the ﬁrst dimension and columns are the second dimension: int[][] theMatrix = new int[4][5]. assume we would like to write the value 37 into every one of the twenty cells of this array. the only diﬀerence between our situation and that one.but that isn’t much diﬀerent than writing 37 into every cell of a stand-alone one-dimensional array! // given a row r. when talking about one-dimensional arrays. is that rather than dealing with a stand-alone. Or in other words. i++) arr[i] = 37. we are trying to write 37 into indices 0 through 4 of a onedimensional array that is part of a two-dimensional array. one-dimensional array. well. c++) theMatrix[r][c] = 37. and we are writing 37 into the cell for each column in that row. since we want to repeat an action (writing 37 into an array cell) over and over again. we can begin our structuring of the solution.. We saw code like that in the previous lecture packet. If we were doing that for a stand-alone one-dimensional array of size 5 named arr: for every index in arr from 0 through index 4 write 37 into the cell of arr at that index and the way to do that for a one-dimensional array would be as follows: // how to write 37 into every cell of some array arr with // indices 0 through 4: for (int i = 0. we note that if we are writing 37 into every array cell. and writing 37 into every array cell in row 3.

the entire body of the loop is run before even the “alteration statement” (the r++ or c++ above) is run. r <= 3. in the case of a for-loop. r <= 3. let alone before the condition is checked. This means that our progression of events is as follows: . Then. r++) for (int c = 0. to do that for every row. and we are done! Remember that the entire statement for a loop (the entire body of the loop) runs from start to ﬁnish before the condition is checked again. that means above. c++) theMatrix[r][c] = 37.e. So.94 That code will write 37 into every column of a given row r in our example array. And. we just use the above loop as the statement of our earlier loop: for (int r = 0. for the outer for-loop: for (int r = 0. the inner for-loop – is run from start to ﬁnish before r is incremented or r is compared to 3 again. r++) the statement for that loop – i. c <= 4.

) . 1) c is incremented to 2 c is compared to 4 enter body of loop write 37 into (0. 2) c is incremented to 3 c is compared to 4 enter body of loop write 37 into (0. 1) (and so on. 0) c is incremented to 1 c is compared to 4 enter body of loop write 37 into (0. 0) c is incremented to 1 c is compared to 4 enter body of loop write 37 into (1. 3) c is incremented to 4 c is compared to 4 enter body of loop write 37 into (0.95 r is initialized to 0 r is compared to 3 enter body of loop c is initialized to 0 c is compared to 4 enter body of loop write 37 into (0. 4) c is incremented to 5 c is compared to 4 loop ends! r is incremented to 1 r is compared to 3 enter body of loop c is initialized to 0 c is compared to 4 enter body of loop write 37 into (1...

(For example. If you wanted to do something other than write 37 into each cell. Note that we needed to supply curly braces to make a compound statement. r++) for (int c = 0. before the row index (the index of the outer loop) is incremented to give us a new row. you would start out with the general form: for (int r = 0. This is the general structure for traversing a two-dimensional array – use two loops. “ﬁgure out the next multiple of 3 and write it into that cell”. you’d move across the array cell by cell in the same manner. the inner for-loop itself is still one statement. will be the previous multiple of 3. even if they aren’t necessary. c++) + 3. you’d follow the same pattern. one nested in the other. r <= 3. That said. c <= { counter = counter theMatrix[r][c] = } } r++) 4. the “whatever you want to do for each cell” is. In either case. The next multiple of 3. and the array cell assignment – as the body of the inner for-loop. r <= 3. writing 37 into every column of an entire row. let’s have a counter keep track of the multiple of 3 that we care about at the moment: int counter = 0. to traverse over every column in a row. So. for (int r = 0. For example. c <= { counter = counter theMatrix[r][c] = } r++) 4.. if we wanted to write the ﬁrst twenty positive multiples of 3 into our array (as we had in an earlier picture in this packet). we add 3 to get 24.96 The inner loop runs from start to ﬁnish. plus 3. only setting up your next value to write into the array before you actually wrote into the array. counter.e we don’t need curly braces around the inner for-loop itself). c++) + 3. That is.) So. given 21. the progression of events would be as follows: . counter. for (int c = 0. However. the above code could also be written as: int counter = 0. since now we are trying to run two statements – counter = counter + 3. then add another 3 to get 27. c++) // whatever you want to do for each cell goes here and then. for every row. so the outer for-loop does not need curly braces around its statement (i. c <= 4. for (int r = 0. and so on. r <= 3. { for (int c = 0. sometimes it helps make the code easier to read if we put them in.

0) . we start with 18.. 4) c is incremented to 5 c is compared to 4 loop ends! r is incremented to 1 r is compared to 3 enter body of loop c is initialized to 0 c is compared to 4 enter body of loop counter is incremented by 3 write 18 into (1. 0) c is incremented to 1 c is compared to 4 enter body of loop counter is incremented by 3 write 21 into (1.. 3) c is incremented to 4 c is compared to 4 enter body of loop counter is incremented by 3 write 15 into (0. 1) counter is incremented by 3 write 3 into (0. so the changes we make to it inside .97 counter is declared and initialized to 0 r is initialized to 0 r is compared to 3 enter body of loop c is initialized to 0 c is compared to 4 enter body of loop c is incremented to 1 c is compared to 4 enter body of loop counter is incremented by 3 write 6 into (0. Note that when we start writing values into the row with index 1. 2) c is incremented to 3 c is compared to 4 enter body of loop counter is incremented by 3 write 12 into (0.and so on. The variable counter was declared outside the outer for-loop. 1) c is incremented to 2 c is compared to 4 enter body of loop counter is incremented by 3 write 9 into (0.

6. . for (int c = 0. Every time we add 3 to the current value of counter. c <= { counter = counter theMatrix[r][c] = } } r++) 4. 9. c++) + 3. for (int r = 0. That code would write 3. { counter = 0. we are adding 3 to the most recent value of counter. The full example for this would be as follows: int counter. and 15 into each row of the array. we had run the line counter = counter + 3. r <= 3. then after it was run counter would be 3.98 the loops are permanent. if instead. 9. 12. which is the value it held after the most recent assignment to counter – which is also the value we most recently wrote into the array. and if counter was 0 before that line was run. and 15 into each of the four rows of the array. 12. counter. That way.. Now. then we would need to reassign counter to zero just before starting the inner loop each time. the ﬁrst cell of each row would always have 3 assigned to it. 6. we wanted to write 3. because just before that assignment.

x. if you had created two arrays of the same size: int[] x = new int[6]. . int[] y = new int[6]. . if you later want to make the two arrays hold the same values.99 Three additional issues with arrays • One interesting and useful property of arrays. it is indeed true. • The assignment operator does not work for arrays the same way it does for single variables. the reasons this is true are beyond the scope of this course – you need to learn a bit more about computer hardware before you can fully understand why this is true. whether the index is 0 or 9999 or 4999 makes no diﬀerence. So. you need to copy the values cell by cell. . for example. is that it takes the same amount of time to access an array cell no matter what the index is. . . int[] y = new int[10]. respectively. You might think it would take longer to access arr[9999]. For example. • Once an array is created. that code would not even compile! If you need an array of size 10 at that point in your program. But at any rate. That is. Instead. since it’s further down the array. but actually. . In fact. as if is just as easily accessible as any other array cell. the ﬁrst and last cells are obtained via the expressions arr[0] and arr[9999]. // this is how you would have to do this int[] x = new int[5]. as in the following code: . no matter the index. Now. you don’t need to be shy about using cells with higher indices – writing a value into arr[9999] rather than arr[0] is NOT going to “slow down” your program. and then wrote diﬀerent values into those array cells. you will need to create a second array – there is no way to expand the size of the ﬁrst array. Treat each array cell. This means that when you write code using arrays.length = 10. the following would not work: int[] x = new int[5]. you should NOT do this: x = y. with respect to how long it takes the computer to access the array cell. you cannot change its size. if you have a one-dimensional array arr of size 10000.

just know that assignment of an entire array to another entire array needs to be done one array cell at a time. not all at once. . indexed // 0 through 5 for (int i = 0. i++) x[i] = y[i]. There are reasons for this that we will discuss soon.100 // this code assumes both arrays have at least 6 cells. for now. i <= 5.

1. . then you’d just have “lo” be 1: ___________________________________ | | | . . we will be able to access each cell of the array in turn. if we have an array arr of size cells. a great deal of our array code will rely on loops. . . and the highest-indexed cell as having index “hi”: ___________________________________ | | .length . size-1 for (int i = 0. i <= hi.101 Lecture 10 : Processing Data Collections Arrays and Loops As you have already started to see. . i <= size . then our loop will need to traverse the entire array: ___________________________________ | | . we can consider the lowest-indexed cell as having index “lo”. 1 through arr. . .length . . for example. As the variable increases from 0 through arr. .1). | | |____|________________________|___| lo . you didn’t want to process the cell with index 0. Whatever collection of array cells we have.1 (that is. hi and in that case. do some sort of particular work on every cell in that collection This usually gets set up using a for-loop statement. i++) We could also access the length value for the array: for (int i = 0. . .length. and only wanted to process the cells indexed 1 through size . the control for our for-loop could be set up as follows: for (int i = lo. That is helpful because much of the loop processing code we write will be of the following form: given a collection of array cells that we care about. size-1 |____________________________| here is the part we care about . and we want to perform work on all the cells. | | arr |____|____|___________________|___| 0 1 . . | | arr |____|________________________|___| 0 . i++) For example. This is because the loop will allow us to progressively increase (or decrease) a subscript variable of an array arr. i++) But if.1. i < arr.

a variable that we had declared on its own. we can add in a loop to vary the index of the array cell we are dealing with. to the highest index of those cells. 2. we need to decide which cells are actually in the collection we care about. Figure out what we want to do for each cell. For example. instead of performing the work only on the array cell with index 0. Finally. suppose that.out.e. in this case. what is that work? For some simpler problems. Presumably we are traversing across the array to perform some particular work at each cell. 3.println(arr[i]). Finally.println(arr[0]). without any chance at all that some cells get processed diﬀerently from other cells. rather than a variable that was a cell of an array). we wish to print every cell (i. the for-loop would start at 1.e. . this step is the hardest part of writing array code. unconditionally At times. this step is easily ﬁgured out from the problem statement itself.1 and vary the array index: for (int i = 0.out. 4. we can consider writing most array-based code as a four step process: 1. the problem statement pretty clearly spells it out – we want to print the value of each cell (and start a newline after each value is printed). it can be easier to ﬁgure step 2 out before worrying about bringing array syntax into the mix. we will perform the work on every array cell we care about. i++) So. you want to perform the exact same work on every cell. how would we print that variable? System. 4. i++) System. how would we do that? Figure that code out. we want to do the above for all indices in the array. // this is how we would do that 3. i < arr. That way. If we wanted to perform that work on the array cell with index 0. not 0: for (int i = 1. and then just have a for-loop run from the lowest index of those cells.1. After that. How would we accomplish this for cell 0 of the array? System. If we just had an ordinary variable x of type int.102 and so in that case.println(x). how would we do that? This step is generally a slight alteration of step 2’s solution – but nevertheless. 2. whenever we are dealing with processing “every cell in our collection”. print the value in each of the cells). Performing the same work on every cell. so have a for-loop run from 0 to size . one per line. i <= size . If we wanted to perform that work on one ordinary variable (i. given an array arr of type int. harder problems. What is it we want to do for each cell? Well.out.length. Let’s consider the four steps above with respect to this problem. 1. for other.

out. add 10 to every cell. we will want to traverse an array. If we had some regular int variable x. but only perform work on certain cells. we have our for-loop run from 0 through arr. . Once again. If we had some integer variable x.println(arr[0]).length. 3. i < arr. print it out. you would use: arr[0] = arr[0] + 10. but of course. given an array arr of type int. we just replace x with arr[0] in both places: if (arr[0] > 60) System. i++) arr[i] = arr[i] + 10. and (2) if so. 2. we look at the problem statement and ﬁgure out what to do at every cell. Once we have the above code snippet. then we could do the following: if (x > 60) to check if x were greater than 60.length . print all values in the array that are greater than 60: 1. It says we want to print all values that are greater than 60. And then. we would have the following code: x = x + 10. 1. 2.103 For another example. then to perform the same sort of code on the ﬁrst cell of an array. conditionally Sometimes. similar to the one we had earlier. For example: given an array arr of type int. what we need to do at every cell is (1) see if the value is greater than 60.println(x). if (x > 60) System. Add 10 to every cell. To do that to cell 0 of the array. To begin with. we don’t know if a value is greater than 60 until we check! So.out. ﬁguring out what to do at every cell is pretty straightforward here – the problem states it pretty clearly. if the condition is true. 4. Performing some work on each cell. 3.1: for (int i = 0. we just have a print statement. since we want to do this at every cell. And ﬁnally. and wanted to add 10 to it.

104 4. And ﬁnally, since we want to check this for every cell, the for-loop again runs from 0 to arr.length - 1, and we vary the array index. for (int i = 0; i < arr.length; i++) if (arr[i] > 60) System.out.println(arr[i]); For another example, given an array arr of type int, we want to print out all even numbers in the array. 1. As with the previous example, we need to at least look at every cell to determine whether it satisﬁes our condition or not. So, the work we need to do at every cell is to see if the value is an even number, and if so, to print it out. 2. Recall that modulus is our way of determining whether a number is even – i.e. divide by 2 and check the remainder: x % 2 == 0 If the above condition is true, the number is even. Otherwise, the result of the modulus operation would be 1 and the expression above would be false and the number would be odd. So, if the condition is true, you print the number. if (x % 2 == 0) System.out.println(x); 3. Performing this calculation on the ﬁrst cell of the array is then a matter of replacing x with that ﬁrst cell: if (arr[0] % 2 == 0) System.out.println(arr[0]); 4. And ﬁnally, we want to perform the above calculation at every cell of the array: for (int i = 0; i < arr.length; i++) if (arr[i] % 2 == 0) System.out.println(arr[i]);

105 Processing a few cells outside of the loop Sometimes we perform work on every cell, but not the same work. It could be that we perform a certain task on most cells (meaning we’d write a loop to handle that repetition of the task), but one or more cells must have diﬀerent work performed on them and thus must be handled on their own, outside of the loop. In that case, step 1 becomes harder to complete, since we need to ﬁgure out what work is done on each cell and it is not always the same. For example, given an array arr of type int, print the values on one line, separated by ellipses, and start a new line when the task is done. For example, if you were given this array: __________________________ | | | | | | | 8 | 9 | 3 | 1 | 7 | |____|____|____|____|____| 0 1 2 3 4

we want to print 8...9...3...1...7 and then start a new line. 1. To begin, it might be easiest to consider the example above: 8...9...3...1...7 There are two diﬀerences between the printing of the last number in that sequence, and the printing of the earlier numbers: • the last number has no ellipses after it • the last number has a new line after it The other numbers are all similar in how they get printed, and so you are repeating the same basic work for all those numbers: 8...9...3...1...7 |__||__||__||__| same work repeated 4 times So, it appears that what we want, is as follows: • For all cells except the last one, print the value of that cell, then print ellipses, but do NOT start a new line afterwards. • For the last cell of the array, print the value, do NOT print ellipses, and then start a new line.

106 2. If we have an integer variable x, and we want to print its value, followed by ellipses, and then NOT start a new line, we would do the following: System.out.print(x + "..."); And, if we ant to print the value, WITHOUT ellipses, and then start a new line, we would do the following: System.out.println(x); Those are the two expressions we need to be using in this problem. The ﬁrst will be used for most of our array cells, and the second will be used for the last array cell. 3. Writing similar code for an array cell is just a matter of replacing x with arr[0]: System.out.print(arr[0] + "..."); System.out.println(arr[0]); 4. Finally, we need to encase the ﬁrst statement above in a loop, and have the second statement above, occur after the loop is done. The loop should NOT process the last cell, so it should only run up through index arr.length - 2, or in other words, the loop should continue as long as our index is less than arr.length - 1. Once the loop is done, we use the second statement above to access the last cell (the cell with index arr.length - 1). for (int i = 0; i < arr.length - 1; i++) System.out.print(arr[i] + "..."); // i is out of scope at this point System.out.println(arr[arr.length - 1]); The following is an alternative to the above code, which keeps i in scope so that it can be used on the last line. int i; for (i = 0; i < arr.length - 1; i++) System.out.print(arr[i] + "..."); // i is still in scope at this point, and if the loop has // exited, then the condition of the loop was false, which // means i == arr.length - 1 System.out.println(arr[i]);

107 As another example, given an array arr of type int, ﬁnd the minimum value in the array. 1. This problem, as stated, is tricky, since we haven’t even made clear what to do with the minimum value. We ﬁnd it, and...? What? We need to do something with the minimum; we can’t just ﬁnd it and forget about it. It is reasonable to assume that if we are searching for the minimum, we intend to make use of that information, and therefore, knowing what the minimum is, might be helpful. So, let’s store the minimum in a variable (we can call this variable min). That restates the original problem as follows: given an array arr of type int, ﬁnd the minimum value in the array and store it in the variable min. Now, it would not be possible to know for sure that we have the minimum value of the array, unless we look at every cell at least once – if there are cells we didn’t look at, those values could have been smaller than the value we think is the minimum, so we can’t get away with not looking at those cells. This means, at any point in the code, if we have not ﬁnished looking at all the cells, we can’t be sure we have the minimum. However – we could, at least hypothetically, know what the minimum is of all the values we have seen so far. For example, if we’ve seen ﬁve values so far, we should at least be able to know what the smallest is out of those ﬁve. We just need to keep track of what we think is the smallest value, and be willing to update that if we ﬁnd a smaller one. For example, if min holds the smallest of the ﬁrst ﬁve values, and we look at the sixth value and that is smaller than min, then min should be updated to hold the sixth value, since that is the smallest value we’ve seen so far. But if the sixth value is NOT smaller than min, then that means not only is min the smallest of the ﬁrst ﬁve values, but it’s also the smallest of the ﬁrst six values, and we can leave min alone for this step. So for most cells, the work at that cell will involve checking to see if that cell is less than or greater than the smallest value we had previously seen up to that point – and if it is less than the smallest value we had seen up to that point, saving our cell’s value as the new “smallest value so far”. The one problem is that we can’t do this to start the code, because if min is not yet initialized, we cannot compare it to anything. So, to begin with, we will just set min equal to arr[0]. That is, if all we have seen is arr[0], well, of course that is the “minimum value so far”, since it’s the only value we’ve seen! And then once min is initialized to be equal to arr[0], then we can run the above “check if next value is smaller” code for all the other cells – for each cell after arr[0], if that cell’s value is less than whatever min is at that moment, then re-assign min to hold that cell’s value.

108 2. Imagine we have a variable x of type int, and are currently storing the minimum value in a variable min. We want to see if x is less than min and, if so, update min to hold x, the new minimum value. We begin with a condition: if (x < min) and in the event that the above condition is true, you write the value of x into min, since you’ve found a new minimum value: if (x < min) min = x; 3. If you want to see how the above code would run on an array cell, just replace x with an array cell access: if (arr[0] < min) min = arr[0]; 4. Finally, as stated in step 1, we shouldn’t have to do any comparisons when looking at the ﬁrst cell of the array – at that point, the ﬁrst cell is the only value we’ve seen, so certainly, it has to be the minimum out of all the values we have seen so far at that point: int min = arr[0]; Once we’ve got a value stored inside min, then for all other array cells, we can compare that cell’s value to the minimum so far. Note that the for-loop starts with i being assigned the index of the array’s second cell, since we’ve already dealt with the ﬁrst cell. int min = arr[0]; for (int i = 1; i < arr.length; i++) if (arr[i] < min) min = arr[i];

**Part 2 : Programming Methodologies
**

As programs get larger and larger, they become harder to manage if all the code for the program is between the inner curly braces of the program skeleton. Imagine a program that was a million lines long! Any programmer trying to make sense of the code inside the program skeleton, would need to understand almost one million lines of code. If the programmer was adding to the code, and misunderstood any of the existing code, then the new code could have logic errors. It could be very diﬃcult to track any logic errors down, when there are any one of one million lines of code that could be wrong. Plus, a project that large might be worked on by multiple people, and it’s hard for diﬀerent people to work on diﬀerent parts of the program when all the code is in the same ﬁle. So, we want to use the principles of composition and abstraction to better organize our work. First of all, we would like to put a set of related instructions together to form a large set of instructions, and then we’d like to use abstraction to “hide that code away”. You have already dealt with this a little bit when using the System.out.println(...) and Keyboard.readInt() statements. In both cases, putting that line in your code, triggered other code to run that you didn’t have to write. We would like to have much of our own code set up the same way, where a one-line statement in the program skeleton activates the running of some other set of instructions we’ve written, in the same way that a one-line statement such as System.out.println(...) or Keyboard.readInt() in the program skeleton, activates the running of some other set of instructions someone else has written. Organizing our code into these small modules – called methods – helps us simplify our programs, since there will be very well-deﬁned means of communications between diﬀerent methods, and thus if there is an error somewhere in the code, it will be much easier to zero in on where that error could be. This means of programming is known as structured programming and will be the topic of lectures 11 through 14. Similarly, we want to put a set of related data values together to form one large piece of data – such as putting many int and double and char values together to form a “student record” – and then use abstraction to hide the details away, by forcing our instructions to treat the data value as a single composition rather than as a collection of small details. For example, for a “student record”, we would prefer sets of instructions that “print a student record”, rather than sets of instructions that “print three int values, ﬁve double values, and eighteen char values”. These modules of data abstraction are known as classes, and eﬀectively, they will be the deﬁnition of new types in the language. If we were to create a “student record” class, for example, then we will have a new type, StudentRecord, that we can then use in ways very similar to the ways in which we have used primitive types. For example, we will be able to declare variables of these new types, create arrays of these new types, and so on. This means of programming is known as object-based programming and will be the topic of lectures 15 through 20.

109

110

**Lecture 11 : Procedural Composition and Abstraction
**

Solving a problem...with help Imagine I have a math problem I would like to solve. We’ll use an arithmetic arithmetic problem, just to keep things simple, even if it will seem a little silly. So, let’s imagine that I am faced with calculating the following value, but don’t know how, i.e. perhaps I have no idea how to do certain arithmetic operations. Speciﬁcally, we will assume that I know how to do addition, but that I do not know how to do subtraction, multiplication, or division. (((2 + 5) * ((3 + 10) - 1)) / 6) + 7 Since I can perform addition, I can evaluate the values of the ﬁrst two additions in the problem, thus giving me: I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7 [I calculate] I have: ((7 * (13 - 1)) / 6) + 7 but I cannot perform the third addition, since I ﬁrst need to know the value of the expression ((7 * (13 - 1)) / 6) since that value is one of the operands of the addition. Since I do not know how to perform subtraction, multiplication, or division, I cannot evaluate that expression, and thus cannot obtain the ﬁrst operand of the remaining addition. So, now it’s time to call on friends for help. Let’s imagine I have a friend, named FriendA, who knows how to do subtraction. Let’s send FriendA the subtraction part of our problem. The chain of events so far is as follows: I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7 [I calculate] I have: ((7 * (13 - 1)) / 6) + 7 --------> I send FriendA: (13 - 1) FriendA has: (13 - 1) FriendA isn’t seeing the entire problem, but FriendA doesn’t need to! All FriendA cares about, is that I have sent a subtraction problem. FriendA doesn’t need to know why I need the subtraction done, in order to do the subtraction. FriendA can simply calculate the result, and send it back to me. Likewise, I don’t need to know how FriendA does the work – I just need to send the problem to FriendA, and receive the solution in return.

111 I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7 [I calculate] I have: ((7 * (13 - 1)) / 6) + 7 -------> I send FriendA: (13 - 1) FriendA has: (13 - 1) [FriendA calculates] FriendA has: 12 <------- FriendA sends back 12 and now, thanks to FriendA, I can replace the expression (13 - 1) with the expression 12: I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7 [I calculate] I have: ((7 * (13 - 1)) / 6) + 7 -------> I send FriendA: (13 - 1) FriendA has: (13 - 1) [FriendA calculates] FriendA has: 12 <------- FriendA sends back 12 I have: ((7 * 12) / 6) + 7

Now, we’ve still got a multiplication and a division to deal with. I have no idea how to do those (or so we are pretending), but I have a friend, FriendB, who claims to know how to do multiplication and division. So, I send FriendB that part of the expression.

112 I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7 [I calculate] I have: ((7 * (13 - 1)) / 6) + 7 -------> I send FriendA: (13 - 1) FriendA has: (13 - 1) [FriendA calculates] FriendA has: 12 <------- FriendA sends back 12 I have: ((7 * 12) / 6) + 7 -------> I send FriendB: ((7 * 12 / 6) FriendB has: ((7 * 12) / 6)

Now, perhaps FriendB knows how to do multiplication, but not division. FriendB lied to us!! But fortunately for FriendB, we don’t need to ﬁnd that out. This is because we’ve asked FriendB to perform a task for us, but we don’t care how that task gets done. All we want is the correct answer returned to us. So, FriendB could likewise ask for help, completely unbeknownst to us. First of all, FriendB performs the multiplication, since FriendB knows how to do multiplication:

113 I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7 [I calculate] I have: ((7 * (13 - 1)) / 6) + 7 -------> I send FriendA: (13 - 1) FriendA has: (13 - 1) [FriendA calculates] FriendA has: 12 <------- FriendA sends back 12 I have: ((7 * 12) / 6) + 7 -------> I send FriendB: ((7 * 12 / 6) FriendB has: ((7 * 12) / 6) [FriendB calculates] FriendB has: (84 / 6)

Next, since FriendB doesn’t know how to do division, FriendB asks another friend, FriendC, for help:

114 I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7 [I calculate] I have: ((7 * (13 - 1)) / 6) + 7 -------> I send FriendA: (13 - 1) FriendA has: (13 - 1) [FriendA calculates] FriendA has: 12 <------- FriendA sends back 12 I have: ((7 * 12) / 6) + 7 -------> I send FriendB: ((7 * 12 / 6) FriendB has: ((7 * 12) / 6) [FriendB calculates] FriendB has: (84 / 6) ------> FriendB sends FriendC: FriendC has: (84 / 6) Note a few things here. Firstly, just as FriendA never saw the entire problem, likewise, FriendB does not get to see the entire problem, and FriendC sees even less. That does not matter! All I need from FriendB is to solve the part I sent to FriendB, just like all I needed from FriendA was to solve the part I sent to FriendA. If FriendA is asked to solve (13 - 1), FriendA does not need to know why I want that answer – FriendA just needs to give me the answer I am asking for. And likewise, FriendB does not need to know why I need the answer to ((7 * 12) / 6) – FriendB just needs to give me the answer I am asking for. And likewise, FriendC doesn’t need to know why FriendB wants the answer to (84 / 6) – FriendC just needs to give FriendB the answer that FriendB is looking for. But likewise, this “secrecy” works in reverse as well! When I sent FriendA part of the original problem, I didn’t care how FriendA got the answer. I just wanted the answer. So maybe FriendA did all the work, or maybe FriendA asked someone else for help. Eitehr way, I don’t need to care. I just need to ask FriendA for an answer to a problem, and then wait to receive that answer. And similarly, when I ask FriendB for help, I don’t care how FriendB got the answer, as long as FriendB sends the answer back to me. Whether FriendB can do all the work, or has to ask someone (such as FriendC) for help, does not concern me. All I care about is that I sent a problem to FriendB and that I get the answer back. So, ﬁnishing our example, FriendC is able to return the answer 14 to FriendB: (84 / 6)

115 I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7 [I calculate] I have: ((7 * (13 - 1)) / 6) + 7 -------> I send FriendA: (13 - 1) FriendA has: (13 - 1) [FriendA calculates] FriendA has: 12 <------- FriendA sends back 12 I have: ((7 * 12) / 6) + 7 -------> I send FriendB: ((7 * 12 / 6) FriendB has: ((7 * 12) / 6) [FriendB calculates] FriendB has: (84 / 6) ------> FriendB sends FriendC: FriendC has: (84 / 6) [FriendC calculates] FriendC has: 14 <------ FriendC sends back 14 FriendB has: 14 and now FriendB can return that result back to me: (84 / 6)

116 I have: (((2 + 5) * ((3 + 10) - 1)) / 6) + 7 [I calculate] I have: ((7 * (13 - 1)) / 6) + 7 -------> I send FriendA: (13 - 1) FriendA has: (13 - 1) [FriendA calculates] FriendA has: 12 <------- FriendA sends back 12 I have: ((7 * 12) / 6) + 7 -------> I send FriendB: ((7 * 12 / 6) FriendB has: ((7 * 12) / 6) [FriendB calculates] FriendB has: (84 / 6) ------> FriendB sends FriendC: FriendC has: (84 / 6) [FriendC calculates] FriendC has: 14 <------ FriendC sends back 14 FriendB has: 14 <------ FriendB sends back 14 I have: 14 + 7 (84 / 6)

at which point I can ﬁnally complete the last addition and ﬁnish the evaluation of the original expression:

1) [FriendA calculates] FriendA has: 12 <------.FriendC sends back 14 FriendB has: 14 <-----.117 I have: (((2 + 5) * ((3 + 10) .FriendB sends back 14 I have: 14 + 7 (84 / 6) [I calculate] I have: 21 .1)) / 6) + 7 -------> I send FriendA: (13 .1) FriendA has: (13 .1)) / 6) + 7 [I calculate] I have: ((7 * (13 .FriendA sends back 12 I have: ((7 * 12) / 6) + 7 -------> I send FriendB: ((7 * 12 / 6) FriendB has: ((7 * 12) / 6) [FriendB calculates] FriendB has: (84 / 6) ------> FriendB sends FriendC: FriendC has: (84 / 6) [FriendC calculates] FriendC has: 14 <-----.

this is how abstraction helps us. it is not necessary for you to worry about every single detail of a computation. In this way. In the example above. “please do this work for me” and then wait for the work to get done (and if you are waiting for an answer of some kind. there were details that I didn’t worry about – I sent sections of the problem to other friends and waited for their answers. and I are using: Column 1 Column 2 Column 3 Column 4 . Something/someone else worries about how the details of the work are done. You didn’t worry about the speciﬁcs of how that System. Again. To do this. wait for that something or someone to return your answer). We can also relate this example to how the machine implements procedural abstraction. Similarly. is known as procedural abstraction. you sent the value you wanted to print.readDouble() or Keyboard. FriendB. even if you have no idea how that task is actually working behind the scenes. I didn’t worry about any of the details of the subtraction. multiplication. and would do the work of obtaining your input value and sending it back to you.out. without knowing any arithmetic other than addition. I sent those problems to someone else and awaited my answer. to a System. You just typed Keyboard. I was able to get the value of a complex arithmetic expression. without worrying about how those friends were doing the work. Since you are freed from having to worry about every little detail. or division – for each of those tasks. When you have printed something to the screen. and all you need to do is ask that something or someone.readInt() or Keyboard. Because of the idea of procedural abstraction. you have more time to focus on the “big picture” and develop that further.out. It is a kind of abstraction in which you hide away the details of how a particular task is done. FriendC.print statement did its work – you just typed the statement and trusted that the statement would do what it was supposed to do.println or System. And even though I did a little bit of the work in our example above (the addition work).118 This concept we are illustrating.out.print statement.println or System. and trusted that that expression would work as we said it would. you didn’t need to type a lot of code in order to input a value.readChar(). but let’s pretend that there is a common sheet of scratch paper that FriendA. You have already used this same idea in your own MPs.out. let’s go through the example again.

some work done.readInt() or System. in your programs. ((7 * (13 . and start working in column 1 of the scratch paper: Column 1 (((2 + 5) * ((3 + 10) . since FriendA has the scratch paper and I do not. at the top of column 2 – thus showing FriendA where that scratch work should be done – and then I will pass the scratch paper to FriendA. I am forced to sit around waiting for FriendA to ﬁnish.1) Column 3 Column 4 Note now that I cannot do anymore work for the time being.1)) / 6) + 7 Column 2 Column 3 Column 4 At this point.println(). when you call Keyboard... This is similar to how..1)) / 6) + 7 Column 2 (13 . but even if there was.. Column 1 (((2 + 5) * ((3 + 10) . .1)) / 6) + 7 Column 2 Column 3 Column 4 After I perform the two additions I can do.some work done.. So I will write the problem I need FriendA to solve...119 I receive the initial problem..1)) / 6) + 7 . I needed to ask FriendA for help.1)) / 6) + 7 . This is because FriendA has the scratch paper! There wasn’t any other arithmetic I could do anyway. ((7 * (13 .out. I have reduced the problem further: Column 1 (((2 + 5) * ((3 + 10) .

and we will further assume that FriendA does not even look at the work I have written in column 1. ((7 * (13 .some work done. If FriendA is not using that area of the scratch paper anymore. I might want to give this scratch paper to someone else later on..1)) / 6) + 7 12 <--. and so that is where FriendA works.1) . we will assume FriendA does NOT work in column 1.. FriendA must do three things..some work done. We have told FriendA to work in column 2.. by writing the answer in our column: Column 1 (((2 + 5) * ((3 + 10) .some work done. seems to be the right thing . before continuing. ((7 * (13 .1) is 12: Column 1 (((2 + 5) * ((3 + 10) .. And what FriendA does is to perform whatever scratch work is needed to ﬁgure out that (13 . FriendA must notify us of the ﬁnal answer. I cannot continue until FriendA ﬁnishes and gives me back the scratch paper.. so that the area is free for someone else to use.readInt() call or the System. First.1) .1)) / 6) + 7 .. than erasing all the remarks there.120 your program sits there and waits for the Keyboard. FriendA should erase column 2. After all... 12 Column 3 Column 4 Second.answer from FriendA Column 2 (13 . Likewise. 12 Column 3 Column 4 Now that FriendA has completed the calculation.1)) / 6) + 7 ..1)) / 6) + 7 Column 2 (13 ..out..println() call to ﬁnish...some work done.. Likewise..

and so I cannot do anything. and .... So. and then will hand the scratch paper to FriendB. and noting it was the result value)..121 to do.1)) / 6) + 7 12 <--.readInt() call to ﬁnish. In that situation. I cannot move to column 2 and mess with any of the work FriendA did.1)) / 6) + 7 . I do not have the scratch paper. but in addition. FriendA should give the scratch paper back to me! Now. since that’s where I do my work: Column 1 (((2 + 5) * ((3 + 10) . And similarly. the next step was to pass the multiplication and division to FriendB. I am supposed to stay in column 1 anyway.answer from FriendA Column 2 Column 3 Column 4 Finally. as with FriendA.readInt() ﬁnishes its job and reports the input integer to your program. just like when Keyboard.. when FriendB is working on the problem. now that I have the scratch paper again. This work is of course done in column 1. since it is the column right next to where I am working. would be wasteful: Column 1 (((2 + 5) * ((3 + 10) ..some work done. I will again use column 2 for this.answer from FriendA ((7 * 12) / 6) + 7 Column 2 Column 3 Column 4 Note that not only did FriendA not mess with anything in column 1 (aside from writing in the result value in the end. that work has been erased.1)) / 6) + 7 12 <--. ﬁnally. taking up space. As with FriendA. and likewise. ((7 * (13 .. your program can then ﬁnally resume work where it had paused while waiting for the Keyboard. To leave all the now-useless remarks there. and it is (and should be) free and available for this purpose. I will write the problem I am handing to FriendB. First of all. at the top of column 2. secondly. FriendB will know to only do work in the designated column. to reduce my expression further.1)) / 6) + 7 . I can do some work again.. I can make use of the value that FriendA gave me. ((7 * (13 . Now.some work done. and is thus completely gone.

answer from FriendA ((7 * 12) / 6) + 7 Column 2 ((7 * 12) / 6) .1)) / 6) + 7 12 <--...1)) / 6) + 7 12 <--. I just have that friend work in the open column to my right – column 2. then when I pass work oﬀ to a friend. Column 1 (((2 + 5) * ((3 + 10) . After all. why shouldn’t FriendB go ahead and use it? There’s no reason not to! If I am working in column 1...some work done.1)) / 6) + 7 .122 will know to NOT mess with my work in column 1. and thus erased everything from column 2! So if column 2 is sitting there unused.some work done. at the top of the next open column. doesn’t mean we can’t use it now for FriendB. FriendA was ﬁnished with the subtraction work... and that is exactly what happens here. or FriendB who does multiplication and division. Regardless of whether that scratch work is scratch work for FriendA who does subtraction.some work done. then when it’s time to have a friend help me out.. and then pass the scratch paper to someone . Just because FriendA was using column 2 for scratch earlier..1)) / 6) + 7 . This is where FriendB had to turn for help to FriendC. FriendB will then do some multiplication work in an attempt to reduce the expression to something simpler: Column 1 (((2 + 5) * ((3 + 10) .answer from FriendA ((7 * 12) / 6) + 7 Column 2 ((7 * 12) / 6) Column 3 Column 4 So what we see here is that our columns are available for general use. ((7 * (13 .. if column 2 is the next open column. in this case. FriendB will do the same thing I did – write the problem for which help is needed.. and with column 2.. that’s the column my friend will use.. (84 / 6) Column 3 Column 4 And now FriendB is stuck. ((7 * (13 .

out. column 3 is the next available open column. If FriendC had subtraction to do.. not only is column 1 taken. and just as FriendA and FriendB did. Whatever area of memory Keyboard. the same kind of work FriendA had done for me in column 2. (84 / 6) Column 3 (84 / 6) Column 4 What has happened here is. it just so happened before.println() work. In the case of your programs so far. I used column 1. But right now. Keyboard. and anyone they ask for help. when you actually write Java code.) Continuing on with our example. and so whatever area of memory main() had used for its variables. Either way. FriendA doing work.readInt() work or the instructions to make System.. FriendA would proceed to work in column 4 – and yet doing subtraction. and asked FriendA for help.some work done.readInt() is using.1)) / 6) + 7 12 <--. but rather. This is an important concept! Someone can use any column that is convenient as their scratch column. We only care that we get some column – whichever one it might be – to do our work in. It is the same when you call System.. that column 2 was always the next available open column. Every time you ask another piece of code to do some work for you.. FriendA. just as I did. any collection of Java instructions such as the instructions to make Keyboard. none of us care which particular column we do our work in. That is.answer from FriendA ((7 * 12) / 6) + 7 Column 2 ((7 * 12) / 6) . FriendA only used column 2 since the person using column 1 (me) was the one who asked for help. uses column 3. if you then call Keyboard. Similarly.readInt() from main().. for example.1)) / 6) + 7 .readInt() erases that memory and gives it back to the system when it is done and returning back to main(). ((7 * (13 . FriendC: Column 1 (((2 + 5) * ((3 + 10) . you called Keyboard. FriendC will perform the work needed to ﬁgure out the result . your Java code will not be bound to run in a particular area of memory. then that person would use column 4.readInt() will use the area of memory next to the area being used by main(). will use whatever area of memory is to the right of where the program had just been working. since that is the next available column. to do its job – and then releases that memory once the code is done. will run in whatever area is most convenient. is not bound to always use column 2. just like FriendA erased column 2 just before handing the scratch paper back to me. FriendC can use any column for scratch work. If FriendC were to ask for help. but column 2 is taken as well. FriendB doing work. so that is the column that gets used by FriendC instead. Keyboard.println(). people who I asked for help used column 2. or FriendC doing work. This does not matter!. to do any scratch work that Keyboard. FriendC needs a scratch column... that piece of code just uses the next available memory to the right of where you were working.out. Whether it is me doing work. (You don’t need to worry about that level of detail. FriendC still gets a scratch area to work in.readInt().readInt() needs to do. but it will help you if you realize that concept.. Right now. So the easiest thing to do is to just use the leftmost column that is not in use.some work done.123 who will work on that problem – in this case.

14 Column 4 And now FriendC will do the same thing that FriendA did earlier.some work done.answer from FriendA ((7 * 12) / 6) + 7 Column 2 ((7 * 12) / 6) .. ((7 * (13 .some work done....1)) / 6) + 7 12 <--. First.. since now FriendC is done.. FriendB in column 2: Column 1 (((2 + 5) * ((3 + 10) . to the person who had asked for help – namely..some work done. have been returned. and the needed result value.some work done.1)) / 6) + 7 ...answer from FriendA ((7 * 12) / 6) + 7 Column 2 ((7 * 12) / 6) . (84 / 6) Column 3 (84 / 6) . FriendC will return the scratch paper back to FriendB.. and there is no sense cluttering up a column someone else could use..1)) / 6) + 7 12 <--.... with scratch work that no longer serves a purpose: Column 1 (((2 + 5) * ((3 + 10) .1)) / 6) + 7 12 <--. is to recognize that the result given by FriendC. and FriendB can resume work now that both the scratch paper.. is the ﬁnal . FriendC will erase all the scratch work in column 3.1)) / 6) + 7 ... all FriendB has left to do....124 of the division: Column 1 (((2 + 5) * ((3 + 10) . (84 / 6) 14 <--. 14 Column 4 Next.1)) / 6) + 7 ..answer from FriendA ((7 * 12) / 6) + 7 Column 2 ((7 * 12) / 6) .some work done. FriendC will return the result..answer from FriendC Column 3 Column 4 And ﬁnally..answer from FriendC Column 3 (84 / 6) . ((7 * (13 . (84 / 6) 14 <--.. Now.some work done..some work done.....some work done.... ((7 * (13 .

.125 answer that FriendB needed: Column 1 (((2 + 5) * ((3 + 10) . since now FriendB is done and so there’s no point in keeping column 2 cluttered up with completed work when someone else could use that column in the future: Column 1 (((2 + 5) * ((3 + 10) .. Second.some work done.1)) / 6) + 7 .. In fact.1)) / 6) + 7 .. ((7 * (13 ...answer from FriendB Column 2 ((7 * 12) / 6) .1)) / 6) + 7 12 <--.1)) / 6) + 7 . FriendB hands the scratch paper back to me..1)) / 6) + 7 12 <--.answer from FriendA ((7 * 12) / 6) + 7 Column 2 ((7 * 12) / 6) .answer from FriendC 14 Column 3 Column 4 Note that I got my result from FriendB...1)) / 6) + 7 12 <--.answer from FriendB Column 2 Column 3 Column 4 And ﬁnally... and I can resume my own work where I ..some work done. I don’t even have any idea FriendC was ever involved.some work done.. ((7 * (13 . (84 / 6) 14 <--. (84 / 6) 14 <--. FriendB tells me what the result is: Column 1 (((2 + 5) * ((3 + 10) ..some work done.. since I have no idea what FriendB did to get this result. ((7 * (13 .... FriendB erases column 2. First.answer from FriendA ((7 * 12) / 6) + 7 14 <--. not FriendC..answer from FriendA ((7 * 12) / 6) + 7 14 <--.answer from FriendC 14 Column 3 Column 4 And now FriendB can prepare to return back to me..some work done.

. when a new area of memory is needed in order to run a new set of instructions.result : 21 So that is an example of how diﬀerent areas of memory (diﬀerent columns. And as the instructions run. ((7 * (13 .answer from FriendA ((7 * 12) / 6) + 7 14 <--.1)) / 6) + 7 .. I can make a substitution: Column 1 (((2 + 5) * ((3 + 10) .readInt()) can make use of any empty area of memory. ((7 * (13 .1)) / 6) + 7 12 <--. Any particular set of instructions (such as main() or Keyboard.answer from FriendB 14 + 7 ..1)) / 6) + 7 12 <--..1)) / 6) + 7 ... Since I now know the result of the ﬁrst addition operand.some work done. in our example) can get used by various sets of instructions..answer from FriendB 14 + 7 Column 2 Column 3 Column 4 And after some calculation work.some work done. ﬁnish the evaluation of the expression: Column 1 (((2 + 5) * ((3 + 10) ..126 left oﬀ. and erase column 1 so that column 1 can be used for a diﬀerent bunch of scratch work the next time I need to do some calculation work in the future: Column 1 Column 2 Column 3 Column 4 <--.some work done.. I can note it somewhere..answer from FriendA ((7 * 12) / 6) + 7 14 <--. 21 Column 2 Column 3 Column 4 Now that I know the result of the expression. I can complete the ﬁnal addition – and with it. we just move over ...

127 to the right. the memory that was being used by those instructions is erased and can be re-used by a diﬀerent set of instructions in the future. And when any set of instructions has ﬁnished. . to the closest-available empty area and use that.

We have already seen the use of a few methods: • The ﬁrst method we saw was the method named main. The method signature is composed of three parts: 1. and when you use a method “label”.println() sends back nothing. For example. In this case. since it deﬁnes what the method does. You can then use the parameters as variables in the method deﬁnition. If you don’t return any value. When writing a method.readInt() sends back an int to whoever called the method.java) is called the method deﬁnition.out. 2.java ﬁle to see the instructions that that “label” told the compiler to run. Keyboard. These parameters appear within the parenthesis of the method. instead. which is part of the “program skeleton” we’ve been using since lecture #3. For example. the set of instructions that form the method are whatever code we’ve written inside main().println().out. a return type – the type of value being returned. you need to indicate the type of the value that is returned by the method (you can return only one value). then you indicate this via the “placeholder” type void. we simply used the “label” – the actual System.readInt() method.println().out print().out. a name – just like variables have names.java ﬁle. the “label” was all you used in your own program. • Finally. • Next. what you ﬁnd in Keyboard. or System. and. every method has a name as well. we made use of System. zero or more parameters – a kind of variable speciﬁc to methods. The ﬁrst line of the method deﬁnition is referred to as the method signature. The method signature and method call The code for a method (for example. In this case. readInt and readDouble are the names of two of the methods in the Keyboard. we made use of the Keyboard. 3. System. System. In both of these cases. you send in values to be stored in these parameters. . we didn’t write the set of instructions for printing.out print() line of code – and the instructions for printing were written into our program by the compiler.128 Lecture 12 : Method Syntax Methods A method (also called a “procedure” or “function” or “subroutine” in some other programming languages) is a programming tool which allows us to label a set of instructions and reuse that set of instructions over and over by simply using the label instead of having to retype the instructions over and over. but you were also able to look inside the Keyboard. after that.

so that is why the parameter needs a type. 1). int value . those three parameters would be listed as follows: type1 name1. d = Add3(3. And. We need the name so that we can refer to this parameter inside the method deﬁnition in the same way we would refer to any other variable. of course. the form for listing a single parameter is: paramType paramName and the form for a list of parameters is to list single parameters. Our example in this packet will involve this method Add3. int. int y. int z) Note the name. 9.129 This “label” that we use to activate a method is known as a method call or a method invocation. type3 name3 For example. and then talk about the pieces in detail. we will give you a quick glimpse of an example of both a method signature and a method call. which does something simple (adds three numbers) so that we can concentrate on learning the actual method syntax instead of understanding a complex calculation. and the parameters (which we’ll discuss in just a moment) inside the parenthesis. For example. and an int as parameters. the return type. ^^^^^^^^^^^^^ The underlined code is the actual method call – values are placed inside the parenthesis to be stored in the parameters. we would pick names for these parameters and list them as follows: boolean done. char oneChar. and it is said that we are calling the method or invoking the method. if we wanted a boolean. One possible method call could be a code snippet like this: int d. type2 name2. separated by commas. This would be an example of a method signature: int Add3(int x. if you had three parameters. So. So. Parameters Listing a single parameter is very similar to declaring a variable. a char. Add3. in that you need both a type and a name. all variables need a type.readInt() method returns a value of type int) to be stored in the int variable d. and the method returns a value of type int (just like the Keyboard.

We can’t call the method unless we know its name.. int z) In this method. and the names of our three parameters of type int are x...130 Completing the method signature The form for the method signature – which appears on the ﬁrst line of a method deﬁnition – is: return-type method-name(t1 p1. or other methods. . and z. from the standpoint of calling the method. the ﬁrst line of our Add3 method should be as follows: int Add3(int x.. That is why we don’t need to understand. or something else. then? – but usually a small bit of documentation will provide that information. We will return to this method in just a bit to ﬁnish writing its deﬁnition.. the return type is int. The sum of three integers will certainly be an integer as well. We simply need to know what to call the method. So. Calling a method The information stored in the method signature is the only information you need to know in order to use the method. if any value is returned at all.. in order to use them. and knowing the parameters allows us to pass the appropriate values into the method to be stored in those parameters. and if so. knowing the return type lets us know how to make use of whatever value might be sent back by the method. the code for Keyboard.out.. and what the type is of the value that gets returned. what? What if we are not doing a calculation.. you would be able to write a syntactically-correct method call. what else does this method accomplish.) Given that information. int y..readInt() or System.. what parameters we need to supply values for.. The details of how the method does its job are unimportant.typeN paramN) We want our “Add3” method to accept three integers and return their sum.tn pn) where (t1 p1. (We generally also need to know what we are accomplishing by calling this method – does this do a calculation? Is is that calculation result that is being returned.tn pn) is (type1 param1.println(). or even see. y..

argument n). b+1. b.. 2). .readInt(). Add3( (2-a-b-c)*7. as part of an assignment statement. There are many ways we might do this. 1. c). c = 5. c+a. 5). public class Program { public static void main(String[] args) { int a. c. method-name(argument1. we need to pass in three arguments of type int. so we would call Add3 the same way we called Keyboard. We might also have int variables a. or – for any of the arguments – an expression that evalutates to type int – since the expression results in a single value: Add3(a.. namely. below is one way. b. where we send in actual integer literals: Add3(2. The values you send in are called arguments. and c that we use as arguments: Add3(a. b*a-c). b. c). Add3(5. . . d. We also need to make use of the value returned by this method. a+b-c.. . because Add3 has three parameters of type int that need values. d = Add3(a. So.. if we want to invoke our Add3 method. and then in parenthesis list any values being sent to the method.131 The general form for calling a method – as you have already seen with the methods you have used – is to write the method name. a = 2. 6+a/c). b. b = 1.

even though you can.132 Completing the method deﬁnition We have the ﬁrst line of our method deﬁnition: int Add3(int x. As we have already seen. This is done via a return statement. . int y.and close. sum = x + y. int y. int y. namely. more on that later). an expression can be very complex. sum. or it can be as simple as a single literal or variable. In this case. Since the method is supposed to return a value of type int. Just as we turned a collection of statements into a single compound statement in conditionals and loops by enclosing them in braces. int z) { int sum. } The statement return sum. The syntax of a return statement is: return expr. sum = sum + z. The diﬀerence here is that you have to have the braces. int z) and now need to ﬁnish the deﬁnition. we need open. int Add3(int x. so let’s declare what we call a local variable to hold the sum. int y. } Completing the method deﬁnition – return statements We have calculated the sum. sends back the value of sum as the return value of this method. and since sum is indeed a variable of type int and thus holds a value of type int. int z) { // code goes here } As far as what code is needed. but the last thing we need in our Add3 method is a way of sending the sum back as the return value of the method. where expr is some expression (perhaps a non-existent one. we are trying to add three numbers. well. // Final version of this method int Add3(int x. we do the same thing with methods. int Add3(int x. sum = sum + z. sum = x + y. and then progressively add to it. everything matches up perfectly. it needs to be enclosed in a pair of braces. and we are pretending that you cannot chain additions together on one line. it will only be a single variable.braces. even if a method has only one line of code. First. return sum. int z) { int sum.

println("Total is " + d). sum = sum + z. } public static int Add3(int x. c).out. d = Add3(a. d.133 Overall code (info on next slide) public class Program { public static void main(String[] args) { int a. System. c = 5. int z) { int sum. b. sum = x + y. b. c. } } . int y. return sum. a = 2. b = 1.

But usually. being the values of the arguments sent in to Add3). • In general. The code will probably tend to be pretty simple in this class to start with. they function exactly the same way as any local variable such as sum that you declare and assign within the braces themselves. and the particulars of the situation you are programming for. and z are “declared” before the open brace is reached. • There is no particular order that the methods need to be in – our example had main before Add3. The local variable sum must be declared and assigned inside the braces. Other than that. and it will complain to you only if it has ﬁnished trying to compile and has still not found the Add3 method. and a method should be written for a task that takes more than just one or two lines of code. • The parameters of Add3 can be thought of as a special kind of local variable. The method signature is what you need in order to be able to use the method. and later on we will learn what it means to leave them out or to use diﬀerent words instead. and are also assigned values before the open brace is reached (the values they are assigned. of course. If the compiler sees a method call before it has encountered that method (as in our example. and thus you can use those parameters in your code in the same way you would use any local variable that you declared and assigned yourself. but the parameters x. since we want you to focus on learning method syntax. but you could have had Add3 before main if you preferred. it’s not a problem – the compiler just makes a note to itself to keep an eye out for the Add3 method. If it does eventually ﬁnd the Add3 method – as it will in our example – then the compiler will make use of that information and will not have any error to report to you in that regard. So for now just that assume all methods you write should have public static in front of them. very simple code is better left in main. The public and static keywords in front of our method deﬁnitions are keywords that are there due to some other issues that we will learn about in a few lectures rather than right now. where the call to Add3 is seen before the Add3 method itself). As with so much else. the code inside a method can be as simple or as complex as you want. programming style. y.134 • Note that “public static” appears in front of our new method just like it appears in front of main(). . knowing when to write a method and when not to is a matter of experience.

java ﬁle). since Add3 and main are within the same class.readInt() – the method is called readInt() (you will ﬁnd it if you look inside the Keyboard. you cannot access main’s local variables from Add3. followed by the method name (readInt). both of the methods were inside the same class. That is. they vanish when the method ends.. It is assumed in the absence of any further information that you are talking about the method with that name from the class you are in. In the code above. If you want to use methods from another class inside the class you are in. and in no other methods anywhere else in the program. So. you list the class. you access it by listing the class (Keyboard). followed by the method call as usual. .Add3(a..methodName(arg1. in place of the method call we do have. main was able to invoke Add3 just by using the name of that method.. the method scope rule is: • Variables declared in a method are only accessible in that method. we could have written: d = Program. followed by a “dot”. so that the system knows in which class to ﬁnd the method you want. but it is not necessary. and since it is in the class Keyboard. if from main you invoke Add3. • Other variables in other methods are not accessible from that method. Using methods from other classes In our program from the last lecture (repeated above). This is done via the following syntax: className. you must list the class name with the method name. Speciﬁcally. b. This is the idea behind the call to Keyboard. • Variables declared within a method have as their scope the life of the method. Thus. followed by a dot. A method’s parameters work the same way as any other local variables of a method and thus go out of scope at the end of the method.135 Scope in methods When we went over loops we talked a little about the scope of variables. Methods work in a very similar manner. So. c).argN). we said that a variable declared within the braces ({}) of a compound statement will vanish from existence and no longer be accessible once you have reached the closing brace of that compound statement block..

If there is a return type other than void – and thus. because it means that the compiler will catch any syntax or calling/returning consistency mistakes you might have made. 5. a return statement in the method – then the value of the expression in the return statement – which is the value being returned – must have a type equal to the stated return type. return. And so on. 2.). you cannot have a method call to a method that returns void as the right-hand-side of an assignment statement. 3. This makes life very nice for you. You can’t return a boolean value from a method with an int return type. the type of the ﬁrst argument must match the type of the ﬁrst parameter. and their types must match in order – i.136 Compilation checks The compiler does a lot of type checking and other such things when compiling code that uses methods. You cannot have a method call to a method that returns a value of type boolean as the right-hand-side of an assignment statement that assigns to a int variable. it’s okay to have no return statement at all. for example. For each method call. You cannot have as an argument to the logical operator NOT (!) a method call that returns int (NOT needs a boolean value for an argument). 4. What you cannot do in this case is have a return statement with an expression. If the return type is void.e. the type of the second argument must match the type of the second parameter. and so on. For example. . the following things are checked: 1. and it’s also okay to have a return statement with no expression (i.e. which simply exists the method at that point. If there is a return type other than void. rather than somehow having those mistakes crash your program when it runs. The method call statement must be used in a way consistent with its return type. by deﬁnition. The name of the method on the calling line must match the name of some actual method that has been written. The number of arguments being sent to this method must match the number of parameters the method has. then there must be a return statement in the method.

Since we are leaving main and thus don’t have access to its variables until we return to it. We have made no assignments. _______________________________________ | | | main | | | | a: 2 b: 1 c: 5 d: ? | | | | | | | |_____________________________________| And now we reach the line that assigns to d. Next. and that the methods on notecards below it (in this case. only main) are methods we will eventually return to later on as we complete method calls we have begun. an analogy to notecards works nicely. Here. so we next perform the call to Add3. they are unique to that notecard and don’t magically appear on other notecards as well. so we consider the values in the variables unknown. we can think of each method call as being on a separate notecard. we will run the three lines of code that assign values to three of the variables. In this manner we make it clear that Add3 (the method on the top notecard) is the currently-active method. as we have now discussed. As always. the scope for a local variable is that method only – it is not available to any other method.137 Visual example of method calling As far as having a visual example of method calling. So. we will place this new notecard on top of the one for main. So. and the variables declared in that method are unique to that method call – just as if we write some numbers on a notecard. This is because. let’s set up a new notecard for Add3. So. that means we must complete the Add3 method call before writing the result to d. we start oﬀ with main (we are again referring to our earlier code example with main and Add3): _______________________________________ | | | main | | | | a: ? b: ? c: ? d: ? | | | | | | | |_____________________________________| Above we see the main method after the declarations have been made. . the expression on the right-hand-side of an assignment statement is evaluated to obtain its value before the actual assignment of that value to the variable can be done.

_______________________________________ | | | main | | | | a: 2 b: 1 c: 5 d: ? | | | | ___________________________________|______ | | | |__| Add3 x: 2 y: 1 z: 5 | | | | | | | | | |________________________________________| Now. That is. the second argument’s value is written into the second parameter. and the value inside c is copied into z. we start the actual code of the Add3 method. First we declare the local variable sum (picture squished to ﬁt it more on slide): . and so on. we have three parameters that need to be assigned values.138 _______________________________________ | | | main | | | | a: 2 b: 1 c: 5 d: ? | | | | ___________________________________|______ | | | |__| Add3 x: y: z: | | | | | | | | | |________________________________________| When we ﬁrst call the method. So. above. the value inside b is copied into y. the ﬁrst argument’s value is written into the ﬁrst parameter. the value inside a is copied into x. These parameters are automatically assigned the corresponding values from the method call code line itself.

... and then the method is concluded. which signiﬁes the end of the method.139 _______________________________________ | main | | a: 2 b: 1 c: 5 d: ? | | ___________________________________|______ | | | |__| Add3 x: 2 y: 1 z: 5 | | | | sum: ? | |________________________________________| Next. What happens here is that the machine stores the value of sum in a “return value” location. .. this is equivalent to liﬁng up the notecard and throwing it away. In pictoral terms. meaning that the parameters and local variables go out of scope and thus ”vanish”. we come to the statement return sum.. _______________________________________ | main | | a: 2 b: 1 c: 5 d: ? | | ___________________________________|______ | | | |__| Add3 x: 2 y: 1 z: 5 | | | | sum: 8 | |________________________________________| Finally...and then we run sum = sum + z. _______________________________________ | main | | a: 2 b: 1 c: 5 d: ? | | ___________________________________|______ | | | |__| Add3 x: 2 y: 1 z: 5 | | | | sum: 3 | |________________________________________| . we run sum = x + y.

However.. The only ways that they can communicate are: 1. and 2. _______________________________________ | | | main | | | | a: 2 b: 1 c: 5 d: 8 | | | |_____________________________________| return value: 8 .after which the return value itself vanishes (it only gets stored temorarily... _______________________________________ | | | main | | | | a: 2 b: 1 c: 5 d: 8 | | | |_____________________________________| And now.. until the assignment statement was over). distinct processing segments.140 _______________________________________ | main | return | a: 2 b: 1 c: 5 d: ? | value: 8 | ___________________________________|______ | |\ / \ / \ / \ / | |__| \dd/ x:\2 / y: \ / z: 5\ / | | \/ \/ \/ \/ | | / \sum: 8 / \ / \ / \ | |/___\_____/___\____/___\_____/___\______| And ﬁnally. it is a value we generated using a method call. the called method can send data back to the calling method by returning a value (which is why we have a return type). . the return value gets used in the assignment statement. we are back in main. Two diﬀerent method calls really are two separate. instead of a value we calculated directly in main. The notecard analogy works especially nicely for methods because methods don’t have the ability to communicate with each other freely due to the scoping rules for local variables of methods. with a value stored inside d. the calling method can send data to the called method by passing arguments obtained in the calling method to the called method’s parameters.

readInt() worked in order to use it. those methods themselves can be broken down in a similar way. where each group of statements accomplishes one task.141 Structured Programming The use of methods can lead to a more organized form of program design that serves us better as our programs become larger. guiding the overall work. in the method. rather than just changing it once in the method. we only need to change it once. without having to inspect every detail of every method. (That’s basically what you’ve done with your use of System. Then.out. reading over main() will still give us a general idea of what is going on in the program. instead we break our large task up into a number of smaller tasks. This form of programming is known as structured programming.readInt(). because you are taking the large collection of statements in main(). in a method. you did not need to understand how Keyboard. . Another advantage to this technique is that we can “reuse” code statements. and instead pasted the same code into many diﬀerent places in the program.) As long as the method names are descriptive and there is good documentation about what those methods do. Rather than write a complete program as a very long main() method. or calls other methods in a certain order to accomplish some work (or both). and each method either performs work that is needed. and providing structure to that collection by breaking those statements into groups. but the details are left to the actual methods that are called from main(). but invoking the various other methods we wrote to handle many of the subproblems involved in the larger task. If we did not have methods. So. and then call that method whenever we need to run that code. then any change to that code would have to be made in all the diﬀerent places where it appeared. where each method makes various other method calls to take care of details related to its computation. This organization also makes a program easier to read. because you can read the “overview” of the code and get a basic idea of how the program runs. So structured programming has a lot of advantages over the “put all your code in main()” approach to program design. there should be little to no reason to have to inspect the details of every method simply to understand how the code works. The main() method then becomes a “summary” of our program. we simply write it once. Instead of having to write many copies of the same code. (For example.) One big advantage to this is that if we need to make changes to the code in question. especially if you are new to the program and are seeing the code for the ﬁrst time.println() and Keyboard. No one method gets too large. and we write a method to handle each task. and not in many places all over the program.

as some big mess of syntax that you need to create an array. in both cases. is declaring and then initializing a variable. Any variable which is not of a primitive type will be a reference variable. So. the former could also have been done on two lines: int[] scores. b = 2. Instead. how are primitive type variables and reference variables diﬀerent? Well. is doing. The latter could have been done on two lines as well: int b. since the type int[] of this variable score is not one of our eight primitive types. the line: int[] scores = new int[10]. and likewise. However.142 Lecture 13 : Reference Variables and Objects Reference variables In reality. we will see many more examples of reference variables. we will see many more examples of variables which are not of primitive types – that is. If a primitive-type variable is similar to a box that holds a piece of data. take note that it is really two diﬀerent things put together: • a variable declaration int[] scores. rather than thinking of the single line: int[] scores = new int[10]. then think of a reference variable as being similar to an “arrow”: . So. what it produces is a reference (that’s why it is called a reference variable). just as the line: int b = 2. score is not a primitive type variable. but the reference variable declaration does not produce an array. the integer variable declaration will produce an integer variable. is quite similar to the line: int b = 2. scores = new int[10]. but not int[]). • an initialization scores = new int[10]. array variables merely happen to be the ﬁrst example of this that we’ve seen. That is. we have put a declaration and an initialization on the same line. Instead. it is something known as a reference variable. and that was not one of our eight primitive types (int is a primitive type. and that the line: int[] scores = new int[10]. But before the semester is over. in that. the type of this varible is int[].

the type is int. Since we have no idea what the value in the variable is. It does not matter whether you are talking about a primitive-type variable or a reference variable. i...) So. and focus on just the ﬁrst three properties. the compiler and run-time environment choose it. and there will be some location where this variable gets stored. because it is not designed to hold your data.. but you don’t store any data value of your own inside the reference variable. A type 3. its job is to refer you to some other memory location where some important data is located – in this case.| 2 | |______| ________ | | scores . all of which are well-deﬁned as soon as the variable is declared. Objects and reference variables There are four things that every variable you declare in Java will have: 1.143 ________ | | b .. Instead. In the declaration int b. let’s look at the section of memory where these variables are stored: .e.. A value that the variable stores..) In the declaration int[] x. (Let’s assume for the sake of this discussion that the compiler chooses address a80 for that variable. and there will be some location where this variable gets stored. until we initialize the variable. (Let’s assume for the sake of this discussion that the compiler chooses address a88 for that variable. the variable name is b. where the array itself is located. an address. A location in memory where it will be stored. A name 2. 4.e.. the type is int[] (i. the variable name is x.| ---|-----> |______| That is. we’re going to set that property aside for now.. (You don’t choose this. you can store an integer value inside the int variable.. ”reference to integer array”).

i. | . the actual value 2 (or rather. we’ll assume reference variables need 32 bits as well.e. | . | .. so therefore the reference variable x would also need 32 bits. When you execute the assignment statement b = 2. a80 a81 a82 a83 a84 a85 a86 a87 a88 a89 a90 a91 a92 a93 a94 a95 a96 a97 a98 a99 a100 a101 a102 a103 a104 a105 a106 _____ | | b | _____| _____ | | x | _____| Variables of type int take up 32 bits. the encoded version of it) gets stored in the memory location corresponding to the variable b (which starts at the memory location with address a80 in our example). |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ | . | . The way these variables diﬀer is in the data they store. four 8-bit memory cells. Likewise. .144 | . so the variable b needs four 8-bit memory cells to store its value. | .

we do this by using the keyword new. And so on. So. x. we have this reference variable. What values can be written into reference variables? And the answer is: memory addresses. their bit string encodings – are what get stored in our reference variables. You could only assign char values to char variables. | . a80 a81 a82 a83 a84 a85 a86 a87 a88 a89 a90 a91 a92 a93 a94 a95 a96 a97 a98 a99 a100 a101 a102 a103 a104 a105 a106 _____ | | | _____| b _____ | | x | _____| Now. |__________________________ | | 32 bits that. The question is. those exact same kinds of labels – or rather. just as we’ve labelled our memory locations with addresses like a5 and a7. | . | . how can we obtain memory addresses to actually store in our reference variables? And the answer is. together. our variable type and value type had to match. | .145 | . | encode the integer 2 |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ | . | . So. remember that in an assignment statement. The data that gets stored inside reference variables are bit strings that tell the machine to go to particular memory locations. You could only assign boolean values to boolean variables. That is. but so far we have not discussed what we would replace the question mark with. we would use an expression such as the following to create an array and return the address of that array: . We want to have an assignment statement such as x = ?.

All expressions involving the Java keyword “new”.146 new int[3]. What this expression is doing is telling the computer to set aside enough memory for an integer array of size 3. This chunk of memory we ask the system to set aside for us is called an object – in Java. then the expression will evaluate to a100. The expression actually evaluates to a memory address. and then inform the program of where the start of that memory is located in the machine. and we said at the start of this notes packet that the above line could actually be split into two parts: int[] x. and thus we say that we are allocating memory. Variables are declared (using variable declaration statements). will set aside some amount of memory for an object. for example. this expression is used in the second line above and whatever the expression evaluates to is being written into the variable x. and objects are allocated. . x = new int[3]. and any memory you obtain through a variable declaration statement is a variable. So. any memory you allocate using new is an object. if the collection of memory cells set aside for the array begins at address a100. This process is known as an allocation. so. You never create objects using variable declarations statements – you only create objects by using new. You have seen this expression before. since that is the starting location of the cells that were set side for the array. as part of an array creation line: int[] x = new int[3].

| . So.147 | . | | . | . | encode the integer 2 |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ | . |__________________________ | | 32 bits that. . beyond the | edge of our picture). | . | . . we would have used this expression as follows: x = new int[3]. together. that address would be what is stored in the reference variable x: . a80 a81 a82 a83 a84 a85 a86 a87 a88 a89 a90 a91 a92 a93 a94 a95 a96 a97 a98 a99 a100 a101 a102 a103 a104 a105 a106 _____ | | | _____| b _____ | | x | _____| _____ | The memory allocated for | the array object begins | here (and ends somewhere | further down. and when the expression new int[3] evaluates to a100.

| . | encode the memory | address a100 |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ | .148 | . step (1) would read the value a100 from x. jump to the start of cell 2 of the array. | . together. when you have a statement such as: x[2] = 17. | encode the integer 2 |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ | 32 bits that. since a100 is cell 0. a100 + 2 * sizeOfInt = a100 + 2 * 4 memory cells . that tells the machine to (1) read the memory address stored in x. So. which uses the square brackets. | . and then step (2) says that. together. | . and then (2) taking that location as the start of the array. |__________________________ | | 32 bits that. a80 a81 a82 a83 a84 a85 a86 a87 a88 a89 a90 a91 a92 a93 a94 a95 a96 a97 a98 a99 a100 a101 a102 a103 a104 a105 a106 a107 a108 a109 a110 a111 a112 _____ | | | _____| b _____ | | | _____| x _____ cell | 0 of | array| _____| cell | 1 of | array| _____| cell | 2 of | array| _____| the entire array (12 memory cells) Finally. and then (3) write 17 into that cell.

is x[2]. we call it dynamic memory. | . a80 a81 a82 a83 a84 a85 a86 a87 a88 a89 a90 a91 a92 a93 a94 a95 a96 a97 a98 a99 a100 a101 a102 a103 a104 a105 a106 a107 a108 a109 a110 a111 a112 _____ | | | _____| b _____ | | | _____| x _____ cell | 0 of | array| _____| cell | 1 of | array| _____| cell | 2 of | array| _____| the entire array (12 memory cells) This memory that we request from the system using new. Note that objects do not have names!!!!. together.e. the only way we can actually refer to objects in our program is by having reference variables that store labels of the memory addresses of those objects. This is why reference variables are so important. Finally. | . Therefore. since that memory location. |__________________________ | | 32 bits that. so go to a108. we say that we allocate that memory from the heap. we declare variables. | encode the memory | address a100 |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ | | 32 bits that. | . together. | encode the integer 2 |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ | 32 bits that. and we call that memory an object – i. but we allocate objects. | . | encode the integer 17 |__________________________ |__________________________ | . Without the . together.149 = a108 must be the address of cell 2. a108. that is the location into which 17 is written: | .

and you’ve used those already. it turns out that reference variables are always automatically initialized by the Java virtual machine. just as if we have an integer variable x. unlike a variable. The ﬁrst are variables of primitive types. a little box with a slash through it. So instead. The value null is a “reference variable literal” in the same way that 2 is an int literal and 45. thinking about speciﬁc memory locations and such is usually more a hinderance than a help.. is the value null. it is pointing to null. (Objects are not bound by normal scope rules. we can draw reference variables as arrows.| ---|-----> |\| |______| i. which we will draw as follows: ________ | | scores . The declaration: int[] scores. and which holds some kind of data. that’s how we convey “reference variable points to null” or “reference variable holds the value null” in our pictures.e.150 ability to store the addresses of object locations – the task which reference variables are designed to do – we could never actually reach our objects.| ---|-----> |______| Now. produces a reference variable. which is stored somewhere in memory just like a variable. there are only three diﬀerent types of data storage. which is why we want to create them.. In fact.. which store memory addresses rather than primitive-type values. as we did earlier. And the third kind of data is an object. just like a variable – but which does not have a name of its own.| \_ | |____\_| Either way. then if: . When a reference variable “points to nothing”. If we want to check if a reference variable “points to nothing” instead of pointing to an actual object. then the reference variable points to null. it is not necessary to think about the array that way in order to program in Java.4 is a double literal. We draw it like this: ________ | | _ scores . The value a reference variable has when you ﬁrst declare it. we tend to hide away all those details and instead just draw pictures. and so there would be no point in creating them.) In Java. The second are reference variables. Our more typical “abstract” picture Now. Sometimes the slash is put through the reference variable picture itself: ________ |\_ | scores . So. we can use boolean expressions such as: scores == null If that expression evaluates to true.

151 x == 2 evaluates to true. ________ | | scores . Now. we don’t worry about where they are located in memory. In our picture. Upon declaration.) So the real picture after declaration is something like this: . But remember that. (Generally. We can also assign a reference variable to store null via an assignment statement such as: scores = null. we know x contains the value 2.| ---|-----> |\| |______| _______________________________ | | | | | | | | | | | | | | |____|____|____|____|____|____| 0 1 2 3 4 5 We simply draw the six array cells together ﬂoating oﬀ in space. the reference variable stores a label – a memory adddress – for some cell in memory.e. We will assume it is a0 in CS125. that is done with the following expression: new int[6] giving the following picture: ________ | | _ scores .| | | |__|___| | | _______________________________ | | | | | | | | |--->| | | | | | | |____|____|____|____|____|____| 0 1 2 3 4 5 We will draw reference variables and arrays that way – as arrows pointing to other large chunks of memory.. Finally. in reality.. just as the assignment statement: x = 2. would store the value 2 in the integer variable x. and the location of the array is written into scores. the new int[6] expression would have been part of an assignment statement: scores = new int[6]. the null address is either the lowest address in the machine – i. which is an address in the machine at which nothing will ever be stored. we have not created the array yet. we will show this by having the reference variable scores point to the array. a0 – or the highest address in the machine. the reference would hold null.

..| a200 | |______| a200 _______________________________ | | | | | | | | | | | | | | |____|____|____|____|____|____| 0 1 2 3 4 5 . the picture would be something like this: ________ | | scores .152 ________ | | scores .| a0 | |______| And then after allocating the object and assigning the reference variable to point to the object.

all variables have names – Object – no. for now. all variables have names – Reference variable – yes. • What can the item store? – Primitive-type variable – values of primitive types – Reference variable – memory addresses – Object – depends on the type of the object (more on that later. the object is gone. the only object we know is arrays and they store many items of the same type) . as we study classes. Once no reference points to the object. it still exists. So there is no particular scope to an object – you can simply use it for as long as it is still accessible from your program in some way. there is no name.153 Summary of data storage • Type of data: – Primitive-type variable – primitive type – Reference variable – non-primitive type – Object – non-primitive type • Does the item have a name? – Primitive-type variable – yes. you cannot use objects directly. but instead must have a reference variable pointing to the object and then work through the reference variable • How is the item created? – Primitive-type variable – variable declaration – Reference variable – variable declaration – Object – you use new to dynamically allocate memory from the heap • What is the scope of the item? – Primitive-type variable – the block or method it was declared in – Reference variable – the block or method it was declared in – Object – as long as a reference points to the object.

the expression involving new sends back the location of the array and it is written into the reference variable B. That is. That is. the assignment statement above copies the memory location label stored in the reference variable A. . Thus. Likewise. int[] A = new int[10]. it will change the reference variable B to point to the same array that the reference variable A points to. into the reference variable B. B = A.154 One last important piece of info Since we are manipulating objects through references. into the cells of the array that B refers to. Assignment of references is not assignment of objects. since we no longer have any references to it. it is important to realize that assignment doesn’t work the same way. when we write B = A. and we write the line: B = new int[10]. // BEFORE __________________ A ----------> | first array | |________________| __________________ B ----------> | second array | |________________| // AFTER __________________ A ----------> | first array | __ |________________| /| / __________________ B --------/ | second array | |________________| and then the array that the reference variable B used to point to will be lost and unaccessible. int[] B = new int[10]. assignment of one reference to another does not automatically copy an object – it simply writes a memory location into a reference. it is just an assignment of a memory location into a reference. That last line will not copy the values of the array that A refers to. When B is declared to be an integer array reference.. . // assume we have some code that // writes values into A’s array. dealing with objects is quite diﬀerent than dealing with variables of primitive types.. Instead.

i < A.155 the reference variable A stored a memory location and that location is then written into B. will print 10 since even though B starts out pointing to an array of length 8. System. B = A. i++) B[i] = A[i].println(B. System. __________________ A ----------> | | . It is important to realize this. .out. In the case of the array.out. int[] B = A. The following code: int[] A = new int[10]. . because A[0] and B[0] are the exact same array cell.length. since A and B point to the same array. We need to explicity copy the object somehow. If they were not. The following code: int[] A = new int[10]. | | __ |___|_________|__| /| 0 . when we assign A[0]. . That example assumes that the array pointed to by A and the array pointed to by B are the same size. then it doesn’t matter which reference you use to access the object.println(B[0]). . A[0] = 3. 9 / B --------/ Therefore. then you’d have to write slightly more complicated code to adjust for that possibility. will print the value 3. . as it means when we deal with objects in general we cannot copy one into another by assigning one’s reference to the other’s reference. we then reassign B to point to an array of length 10. B[0] = 19. that would be done through a loop: for (int i = 0. we are assigning B[0] as well – replacing the 19 with a 3. int[] B = new int[8]. If two reference variables are pointing to the same object.length).

out.println("Score at index #" + i + " is " + arr[i] + ". scores = getArray(4).length).readInt().out. System.print("Enter score for index #" + i + ": ").out. public class Lecture14 { public static void main(String[] args) { int numberOfScores.out.readInt().print("How many scores are there?: "). System. } } public static void printData(int[] arr) { for (int i = 0.").out. readData(scores). printData(scores).length. badInit(scores). i++) System.156 Lecture 14 : Objects and Methods Passing objects to methods via reference variables What we will do now is to look at a program whose methods have references to arrays as parameters and as return values. System. i++) { System.println(scores.length). i < arr. } . i < arr. System.length. System. } public static void readData(int[] arr) { for (int i = 0. numberOfScores = Keyboard.println(scores.println(scores[2]). scores = new int[numberOfScores].println(scores[2]).out.out. int[] scores. arr[i] = Keyboard.

like we would do for variables of primitive types. i < arr. we simply need to put the reference variable name in the method parenthesis. } } // end of the class Lecture14 Note the statements: readData(scores). Each of those statements is a method call. is a memory address – speciﬁcally.157 public static void badInit(int[] arr) { arr = new int[3]. the memory address stored in the reference variable scores. for (int i = 0. i++) arr[i] = -1. So. just like the variable scores does when we ﬁrst declare that array reference variable in main: . in each of the three method calls above. a reference variable name. one that evaluates to the value stored in the variable. as we’ve already discussed a little bit. To use a reference variable as an argument. and badInit. the value we are sending to the method as an argument. when used as an expression. } public static int[] getArray(int n) { int[] temp. For each of those method calls. the parameter that matches the reference variable argument has int[] as the type. badInit(scores). Reference variables are no diﬀerent. evaluates to the memory address stored in the reference variable. return temp. Note also that in the method signatures for readData. printData. temp = new int[n]. to a method that appears later in the program. Of course. in main. any variable name is an expression.length. the reference variable scores is an argument for the method. printData(scores).

} public static void readData(int[] arr) .. and thus the array that gets allocated in main is of size 6. with respect to type matching. public static void badInit(int[] arr) Since the argument is of type int[] – a type that holds memory addresses as values – the parameter is also of type int[]. even though the type is now a non-primitive type. . . in the case of the methods calls we are discussing – just like any other variable evaluates to the value the variable holds. .. That expression is evaluated to produce a “value of type int[]” (the value of any reference variable type is a memory address). and that expression would be evaluated to produce a value of type boolean and that value would be sent to the method and stored in the parameter variable of type boolean. • The parameter “type and variable name” pair will look just like a variable declaration of that type. let’s assume that the ﬁrst integer inputted by the program from the user was a 6. • The argument will still be some expression that evaluates to a value of the needed type.e. parameters of type int[] or double[] or any other non-primitive type work exactly the same way as parameters of primitive types. If the array we allocated in main was .. the expression we use in the method call is simply a variable name. whether that common type is int or double or int[]. but now that our parameter type can be int[]. . send an expression of type boolean as an argument. That is the very important point here – fundamentally. This is no diﬀerent than our earlier method examples. is a value of type int[].. namely. So. i. we send an expression of type int[] as an argument. The fact that now our type is a non-primitive type doesn’t change that. say. In our example program. we just have a pair such as int[] arr – still a “type and variable name” pair. Our parameters previously were pairs such as int x or boolean notDone or char choice. but that variable name will evaluate to some value of the appropriate type – a memory address. and that value is sent to the method and copied into the parameter variable of type int[]. . public static void printData(int[] arr) .158 public static void main(String[] args) { . In our code above. int[] scores. For example. a memory address. that the value of the method call’s argument is copied into the method’s parameter. The argument type and the parameter type match – as they should – and the value that gets copied into the parameter. the parameter-passing rules apply for reference variables just as they did for primitive type variables: • the parameter type and the argument type must match. when we would.

the variable of type int[].e.159 placed at memory address a8000 by the sytem. (The array object could have been located anywhere in memory. we sent an int[] variable as an argument to an int[] parameter. then scores. This is no diﬀerent than when you send an int variable as an argument to an int parameter.) ________________________________ | _______ | | main | | | | scores |a8000 -|------------> | |_______| | | | |______________________________| a8000 _______________________________ | | | | | | | | | | | | | | |____|____|____|____|____|____| 0 1 2 3 4 5 So. and that value is sent to the method. There could be ﬁfty diﬀerent reference . for our example. and is written into the parameter. scores is a variable that holds the memory address a8000. The readData code will write into the array object shown above just as easily as if we had put that code in main and used scores instead of arr as the reference variable that array access depended on. would hold the memory address a8000. | ____________________________________ /|\ |___| _______ | | | readData | | | / | arr |a8000 -|------/ | |_______| | | | |__________________________________| Now. what prevents us from accessing the array through arr in exactly the same way we access it through scores? The answer is. and so now the parameter of the method readData also holds the memory address a8000. we chose a8000 just to pick something. Above. but an expression nonetheless) gets evaluated to the value a8000. and thus conceptually they are completely interchangable. This illustrates a very important quality of objects and reference variables: It doesn’t matter what reference variable you access an object through. the expression scores (a single variable name. and thus are pointing to the same array object. the same memory address: ________________________________ a8000 | _______ | _______________________________ | main | | | | | | | | | | | scores |a8000 -|-----------> | | | | | | | | |_______| | |____|____|____|____|____|____| | | 0 1 2 3 4 5 | | . and afterwards the int variable and the int parameter hold copies of the same int value. The only diﬀerence between them is that one of them is in the scope of main and one is in the scope of readData. And that means that when we have scores as an argument to a method. nothing! Both of those variables – the one in main and the one in readData – are variables of type int[] which hold the memory address of the same dynamicallyallocated array. and so both the int[] variable and the int[] parameter hold copies of the same int[] value – i. But they hold the same memory address. but the object might have been located elsewhere instead.

.length will evaluate to 6. goes out of scope. the local reference variable scores is back in scope. All that happens when we return from readData is that our second reference variable. -9. and 71. we pass in a reference to it.readInt() calls are. then afterwards. Everything else stays the same: ________________________________ | _______ | | main | | | | scores |a8000 -|-----------> | |_______| | | | | | | | |______________________________| a8000 _______________________________ | | | | | | | | 56 | -9 | 22 | 83 | 43 | 71 | |____|____|____|____|____|____| 0 1 2 3 4 5 In short.). 22. we can read and write the same object from any method that has a reference to that object. and the actual array object is still accessible through scores. 43.160 variables to the same object inside your program. respectively. 56. 83.. Since objects are not bound by scope rules. with the exception of the names and/or scopes of the variables being diﬀerent from each other. | ____________________________________ /|\ |___| _______ | | | readData | | | / | arr |a8000 -|------/ | |_______| | | | |__________________________________| When we write 56 into the array cell arr[0]. we are also writing it into the array cell scores[0]. the expression arr. Note that we never actually have an object as an argument to a method. and we will be accessing the same object regardless of which of the ﬁfty reference variables we use. So. even though the array object was created in main(). our picture will look like this: ________________________________ a8000 | _______ | _______________________________ | main | | | | | | | | | | | scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 | | |_______| | |____|____|____|____|____|____| | | 0 1 2 3 4 5 | | . and that value 56 is still sitting in the cell with index 0. Reference variables follow the same rules as variables of the primitive types – the value the argument evaluates to is copied into the . If the user-inputted values for those six Keyboard. because they are the exact same array cell!!. we were able to change our array object from within the method readData(. That doesn’t change. Accessing the object – for reading or writing – works the same way regardless of which of the ﬁfty reference variables we use. all of those reference variables are exactly equivalent. So. in readData. when we return from readData. and thus the body of the forloop will run six times. and the parameter reference variable arr goes away. Instead of passing in the object itself to a method. arr.

and so on. in our above example). as the printData(. we are passing around the address of the object from argument to parameter. But. that means there are now two copies of the data – one in the argument variable. we were sending a value to be copied into a parameter. instead of passing the actual object itself.. it’s how it worked if they were of type boolean.) method successively prints out arr[0]. The only diﬀerence is the type of value that is copied – a value of type int in the ﬁrst case.. a value of type boolean in the second case.. So. If that argument was a variable. the method is actually printing out scores[0]. When we call the printData(. and then use that copy of the object’s address to access the object from the new method (that new method being readData(.) method.)). via the use of a completely diﬀerent method (printData(.161 method parameter.. arr[1] and scores[1] are the same array cell. and so on. we again pass the value of scores – a8000 in our example – as an argument. scores[1]. let’s look at another example. to a reference variable parameter: ________________________________ a8000 | _______ | _______________________________ | main | | | | | | | | | | | scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 | | |_______| | |____|____|____|____|____|____| | | 0 1 2 3 4 5 | | . We can then refer to the original object from our method – and even change it.... and it’s how it works if they are of type int[]. and so on – because arr[0] and scores[0] are the same array cell.). we only make a copy of its address. and one in the parameter. | ____________________________________ /|\ |___| _______ | | | printData | | | / | arr |a8000 -|------/ | |_______| | | | |__________________________________| Now.. We eventually return back to main(). rather than via code written inside the main() method. arr[1]. That was how things worked for variables/parameters of type int. and a memory address in the third case. We never make a copy of the object. This is called passing by value because from the method call. . having printed the entire array pointed to by scores.

as the expression new int[3] gets evaluated.. changing x. let’s consider the three lines in our main() that appear after the call to printData(.. When we print out scores[2].. Next. Likewise here. and z did not change a. the arguments are expressions that evaluate to values. since 22 is the value stored at cell 2 of the array pointed to by scores. y. as we did with readData(. we call the method badInit(. this is not very diﬀerent from the calls to readData(. Again. b.. and c to the Add3 method – we did not send the actual variables themselves. (1) allocate a new integer array object of size 3.) and printData(. is something we could not do with variables of primitive types that were arguments in the method call.. since we only sent the values of a. The ﬁrst line of the method deﬁnition is: arr = new int[3]. As we have previously seen.) method. or c. changing the value of arr – that value being the memory address stored inside arr – will not change the value held in scores. we have a picture like this: . this is because arguments in Java are passed by value. When you call a method....).162 ________________________________ | _______ | | main | | | | scores |a8000 -|-----------> | |_______| | | | | | | | |______________________________| a8000 _______________________________ | | | | | | | | 56 | -9 | 22 | 83 | 43 | 71 | |____|____|____|____|____|____| 0 1 2 3 4 5 Note that altering the object from a diﬀerent method. b. What is diﬀerent is what happens inside the badInit(.). In the Add3 method in Lecture Notes #11. So.). and end up with the following picture: ________________________________ a8000 | _______ | _______________________________ | main | | | | | | | | | | | scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 | | |_______| | |____|____|____|____|____|____| | | 0 1 2 3 4 5 | | .. we will print 22 to the screen. As an example.. and you simply copy those values into the method parameters. nor can we do it with reference variables that are arguments in the method call.. a statement like that will.. and (2) write the location of that array into the variable arr.). | ____________________________________ /|\ |___| _______ | | | badInit | | | / | arr |a8000 -|------/ | |_______| | | | |__________________________________| So far.

and local variables are bound by scope. The expression arr.. to reassign a variable that is local to main(). So. ..) The array at a5000 is allocated as part of the evaluation of the expression new int[3]. We can’t change scores from badInit because scores is a local variable.) or any other non-main() method.. it could have been stored at plenty of other diﬀerent places as well but we just picked one for the example.) will now evaluate to 3.). Once the array has been allocated. There is no way from within badInit(..163 ________________________________ a8000 | _______ | _______________________________ | main | | | | | | | | | | | scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 | | |_______| | |____|____|____|____|____|____| | | 0 1 2 3 4 5 | | . since the array object that the reference variable arr points to. is of size 3. This is just like in Lecture Notes #11.. in that for-loop inside badInit(. | ____________________________________ /|\ |___| _______ | | | badInit | | | / | arr |a8000 -|------/ a5000 | |_______| | ________________ | | | | | | |__________________________________| | | | | |____|____|____| 0 1 2 (The picture assumes that the new array object was stored at memory address a5000.. when we could not change the value of a in main by messing with the value of x in Add3.length later in badInit(. the expression evaluates to the address of the array – a5000 – and then that address is written into arr by the assignment statement. giving the following picture once the assignment statement is complete: ________________________________ a8000 | _______ | _______________________________ | main | | | | | | | | | | | scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 | | |_______| | |____|____|____|____|____|____| | | 0 1 2 3 4 5 | | | ____________________________________ |___| _______ | | badInit | | | | arr |a5000 -|------\ a5000 | |_______| | \ ________________ | | \_____\ | | | | |__________________________________| / | | | | |____|____|____| 0 1 2 We have changed the value of arr by pointing it to a diﬀerent object – but scores still holds the same address and thus still points to the same object.

. the local variables and parameter variables of that method go out of scope. and thus there are no more references to the array at a5000: ________________________________ | _______ | | main | | | | scores |a8000 -|-----------> | |_______| | | | | | | | |______________________________| a8000 _______________________________ | | | | | | | | 56 | -9 | 22 | 83 | 43 | 71 | |____|____|____|____|____|____| 0 1 2 3 4 5 a5000 ________________ | | | | | -1 | -1 | -1 | |____|____|____| 0 1 2 And. since there are no longer any references in the program to the array object at a5000. the system will reclaim that memory. When we return from a method. we can return from the method badInit(. the variable arr in badInit(..) back to main(). with the for-loop complete. Thus.164 the body of the loop will run three times.) goes out of scope.. and thus three assignments will be run – one for each of the three cells in the array pointed to by arr: ________________________________ a8000 | _______ | _______________________________ | main | | | | | | | | | | | scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 | | |_______| | |____|____|____|____|____|____| | | 0 1 2 3 4 5 | | | ____________________________________ |___| _______ | | badInit | | | | arr |a5000 -|------\ a5000 | |_______| | \ ________________ | | \_____\ | | | | |__________________________________| / | -1 | -1 | -1 | |____|____|____| 0 1 2 Now. since your program is no longer using it: ..

The variable scores still holds the address a8000.). just as it would have done at any earlier point in main() after the array was allocated. is the non-local data of main() – speciﬁcally. ________________________________ | _______ | | main | | | | scores |a8000 -|-----------> | |_______| | | | | | | | |______________________________| a8000 _______________________________ | | | | | | | | 56 | -9 | 22 | 83 | 43 | 71 | |____|____|____|____|____|____| 0 1 2 3 4 5 The only thing main() has access to that we could have changed in badInit(.. there is still an array of size 6 at the address a8000.. .165 ________________________________ | _______ | | main | | | | scores |a8000 -|-----------> | |_______| | | | | | | | |______________________________| a8000 _______________________________ | | | | | | | | 56 | -9 | 22 | 83 | 43 | 71 | |____|____|____|____|____|____| 0 1 2 3 4 5 \ (object is eliminated.). and that array still holds the six values we inputted earlier. The ﬁrst printing of scores. the object at a8000 to which main() has a reference..) – we reassigned arr before making changes to the cells of the array arr pointed to – we therefore have not changed anything within badInit(. We will now inspect the next statement in main(): scores = getArray(4).... Since we did not change that object within badInit(..length (the third-to-last statement inside the deﬁnition of main()) will print the value 6 to the screen.) that main() had access to. system reclaims memory) a5000 / \ _____________/__ \|___ | __|_/ | |___\|_/__|_-1 | /|____|____|_\__| / 0 1 2\ / \ So now we are back in main().. and so there are no permanent eﬀects of the work we did in badInit(.

) .166 As far as passing parameters goes. (We’ll assume that the array object is allocated at memory address a7000. just to pick something for our example. and we’ve seen parameters of primitive types before: ________________________________ | _______ | | main | | | | scores |a8000 -|-----------> | |_______| | | | | | | ____________________________________ |___| | | getArray | | _____ | | | | | | n| 4 | | | |___| | |__________________________________| a8000 _______________________________ | | | | | | | | 56 | -9 | 22 | 83 | 43 | 71 | |____|____|____|____|____|____| 0 1 2 3 4 5 Within the method. this reference variable is automatically initialized to null: ________________________________ a8000 | _______ | _______________________________ | main | | | | | | | | | | | scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 | | |_______| | |____|____|____|____|____|____| | | 0 1 2 3 4 5 | | | ____________________________________ |___| _______ | | getArray | | | ___ | _____ temp| a0 -|-----------> |/| | | | |_______| | | n| 4 | | | |___| | |__________________________________| Then the next statement will allocate an array object of size 4. we ﬁrst declare a local reference variable of type int[]. As with any reference variable declared via a variable declaration statement. since the one parameter is an integer. and write the memory address of that array object into the reference variable temp. this method call is a bit more straightforward than the previous three.

(Remember. Remember that in any return statement of the form: return expr. we evaluate the expression. the return type is int[] and our return value is a value of type int[].. . reference variables evaluate to the memory addresses they hold. This matches our return type.167 ________________________________ a8000 | _______ | _______________________________ | main | | | | | | | | | | | scores |a8000 -|-----------> | 56 | -9 | 22 | 83 | 43 | 71 | | |_______| | |____|____|____|____|____|____| | | 0 1 2 3 4 5 | | | ____________________________________ |___| _______ | | getArray | | | | _____ temp|a7000 -|------\ a7000 | | | |_______| | \ ____________________ | n| 4 | | \____\ | | | | | | |___| | / | | | | | |__________________________________| |____|____|____|____| 0 1 2 3 Finally. a memory address.e.) now ends. is a memory address. So. and that is what we return. evaluates to a7000. the last statement of the method: return temp.. the value of any reference type. and return that value as our return value. our expression temp in our return statement. the method getArray(. i. is the interesting part. and thus temp and n go out of scope. As we have already discussed. and the method returns a7000 as its return value.) So.

.168 ________________________________ | _______ | | main | | | | scores |a8000 -|-----------> | |_______| | | | | | | | |______________________________| a8000 _______________________________ | | | | | | | | 56 | -9 | 22 | 83 | 43 | 71 | |____|____|____|____|____|____| 0 1 2 3 4 5 /|\ | | the value a7000 is returned by getArray(.. That means the system can reclaim the memory being used for the array object at a8000.) And thus the right-hand side of the statement: scores = getArray(4). That gives us the following picture: ________________________________ a8000 | _______ | _______________________________ | main | | | | | | | | | | | scores |a7000 -|------\ | 56 | -9 | 22 | 83 | 43 | 71 | | |_______| | \ |____|____|____|____|____|____| | | \ 0 1 2 3 4 5 | | \ | | \ |______________________________| \ \ \ a7000 \ ____________________ \____\ | | | | | / | | | | | |____|____|____|____| 0 1 2 3 That is.) method. since no reference variable in the program is pointing to it anymore: . scores now points to the array object that was allocated in the getArray(.. a7000 ____________________ | | | | | | | | | | |____|____|____|____| 0 1 2 3 evaluates to a7000. and so that value is what the assignment statement writes into the reference variable scores.. and there are no longer any references holding a8000.

) call. So if a parameter is a reference variable.. scores pointed to a completely diﬀerent array object.) call in our main()).. since at that time. . If we return a reference variable. and are not tied in any way to the arguments themselves... it evaluated to 6. If we have a reference as an argument. we are returning a memory address value. it holds a memory address. The general rule to remember is that variables – whether primitive-type variables or reference variables – follow the “pass by value” rule.169 ________________________________ | _______ | | main | | | | scores |a7000 -|------\ | |_______| | \ | | \ | | \ | | \ |______________________________| \ \ \ a7000 \ ____________________ \____\ | | | | | / | | | | | |____|____|____|____| 0 1 2 3 And that is what we are left with at that point in our main() method. and so the method call expression evaluates to that memory address value (as in the getArray(. The reference variable scores points to an array of size 4. Parameters of methods merely hold copies of the argument values. since scores. we are sending a memory address value – NOT an object – to be copied into a reference variable parameter.length will now evaluate to 4 (whereas before the getArray(. and so the ﬁnal print statement of the main() method will print 4.

Next. // assign variables for clock at office. .. for each clock that we want to represent. How would we represent a clock with what we know? One way is with the following set of variables: int variable to hold hour (from 1 . homeMinutes = 15. boolean homeAM. // representing the time 7:14PM officeHour = 7. public class ClockTest { public static void main(String[] args) { // declare variables for clock at home int homeHour.59) boolean to hold true if AM. int homeMinutes. // representing the time 2:15AM homeHour = 2. homeAM = true. we are attempting to represent two clocks.170 Lecture 15 : Data Composition and Abstraction: Classes and Instance Variables Representing non-primitive items Imagine a clock. // main() continues on next slides.12) int variable to hold minutes (from 0 . To do this. int officeMinutes. boolean officeAM. // declare variables for clock at office int officeHour. we ﬁrst declare a set of variables of the above types. officeAM = false.. // assign variables for clock at home. false if PM In the program below. officeMinutes = 14. we can assign separate values to these separate sets of variables.

public int minutes. int minutes."). } } // end of class The class – deﬁnition of a new type We spoke earlier of a class being “a helpful box to store stuﬀ” – for example. What if we take the variables we need to represent a clock.171 Finally. public boolean AM. print these values out using the method below: // Prints the "home" clock printClock(homeHour. } If we were to do that. officeMinutes.println("AM. officeAM).").out. boolean AM) { // print variables for clock System. and gather them into one class? public class Clock { public int hour. if (minutes < 10) System. the way the class Keyboard held our useful command-line input methods in one place. else // AM == false System. and that is to (among other things) collect variable declarations together into one. homeMinutes. if (AM == true) System. However.println("PM. System.out.out.out.print(minutes + " "). // Prints the "office" clock printClock(officeHour. // end main } public static void printClock(int hour. then Clock is now a new type in the language! .print("Time is " + hour + ":"). classes have a diﬀerent purpose as well.out.print("0"). homeAM).

You can use the Clock class above to create many diﬀerent objects of type Clock then say. and AM in one object of type Clock. minutes. then customize objects with unique data values. . just as the house’s creation is described by the blueprint. and a variable AM of type boolean. and likewise.e.172 Some terminology • class . is also known as an instance of that class. But each clock has two integers and a boolean. nor were they parameter variables. a new instance of that class). called an instance variable. • You can use a blueprint to create many houses. won’t aﬀect the values of any instance variables in any other objects of type Clock. a variable minutes of type int. • object . won’t change the color of the walls of other houses.a blueprint. set one clock to store “2:14 AM” and another to store “7:15 PM”. You can create as many houses as you want from the same blueprint. The class tells us “how to make a clock”. we have created a variable hour of type int. you can create as many objects as you want that all • The variables we declared inside that Clock class were not declared inside any method – and thus they were not local variables. and so on. a new roof. and similarly. use one class to create many identical objects. These variables are called instance variables because every time you create a new object of a class (i. “how to make a house”. They are a third kind of variable. you also create one copy of each of the instance variables.a creation whose design is described by the class. you have a new set of walls. • This is similar to building houses – every time you build a house. Every time we create an object of type Clock. An object of a class. Likewise. changing the value of hour. in the same way that a blueprint might tell us. Painting the walls of one house red.

The type of a reference variable and the type of the object it refers to must match! The compiler will catch type-mismatch mistakes of that nature. we can use the expression “new Clock()” to create an object of type Clock. objects in general do not have names. A quick terminology issue: when we create objects of type Clock in this manner.e. likewise hour. we can use a reference variable of type Clock to store the address returned by the “new Clock()” expression – just as we had a reference of type int[] store address returned by the expression “new int[6]”. The same applies for any other type of reference variable or object. just as we said was the case with array objects. and we can only access them by having reference variables refer to those objects and then working through the reference variables. Clock objects – or objects of any other type (i. anything else created using new) – are allocated oﬀ the heap – a section of memory where the object does not go out of scope when we reach the end of the block from which it was allocated. Just as length was a variable built into every array object. instead of saying “reference variable of type Clock”. All of the rules we had for reference variables and objects when we were discussing arrays likewise are true for reference variables and objects of other types. Once the object of type Clock is created. In both cases. Similarly. though both phrases mean the same thing). just as we used the expression “new int[6]” to create an object of type “int array”. For example. the ﬁrst line below will generate a compiler error. just as we declared a reference variable of type “int array”. In both cases. we can say “Clock reference variable” or “Clock reference”.173 Reference variables and objects We can declare a reference variable of type Clock. the reference variable serves the same purpose – it will hold the address of a memory location. Clock c1 = new Clock(). and AM are variables built into every Clock object. . the reference variable holds the address of the memory location where the new object is stored in memory. but the second one will not: int[] arr = new Clock(). minutes. As with array objects. We access the individual variables of the object by using the same dot syntax we used for arrays. Similarly. Likewise. we can refer to them as “Clock objects” (a shorter phrase to say than “objects of type Clock”.

home.AM = true. int minutes. home. office. System.hour.print("Time is " + hour + ":").174 An example using what we’ve seen so far public class ClockTest { public static void main(String[] args) { // declare reference variables Clock home. // end main } // This method has not changed.minutes.hour = 7. // object created home. Clock office. office.hour. // create/initialize "home" clock object home = new Clock(). // object created office.println("AM. home. else // AM == false System.out.AM).println("PM. boolean AM) { // print variables for clock System.print("0"). if (AM == true) System. home. if (minutes < 10) System.hour = 2.AM = false.out.").minutes. office.minutes = 14. // Prints the "home" clock printClock(home. office. // Prints the "office" clock printClock(office.AM)."). public static void printClock(int hour.out. } } // end of class . // create/initialize "office" clock object office = new Clock().out.minutes = 15.print(minutes + " ").out.

home. home.out.").print("Time is " + c.hour = 2.println("PM.minutes < 10) System.AM = false. if (c.println("AM.").print("0").minutes = 14. // object created home. // create/initialize "office" clock object office = new Clock(). // Prints the "office" clock printClock(office).hour = 7.out.out.AM == true) System. // Prints the "home" clock printClock(home).175 Clock references as parameters public class ClockTest { public static void main(String[] args) { // declare reference variables Clock home.AM = true.out. System.minutes = 15.hour + ":"). Clock office. } // end main // This method now has a Clock reference for a parameter public static void printClock(Clock c) { // print variables for clock System. office. if (c.out. // create/initialize "home" clock object home = new Clock().minutes + " "). // object created office. else // AM == false System. office.print(c. } } // end of class .

AM == true) System.minutes = 14. // initialize instance variables of object office = new Clock().. // print the time on each of the two clocks printClock(home).print("Time is " + c.hour = 2. // initialize instance variables of object home = new Clock(). } // end of class } . office. in a Clock object.println("PM. if (c. home.hour + ":").minutes = 15. // allocate object. Clock office.) method.print("0").minutes < 10) System.out.hour = 7.println("AM.AM = true. assign address to reference variable. System. printClock(office).print(c. Reference Variables. where we stored our information for a clock. office.").. // object created office.AM = false. home.out.out.minutes + " ").) method: public class ClockTest { public static void main(String[] args) { // declare reference variables Clock home.. and null Clock references as parameters Last time.out. and passed a reference to that Clock object to the printClock(. else // AM == false System.. home. rather than passing two int values and a boolean value to the printClock(.out. } public static void printClock(Clock c) { // print variables for clock System. assign address to reference variable. we saw this example.").176 Lecture 16 : Classes. // allocate object. if (c.

The method printClock(.177 So far. public boolean AM. as long as the method has a reference to that object. let’s add in a new method to our ClockTest class – one that will assign to the variables of a Clock object. but if it changed its own copies. from printClock(. the important concept here is that objects are not bound by the scope rules. and that’s exactly what we are doing now... What we could not do.. and so though we could not read main()’s clock-related variables from printClock(. So.. we called a method to print out the clock information..).. just as the methods in Lecture Notes #14 could read and write the array cells of the array object created in main().... So main() could send the values of local variables to other methods.). In Lecture Notes #14. In our ﬁrst example yesterday. to have Clock references as parameters – and then since a method like printClock(. However. We will keep the Clock class the same for this entire notes packet. we can now access those objects from other methods. since those other methods had their own references to that array object. public int minutes. Any method can access any existing object.): . from other methods. via the addition of a method setTime(. we “eﬀectively” had read-access to those variables anyway. to being stored in instance variables in objects. In the ﬁrst example last time. but those other methods could not read – or write – the actual local variables of main(). our non-main() methods were able to both read and write to the array object created in main(). we could copy the values of our local variables. the use of the Clock class: public class Clock { public int hour.. however. since we could copy the data of those variables from main() to printClock(. as well. the method could read and write the instance variables of the Clock object created in main(). just as we did in Lecture Notes #14 when we accessed an array created in main(). } has not allowed us to do anything we couldn’t do before. for now.. now that we’ve moved the clock-related data from being stored in local variables in main().) has its own reference to the Clock object created in main(). that had no eﬀect on the original variables back in main(). we are only changing the code inside the ClockTest class. into the parameters of a new method..) could store copies of the values.). we can design methods like printClock(. Again.. Similarly..) as part of the method call. was write to the variables of main().

PrintClock(office).hour = theHour. false).println("AM.print(c. int theMinutes.out. if (c. 15.").AM == true) System. boolean theAM) { c. c. System. } public static void PrintClock(Clock c) { // print variables for clock System.minutes = theMinutes.print("0"). else // AM == false System. // print the time on each of the two clocks PrintClock(home).178 public class ClockTest { public static void main(String[] args) { // declare reference variables Clock home. // object created // set the time on each of the two clocks setTime(home. c. Clock office. 2.out. // object created office = new Clock(). } // end main public static void setTime(Clock c. // allocate objects and assign addresses to the reference variables home = new Clock(). 14. setTime(office.println("PM.out. true). 7.print("Time is " + c.AM = theAM.minutes + " "). } } // end of class . int theHour.minutes < 10) System.out.hour + ":"). if (c.out.").

to see what happens. The ﬁrst allocation occurs in the statement: home = new Clock(). true).179 Let’s trace though the beginning of this program. and thus the address of that object is written into the reference variable home. respectively. just for the sake of assuming some addresses for our example. In real life. we have our ﬁrst call to setTime(. 15.): setTime(home. Let’s assume these two objects are allocated at addresses a5000 and a7800. the statement after that one will allocate a second object and store its address in the reference variable office. First of all.. . but could also have been almost anywhere else in memory instead. 2.. we declare the two reference variables in main(): ____________________________________ | ___________ | | main | | | | home | a0 --|---|----> | |_________| | | | | ___________ | | | | | | office | a0 --|--|----> | |_________| | |__________________________________| ___ |\| ___ |\| Then we allocate two objects. Likewise. the objects could have been there. a5000 __________ /---> | | hour ____________________________________ / |________| | ___________ | / | | minutes | main | | | / |________| | home | a5000 --|---|--------/ | | AM | |_________| | |________| | | | ___________ | | | | | | office | a7800 --|--|-----------\ a7800 | |_________| | \ ___________ |__________________________________| \--> | | hour |_________| | | minutes |_________| | | AM |_________| Next.

since c and home point to the same object: .180 Each of the four arguments in that method call are evaluated.. and true. when we run the three lines of the setTime method: c. Those four values are then copied into the four parameters of the setTime(.. also writing into the instance variables of the object that home points to. c. c.minutes = theMinutes. and control is transferred to the setTime(. and thus.. 15.) method. 2.) method: a5000 __________ /------> | | hour ____________________________________ / /--> |________| | ___________ | / / | | minutes | main | | | / / |________| | home | a5000 --|---|--------/ / | | AM | |_________| | / |________| | | / | ___________ | / | | | | / | office | a7800 --|--|--------/--\ a7800 | |_________| | / \ ___________ |__________________________________| / \--> | | hour | | / |_________| | setTime ____________ | / | | minutes | | | | / |_________| | c | a5000 ---|-|--/ | | AM | |__________| | |_________| | _______ ________ | | | | | | | | theHour | 2 | theAM | true | | | |_____| |______| | | | | __________ | | | | | | theMinutes | 15 | | | |________| | |__________________________________| And now..AM = theAM.hour = theHour. to obtain the four values a5000. we are writing into the instance variables of the object that the reference variable c points to.

once we return from setTime(.) back to main(). the object home points to has been initialized: ...181 a5000 __________ /------> | 2 | hour ____________________________________ / /--> |________| | ___________ | / / | 15 | minutes | main | | | / / |________| | home | a5000 --|---|--------/ / | true | AM | |_________| | / |________| | | / | ___________ | / | | | | / | office | a7800 --|--|--------/--\ a7800 | |_________| | / \ ___________ |__________________________________| / \--> | | hour | | / |_________| | setTime ____________ | / | | minutes | | | | / |_________| | c | a5000 ---|-|--/ | | AM | |__________| | |_________| | _______ ________ | | | | | | | | theHour | 2 | theAM | true | | | |_____| |______| | | | | __________ | | | | | | theMinutes | 15 | | | |________| | |__________________________________| As a result.

..) method. and false...) method: . Each of the four arguments in that method call are evaluated. false).182 a5000 __________ /---> | 2 | hour ____________________________________ / |________| | ___________ | / | 15 | minutes | main | | | / |________| | home | a5000 --|---|--------/ | true | AM | |_________| | |________| | | | ___________ | | | | | | office | a7800 --|--|-----------\ a7800 | |_________| | \ ___________ |__________________________________| \--> | | hour |_________| | | minutes |_________| | | AM |_________| The same would happen with the next line of code in main() – the second call to setTime(. 7. 14. Those four values are then copied into the four parameters of the setTime(. and control is transferred to the setTime(.): setTime(office. 14. 7. to obtain the four values a7800...

183 a5000 __________ /------> | 2 | hour ____________________________________ / |________| | ___________ | / | 15 | minutes | main | | | / |________| | home | a5000 --|---|--------/ | true | AM | |_________| | |________| | | | ___________ | | | | | | office | a7800 --|--|-----------\ a7800 | |_________| | \ ___________ |__________________________________| \--> | | hour | | /----------> |_________| | setTime ____________ | / | | minutes | | | | / |_________| | c | a7800 ---|-|--/ | | AM | |__________| | |_________| | _______ ________ | | | | | | | | theHour | 7 | theAM |false | | | |_____| |______| | | | | __________ | | | | | | theMinutes | 14 | | | |________| | |__________________________________| And now.minutes = theMinutes. since c and office now point to the same object. also writing into the instance variables of the object that office points to. and so by writing to the instance variables of what c now points to.AM = theAM.) the ﬁrst time: . when we run the three lines of the setTime method: c. c.. we are writing to a diﬀerent object than we were writing to when we called setTime(. we are writing into the instance variables of the object that the reference variable c points to. c. Our second method call has written a diﬀerent value into the parameter c.. and thus.hour = theHour.

the object office points to has also been initialized: ..184 a5000 __________ /------> | 2 | hour ____________________________________ / |________| | ___________ | / | 15 | minutes | main | | | / |________| | home | a5000 --|---|--------/ | true | AM | |_________| | |________| | | | ___________ | | | | | | office | a7800 --|--|-----------\ a7800 | |_________| | \ ___________ |__________________________________| \--> | 7 | hour | | /----------> |_________| | setTime ____________ | / | 14 | minutes | | | | / |_________| | c | a7800 ---|-|--/ | false | AM | |__________| | |_________| | _______ ________ | | | | | | | | theHour | 7 | theAM |false | | | |_____| |______| | | | | __________ | | | | | | theMinutes | 14 | | | |________| | |__________________________________| And now. once we return from setTime(..) back to main().

but we can write a separate method to initialize the instance variables of some object. . we gain the ability for other methods to read and write those variables freely. So. to being in an object. by moving the variables from being local to main(). into main(). We could not have written a separate method to initialize local variables within main(). we can read or write the same object from many diﬀerent methods.185 a5000 __________ /---> | 2 | hour ____________________________________ / |________| | ___________ | / | 15 | minutes | main | | | / |________| | home | a5000 --|---|--------/ | true | AM | |_________| | |________| | | | ___________ | | | | | | office | a7800 --|--|-----------\ a7800 | |_________| | \ ___________ |__________________________________| \--> | 7 | hour |_________| | 14 | minutes |_________| | false | AM |_________| That is one of the big beneﬁts of using objects – since we can access objects from any method as long as that method has a reference to the object. rather than being forced to (for example) put all the variable assignment code we ever want to run.

office.| --|---------------> |______| _____________ | | | 7 | hour |___________| | | | | minutes |___________| | | | | AM |___________| That’s how things are supposed to work. we ﬁnd an hour variable there. we are using the “dot syntax” on the variable office to “follow the arrow” from office to the object it points to. and once we have “arrived” at the object that office points to. when we have the statement office.186 The NullPointerException Consider the following code: Clock office. But what if we leave out the allocation? Clock office..hour = 7.| --|------> |\| |______| then we allocate a Clock object for the reference variable to point to: ________ | | office .hour = 7.. like any other reference variable): ________ | | ___ office . We have discussed how this code works before. office. First we declared the reference variable (which is automatically initialized to null. and can assign it the value 7: ________ | | office . ..| --|---------------> |______| _____________ | | | | hour |___________| | | | | minutes |___________| | | | | AM |___________| and ﬁnally.hour = 7. office = new Clock().

you wrote null into the reference variable at some point. actually have names. you cannot use the bracket syntax on such a reference variable.hour = 7. the program crashes. it means that some reference variable you are using the “dot syntax” on. This is a common error in Java. given the value inside the reference variable. This is a problem – we have an assignment statement in our code that we cannot complete! So. But the individual cells of an array are accessed using integer indices – i. when you didn’t mean to do so. as well as the “dot syntax”.. the variable "scores" holds the value "null" int num. However. since the syntax “scores[0]” is basically doing the same thing that a line such as office. that you are worried about. So the line office.e. // right now. points to null instead of an object. and one you are likely to encounter frequently. Whenever you do encounter it. can’t do anything. it’s the “bracket syntax”. or it could mean you incorrectly assigned to the reference variable when you did assign to it. The last line will trigger a NullPointerException.187 In that case. the variable "scores" holds the value "null" you will again get a NullPointerException from the last line. we only declare the reference (which is initialized to null). again because you are using the “dot syntax” on a reference variable that holds the value null. Note that this can also occur with arrays – only in that case. you cannot ask for the length of an array that doesn’t exist: int[] scores. either. We have mentioned “exception error messages” before – we said that if you access an array with an illegal index. The diﬀerence between the two situations is basically one of naming. and the error messages printed when it crashes will tell you of some illegal condition that was encountered when the program ran. and will announce to you that you have a NullPointerException.hour would do – it is giving you access to a variable within the object the reference points to. there is no object there of any kind!!!. It might mean you haven’t assigned a value to the reference variable yet. and so you access them from their references using the “dot syntax”. i. since there’s no hour variable there to assign. you are trying to access an object at the null location. Hence.length. We “follow the arrow” to a location where there isn’t an hour variable. In any case. and notiﬁes you that you had an ArrayIndexOutOfBoundsException. So in the following code: int[] scores. // right now. scores[0] = 7. the NullPointerException is the end result.e. In this case. the “names” of those . It’s a similar situation here. we “follow the arrow” for the following picture: ________ | | ___ office . in that the program will crash. so when we then use the “dot syntax” on the office variable. you have triggered a NullPointerException. num = scores. the program will crash. The length variable of an array object and the hour variable of a Clock object. and thus using the “dot syntax” on that reference variable doesn’t make sense. First of all. and since there isn’t any object at the null location. you have a problem – you are asking for the impossible!.| --|------> |\| |______| and when we arrive at the spot that office points to.

holds the value null instead of the address of an actual object. And so in that case.5 = 14. . we use the “bracket syntax” instead of the “dot syntax”.0 = 7.5). and that looks a little strange. scores. if our “names” are actually integer indices. we’d get things like the last three lines of the following code: int[] scores. scores = new int[6]. scores[0] = 7. System. not actual names. are numbers.188 variables. to the object this reference points to” – and thus they can both trigger a NullPointerException if the reference you are using the “dot syntax” or “bracket syntax” on. scores[5] = 14. So that’s why we use the bracket syntax instead. scores.out.out. rather than real names like hour or length: int[] scores.println(scores[5]). System. The point here is that the “dot syntax” and the “bracket syntax” mean the same thing – “follow the arrow. since if we used the “dot syntax”.println(scores. scores = new int[6].

then you actually know what value will be printed – the value you had written into that cell a statement earlier. // varname = expr.out. you can allocate a Clock array object for the Clock array reference to point to. scores = new int[6]. There is a concern. however: scores[4] = 47. 47. and now you could also use a non-primitive type. you could declare a reference to a Clock array using the same syntax you declare a reference to an int array: // Type varname.. when you needed a type before. namely.) statement. and then just as you can allocate an integer array object for the integer array reference to point to.| | | |__|___| | | _______________________________ | | | | | | | | |--------->| | | | | | | |____|____|____|____|____|____| 0 1 2 3 4 5 Now consider the statement: System. What happens if we now run that statement? What will get printed? We really have no idea. however. System.out. you still need to initialize it.println(scores[4]).189 Arrays of non-primitive types Just as you could create arrays whose cells held values of primitive types. int[] scores. For example. . Clock[] times.println(scores[4]). Remember that when you create an array. you likewise can create arrays whose cells hold reference variables of a particular type. times = new Clock[6].. If we initialize scores[4] and then run the System. since we never initialized scores[4]..println(. you used a primitive type. we would have the following picture: ________ | | scores . the only diﬀerence is that.out. If we had the integer array declaration and allocation above. The syntax for doing this is the same as it was for primitive types.

all of which point to null: ________ ___ ___ ___ ___ ___ ___ | | |\| |\| |\| |\| |\| |\| times . is six array cells. (The compiler might even complain. When you create this array.println(x). not quite. After all. But just as with the integers above.. System. And with any other variable. the integer is a stand-alone local variable. Well. and in the second example (the four lines of code above).. the integer we want to print is part of an array. System. consider again the declaration of a Clock reference variable and the allocation of a Clock array object: Clock[] times. 47 gets printed The only diﬀerence between the ﬁrst example and the second one.) So what does all this have to do with objects and classes? Well. you will have not initialized those six array cells yet. In either case. however. is that in the ﬁrst example.| | | |__|___| | | _______________________________ | | | | | | | | |--------->| | | | | 47 | | |____|____|____|____|____|____| 0 1 2 3 4 5 In this respect. x = 47. when we discussed arrays. times = new Clock[6].| | | |__|___| /|\ /|\ /|\ /|\ /|\ /|\ | | | | | | | | ___|____|____|____|____|___|___ | | | | | | | | | | | | | | |--------->| | | | | | | |____|____|____|____|____|____| 0 1 2 3 4 5 . what we really have. or else you have no idea what value you’ll actually print: int x. in some cases. // what gets printed? who knows? // this time. if we read the variable before we ever initialize the variable.out. So. you are creating an array of six reference variables. array cells – which are eﬀectively variables – aren’t any diﬀerent than standalone variables. that the virtual machine automatically initializes all reference variables to null. So you have an array of six un-initialized reference variables. you’d have to initialize it before printing it. we don’t have any idea what value we’ll see there.out. We did say.println(x). we want to write a value into the variable before reading the variable.190 ________ | | scores . we said to treat an array cell just like any other variable.

. holding null. just because your array reference variable points to an array object. So. people get confused on this point. and a Clock reference variable that is one of the cells of an array. And so. the code immediately above will generate a NullPointerException – since we have not assigned the variable office to point to a new object. themselves point to objects. since the only diﬀerence between the two is in how the Clock reference variable was obtained. Sometimes. As we have already discussed. just as the line: office. So. office is a reference variable of type Clock. the line: times[4]. That is why drawing pictures is so useful.| --|------> |\| |______| and that with these two lines: Clock[] times. just as in the above example.hour = 7. in our array example. other than the syntax you use to obtain the “name” of the variable. imagine trying to initialize the hour variable of one of the clocks: times[4]. Indeed it has. It’s no diﬀerent than when you declare one Clock reference variable – that doesn’t mean the reference variable automatically points to a Clock object.191 So now. holding null. each of the cells indexed 0 through 5 is a reference variable of type Clock. gives you this picture: ________ | | ___ times . there is no real diﬀerence between a stand-alone integer variable and an integer variable inside an array. And as we pointed out with integers above. It’s no diﬀerent with reference variables – there’s no real diﬀerence between a stand-alone Clock reference variable. times = new Clock[5]. . but if the array is allocated. since they know the array has been allocated. likewise. that only means the ﬁve Clock reference variables have been created – it doesn’t mean any of them actually point to Clock objects. office. will generate a NullPointerException as well.hour = 7.hour = 7. it points to null and thus there is no hour variable to assign to. it does not mean all the references inside that array object. will generate a NullPointerException. other than the syntax you use to obtain the Clock reference variable. if you remember that this line: Clock[] times. This syntax is no diﬀerent than if we had done the following: Clock office.hour = 7.

Now that we are also allocating an object for times[4] to point to. times = new Clock[5].192 you get the following picture: ________ ___ ___ ___ ___ ___ ___ | | |\| |\| |\| |\| |\| |\| times . what you need to do if you want to write into an hour variable. times[4] = new Clock(). we get the following picture: _______ | | | | hour |_____| | | | | minutes |_____| | | | | AM |_____| |------------> | | ________ ___ ___ ___ ___ | ___ | | |\| |\| |\| |\| | |\| times . is to have the following three lines (the third one is the one we’ve added to the previous two examples): Clock[] times.| | | |__|___| /|\ /|\ /|\ /|\ /|\ /|\ | | | | | | | | ___|____|____|____|____|___|___ | | | | | | | | | | | | | | |--------->| | | | | | | |____|____|____|____|____|____| 0 1 2 3 4 5 then that might help you remember that the individual Clock reference variables also need to be assigned to point to objects. So.. just as the array reference variable times was assigned to point to an object..| | | | |__|___| /|\ /|\ /|\ /|\ | /|\ | | | | | | | | ___|____|____|____|____|___|___ | | | | | | | | | | | | | | |--------->| | | | | | | |____|____|____|____|____|____| 0 1 2 3 4 5 .

hour = 7. data. we get the following: . to an object. data.193 And so.scores[0] = 89..average = 80. data. if we add a fourth line to our code – the assignment to an hour instance variable that we wanted to perform earlier: Clock[] times.examNumber = 1. data. times = new Clock[5]. if you had a class which had instance variables that were not of a primitive type. and thus the assignment works just ﬁne: _______ | | | 7 | hour |_____| | | | | minutes |_____| | | | | AM |_____| |------------> | | ________ ___ ___ ___ ___ | ___ | | |\| |\| |\| |\| | |\| times . consider the following class: public class ExamScores { public int examNumber. public int[] scores.3.scores[2] = 95. rather than null – and thus there is indeed an hour variable there. when we follow the arrow from the Clock reference at times[4]. times[4] = new Clock(). public double average.| | | | |__|___| /|\ /|\ /|\ /|\ | /|\ | | | | | | | | ___|____|____|____|____|___|___ | | | | | | | | | | | | | | |--------->| | | | | | | |____|____|____|____|____|____| 0 1 2 3 4 5 Note that this same problem could happen in reverse. times[4].scores[1] = 57. there is indeed a Clock object at the end of that arrow. because the reference variable scores is null. For example. data = new ExamScores(). Each of the last three lines is a problem. data. } One easy mistake to make. is to write code such as the following: ExamScores data. When we create a new ExamScores object. this time.

scores = new int[3].examNumber = 1. data = new ExamScores().scores[0] = 89. The scores variable. when data.average = 80.. with the indicated line added in the middle): ExamScores data. if we then try and access individual cells of the array object data. Running the above code up through the allocation of the integer array (i. data. data. is initialized to null. // this is the line we have added to the earlier example data.scores doesn’t actually point to an array object in the ﬁrst place. as in the following code (which is the earlier code. data. And as we discussed earlier.3.scores points to. we need to allocate an object for that reference variable to point to as well.e. being a reference variable.194 ________ | | ___________ data . but also. data. are uninitialized. and so we do not know what values those variables hold.| --|------> | | |______| | ? | examNumber |_________| | | | ? | average |_________| | | | | | scores |_____|___| | \|/ ___ |\| The int variable examNumber and the double variable average. since scores is a reference variable.scores[1] = 57. ignoring the last three lines for now) would give us the following picture: .scores[2] = 95. we would get a NullPointerException. data. We need to not just allocate the ExamScores object.

when dealing with references – whether local reference variables. or parameter reference variables. .195 ________ | | ___________ data .| --|------> | | |______| | 1 | examNumber |_________| | | | 80. 1. or reference variables that are the instance variables of objects – keep in mind that you need to always initialize a reference to point to an object.3 | average |_________| | | | | | scores |_____|___| | | ______________________ | | | | | |---------> | 89 | 57 | 95 | |______|______|______| 0 1 2 So. before you can use the “dot syntax” or the “bracket syntax” on that reference. or reference variables stored in the cells of an array. and we can write the cells indexed 0.| --|------> | | |______| | 1 | examNumber |_________| | | | 80. and 2 at that object: ________ | | ___________ data ..scores does indeed point to an array object..3 | average |_________| | | | | | scores |_____|___| | | ______________________ | | | | | |---------> | | | | |______|______|______| 0 1 2 and thus we can run the last three lines as well. since now data.

out.AM == true) System. int theMinutes.AM = theAM. boolean theAM) { c.println("PM.println("AM. } // end of class } . } // end main public static void setTime(Clock c.minutes < 10) System.out. else // AM == false System. c. office = new Clock(). c.hour = theHour.196 Lecture 17 : Instance Methods During the last lecture. 14. System. we used the following code to make use of a class: public class ClockTest { public static void main(String[] args) { // declare reference variables Clock home.print("Time is " + c."). // set the times on the clocks setTime(home. 7.out. Clock office. int theHour.minutes + " ").out. setTime(office. } public static void printClock(Clock c) { // print variables for clock System. false). 2.").print("0"). if (c. 15.hour + ":"). printClock(office). // print the clocks printClock(home).out.minutes = theMinutes. if (c.print(c. true). // allocate objects and assign values to reference variables home = new Clock().

) and printClock(. That would lead to the following Clock.. to use Clock objects.. if we did that.) and printClock(..197 And this was the class we were using: public class Clock { public int hour.java ﬁle to a new directory.. why do we have setTime(..) and printClock(. we would copy Clock. working on our new project requires us to deal with both Clock. then when we copy the Clock.) in the ClockTest class? If we wanted to use the Clock. since the code we want is scattered across both those ﬁles.java into our directory as well.java ﬁle? After all. we still wouldn’t have setTime(.java and copy setTime(...) so that they could be pasted into some other ﬁle in our project directory... This way. you would only have to copy one ﬁle – Clock.) available to us. but also. in the Clock.java into the directory where the rest of our new project was.. public int minutes. If we had the instance variables and those two methods. we are copying not just the blueprint for Clock objects. First of all.java ﬁle: . so we’d never use those methods without also needing the Clock.java and ClockTest. and the methods that use those objects.. we are copying the methods for manipulating Clock objects. We’d have to either also copy ClockTest. Either way. But..java.. } We’re going to start changing that code a bit. the purpose of those two methods is to manipulate Clock objects.java. public boolean AM.java – which contains all the code that pertains to Clock objects – the instance variable declarations that form the blueprint for those objects.or else we’d have to open ClockTest. Keeping all the Clock-related code in one ﬁle.java ﬁle to provide the instance variables.java ﬁle. makes more sense than does scattering that code across two or three or four ﬁles.. Wouldn’t it be easier if those two methods were in the Clock.java ﬁle in the future. since those methods are not in Clock.

) can remain exactly the same.java ﬁle.hour + ":"). the only method you’d have left is main(. rather than just the expression readInt(). That would mean that in the ClockTest. if (c. c. you need to put that diﬀerent classname in front of the method call. boolean theAM) { c.print("Time is " + c.. with one exception – since now.printClock(home).minutes < 10) System. else // AM == false System. System.AM = theAM. except that now we have also moved the two non-main() methods from ClockTest. into Clock.println("PM..out.AM == true) System.. int theHour. the ClockTest. Thus. public boolean AM. to input an integer.out. public int minutes. c. those methods are in a diﬀerent class than ClockTest. followed by dot. } } The Clock class above is the same as the Clock class from the earlier example.out. int theMinutes. when the methods setTime and printClock are called.java ﬁle now looks like this: .out. in your own MPs.print("0").minutes = theMinutes. And the code within main(. if (c.java.readInt().. This is no diﬀerent than how.minutes + " ").println("AM. public static void setTime(Clock c.java.out. rather than a method call such as: printClock(home). as well. That is.print(c.). you now need to have: Clock. } public static void printClock(Clock c) { // print variables for clock System.").hour = theHour."). you need to use the expression Keyboard.198 public class Clock { public int hour.

.) and printClock(.. “Oh.. consider for a moment the Clock class. without us having to list it explicitly – since we wouldn’t put a method in the Clock. is a little bit annoying.. // set the times on the clocks Clock.).setTime(home.). both of which have a Clock reference as a parameter.java ﬁle.) and printClock(. all of them designed to manipulate Clock objects in some way. minutes. We have two methods.. What if we had many more methods in Clock.. since there’s no way a method could manipulate a Clock object.. we might have a method incrementOneMinute(. 2. for every single method in that ﬁle. true).printClock(office).. you could argue that the need for a Clock reference as a parameter in each and every one of those 100 methods. imagine you had 100 diﬀerent methods in the Clock. So. all of which would be designed to write or read the instance variables of a Clock object. all of which manipulated Clock objects? For example..) do – and thus all of them having a Clock reference as a parameter. and has no need to directly read or write the hour. as it is currently written..) that would convert the time from “standard time” to “daylight savings time”. or AM variables of the Clock objects. You can imagine many other methods you might write.. Clock. and this method needs a Clock reference too!”.. just like setTime(. so that I don’t have to list that parameter in each method!”? . // print the clocks Clock. We might have a method changeToDST(. 14.) and printClock(. each of these methods would need a Clock reference as a parameter.java ﬁle in the ﬁrst place unless we also felt it needed a Clock reference as a parameter. Can we do this? Is there some facility in the Java language for saying “Assume every method in this class has a Clock parameter.) and printClock(.. setTime(. And thus. 7.. Clock office... office = new Clock(). That’s why they are in the Clock. It would be nice if every method in the Clock. // allocate objects and assign reference variables home = new Clock(). of course each of these 100 methods needs access to a Clock reference.java ﬁle in the ﬁrst place – because they manipulate Clock objects! It seems like it’s a bit redundant to then have to say. In such a case. Clock.java. Now.) do. unless it had access to a Clock reference that pointed to that object.199 public class ClockTest { public static void main(String[] args) { // declare reference variables Clock home. in addition to setTime(... } // end main // end of class } Note that the main() method simply uses existing methods to manipulate the Clock objects. 15.) which would move a Clock object’s time forward one minute. just like setTime(.printClock(home).. After all. false).setTime(office..java ﬁle automatically had a Clock reference.

else // AM == false System. However.java ﬁles in this packet. int theHour. That will be the last Clock. we will show you the “code so far”. The second change we want to make.out. we’ll be making four changes to the last code example. The word static is basically a signal to the compiler – when the word is there. and which cannot be used as the name of any other kind of variable – this can only be the name of a parameter we are trying to have the compiler assume “automatically exists”. is to remove the word static from the ﬁrst line of each of the two methods. It will only be once we’ve made all four changes. public int minutes. this.out.print(this. . and all four changes have to be made.hour + ":")."). as well): public class Clock { public int hour. but the intermediate steps will not. that the resultant code will compile. the variable name this is a reserved word in Java.java and Clock. That is. will be to change the parameter name in the two methods in Clock.println("AM. when we’ve made only one of the changes.out. to produce our new code example. The ﬁrst of our four changes.) The syntax for accomplishing this is what we will look at next. but what we’re allowed to do will be good enough.println("PM.print("0").200 The answer is.java from c to this (and thus we will change all the code that uses that parameter name.minutes = theMinutes.out.print("Time is " + this. that is speciﬁcally used for the sorts of situations we are talking about now. if (this. if (this. but those examples won’t compile.out. System.java and ClockTest.hour = theHour.minutes < 10) System.minutes + " "). That’s not quite what we’re allowed to do. or two.java ﬁles in this packet will compile. there is! (Well. yes. public boolean AM."). and the ﬁnal pair of ClockTest. } } The new parameter name speciﬁcally has to be this. or three of them. public static void setTime(Clock this. int theMinutes. this.AM == true) System. So. almost. } public static void printClock(Clock this) { // print variables for clock System. before we begin. as we are in the process of discussing right now.AM = theAM. boolean theAM) { this. a word of warning – the earlier code examples in this packet compiled.

In fact. fundamentally. public boolean AM. this. once we have removed static from the ﬁrst line of each of the two methods: public class Clock { public int hour. we are required to not list it. boolean theAM) { this.201 one signal is sent to the compiler.minutes < 10) System. such methods are called instance methods. else // AM == false System. static doesn’t have any inherent meaning itself. every method we’ve written has had static on it’s ﬁrst line. However. for now.println("AM. passing it some values. Now.print(this. int theHour.minutes = theMinutes. One of the advantages an instance method has over a class method. and perhaps getting a value back when the method has completed.AM = theAM. we are writing methods that do NOT have static on the ﬁrst line.").print("Time is " + this. public int minutes. not only are we allowed to not list it. So. but in fact. We’ll elaborate on that more in the next packet.AM == true) System. Up to now. removing the static from the ﬁrst . it’s the presence or lack of the word static that is important. and when the word is not there. the use of that slightly diﬀerent syntax will result in us thinking about instance methods in a slightly diﬀerent way than we think about class methods. System. } public void printClock(Clock this) { // print variables for clock System. i.out. just think of an “instance method” as a slightly diﬀerent syntax for doing the same thing as a “class method”.hour + ":"). if (this.println("PM. } } So. int theMinutes.e.out. such methods are called class methods. if (this. a diﬀerent signal is sent to the compiler.minutes + " "). is that we no longer speciﬁcally need to list the Clock parameter in our method signatures. calling a method. we are simply making use of procedural abstraction. instance methods make use of slightly diﬀerent syntax to accomplish the same things that class methods accomplish. Here is the code so far."). this. and that slight change in our thought process is what we are most concerned about. what is the diﬀerence between an instance method and a class method? Well.out.print("0").out.out.hour = theHour. they are the exact same thing – in both cases. public void setTime(Clock this. for the ﬁrst time. The diﬀerences between the two are syntax-related.

} } .minutes < 10) System.hour + ":"). int theMinutes. means that the compiler will declare a Clock this parameter for us.out. and so we should not speciﬁcally do so ourselves. because // setTime(.AM == true) System.AM = theAM.print("Time is " + this. if (this.minutes = theMinutes.202 line of the method.println("PM.out.print(this. this.. if (this.hour = theHour.out. Removing that parameter from the parameter list of both methods.println("AM. boolean theAM) { this. this.minutes + " ").. } // There is a "Clock this" parameter here automatically..."). because // printClock(.print("0"). public int minutes. System. // There is a "Clock this" parameter here automatically.out.) is an instance method public void printClock() { // print variables for clock System. is our third change.) is an instance method public void setTime(int theHour.out."). public boolean AM. else // AM == false System. and it leads to the following code: public class Clock { public int hour.

java ﬁle is correct. we move to this syntax: home. to the following: Clock. but since with instance methods. With class methods. the syntax for calling an instance method. but also. But home not only points to the object that we want this to point to.AM = theAM. true).setTime(2. where we actually call the instance methods.) might be automatically given. The diﬀerence in syntax. in that order – and those three arguments match our parameter list from the new version of setTime(. we need to know if this should point to the same object as home points to. In fact..setTime(home. 15.setTime(2. with the above change. and so we do not need to explicitly list the typename “Clock” after home in the method call. happens in our ClockTest. since the argument list always needs to match with the parameter list.203 Now. boolean theAM) { this. we still have a problem.) in our Clock. 2.. Our this parameter in setTime(. we made this clear by passing home or office as an argument in the argument list. We do not need both home and Clock in front of the method call.hour = theHour. The fourth change we need to make. and place a dot after it. int theMinutes. that tells the compiler to look in the Clock class for this method. true). 15.minutes = theMinutes. we don’t even have the option of leaving it there if we want – since the extra use of the typename “Clock” is redundant. we have to remove it.. since home is of type Clock. as usual. the this parameter is not in the regular parameter list. because // setTime(.java ﬁle: // There is a "Clock this" parameter here automatically..java ﬁle. } However. Now. is slightly diﬀerent from the syntax for calling a class method.. this. true).Clock.java. our Clock. Without that parameter. The argument home – whose value we want to copy into the automatic parameter this – gets placed in the front of the method call. That is. We’ve already explained that home has to go there. That suggests we should change our method call to setTime(. we also don’t want to list its argument in the regular argument list. 15. but we still need an “argument” for it. or if instead it should point to the same object as office points to. we move that argument to the front of the method call. Speciﬁcally.. is related to our now-removed parameter in the methods in Clock. And the other arguments – whose values get copied into the parameters we actually listed in the method’s parameter list – get placed in the actual argument list within the parenthesis. we should not be sending in an argument.. but in addition. this. Both methods in that ﬁle are now correctly-written instance methods. Not only is the instance method syntax slightly diﬀerent from the class method syntax. we have three arguments – two int values and one boolean value. So where do we put the argument home or office? The answer is. This leaves us with the ﬁnal version of this method call: .) is an instance method public void setTime(int theHour.. There is one more change left to make.) (for example) from the following (which is what we have now): Clock.

Clock office. 7. false). Clock.printClock(). true).printClock(home).setTime(office. 15. office. 14. Clock. // allocate objects and assign reference variables home = new Clock().204 home. } // end main // end of class } which compiles together with the ﬁnal version of Clock. ----> office. ----> office. false). // set the times on the clocks home.java: public class ClockTest { public static void main(String[] args) { // declare reference variables Clock home.printClock(). And then the other three method calls in main() are likewise changed in this manner: Clock. true). 14. office = new Clock(). false). office.setTime(7.printClock(office).setTime(2. And that change – which is our fourth and ﬁnal change – gives us the following version of ClockTest.printClock().printClock(). 14.setTime(2.java we had earlier: . 15. ----> home. // print the clocks home.setTime(7.

. if (this. What we are doing here is invoking printClock oﬀ a Clock reference.out. because // printClock(..hour + ":").println("PM. else // AM == false System.out.. because // setTime(. consider this code snippet from our newest version of ClockTest. to using instance methods.out.205 public class Clock { public int hour.print("Time is " + this. // There is a "Clock this" parameter here automatically.printClock().").AM == true) System. our memory looks something like this: .print("0"). the instance method version just has slightly diﬀerent syntax.minutes + " ").print(this.) is an instance method public void setTime(int theHour.hour = theHour. we have converted our example over from using class methods. boolean theAM) { this.AM = theAM.println("AM. System.").) is an instance method public void printClock() { // print variables for clock System.minutes = theMinutes. this.java: home. public boolean AM. } } Now that those four changes have been made. this.minutes < 10) System. The two examples work basically the same way. public int minutes. Before we actually enter the printClock method. } // There is a "Clock this" parameter here automatically.out. For example. if (this..out. int theMinutes.

in these examples. . our memory looks something like this: _____________________________ | | | main | _______ | | | | | home ----------|--------> |Clock | | | |object| | | |______| | | ^ | ______________________|_______ | | | | | | | printClock this -----|-----| |_____| | | | | | |____________________________| That is. this points to the same object that home points to. matches the class the instance method is in). instead of having our argument in front of the method call and our parameter automatically created by the compiler. If the instance methods were in the String class. and that machine address is copied into this. for example. The reference variable home holds the machine address where the Clock object is located (which is why we say home “refers” to the object). and (2) we actually sent home as an argument in that case. except that (1) we called the parameter in printClock(. this is of type Clock only because the instance method is in the Clock class.) “c” instead of “this”.. The type of an instance method’s this reference matches the type the instance method is a part of (i. so that this now also holds the machine address where the Clock object is located (which is why we say that this now “refers” to the Clock object too). The class method version of this program worked the same way..e. the this in each instance method would be of type String. to an actual parameter c. Of course.206 _____________________________ | | | main | | | | home --------|--------> | | | | | | | | |___________________________| ________ | | |Clock | |object| |______| Once the method call actually begins.

out. After all. and AM if we want.) The one exception to this would be if you have a parameter or local variable with the same name as your instance variables: public void printClockStrangeExample(int hour) { int minutes = 10.out. if the compiler will put it in for you anyway.out.207 Taking advange of the this default use One nice thing about the use of this is that it is often assumed by default. in CS125. if (this. else // AM == false System. the compiler assumes that we meant to put it there anyway. } In the code above. (Now.print(minutes + " "). instance methods are usually written as you see directly above. oﬀ. However. System.out.out. our printClock() instance method could also have been written as follows: public void printClock() { System.print("Time is " + hour + ":"). System.").out. when hour is printed to the screen. For example. } We can put the this. Likewise. if (AM == true) System. where it is needed.out.println("PM. why bother typing it in yourself? That is another way that using instance methods makes our life as programmers a little bit easier. we will always write this. in front of the instance variables of the class.print("0").println("PM. System.out. but we don’t have to – if we leave the this. it will be the local variable minutes which we have assigned the value 10. else // this."). if (minutes <= 9) System. you are not required to do so yourself. if (AM == true) System.").minutes <= 9) System.out.println("AM.print("Time is " + hour + ":"). minutes.println("AM. For that reason. when minutes is printed to the screen. without the explicit use of this. just to remind you what is going on.").AM == false System.out. the order it checks things in is as follows: . and so it will put it in for us. in front of hour.print("0"). not the instance variable hour. When the compiler sees a variable in a method. it will be the parameter hour.print(minutes + " ").

} Note that having local variables with the same names as your parameters. The version of printClockStrangeExample below shows the above code. And since this issue doesn’t come up often. since there’s no confusion there. there is one additional possibility – that the variable is an instance variable of the class the method is in.out. But we need the this. if (this.out. 2. If the method is an instance method. there are no other options and the compiler will alert you to an error. or having local variables or parameter variables with the same names as your instance variables. changed so that the hour and minutes that are printed to the screen are the instance variables and not the parameter and local variable. and that we are trying to access that instance variable but just didn’t bother to put a this. else // this. rather than assuming the use of minutes is the local variable (choice 1 in the list above) or assuming that the use of hour is the parameter variable (choice 2 in the list above). Note that we still can avoid putting a this. it sees if this was a local variable declared in this method. is NOT a good idea. . public void printClockStrangeExample(int hour) { int minutes = 10. in front of hour and minutes now to make it clear that we want the instance variable version.println("AM.208 1.").minutes + " "). in front of AM. oﬀ and the compiler will assume you meant to put it in.hour + ":"). if (AM == true) System.").AM == false System. and everything will be ﬁne. then. if the method is a class method. due to the fact that it can cause exactly the confusion we have described above.out.print(this.minutes <= 9) System.print("0"). however. System. in front of the variable name. it sees if it was a parameter variable for the method.println("PM.out.out. System. 3. First.print("Time is " + this. If it is not either of the above two. as a general rule you can just leave the this. So this issue tends to not really come up since usually you give your parameter variables and your local variables diﬀerent names than you give to your instance variables. If not.

for objects of type Clock) is always four cells from the beginning of the object. If your object instead begins at a10084. So the compiler. and that is the only information we are missing! .209 Instance variables in memory The machine is able to access the instance variables of an object precisely because it knows the starting address of the object. instead begins at a10088. when compiling the code for the method. we know where the overall object begins. When you have a line of code such as: home. as we have discussed. deﬁnes all the accessing of an object in terms of the starting address of the object. the starting address of the object is what is located in the reference variable. then your instance variable (such as minutes) is at a64. you simply move four more cells downward and there is the particular instance variable you are looking for. If the object begins at a60. when once you know the starting address of the object. If some instance variable within the object (such as minutes. So we need the reference variables – they tell us the starting address of the object. So. then your instance variable that is four cells from the start.printClock().

(In a sense. that can be accessed from anywhere in the program. you choose an instance.) . without you needing to write code to do so) – instance variable – a variable declared using a variable declaration statement within a class but not within any method. and then the method call manipulated that object (i. and AM are instance variables of the class Clock. independent version of an instance variable.. and without the keyword static on the declaration line.) – class variable – a variable declared using a variable declaration statement within a class but not within any method. Every object is an instance of a particular class.e.e. • The four kinds of variables: – local variable – a variable declared using a variable declaration statement within the curly braces of a method – parameter variable – a variable “declared” in the parameter list of a method (the this variable of an instance method gets included in this category. when you called the method printClock(. Keyboard. the indexed array cells of an array are also instance variables.210 Lecture 18 : static versus non-static. its this reference pointed to that object). but rather is just a helpful method that we’ve put in this class because it’s related to this class. In the last three notes packets. through the class name itself – rather than one copy per instance. • The two kinds of methods: – instance method – does NOT have static on its ﬁrst line. the method call is a syntax error unless you have a reference and dot in front of it) – class method – has static on its ﬁrst line. run the method on an instance and it operates on the variables of that instance and no other. this method is tied to the instance – i. until that method call has completed. by listing that reference before the dot in front of the method call. such a variable is tied to an instance – i. and Constructors Some more terminology • instance – an object.e. (We’ll talk more about class variables in a moment. and with the keyword static on the declaration line. we don’t usually call them “instance variables”. and two diﬀerent instances will each have their own. you indicated what reference’s object it should manipulate. rather than sharing the same variable. If you do not specify which instance the method should manipulate.. there is one copy of this variable. this method is not tied to any particular instance.e. or “instantiate an instance of that class”. • instantiation – the allocation of an object. that gets accessed through a reference to that instance. printClock() from the last lecture is an instance method of the class Clock. though since they are “named” with indices instead of names. minutes. hour.readInt() is a class method.). each instance of that class has its own version of the instance variable. We have a class. and we “allocate an object of that class”. then you cannot call the method (i. though the compiler declares it automatically.

You won’t have to choose which is the better way in this course.minutes = 53. but you do want to understand the syntax for writing and using both kinds of methods. So. public static double cos(double x) {. If you have a class variable or class method. c1. rather than thinking of an object as something you send to a method. For example: Clock c1 = new Clock().e.e.cos(myVal). They are just two diﬀerent sets of syntax for accomplishing the same thing – it’s just that the syntax for instance methods lets you think of a method as something that runs on an object. you access it by using the class name. followed by a dot. You can have classes with a mix of class variables and methods. why might you use one over the other? Well. basically..211 So.AM = true.} and you’d use those things as follows: double myVal = Math. in the Math class you might see the following: public static double PI = 3. just via diﬀerent syntax. c1. though. and instance variables and methods. there’s no real diﬀerence in the machine between the two. followed by the variable or method. So the use of instance methods can change the way you view your program – you tend to think of your data ﬁrst. we saw last time that instance methods and class methods basically did the same thing. double cosOfMyVal = Math.hour = 9. That’s a subtle diﬀerence. so for now. That is to say.printClock(). we will make something an instance method if we can. . a reference to that object). followed by the variable or method.e. Generally..1415926. rather than thinking about your subroutines ﬁrst. For example.PI. but we won’t do that too often in this course – our classes will tend to have only “class stuﬀ” (i. c1. If you have an instance variable or instance method. c1. followed by a dot. and other times instance methods are more convenient. “non-static stuﬀ”). instance methods place the “focus’ on your data instead of on the method. Another way to look at the instance method syntax is that it makes calling instance methods consistent with using instance variables. we’ll just say that sometimes class methods are more convenient. you access it by using a reference to that instance (i. “static stuﬀ”) or only “instance stuﬀ” (i.

hour = theHour.minutes + " "). int theMinutes.212 Constructors Constructors are instance methods used to initialize an object when it is ﬁrst created. part public void printClock() { System. this. else // AM == false System. } // but you don’t *need* the this.print(this. but this way the class will still ﬁt on one slide) and add a constructor which does the same thing as SetTime did). (what if you don’t write one? More on that later.println("AM. System. this.print("0").out.").").out..println("PM.) Let’s look at the Clock class again.out. } // end of class } .out.minutes = theMinutes. public Clock(int theHour. public int minutes.hour + ":").. some constructor is always called. if (this. public class Clock { public int hour.but this time let’s leave out the instance method SetTime (we could have kept it as well.print("Time is " + this.AM = theAM.AM == true) System.minutes < 10) System. public boolean AM. if (this. • Same name as class • No return type • When object is created.out. boolean theAM) { this.

// initialize "home" reference home = new Clock(2. office. // create "office" object. 15. 15. // create "home" object. } } The expression: new Clock(2. true) ---------------------| address <------| (3) address of object in memory is returned and then of course that returned address would be stored in a reference if the expression new Clock(2. // initialize "office" reference office = new Clock(7. 15. true) -----------------(2) object is allocated object is initialized using constructor new Clock(2. Clock office. is a three-step process: new Clock(2. 15. false). 15. 15. true) were used in a line like this: Clock c1 = new Clock(2. true) --------(1) new Clock(2. true). any object allocation (except for an array). .printClock().printClock(). 14. // call instance method to print // instance variables home.213 public class ClockTest { public static void main(String[] args) { // declare reference variables Clock home. true). true) or. 15. in fact.

println(. If the name is the same. // a constructor public Clock() { this. The method with one integer parameter. public int minutes. The method with no parameters. since we are overloading the method name with multiple deﬁnitions. this.214 Method Overloading You can have multiple constructors in one class.minutes = c.minutes = 0. this.. matches the case where you have no arguments in your System. the compiler can only ﬁgure out which method we want by comparing parameter lists.out. matches the case where you have one integer argument for your method call. When you call the System.hour. And so on.out. so the parameter lists must then be diﬀerent. Having two methods with the same name (but diﬀerent parameter lists) is known as method overloading.minutes.AM = theAM. the same idea exists there – there are many diﬀerent versions of that method. } // a third constructor public Clock(Clock c) { this. this. } .AM = true.AM. int theMinutes.hour = 12. this. } // another constructor public Clock(int theHour.hour = theHour.minutes = theMinutes. This can be done for constructors or for any other methods.hour = c. boolean theAM) { this. all with diﬀerent parameter lists. public class Clock { public int hour. this. this..) methods. public boolean AM.AM = c.println() call.

office.println("PM.hour = theHour.printClock().minutes + " "). car = new Clock(home). // call instance method to print // instance variables home. false). if (this.out.out.out. // change time on "home" clock home. initialize references home = new Clock(2. 13.AM = theAM. // Time is 2:15 AM.out. Clock car. else // AM == false System. if (this.print("Time is " + this. // Time is 8:13 PM.printClock().println("AM. } // end of class } public class ClockTest { public static void main(String[] args) { // declare reference variables Clock home.").215 // a non-constructor instance method public void printClock() { System.AM == true) System. } } . int theMinutes. System. Clock office. office = new Clock().print("0"). 15. this.setTime(8. car.printClock().minutes = theMinutes. // create objects. boolean theAM) { this. this.minutes < 10) System.out. true). // Time is 12:00 AM.").print(this.hour + ":"). } // another non-constructor instance method public void setTime(int theHour.

You can’t write a 2-argument constructor and assume the no-argument one will be added by default. then. since there has to be some constructor called everytime a (non-array) object is allocated. . you have to write the no-argument constructor. if you want two constructors and one of them is the no-argument constructor. This provided constructor would be empty: public Clock() { // nothing of value here } but at least there is some constructor to call. So. This is because the system provides a default constructor if we don’t write one. That is why the code in lecture packets 15 through 17 would compile – the compiler would have at least given us an empty. we still had lines like home = new Clock(). If there are any constructors given.216 Recall that before we introduced constructors. You only get the no-argument constructor by default if you have no constructors at all in your source code. useless no-argument constructor so that some constructor exists to call. no default is provided.

else // AM == false System. int theMinutes.").out.print("0"). this. public Clock() { this.println("PM.minutes + " "). } public Clock(int theHour. this.println("AM.AM == true) System. boolean theAM) { this. } } . System.minutes < 10) System. let’s hide them away! public class Clock { private int minutes.out.hour + ":"). private int hour.out."). private boolean AM.AM = true. if (this.print("Time is " + this.AM = theAM.out.minutes = theMinutes. this.hour = 12. } public void printClock() { System.minutes = 0.217 Lecture 19 : Access Permissions and Encapsulation Access Permissions Since we don’t actually need to access the three variables.hour = theHour. this.print(this.out. if (this.

// print variables for clock at office office. then that implies package access. office = new Clock(). an example.AM = true. } } . office. public class ClockTest { public static void main(String[] args) { // declare reference variables Clock home. The code which makes the interface work – the instance variables. // these two lines will cause compiler // errors because variables are private home. true). why would we want to do this? More on that in a minute. and any private instance methods you have written – is called the implementation. 15.printClock(). the implementation is hidden from the client! This is called data hiding or encapsulation. • The question is. the deﬁnitions for the public instance method signatures.minutes = 34. you have no access permission listed at all. • private means accessible only to this class and its instance methods and instance variables • (If instead of having public or private. // point references to those clocks home = new Clock(2. Clock office. We will not be using that in this course – we will always have some speciﬁc access permission keyword instead of leaving one out entirely. // print variables for clock at home home. We force the client to use only the public methods – the interface – to access an object. • By keeping the instance variables private. // allocate and initialize new clocks.printClock(). First.218 • public means accessible to client code – the code which declares references to this type and allocates objects of this type.) • The collection of method signatures for the public instance methods (along with documentation telling the client what those methods do) is called the interface of a class.

and make it use the new collection of instance variables instead. just like any other Clock object.” That’s the idea of making something private. and it knows where AM is within that object. will cause compiler errors. Too much ﬂexibility can be dangerous – why oﬀer it if you don’t want the client to have it? 2. because it knows the deﬁnition of Clock. for no other reason than that we want the compiler to complain about it”. A new programmer doing code maintenance has to understand a lot of code written using various speciﬁc instance variables. Go away. Why put ourselves in the position of having to do so much work? 3. x does exist. All that changes is that now. However. and we thus have no idea where its variables are. we are not saying that home. it is a variable. and change the collection of instance variables we use to implement the class. then we have to ﬁnd and change all code that used the old collection of instance variables. Imagine if we had said: int x. “Sure. Why put the maintenance programmer in that position? . If we use instance variables directly. and then every time we wanted to use x. The only way for us to get to those variables is by using Java syntax such as the line above. those variables are somewhere in memory. When we say that the line: home. When we make our AM instance variable private. “have the compiler complain about this. So there is no problem with using the line above.AM = true. Why do this? Why hide the implementation from the client so that the client cannot use it directly? 1. and that Clock object does indeed have two integer variables and a boolean variable. the compiler now complains instead when we use the Clock variables. because of home. in theory. and it stores a value.219 What you are restricting is access to the name – the name of a variable or name of a method.AM does not exist. The code we run knows where the object is. We have no idea where the object is. we are saying. instead of the compiler letting us do whatever we want to the Clock object variables. the compiler gave us an error message and said. Nothing changes in memory as a result of using private instead of public. Quite the contrary – home does indeed point us to a Clock object. but I don’t want you using x.

These are given intuitive names to make code easier to understand. This was the same idea that we saw with procedural abstraction – in that case. and then writing classes to represent those types and creating objects of those types to hold our data. Why put ourselves in the position of having to do so much work? Client code uses only the interface. and make it use the new collection of instance variables instead. In our Clock example. and focused on the big picture of what particular task those details ultimately accomplished.) 3.220 How does hiding implementation solve these problems? 1. it is hard to reuse code in later programs. then we can change the implementation as much as we want but client code doesn’t break unless we change the interface (because client code doesn’t use the implementation directly). in a new program you can simply create new objects of that class – you’ve already written the class and have it sitting in a ﬁle. but cannot access the instance variables directly. A new programmer doing code maintenance has to understand a lot of code written using various speciﬁc instance variables. we hid the details of how a task was performed. and focusing only on the big picture of what the data is meant to represent conceptually. Thus client code focuses on setting big ideas into motion. . • In addition. • The four advantages suggest using classes is a very good idea. The interface must be well-designed and thought out before we oﬀer the class to the world. those are four arguments for using classes instead of declaring all data in main and declaring all methods in the class which holds main. easily used in any new program without having to copy-and-paste or rewrite any code. and focused on the big picture of how we would want to use a Clock (rather than how we would want to use integers and booleans). • The technique of using encapsulated classes is known as object-based programming. But if you write a class instead. then we have to ﬁnd and change all code that used the old collection of instance variables. 2. and change the collection of instance variables we use to implement the class. throw in one more useful idea of classes in general. since the focus is on deciding what types we need. What we have accomplished is data abstraction – hiding the details of a data implementation away behind the scenes. (See the end of the packet for a re-implementation of the Clock class. If all your data is declared in main. Too much ﬂexibility can be dangerous – why oﬀer it if you don’t want the client to have it? If the client can only invoke the instance methods you provide with the class. If we use instance variables directly. and the details are hidden in the implementation and the maintenance programmer doesn’t necessarily need to know them. But once we do. Why put the maintenance programmer in that position? The maintenance programmer can read code which uses only the method calls which are part of the interface. • So. we have hidden away the details of how a Clock is implemented. then the client can only read and alter the instance variables in the ways that you decide “make sense” ahead of time.

public Clock() { this.hour = 12.minutes = theMinutes.hour = 0. this. } else // theAM == false { if (theHour == 12) this.221 public class Clock { private int hour. } . } this.hour = theHour.hour = theHour + 12. private int minutes. boolean theAM) { if (theAM == true) { if (theHour == 12) this.hour = 0. } public Clock(int theHour.minutes = 0. int theMinutes. else this. else this.

} } . System.out.out.out.out.println("PM. else if ((this.minutes + " ").hour). else // ((this.print("0").222 public void printClock() { // print variables for clock System.print(this.hour == 0) System. if (this.print("Time is ").minutes < 10) System.out.out.hour <= 12)) System.out.hour <= 23)) System.").hour >= 12 System. if (this.out.out.print(this. System.print(this.hour < 12) System. else // this.print(12).hour .print(":").println("AM.").12). if (this.hour >= 1) && (this.hour >= 13) && (this.

int theMinutes.hour = theHour.minutes. this.AM = theAM. this.223 Lecture 20 : Copying and Mutability Copying an object in a constructor Recall our code from the constructor lecture notes: public class Clock { private int hour.minutes = theMinutes. private boolean AM.AM = c.hour.hour = 12. } .minutes = 0. this. this.hour = c. boolean theAM) { this. } // a third constructor public Clock(Clock c) { this. private int minutes.minutes = c.AM = true. this. this. } // another constructor public Clock(int theHour.AM. // a constructor public Clock() { this.

print("0"). } // end of class } public class ClockTest { public static void main(String[] args) { // declare reference variables Clock home.out.print(this.AM = theAM. office. true).minutes + " "). car = new Clock(home). car.printClock(). // change time on "home" clock home.out.").minutes = theMinutes.out. if (this.printClock(). if (this. initialize references home = new Clock(2.224 // a non-constructor instance method public void printClock() { System. // Time is 2:15 AM. this. this. 13.hour + ":"). // call instance method to print // instance variables home.print("Time is " + this. boolean theAM) { this. // Time is 8:13 PM.out.").out.AM == true) System. false). // Time is 12:00 AM.setTime(8. // create objects.printClock(). else // AM == false System. } } .println("AM.println("PM.hour = theHour. System. Clock office.minutes < 10) System. } // another non-constructor instance method public void setTime(int theHour. int theMinutes. 15. Clock car. office = new Clock().

we would have assigned car as follows: car = home.hour = c. the constructor argument.AM = c.hour. that after the assignment of car was completed.minutes. consider our ExamScores class from Lecture Notes #16 (though we’ve changed the access permission on the instance variables here): public class ExamScores { private int examNumber. points to. private double average.AM. You did not have two references pointing to the same object. if we had wanted that. you need to be a bit more careful when reference variables come into play. For example. which called the following constructor: // a third constructor public Clock(Clock c) { this. which hold the same values. You have two diﬀerent objects. was a copy of the object that home pointed to. is the same object that car points to). this. private int[] scores.225 We pointed out at the time. } If you write out a constructor to initialize a copy. this. If you are making a copy of an object like this. to hold the same values as the object that c points to (which is the same object that home.minutes = c. we used the following line: car = new Clock(home). the object that car pointed to. like this: . Instead. } and thus assigned the object that this pointed to (which in the end.

First of all. then they point to the same object! This can lead to trouble if it is possible to alter the array at all as a client. it’s ﬁne for the constructor to not only read and write the variables in the object that this points to. } } Then we have a small problem. similarly. (We also want to make it clear that we would need other methods in this class. references lead to objects!! – if two reference variables hold the same address. but also. Even if we only show the one or two methods we are talking about.scores = origVal. they can still be used directly by name anywhere in the ExamScores class. the problem with the above constructor is that we are copying the address in origVal. Instance methods of ExamScores can read and write any ExamScores object they have a reference to.examNumber = origVal. But. It is when we are outside of the ExamScores class that the access permissions are relevant.scores.scores. to read and write the variables in the object that origVal points to. For example: . So. if we wanted it to be a useful class.examNumber this.average. we would want others as well. this. into this. Both examNumber variables hold their own copy of the same integer. such as with the other two assignments. private int[] scores. private double average. public ExamScores(ExamScores origVal) { this.) That said.scores. we want to make it clear that access permissions are not the problem here – even though the instance variables are private.226 public class ExamScores { private int examNumber.average = origVal. both scores variables hold their own copy of the same address. and both average variables hold their own copy of the same ﬂoating-point value. Copying the value from one variable to another is ﬁne when you are copying a primitive type. But unlike with primitive-type variables.

the other instance gets initialized as well – writing to the array cells of one ExamScores object. but we made the copy in a way that allowed both objects to share a piece of dynamic memory. not just its location: . If we want a truly independent copy – a hard copy – then we need to copy the actual array object. They are still tied together.length.average = origVal. } } If you make a copy using the given constructor. then both ExamScores objects point to the same array. means writing to the array cells of the other ExamScores object as well. private int[] scores. since the two ExamScores objects point to the same array!! This is known as a soft copy – we wanted to make a copy of an ExamScores object.average.scores. if we change that dynamic memory for one object. it’s changed for the other as well – meaning the objects are not actually independent copies of each other. since we can’t change one without changing the other.examNumber this.scores[i] = 0. i < this.) on one of the instances. this.scores. private double average. As a result.. } public void initialize() { for (int i = 0..227 public class ExamScores { private int examNumber. public ExamScores(ExamScores origVal) { this. Now. i++) this. if you call initialize(.examNumber = origVal.scores = origVal.

it would seem that the constructor for making a copy should be written like this: . i < this.examNumber this.scores. } } Now the two ExamScores objects are completely independent. rather than a number: public class ExamScores { private String examName. this.scores[i].average = origVal. those arrays hold the same primitive-type values. public ExamScores(ExamScores origVal) { this. For example.scores[i] = origVal.length].scores. Note that you can sometimes get away with a “partial” soft copy.examNumber = origVal. what if we have a name for our exam. The array references even point to diﬀerent arrays – though for now. } public void initialize() { for (int i = 0. i++) this. and it will have no eﬀect on the other object. private double average. private int[] scores.228 public class ExamScores { private int examNumber. That is what we prefer.scores.length.scores[i] = 0. for (int i = 0. i++) this.average. private double average. } In such a case. you can change one object in any way you want. So. private int[] scores.length.scores = new int[origVal. i < this.

examName = origVal. point to a diﬀerent String object. for (int i = 0. Actually.scores.scores = new int[origVal. you’d never have a situation where it was changed through one object and then as a result was changed for the other object as well. we can also do this for that method: public ExamScores(ExamScores origVal) { this.length]. objects that are mutable – objects that can be changed – would need to be properly copied. i < this. the other ExamScores object’s scores variable could still point to the ﬁrst String.average = origVal. this.scores.average. i++) this.scores. though.average.length]. The worst you could do is have one of the String reference variables named scores. . this. public ExamScores(ExamScores origVal) { this.scores[i] = origVal. Or in other words.scores = new int[origVal. this.examName). for (int i = 0.length.scores[i] = origVal.length. but in that case. this. i++) this. private int[] scores. it’s safe for two ExamScores objects to have references to the same String object – since you can’t change the String object through either ExamScores object.average = origVal. That is. } } Such code takes advantage of the “initialize as a copy” constructor that exists in the String class. even though the above code is correct. i < this. it’s impossible to change a String object once it is created. In contrast.scores.scores[i]. once a String object is created.examName = new String(origVal. This is what we did with our array. Changing the scores reference variable of one ExamScores object won’t change the other reference variable in the other object. } Why is the soft copy okay for a String object? Because the String type is immutable. private double average. So. none of the variables are public and none of the instance methods allow any of the instance variables to be changed.scores[i].examName.229 public class ExamScores { private String examName.

230 .

When we develop complex algorithms to solve complex problems. one of our most important design tools is a concept known as recursion. Finally. in comparison. You cannot write code until you know what particular series of steps you want to code in the ﬁrst place. our solutions – the relevant collections of steps that when run. and optimize it so that the downsides of using recursion are eliminated from the code. and especially as our problems get more complex.println(x). and generally a far harder task. we will explore how to take code developed using recursion. we will learn various algorithms and see how to code those algorithms using recursion. we turn our attention to the development of algorithms. but that is to be expected – when you are learning your ﬁrst programming language. // how you print x in C++ // how you print x in Java or whatever. or the statement: std::cout << x << std::endl. understanding the steps that are needed for your procedure. once you have the steps and are comfortable with the syntax of a languge.also become more complex. we will be exhaustively studying the idea of recursion. is of far greater importance than the particular syntax you use to code those steps in a particular language. Coming up with the necessary collection of steps is thus a diﬃcult task.e.out. And thus we now turn our attention to the development of these collections of steps that solve our problems – i. “here is where I should print the variable x to the screen” is of far greater importance. but ultimately. In addition. You might have found it diﬃcult. 231 . solve our problems . if you had not programmed before this course.Part 3 : Algorithm Design and Recursion Learning the syntax of a language is all ﬁne and good. That is. then speciﬁcally remembering whether to accomplish this via the statement: System. coding the steps in that language is an easy task. In the next part of the course. you will ﬁnd that being able to ﬁgure out. you are also learning the principles of programming at the same time. and seeing how this concept helps us design algorithms. and that is what causes the diﬃculty! But as you learn additional programming languages. it’s not the diﬃcult part of software design.

yes. • Solving – the process must actually produce the desired solution. sometimes all that is wanted is a solution that is “close enough” to optimal. • Problem – the process must solve the general problem. a description of how to solve a problem. in certain special situations perhaps the computer can recognize there is an inﬁnite loop. Some things have no computational solutions.but usually those special situations don’t apply and usually the computer cannot recognize an inﬁnite loop in general. and all the necessary steps must be speciﬁed. That is. Algorithm design Our main focus for much of the next few weeks will be on algorithm design. If you want to return the sum of ﬁve numbers. there is no generalized way for the computer to do this – meaning. computational things. An algorithm is a process. we will be concerned with the creation of new algorithms ﬁrst and foremost. An algorithm is a computational process for solving a problem. Algorithms are mathematical.232 Lecture 21 : Introduction to Algorithm Design and Recursion Our focus now turns to algorithm design. in a particular language. and we can discuss whether the algorithm solves our problem or not – all without needing to discuss actual code. . as opposed to worrying about how to make the algorithm run as fast as possible. even though that solution does work in some cases. even though you can detect that your program has an inﬁnite loop. We can discuss whether the algorithm is suﬃciently detailed to be translated into code. This deﬁnition has three important parts: • Computational – the process must be something a computer could work through in a stepby-step manner. on a particular machine. not just a speciﬁc instance of it.. where “close enough” has some formal deﬁnition. For example. just returning the integer literal “2” is not an accurate algorithm.. usually what is desired is an exact solution to the problem.

# of steps away --------------0 solution to that problem -----------------------1) knock on door Now. } Notice that this required visualizing the entire solution at once. We know that we had previously created a “zero steps away” solution. but what the hey). As before. and is one you could already write: // start out n steps from door void EventuallyKnock(int n) { while (n > 0) { TakeOneStepTowardDoor(). This is the loop solution. a new way of looking at a problem. so we’ll assume for the rest of this discussion that n will always be non-negative. and let’s just run that. This way. If you are two steps from the door. or a method. is deﬁned in terms of itself. If you are right next to the door. if we look back at the details we can read what the “zero steps away” solution was (“knock on door”) but we don’t have to bother. For example (the example works better when it’s physically being given during lecture. we just won’t know what it is. # of steps away --------------0 solution to that problem -----------------------[don’t look] Now. n = n . we’ll know we have a solution. and then knock. Call that the “zero steps away” solution. you can just knock on that door. let’s cover up the solution to the “zero steps away” problem. if you are one step away. imagine that we handle this situation by referring to “simpler problems”. instead. whatever it is. from start to ﬁnish. So. but rather. one that allows you to often focus on the major portion of the solution without worrying about every start-to-ﬁnish detail. } KnockOnDoor(). you take one step toward the door. our “one step away” solution is to take one step. It’s not a new power in addition to what you have.1. take n steps and then knock on the door.) Now. if you are right next to the door. And so on. and then run the “zero steps away” solution. just knock. Every detail is accounted for by that loop. if you are n steps from the door. What is that solution? Who cares? Certainly.233 Recursion Recursion is the language of algorithm design. if you took just one step toward the door. . you take two steps and then knock. In general. Recursion is when a solution formula. If you are one step away. you’d be zero steps away and could run the “zero steps away solution”. (Notice also that it makes no sense for n to be negative. well. consider crossing a room to knock on a door.

but we don’t really need to think about all of it. And we have a solution for that problem already: the “one step away” solution. it’s enough to just say. All we need to do is realize that if we take just one step. . so we can simply take a step. So. whatever it might be. so just run that”. we are now only one step away. now there’s a bit more work to do. # of steps away --------------0 1 solution to that problem -----------------------[don’t look] [don’t look] So.234 # of steps away --------------0 1 solution to that problem -----------------------[don’t look] 1) take a step 2) run 0-steps-away solution Now. in trying to solve the “three steps away” problem. what happens when we are two steps away? Well. # of steps away --------------0 1 2 solution to that problem -----------------------[don’t look] [don’t look] 1) take a step 2) run 1-step-away solution We can then cover this solution up as well: # of steps away --------------0 1 2 solution to that problem -----------------------[don’t look] [don’t look] [don’t look] and then. the “two steps away” solution is to take one step. we can cover up the “one step away” solution as well. and then run the “one step away” solution. whatever it might be. “we discovered the one-step-away solution earlier. Let’s not worry what the details are. we can note that we have a “two steps away” solution. and then run the “two steps away” solution.

Then. . .235 # of steps away --------------0 1 2 3 solution to that problem -----------------------[don’t look] [don’t look] [don’t look] 1) take a step 2) run 2-steps-away solution In general. since that is now our situation. by reducing the problem to a smaller subproblem – the same problem. thanks to us having just taken one step. . we can solve that problem by taking one step – bringing us one step closer to the door – and then treating our new situation as a new problem to be solved. . we need to know how to ﬁnish the solution once we are zero steps away. by running the solution appropriate for being (n-1) steps away. the solution to our problem is to ﬁrst take one step. n The general rule is that if we are n steps away. and we need to know how to start the solution. 1) take a step 2) run (n-1)-steps-away solution 2 3 . In every situation where we’re a positive number of steps away from the door. and applying our rule all over again. We only need the ﬁrst step and the last step – that is. we don’t have to have our code consider every detail from the start to the ﬁnish of the problem. we have the following pattern: # of steps away --------------0 1 solution to that problem -----------------------1) knock on door 1) take a step 2) run 0-steps-away solution 1) take a step 2) run 1-step-away solution 1) take a step 2) run 2-steps-away solution . we can ﬁnish solving the problem. just on fewer data values or smaller data values) – and then running the solution to that subproblem. Thus to write the solution to this problem. leaving us (n-1) steps away. .

if we sent in 10 as an argument. and to solve that. EventuallyKnock(0) is not deﬁned in terms of other solutions to EventuallyKnock. All the details of ﬁguring each solution out in turn is left to the deﬁnition itself. you by deﬁnition need to know the solution to EventuallyKnocks(0). But we can’t deﬁne every problem in terms of the solution to other problems. and then make the call EventuallyKnock(9). When a method calls itself. which will pass 9 as an argument to the same method. which our deﬁnition already provides us. we deﬁne that solution partly in terms another solution to EventuallyKnock. and which will solve the problem of being nine steps away. the method will hit the recursive case. |_____ if n > 0 EventuallyKnock(n) if n == 0 The ﬁrst case is called the recursive case – the general case we use to solve most problems. We only need to explain two important things : how to reduce the current problem to a smaller one (the recursive case). We could then take that formal description above and create a recursive method from it: void EventuallyKnock(int n) { if (n > 0) { TakeOneStepTowardDoor(). you by deﬁnition need to know the solution to EventuallyKnocks(1). you need to know the solution to EventuallyKnocks(2). | EventuallyKnock(n-1). --| | KnockOnDoor(). } When we pass this method a positive value as an argument. (For example. EventuallyKnock(n-1). we need to eventually stop or we’d forever be solving!! And so the second case is the base case. This is the case whose answer is not deﬁned in terms of a subproblem (i. we will take a step. and will call itself with a new argument. . } else KnockOnDoor(). it is a recursive method.. because we want to solve the problem of being 10 steps away. Since the problem can be solved in exactly the same way for almost every value of n – take one step. Whereas for any n greater than 0. Describing things recursively provides the advantage of not having to describe every single detail of the solution. and to know that. then by deﬁnition. The recursive case is the case that is deﬁned in terms of a solution to a subproblem. and then run the solution to the next smaller problem – this recursive case is what we run for almost every value of n.236 We could write things out formally as follows: ______ | TakeOneStepTowardDoor().e. whose answer is not deﬁned in terms of the same algorithm run on a a smaller data quantity or smaller data value). If you want to know the answer to EventuallyKnocks(3). and how to solve the smallest possible problem (the base case).

it must also be true for n==3. it makes solutions you do come up with easier to understand. since the above problem is very simple and since you are just learning recursion. which you will learn in CS173. And that is why it is such a nice proof technique for proving that a recursive algorithm is correct. while the recursive solution in the same circumstances could be quite obvious. and then you would prove that if the formula was true for some value n==k. and proving a recursive case about it. and the idea of recursion we are discussing. the loop version (called an iterative version) for the above problem probably seems a bit clearer to you right now. describing just the ﬁrst and last step makes for a more concise. those solutions are also easier to prove correct. you would ﬁrst prove that the formula worked for n==1 (the base case). you know it’s true for n==2. But for more complex problems. And then since it’s true for n==2. due to the proof technique of mathematical induction. it must also be true for n==4. Second. it might not be at all clear how to solve it with a loop. For simple problems. And then since it’s true for n==3.237 Not having to provide every little detail to handle the entire problem from start to ﬁnish in one method call has three advantages – ﬁrst. But as we said above. if you wanted to prove that the sum of all positive integers from 1 through n was given by the formula (n * (n+1))/2. this might not be the case – for example. clear description than trying to trace out the calculation from start to ﬁnish. well. That is the idea of proof by mathematical induction – as you can see. Once you’ve proved that second statement. it is often easier to come up with solutions when you don’t have to think all the way through every detail. then it must also be true for the value n==k+1. Again. (Very brieﬂy – proving something via the use of mathematical induction involves proving both a base case about that something. then since you’ve already proved the formula is true for n==1. you will learn more about this in CS173. there’s a very close relationship between that proof technique. For example. this might not seem true – but for more complex problems.) . And so on. And third.

This is what we want to have happen. we had the “. but this time. If we are trying to calculate n!.. and thus we will assume that there is always a non-negative value passed as an argument to the parameter n.. while (n > 0) { product = product * i. Which of these might help us in calculating n!? Well. You might recall the deﬁnition of “factorial”.. in terms of itself. we only need to multiply it by n to get the ﬁnal result! That is to say: // product of all numbers from 1 through n. mathematically-based problem.. 0! is deﬁned to be 1. 2!. * 2 * 1 |___________________________________| this part is (n-1)! giving us the shortened formula: n! = n * (n-1)! . and so the missing terms should continue in that same pattern n-4. we would get 1 back as the return value. n-5. and so on. n = n .. with an actual real. if we had sent 0 in as the argument. to indicate “follow the same pattern”.” in our deﬁnition of factorial above.1. * 2 * 1 We can write an iterative method (i.. n! = n * (n-1) * (n-2) * (n-3) * . mathematically. n-6. inclusive: // product of all numbers from 1 through n. We do not deﬁne factorial for negative values. } return product. } Notice that. if we have (n-1)!. Let’s try again.e. n! = n * (n-1) * (n-2) * (n-3) * .. We could also deﬁne fac(n) a diﬀerent way. in the above code. in this situation. then consider all possible subproblems: 1!. all the way up to (n-2)! and (n-1)!. Now. “n factorial” (notated n!) is the product of all the integers from 1 through n. and we are assuming that anyone who looks at that deﬁnition can see that we are subtracting a value one larger from n each time.238 The last problem was somewhat abstract. etc. a method using loops) to calculate n!: // Iterative version public static int fac(int n) { int product = 1.

239 Of course.. since mathematically.and that gives you 2.. else // n == 0 return 1. and since we don’t deﬁne factorial for anything less than 0 anyway..and you get 120.and you get 6.and that gives you 1. And now that you know 4! is 24.. we need a base case as well.. That gives us the following algorithm for calculating factorial: | | n * (n-1)! | | 1 n! (n>=0) = if n > 0 if n == 0 For example.. And now you know that 5! is 120. leading to the following code: // reminder: we are assuming n is non-negative public static int fac(int n) { if (n > 0) return n * fac(n . you substitute 2 for 2! in the expression 3 * 2!.. you substitute 1 for 1! in the expression 2 * 1!.. you substitute 1 for 0! in the expression 1 * 0!. you would compute (n-1)! via the method call fac(n-1). And now that you know that 2! is 2. So now that you know 1! is 1. you substitute 24 for 4! in the expression 5 * 4!. we deﬁne 0! to be equal to 1. you would compute 5! as follows: 5! = 5 * 4! /|\ | 4! = 4 * 3! /|\ | 3! = 3 * 2! /|\ | 2! = 2 * 1! /|\ | 1! = 1 * 0! /|\ | 0! = 1 Then once you know 0! is 1. And now that you know that 3! is 6.and you get 24. Let’s take n==0 as our base case.. If you are trying to compute n! via the method call fac(n). you substitute 6 for 3! in the expression 4 * 3!.1). You can write the above algorithm as a recursive method fac. } ..

e.1) | i. so we can use its value to | complete the multiplication in the | expression | | n * fac(n . | x = fac(4). i. // we call the fac method | |____________________________________________________________________ Now. which requires we calculate (n − 1)!. | x = fac(4). when we call that method. 3!. // we call the fac method.240 What we are going to do next is go step by step through the calculation of 4! using the recursive method we just wrote.e. | // set up the "fac" notecard below: |____________________________________________________________________ | fac (n: 4) | (#1) | | need to calculate: fac(3). Since n is not zero. | x = fac(4). // we call the fac method. We start with a main() method that makes a call to fac(4): _____________________________________________________________________ | main | int x. we run the fac method on the data in the fac notecard. 4 was passed to this method as an | // argument. _____________________________________________________________________ | main | int x. It’s no diﬀerent with recursive methods: _____________________________________________________________________ | main | int x. as with any method. we will take the recursive case. | 4 * fac(3) |__________________________________________________________________ . we set up a “notecard” (some area of memory inside the machine) to store the parameters and local variables for that method call. and thus is stored in the parameter "n" | |_____________________________________________________________________ Now that we’ve set up the fac notecard. | // set up the "fac" notecard below: |____________________________________________________________________ | fac (n: 4) // this is the notecard for the first call to | (#1) // fac. and in doing so. and in doing so.

| x = fac(4). and get the result. c is 9. c is 2. and d is 6”. “a is 5. |_________________________________________________________________ | fac (n: 4) | (#1) | | need to calculate: fac(3). b. both of them being scratch areas for the same set of instructions. one where n is 3. Since the notecards have diﬀerent values for n. and I could give the other piece of paper to a diﬀerent student in CS125. in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | need to calculate: fac(2). This is perfectly acceptable! In past method examples.e. the student could plug those values into the expression. from the call fac(4).d) on two diﬀerent pieces of paper. __________________________________________________________________ | main | int x. so we can use its value to | complete the multiplication in the | expression | | n * fac(n . just like two diﬀerent students can both work on evaluating their own copy of the same expression. You can indeed have the same set of instructions run on two diﬀerent notecards. So. if I tell the ﬁrst student “a is 4. in this case. when you made a method call. and therefore we will start a second fac notecard. This does not matter. | 3 * fac(2) |__________________________________________________________________ . and that student could plug those values into the expression. I could then tell the second student. If you’re having trouble understanding this. and d. That is. and get the result 18. we make another call to our fac method.1) | i. I could give one of those pieces of paper to one student in CS125. Similarly.241 So. c. both students have the same expression in front of them. and d is 3”. the method you were leaving and the method you were starting were two diﬀerent methods. b is 1. If the values stored on the ﬁrst notecard are diﬀerent than the equivalent values stored on the second nodecard (such as n being 4 versus n being 3). they are the same method. -11. Both students can evaluate the expression. imagine if I write the expression: (a + b) * (c . we will make the call fac(3). you can have two diﬀerent notecards both setup to run the same method. you’ll get diﬀerent results. b is 7. the method calls will accomplish diﬀerent work. they have diﬀerent values for a. Now.but there’s no reason you can’t have two diﬀerent notecards. so they get diﬀerent results – but you can indeed have two diﬀerent students evaluate the same expression...

in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | need to calculate: fac(2).e. so we make that call: . we will ﬁnd that we need a fac(2) call.242 As that last picture showed.1) | i. | x = fac(4). in order to calculate 3 * fac(2) | |__________________________________________________________________ | fac (n: 2) | (#3) | | need to calculate: fac(1). so we can use its value to | complete the multiplication in the | expression | | n * fac(n . we will need a fac(1) call. | 2 * fac(1) |__________________________________________________________________ From within the fac(2) call. |_________________________________________________________________ | fac (n: 4) | (#1) | | need to calculate: fac(3). and so we will make that call: __________________________________________________________________ | main | int x. within the fac(3) call.

|_________________________________________________________________ | fac (n: 4) | (#1) | | need to calculate: fac(3). | 1 * fac(0) |__________________________________________________________________ From within the fac(1) call. in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | need to calculate: fac(2). so we make that call: .1) | i.243 __________________________________________________________________ | main | int x. in order to calculate 2 * fac(1) | |__________________________________________________________________ | fac (n: 1) | (#4) | | need to calculate: fac(0). in order to calculate 3 * fac(2) | |__________________________________________________________________ | fac (n: 2) | (#3) | | need to calculate: fac(1). | x = fac(4). so we can use its value to | complete the multiplication in the | expression | | n * fac(n . we will need a fac(0) call.e.

in order to calculate 1 * fac(0) | |__________________________________________________________________ | fac (n: 0) | (#5) | | base case! (we have not returned yet) |__________________________________________________________________ We make and pause four separate calls to fac. ﬁnally making our ﬁfth call which is our base case.244 __________________________________________________________________ | main | int x. |_________________________________________________________________ | fac (n: 4) | (#1) | | need to calculate: fac(3). in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | need to calculate: fac(2). in order to calculate 3 * fac(2) | |__________________________________________________________________ | fac (n: 2) | (#3) | | need to calculate: fac(1). in order to calculate 2 * fac(1) | |__________________________________________________________________ | fac (n: 1) | (#4) | | need to calculate: fac(0). | x = fac(4). .

| x = fac(4). we can start returning from there.245 Then. |_________________________________________________________________ | fac (n: 4) | (#1) | | need: fac(3). in order to calculate 1 * fac(0) | |__________________________________________________________________ | fac (n: 0) | (#5) | | base case! ------> returns 1 |__________________________________________________________________ . in order to calculate 2 * fac(1) | |__________________________________________________________________ | fac (n: 1) | (#4) | | need: fac(0). in order to calculate 3 * fac(2) | |__________________________________________________________________ | fac (n: 2) | (#3) | | need: fac(1). in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | need: fac(2). since the base case doesn’t need to make any further recursive calls. The base case itself returns 1: __________________________________________________________________ | main | int x.

in order to calculate 2 * fac(1) | |__________________________________________________________________ | fac (n: 1) | (#4) | | need: fac(0).so now this expression is replaced by the value 1 . the value 1 is returned back to the previous call and is substituted for “fac(1)”. in order to calculate 1 * fac(0) | -----|__________________________________________ /|\ __ | base case returned 1.. | x = fac(4). | the method call fac(0) returned 1. that is. in order to calculate 3 * fac(2) | |__________________________________________________________________ | fac (n: 2) | (#3) | | need: fac(1). |_________________________________________________________________ | fac (n: 4) | (#1) | | need: fac(3).. the method call expression that triggered the base case to be started: __________________________________________________________________ | main | int x.246 Therefore. in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | need: fac(2).

in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | need: fac(2). we can ﬁnally complete the multiplication. in order to calculate 2 * fac(1) | |__________________________________________________________________ | fac (n: 1) | (#4) | | this method call is supposed 1 * 1 | to return 1 * fac(0) which -----. | x = fac(4).247 Now that we have a value for fac(0). |_________________________________________________________________ | fac (n: 4) | (#1) | | need: fac(3). in order to calculate 3 * fac(2) | |__________________________________________________________________ | fac (n: 2) | (#3) | | need: fac(1).so now this expression | is 1 * 1 which is 1 evaluates to 1 |_____________________________________________________ . since now we have both operands: __________________________________________________________________ | main | int x.<---.

| x = fac(4).<---. |_________________________________________________________________ | fac (n: 4) | (#1) | | need: fac(3). in order to calculate 2 * fac(1) | |__________________________________________________________________ | fac (n: 1) | (#4) | | this method call is supposed 1 | to return 1 * fac(0) which -----. we are supposed to return the result of that multiplication: __________________________________________________________________ | main | int x.so now this value | is 1 * 1 which is 1 is returned |_____________________________________________________ . in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | need: fac(2). in order to calculate 3 * fac(2) | |__________________________________________________________________ | fac (n: 2) | (#3) | | need: fac(1).248 Finally.

| x = fac(4).. then the program returns back to the third fac call. in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | need: fac(2). |_________________________________________________________________ | fac (n: 4) | (#1) | | need: fac(3). in order to calculate 2 * fac(1) | -----|__________________________________________ /|\ __ | | the method call fac(1) returned 1.249 When the fourth fac call returns 1.so now this expression is replaced by the value 1 . in order to calculate 3 * fac(2) | |__________________________________________________________________ | fac (n: 2) | (#3) | | need: fac(1). and now that the fac(1) call is over. it is replaced by its return value.. which is 1: __________________________________________________________________ | main | int x.

|_________________________________________________________________ | fac (n: 4) | (#1) | | need: fac(3). in order to calculate 3 * fac(2) | |__________________________________________________________________ | fac (n: 2) | (#3) | | this method call is supposed 2 * 1 | to return 2 * fac(1) which -----.250 And then this call works as the previous one did.<---. | x = fac(4). the multiplication is completed: __________________________________________________________________ | main | int x. in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | need: fac(2).so now this expression | is 2 * 1 which is 2 evaluates to 2 |_____________________________________________________ . First.

| x = fac(4). in order to calculate 3 * fac(2) | |__________________________________________________________________ | fac (n: 2) | (#3) | | this method call is supposed 2 | to return 2 * fac(1) which -----. |_________________________________________________________________ | fac (n: 4) | (#1) | | need: fac(3).<---. in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | need: fac(2).so now this value | is 2 * 1 which is 2 is returned |_____________________________________________________ .251 And then we were supposed to return the result of that multiplication: __________________________________________________________________ | main | int x.

. First. | x = fac(4). in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | this method call is supposed 3 * 2 | to return 3 * fac(2) which -----.<---. and now that the fac(2) call is over. |_________________________________________________________________ | fac (n: 4) | (#1) | | need: fac(3).so now this expression is replaced by the value 2 And then this call works as the previous two did. which is 2: __________________________________________________________________ | main | int x. |_________________________________________________________________ | fac (n: 4) | (#1) | | need: fac(3). then the program returns back to the second fac call.so now this expression | is 3 * 2 which is 6 evaluates to 6 |_____________________________________________________ . in order to calculate 3 * fac(2) | -----|__________________________________________ /|\ __ | | the method call fac(2) returned 2. in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | need: fac(2). | x = fac(4). it is replaced by its return value.. the multiplication is completed: __________________________________________________________________ | main | int x.252 When the third fac call returns 2.

| x = fac(4). and now that the fac(3) call is over. | x = fac(4). it is replaced by its return value. which is 6: __________________________________________________________________ | main | int x.<---.. in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | this method call is supposed 6 | to return 3 * fac(2) which -----. |_________________________________________________________________ | fac (n: 4) | (#1) | | need: fac(3).so now this expression is replaced by the value 6 ..253 And then we were supposed to return the result of that multiplication: __________________________________________________________________ | main | int x. then the program returns back to the ﬁrst fac call. |_________________________________________________________________ | fac (n: 4) | (#1) | | need: fac(3).so now this value | is 3 * 2 which is 6 is returned |_____________________________________________________ When the second fac call returns 6. in order to calculate 4 * fac(3) | -----|__________________________________________ /|\ __ | | the method call fac(3) returned 6.

then the program returns back to main().<---. the multiplication is completed: __________________________________________________________________ | main | int x. First. |_________________________________________________________________ | fac (n: 4) | (#1) | | this method call is supposed 4 * 6 | to return 4 * fac(3) which -----.so now this expression | is 4 * 6 which is 24 evaluates to 24 |_____________________________________________________ And then we were supposed to return the result of that multiplication: __________________________________________________________________ | main | int x. it is replaced by its return value. and now that the fac(4) call is over. |_________________________________________________________________ | fac (n: 4) | (#1) | | this method call is supposed 24 | to return 4 * fac(3) which -----. ________________________________________________________ | main | int x. | x = fac(4). since the method call fac(4) has returned 24 . | x = fac(4). | x = fac(4). which is 24. | --------|________ /|\ __________________________________________ | | this expression is replaced by 24.<---.so now this value | is 4 * 6 which is 24 is returned |_____________________________________________________ When the ﬁrst fac call returns 24. which then gets written into the variable x by the assignment statement.254 And then this call works as the previous three did.

.255 That is how recursion works on the actual machine – as with any other method call. This is why the ﬁrst fac call (the one where n==4) sits paused for a long time – until the fac(3) call it makes. ﬁnally returns. your calculation cannot proceed until that method call is ﬁnished. when you make a recursive method call.

If we have a multidimensional array. we can reduce the number of indices we are working with for that particular dimension – meaning we have reduced the amount of data we are dealing with. in terms of the solution to one or more similar. Or perhaps the subarray is only half the size of the original array. and make a recursive call to count the number of items in each of those smaller piles. we want to then run the same algorithm. rather than the original data. just run on a smaller collection of data. we could add those values together to get the number of items in the original large pile. we might break that pile into a number of smaller piles. and our subproblem was to calculate (n − 1)! – that is. For example: • If your parameters are integers. • For dealing with graphics or pictures. on that smaller amount of data. • If we are trying to count the number of items in some pile of items. In any case. once we have the number of items in each of the smaller piles. By increasing the low index for a particular dimension.e. or maybe all of them get reduced in some way. but only look at part of it. it would be more useful to reduce the integer parameter by subtracting 2 from it. since our problem is to count the number of items in the overall pile. but smaller problems. or it could perhaps instead contain all the rows but one. We will examine this idea further in lecture 23.256 Lecture 22 : Subproblems To write a recursive function f(x). or decreasing the high index for that dimension. For example. you need to express f(x) in terms of the solution to one or more subproblems – i. or the cells 5 through 9. later in this lecture. a subproblem is the same algorithm run on a subarray. or all the columns but one. the subproblem is to count the number of items in one of the smaller piles – the same algorithm. • If you have an array. So if the original array has indices 0 through 9. Perhaps for some other problem with an integer parameter. in the factorial method from the last lecture. and running the same code with those values for the parameters. in such a case. The original problem was to calculate n!. our subarray might consist of the cells 0 through 4. but on that set of “reduced” data. and our subproblem can be to run the same algorithm. a subproblem could be just drawing a smaller piece of the picture. . Then. and then calculated the factorial of that value. we took our one integer parameter and reduced it by 1. Our algorithm will then be designed to run only on the section of the array that is deﬁned by those indices. our subproblem was to run the exact same algorithm. or the cells from 0 through 8. you might obtain a subproblem by reducing one or more of the integers. That is our subproblem. The idea of a subarray is that you take the original array. just on a smaller value of n. or dividing it by 3. Or if we have multiple integer parameters. then the subarray could contain all the cells but one. We will see a few additional examples with integer parameters. perhaps the recursive call deals only with the cells from 1 through 9. or a smaller version of the picture. In this case. The idea is that we need to deﬁne the low and high indices we are concerned with for each dimension of the array. We will examine this idea further in lectures 25-27. maybe only some of them get reduced in some way. We will examine this idea further in lecture 24.

original problem.then you’ve got the key information needed to write your recursive algorithm. assume you can just snap your ﬁngers at any time. We simply used (n-1)! in our algorithm above. You want to ﬁnd a subproblem for which having it solved does make the original problem easier to solve. larger problem. would help you solve the original problem. without there being a need for us to have to write any additional algorithms or add anything else to the above algorithm. so that you can eventually stop breaking your problem into a smaller subproblem. . 4. The procedure for writing a recursive algorithm is to: 1. and the work represented by the subproblem will have “magically” been completed! Once you ﬁnish writing your algorithm. | | 1. 3. then in a sense. Assume that your chosen subproblem could be solved just by reapplying the same recursive algorithm you are writing for the original.. It works the same way for any recursive algorithm. If you’ve found a way to make use of a particular subproblem solution in order to solve the original problem. Figure out what the base case should be. its value would “automatically” exist and could be substituted right into the expression n * (n-1)! above. The subproblem is the part that you can “assume is solved already”. | if n > 0 if n == 0 n! (n>=0) = we didn’t then write separate a separate algorithm to evaluate (n-1)!. so you need to ﬁgure out which of those subproblems is a useful one. There are many subproblems available for you to choose. coming up with the correct subproblem is one of the keys to designing a recursive algorithm. and assumed that when the value of (n-1)! was needed. when we wrote the above algorithm. Figure out how having that subproblem solved. that’s what happens – you make a recursive call to run the subproblem. However. Choose a subproblem. when we wrote the factorial algorithm in the last lecture: | | n * (n-1)!. The steps don’t have to go in that exact order – you might be able to ﬁgure out the base case before you’ve got the subproblem ﬁgured out – but that’s the basic idea.. when that recursive call returns. for which having that subproblem solved will be no help whatsoever in solving the larger.257 In any cases. That is. if you can’t come up with any way to make use of the solution to a particular subproblem. and should consider a diﬀerent one instead. the subproblem has been completed while you were just sitting there waiting. For example. and then wait. That is. we just assumed that the calculation (n-1)! was already done and we could freely use the result of that calculation. Part of why this process is diﬃcult is that there are generally many possibilities to choose from when selecting your subproblem. 2. you might have the wrong subproblem.

for example. we return 1. of course. Of those subproblems above. say. That is. but let us at least consider the ones above for a moment. we want to calculate the values of expressions such as 25 or 137 or 821 . The recursive case tells us how to solve any general problem in terms of a subproblem. we said we’d allow our exponent to get as low as zero (anything less and our results are no longer integers) and any positive number raised to the zero power is 1. then you’d probably see right away how easy it was to calculate 35 if you already had the value of 34 – and thus you’d conclude that the “lower the exponent by one” subproblem was indeed a useful one. we can calculate baseexp with the formula base ∗ baseexp−1 . Trying to calculate 35 if given the value of 25 .e. exp >= 0) ____________ | | | |___________ 1.258 The ﬁrst example we will look at in depth is exponentiation. and therefore that the “lower both the base and the exponent by one” subproblem wasn’t much help. for recursion. it’s not all we need. reduce base by one and reduce exponent by two There are other possibilities as well. or 23 wouldn’t be anywhere near as simple. we only need to multiply it by the base (3) again to get 35 . if exp > 0 . 24 . (You might even recognize that as a special case of the baseexp = basey ∗ baseexp−y exponent rule you likely learned in whichever early math class ﬁrst taught you about exponents. i. Further more. since you’d already found a way to use the result of that subproblem! So. so that we don’t have to deal with fractional results. reduce base and exponent by one • 23 . we’ll assume that exp ≥ 0. if we generalize our result above. i. so our base case could be that when the exponent is 0. reduce base by one • 34 . and we’ll assume that base ≥ 1 so that we avoid having to deal with 00 in our algorithm. you would likely conclude pretty quickly that knowing 24 didn’t seem to be much help in calculating 35 .e. More generally. we need a base case as well. and try and use that value to help calculate 35 .e. reduce exponent by one • 24 . i. if you were to take the value 34 . and the base case tells us when to stop breaking the problem into yet another subproblem. We might consider.) However. the one that could help the most would be to decrease the exponent but not the base. That gives us the following mathematical formula: exp base = (base >= 1. Some possible subproblems are: • 25 . If we have 34 . In the case of exponentation. 35 . we are trying to calculate: baseexp We’ll assume for this problem that base and exp are integers. if exp == 0 exp-1 base * (base ). For what values of x ≤ base and i ≤ exp can xi be used to calculate baseexp ? It sometimes helps to start with a concrete example.e. and try and ﬁgure out what subproblem solution might help us calculate 35 . But on the other hand. even though that formula works in most cases. i. If you were to take the value 24 .

the exp > 0 case in the mathematical description above gives us the following as our answer: . in the exp == 0 case in our mathematical description. We could also have written the above out in some form of pseudocode.259 The above is our algorithm. we will need a conditional to decide between those two cases: public static int pow(int base. Implementing the exponentiation algorithm Since we will need the values of base and exp in order to calculate baseexp .we need to add some code here] } and likewise. or in English.we need to add some code here] } and then. exp >= 0) ____________ | | | |___________ 1. but since it’s a mathematical computation..we need to add some code here] else // exp > 0 // [. it would follow that those two values should be arguments that we send to an exponentiation method.. if exp > 0 we can just convert this straight into a recursive method. so let us return 1 from our method in that case: public static int pow(int base.. however. one where exp == 0 and one where exp > 0. we say the answer is 1. the above mathematical form is a nice way to express the algorithm. int exp) We’ll name the method pow since that is short for “power” and we are calculating “base to the exp power”. is that the above tells us precisely how to calculate baseexp for any legal values of base and exp. to be stored in parameters. if exp == 0 exp-1 base * (base ). int exp) { if (exp == 0) return 1. The point.. int exp) { if (exp == 0) // [. Given the mathematical description of exponentiation that we had earlier: exp base = (base >= 1. That would give us the following ﬁrst line for our method (the method signature. else // exp > 0 // [... plus the public static tagged on to the beginning of the method): public static int pow(int base. Since we have two cases above.

int exp) // base >= 1.return value : an integer holding the value of base // to the exp power // . which is now complete: // implementation of our exponentiation algorithm public static int pow(int base.1).1). exp . exp . } we raise is >= 0 raised value If you make assumptions about the parameter values in your code.1) and thus we have the following recursive method. it’s generally a nice idea to add a comment somewhere about that.the exponent to which we want to // our base value.calculates "base to the exp power" and returns that public static int pow(int base.the base of our exponentiation. } Note the comment at the end of the method signature – it’s a reminder of what values are legal arguments for this method and what values are not. so that other people using your code understand what values are and aren’t legal to send as arguments to your code. that would have told us the same information: // pow // . We might also have provided a more complete method header comment for this method. the recursive calculation in our mathematical description. else // exp > 0 return base * pow(base. exp-1). translates to base * pow(base.260 exp-1 base * (base ) Since baseexp−1 will just be converted to pow(base. exp >= 0 { if (exp == 0) return 1. int exp) { if (exp == 0) return 1. // assume this value is >= 1 // : exp . we assume this value // .parameters : base . else // exp > 0 return base * pow(base. . exp .

.1) | i. 3) | | hence the call to pow and the | new notecard below this one. i. and are stored in the parameters | // "base" and "exp". 3 and 4 were passed to this method as | // arguments. since exp is not zero. 4). 4). 4). 3). from the call pow(3. __________________________________________________________________ | main | int x. | // set up the "pow" notecard below: |____________________________________________________________________ | pow (base: 3) // this is the notecard for the first call to | (#1) (exp: 4) // pow. respectively |_____________________________________________________________________ When we run the pow code on the data in the pow notecard. 33 .e. 3). | 3 * pow(3.261 A run of the recursive method with base being 3 and exponent being 4 could start like this: _____________________________________________________________________ | main | int x. which requires we calculate baseexp−1 . |_________________________________________________________________ | pow (base: 3) | (#1) (exp: 4) | | need to calculate: pow(3. we will take the recursive case. we make another call to our pow method. we will make the call pow(3. and therefore we will start a new notecard where the base and exp arguments are both 3. and in doing so. which | stores the parameters for that call |__________________________________________________________________ So. so we can use its value to | complete the multiplication in the | expression | | base * pow(base. That is. exp .e. | x = pow(3. // we call the pow method. | x = pow(3.

from within the pow(3. exp . in order to calculate 3 * pow(3. 2).e. 4). 2) call. 3) call. which | stores the parameters for that call |__________________________________________________________________ As you can see above. | 3 * pow(3. |_________________________________________________________________ | pow (base: 3) | (#1) (exp: 4) | | need to calculate: pow(3. 2) | | hence the call to pow and the | new notecard below this one. 3).1) | i. we will decide that we need a pow(3. and so we will make that call: .262 __________________________________________________________________ | main | int x. | x = pow(3. 3) | |__________________________________________________________________ | pow (base: 3) | (#2) (exp: 3) | | need to calculate: pow(3. so we can use its value to | complete the multiplication in the | expression | | base * pow(base.

which | stores the parameters for that call |__________________________________________________________________ From within the pow(3. in order to calculate 3 * pow(3. 1) call.263 __________________________________________________________________ | main | int x. | x = pow(3. 2) | |__________________________________________________________________ | pow (base: 3) | (#3) (exp: 2) | | need to calculate: pow(3. 2) call. so we make that call: . in order to calculate 3 * pow(3. 3) | |_________________________________________________________________ | pow (base: 3) | (#2) (exp: 3) | | need to calculate: pow(3. |_________________________________________________________________ | pow (base: 3) | (#1) (exp: 4) | | need to calculate: pow(3. 1). 3). | 3 * pow(3. 4). exp .e. so we can use its value to | complete the multiplication in the | expression | | base * pow(base. 1) | | hence the call to pow and the | new notecard below this one.1) | i. 2). we will need a pow(3.

1) | |__________________________________________________________________ | pow (base: 3) | (#4) (exp: 1) | | need to calculate: pow(3. 2).1) | i. 0) call. in order to calculate 3 * pow(3. in order to calculate 3 * pow(3. so we make that call: . |_________________________________________________________________ | pow (base: 3) | (#1) (exp: 4) | | need to calculate: pow(3. 2) | |__________________________________________________________________ | pow (base: 3) | (#3) (exp: 2) | | need to calculate: pow(3. so we can use its value to | complete the multiplication in the | expression | | base * pow(base. 4). exp . 1) call. which | stores the parameters for that call |__________________________________________________________________ From within the pow(3. 0).264 __________________________________________________________________ | main | int x. we will need a pow(3. 1). 3) | |_________________________________________________________________ | pow (base: 3) | (#2) (exp: 3) | | need to calculate: pow(3. in order to calculate 3 * pow(3. 0) | | hence the call to pow and the | new notecard below this one. | 3 * pow(3. 3). | x = pow(3.e.

|_______________________________________________________ | pow (base: 3) | (#1) (exp: 4) | | need: pow(3. in order to calculate 3 * pow(3. 4). in order to calculate 3 * pow(3. 0) |______________________________________________________ | pow (base: 3) | (#5) (exp: 0) | | base case! (we have not returned yet) |____________ We make and pause four separate calls to pow. 2). in order to calculate 3 * pow(3.265 ________________________________________________________ | main | int x. . 0). in order to calculate 3 * pow(3. 2) |_______________________________________________________ | pow (base: 3) | (#3) (exp: 2) | | need: pow(3. 1) |_______________________________________________________ | pow (base: 3) | (#4) (exp: 1) | | need: pow(3. 3). | x = pow(3. 3) |_______________________________________________________ | pow (base: 3) | (#2) (exp: 3) | | need: pow(3. 1). ﬁnally making our ﬁfth call which is our base case.

266 Then. 3) |_______________________________________________________ | pow (base: 3) | (#2) (exp: 3) | | need: pow(3. |_______________________________________________________ | pow (base: 3) | (#1) (exp: 4) | | need: pow(3. in order to calculate 3 * pow(3. since the base case doesn’t need to make any further recursive calls. 0) |______________________________________________________ | pow (base: 3) | (#5) (exp: 0) | -----> returns 1 | base case! |_______________________________________________________ . in order to calculate 3 * pow(3. 0). 3). 2). The base case itself returns 1: ________________________________________________________ | main | int x. 1). in order to calculate 3 * pow(3. 1) |_______________________________________________________ | pow (base: 3) | (#4) (exp: 1) | | need: pow(3. 2) |_______________________________________________________ | pow (base: 3) | (#3) (exp: 2) | | need: pow(3. 4). | x = pow(3. in order to calculate 3 * pow(3. we can start returning from there.

|_______________________________________________________ | pow (base: 3) | (#1) (exp: 4) | | need: pow(3. | x = pow(3. that is. the value 1 is returned back to the previous call and is substituted for “pow(3.. 0)”. 3). 2).267 Therefore. the method call expression that triggered the base case to be started: ________________________________________________________ | main | int x.so now this expression is replaced by the value 1 . 0) | --------|_______________________________________________ /|\ __ | base case returned 1. 1) |_______________________________________________________ | pow (base: 3) | (#4) (exp: 1) | | need: pow(3. 0). in order to calculate 3 * pow(3. in order to calculate 3 * pow(3. in order to calculate 3 * pow(3. 1). | the method call pow(3. 0) returned 1. 2) |_______________________________________________________ | pow (base: 3) | (#3) (exp: 2) | | need: pow(3. 4). in order to calculate 3 * pow(3.. 3) |_______________________________________________________ | pow (base: 3) | (#2) (exp: 3) | | need: pow(3.

268 Now that we have a value for pow(3.so now this expression | is 3 * 1 which is 3 evaluates to 3 |_____________________________________________________ . in order to calculate 3 * pow(3. in order to calculate 3 * pow(3. 4). 3). |_______________________________________________________ | pow (base: 3) | (#1) (exp: 4) | | need: pow(3. 1). 0) which -----. 3) |_______________________________________________________ | pow (base: 3) | (#2) (exp: 3) | | need: pow(3. | x = pow(3. 2). we can ﬁnally complete the multiplication. in order to calculate 3 * pow(3. since now we have both operands: ________________________________________________________ | main | int x. 1) |_______________________________________________________ | pow (base: 3) | (#4) (exp: 1) | | this method call is supposed 3 * 1 | to return 3 * pow(3. 0). 2) |_______________________________________________________ | pow (base: 3) | (#3) (exp: 2) | | need: pow(3.<---.

we were supposed to return the result of that multiplication: ________________________________________________________ | main | int x. in order to calculate 3 * pow(3.269 Finally. 3) |_______________________________________________________ | pow (base: 3) | (#2) (exp: 3) | | need: pow(3. 1) |_______________________________________________________ | pow (base: 3) | (#4) (exp: 1) | | this method call is supposed 3 | to return 3 * pow(3. | x = pow(3. 3). 2) |_______________________________________________________ | pow (base: 3) | (#3) (exp: 2) | | need: pow(3. 4).<--. 2). in order to calculate 3 * pow(3. |_______________________________________________________ | pow (base: 3) | (#1) (exp: 4) | | need: pow(3.so now this expression | is 3 * 1 which is 3 is returned |_____________________________________________________ . 0) which -----. 1). in order to calculate 3 * pow(3.

4). |_______________________________________________________ | pow (base: 3) | (#1) (exp: 4) | | need: pow(3. 2) |_______________________________________________________ | pow (base: 3) | (#3) (exp: 2) | | need: pow(3. 1) call is over. in order to calculate 3 * pow(3.270 When the fourth pow call returns 3. | x = pow(3. it is replaced by its return value. and now that the pow(3.. in order to calculate 3 * pow(3. which is 3: ________________________________________________________ | main | int x. 1) returned 3.. 3) |_______________________________________________________ | pow (base: 3) | (#2) (exp: 3) | | need: pow(3.so now this expression is replaced by the value 3 . 1) | --------|_______________________________________________ /|\ __ | | the method call pow(3. then the program returns back to the third pow call. 2). in order to calculate 3 * pow(3. 1). 3).

3) |_______________________________________________________ | pow (base: 3) | (#2) (exp: 3) | | need: pow(3. in order to calculate 3 * pow(3.<--. 3). |_______________________________________________________ | pow (base: 3) | (#1) (exp: 4) | | need: pow(3. the multiplication is completed: ________________________________________________________ | main | int x. in order to calculate 3 * pow(3. 4). 2) |_______________________________________________________ | | pow (base: 3) | (#3) (exp: 2) | | this method call is supposed 3 * 3 | to return 3 * pow(3. 1) which -----.271 And then this call works as the previous one did. 2). First.so now this expression | is 3 * 3 which is 9 evaluates to 9 |_____________________________________________________ . | x = pow(3.

4).272 And then we were supposed to return the result of that multiplication: ________________________________________________________ | main | int x. 1) which -----. |_______________________________________________________ | pow (base: 3) | (#1) (exp: 4) | | need: pow(3. in order to calculate 3 * pow(3.<--. 3). 2). | x = pow(3.so now this value | is 3 * 3 which is 9 is returned |_____________________________________________________ . 2) |_______________________________________________________ || pow (base: 3) | (#3) (exp: 2) | | this method call is supposed 9 | to return 3 * pow(3. 3) |_______________________________________________________ | pow (base: 3) | (#2) (exp: 3) | | need: pow(3. in order to calculate 3 * pow(3.

. the multiplication is completed: ________________________________________________________ | main | int x. |_______________________________________________________ | pow (base: 3) | (#1) (exp: 4) | | need: pow(3. | x = pow(3. and now that the pow(3. 3) |_______________________________________________________ | | pow (base: 3) | (#2) (exp: 3) | | this method call is supposed 3 * 9 | to return 3 * pow(3. 3) |_______________________________________________________ | pow (base: 3) | (#2) (exp: 3) | | need: pow(3. 2) returned 9.so now this expression | is 3 * 9 which is 27 evaluates to 27 |_____________________________________________________ . which is 9: ________________________________________________________ | main | int x. in order to calculate 3 * pow(3. First. then the program returns back to the second pow call. it is replaced by its return value. in order to calculate 3 * pow(3.<--. 2) | --------|_______________________________________________ /|\ __ | | the method call pow(3.273 When the third pow call returns 9. in order to calculate 3 * pow(3. |_______________________________________________________ | pow (base: 3) | (#1) (exp: 4) | | need: pow(3. 4). 3). | x = pow(3. 2).. 2) call is over. 4).so now this expression is replaced by the value 9 And then this call works as the previous two did. 2) which -----. 3).

3) |_______________________________________________________ || pow (base: 3) | (#2) (exp: 3) | | this method call is supposed 27 | to return 3 * pow(3.274 And then we were supposed to return the result of that multiplication: ________________________________________________________ | main | int x.so now this value | is 3 * 9 which is 27 is returned |_____________________________________________________ When the second pow call returns 27. 4). 4). 2) which -----. 3). 3) | --------|_______________________________________________ /|\ __ | | the method call pow(3. in order to calculate 3 * pow(3. then the program returns back to the ﬁrst pow call.. in order to calculate 3 * pow(3. | x = pow(3. 3) call is over. | x = pow(3.<--. 3). |_______________________________________________________ | pow (base: 3) | (#1) (exp: 4) | | need: pow(3.. and now that the pow(3. |_______________________________________________________ | pow (base: 3) | (#1) (exp: 4) | | need: pow(3.so now this expression is replaced by the value 27 . which is 27: ________________________________________________________ | main | int x. it is replaced by its return value. 3) returned 27.

4). First. |_______________________________________________________ | | pow (base: 3) | (#1) (exp: 4) | | this method call is supposed 3 * 27 | to return 3 * pow(3. | x = pow(3.<--.275 And then this call works as the previous three did. 4) has returned 81 . | x = pow(3.so now this expression | is 3 * 27 which is 81 evaluates to 81 |_____________________________________________________ And then we were supposed to return the result of that multiplication: ________________________________________________________ | main | int x. 4). which is 81.so now this value | is 3 * 27 which is 81 is returned |_____________________________________________________ When the ﬁrst pow call returns 81.<--. | x = pow(3. | --------|________ /|\ __________________________________________ | | this expression is replaced by 81. 4). and now that the pow(3. |_______________________________________________________ | | pow (base: 3) | (#1) (exp: 4) | | this method call is supposed 81 | to return 3 * pow(3. then the program returns back to main(). which then gets written into the variable x by the assignment statement. since the method call pow(3. the multiplication is completed: ________________________________________________________ | main | int x. ________________________________________________________ | main | int x. 4) call is over. 3) which -----. 3) which -----. it is replaced by its return value.

so the sum-of-digits of that number is just itself.e. is adding together the digits of a non-negative integer. i. i. Imagine we are trying to ﬁnd the sum of the digits in the integer 51460 (which is 16. i. (number − 1) 24 • 51457. but we’ll pretend we don’t know that yet).276 Another example we can consider. and none of them seem particularly promising – all of them have a greater sum of all digits than the original sum. For example: Number ---------0 4 92 305 45924 59480 Sum of digits ------------0 4 11 (9 8 (3 22 (4 26 (5 + + + + 2) 0 + 5) 5 + 9 + 4) 9 + 4 + 8 + 0) What subproblem is helpful in this case? Well. what smaller subproblem helps us out? • 51459. once again. let’s try a concrete example. What we really want to be doing is counting “fewer digits” for our recursive call: sumOf(51460) == sumOf(5146) + 0 sumOf(5146) == sumOf(514) + 6 sumOf(514) = sumOf(51) + 4 sumOf(51) = sumOf(5) + 1 sumOf(5) == 5 Note that once our number drops below ten. If our number is 51460. (number − 3) 22 • 25730. The above looks nice.e. we can use the modulus and division operators: num % 10 == the one’s place of the number (ex: 45926 % 10 == 6) num / 10 == all *but* the one’s place of the number (ex: 45926 / 10 == 4592) That gives us the following code: .e. (number/2) 17 Those are three possibilities. but how can be extract individual digits from a number? Well. we only have one digit.

277 public static int addDigits(int n) // assume n >= 0 { if (n <= 9) return n. else { int onesPlace = n % 10. return sumOfRest + onesPlace. int sumOfRest = addDigits(n/10). } } .

We can allow a height of 0 if we want – in that case. We will use recursion for these particular pictures because they will have a “recursive nature” to them – that is. they should send in a height that would enable something to be drawn. as a subproblem. we will NOT consider 0 a legal argument to the method. So. suppose we would like to draw a triangle of a given height. Either way is okay. assume we have a method to print out the same character repeatedly. will be part of the larger picture. num . you could write that using a loop as well. that method would need to know the height of the triangle: public static void drawTriangle(int height) Now. int num) { if (num > 0) { System. and handle it with a recursive call. i. as long as we clearly state the method’s expectations for anyone who wants to use the method.1).print(item). that borders the left side of the monitor: X XX XXX XXXX XXXXX If we want to write a method to do this. we could instead require a positive argument.278 Lecture 23 : Picture Recursion Today. and thus demand that if the client wants to draw a triangle. Now. a similar-butsmaller version of the picture. and so we could (potentially) treat the drawing of the similar-but-smaller version of the picture. Finally. having a negative height makes no sense at all. but as is the case for many of our examples. the method would do nothing (since there’s nothing to draw) and then return.e. for example. with no newline at the end: // assume that num is non-negative public static void printRepeatedChar(char item.out. printRepeatedChar(item. note that a triangle of height 5 contains a triangle of height 4 within it: . we will attempt to draw some pictures using recursion. On the other hand. Our choice in these examples will be to only allow a positive height. our goal is to demonstrate recursion. That is how this method works. 7) would print the following: XXXXXXX /|\ | next character to print. } } Certainly. goes right here Seven of the ’X’ character are printed. the call printRepeatedChar(’X’. so we’ll stick with the recursive version above. To start with. so we will at least assume the argument sent to that height parameter is non-negative.

is to ﬁrst draw a triangle of one smaller height. the width of the base was always equal to the height of the triangle. height). we ﬁrst draw a triangle of height 4. height).) doesn’t end a line. we get the following code: . we would end up with the following picture: XXXXXXXXXXXXXXX ^ cursor would be right here. and then add the base to make it a triangle of height 5. we need to end the lines ourselves. // draw smaller triangle printRepeatedChar(’X’. since in our triangle pictures. after last ’X’ Since printRepeatedChar(.. and then to add the base to make it a triangle of the desired height. That gives us the following code: // rough draft #1 public static void drawTriangle(int height) // height >= 1 { if (height > 1) { drawTriangle(height . Above. Once we ﬁx that.above: triangle of height 4 below: the base we add to get a triangle of height 5 XXXXX This suggests that one way to draw this picture.. // prints one ’X’ } } Note that we passed height in as the argument to printRepeatedChar(..1).. We actually have an error in the code! If we pass in 5 to the height right now. // draw base } else // height == 1 { printRepeatedChar(’X’.).279 X XX XXX XXXX -------------------. to draw a triangle of height 5.

println(). rather than duplicate that code in both cases. // draw smaller triangle printRepeatedChar(’X’. since there’s only one statement between them: // final version public static void drawTriangle(int height) // height >= 1 { if (height > 1) drawTriangle(height .out.out.println().1). } printRepeatedChar(’X’. height).println(). System. // start new line of triangle } else // height == 1 { printRepeatedChar(’X’.out. // start new line of triangle } } Finally. also appear in the if-case (only the comment is diﬀerent). System. we don’t need the curly braces for the if case. note that the two lines in the else case. height). } height) // height >= 1 // draw smaller triangle // draw base // start new line of triangle and since the else case only has an empty statement now. } else // height == 1 { . // draw base System. // prints one ’X’ System. it can be completely eliminated.280 // rough draft #2 public static void drawTriangle(int height) // height >= 1 { if (height > 1) { drawTriangle(height . // draw smaller triangle printRepeatedChar(’X’.println(). height). we could just pull it out of the conditional altogether: // rough draft #3 public static void drawTriangle(int { if (height > 1) { drawTriangle(height . height). } Next. Also.1). suppose we would like to draw a pyramid instead of a triangle.out.1). For example. here is a picture of a pyramid of height 5: // draw base // start new line of triangle .

the pyramid of height 5 was just a pyramid of height 4: X XXX XXXXX XXXXXXX followed by drawing the bottom line.1. just as we did for the triangle. the picture is merely smaller. Note also that the top four lines of that picture. the pyramid’s base – which is twice the triangle base. are also a pyramid – the shape is the same. the base of the pyramid of height 5. its top three lines form a pyramid of height 3: X XXX XXXXX In general. not twice. XXXXXXXXX So it would seem like we can do the following: DrawPyramid(int n) 1) Draw a Pyramid of height n-1 2) Draw the base of the pyramid of height n . minus one. X XXX XXXXX XXXXXXX And similarly. and so we should only count that once.281 X XXX XXXXX XXXXXXX XXXXXXXXX Note that the picture is basically just two triangles facing in opposite directions. pushed together so that their vertical edges overlap: X XX XXX XXXX XXXXX X XX XXX XXXX XXXXX X XXX XXXXX XXXXXXX XXXXXXXXX --------> that means that the base of the pyramid. we would like to describe a pyramid of height n. minus one – is equal to 2 * height . with a one-line base. for that pyramid of height 4. We subtract one because the vertical edges overlap. as being a pyramid of height n-1. is equal to twice the base of a triangle of the same height. For example. Since the base of our triangle picture is equal to the height of our triangle picture.

2 * height . there is an error with that code! If you run that with an initial argument of 5 you’ll get this: X XXX XXXXX XXXXXXX XXXXXXXXX What went wrong? Well.) This gives us the following code: // rough draft #1 public static void DrawPyramid(int height) // height >= 1 { if (height > 1) { DrawPyramid(height . And thus. we will choose n==1 as our base case. since we have not discussed indentation at all.1). } } However. that we would expect if we passed 4 as the initial argument to the method: X XXX XXXXX XXXXXXX . (That is. you could choose n==0 as the base case. be aware that this is not the only way it could be done. This is the pyramid of height 5: X XXX XXXXX XXXXXXX XXXXXXXXX and this is the pyramid of height 4.1). we have made a mistake when choosing our subproblem. and have the method simply print nothing at all in that case. 2 * height .println().out. System. } else // height == 1 { printRepeatedChar(’X’. if you wanted.out. we are still assuming that the pyramid that we print will border the left-hand side of the monitor.1).282 As with the triangle. too. // could also hardcode 1 // for second argument System.println(). and assume no one will send in an argument that would result in nothing being printed. we’ll assume no one will pass in an argument less than or equal to 0. Again. printRepeatedChar(’X’.

// indent the requested amount printRepeatedChar(’X’. is indented one space over from the triangle of height 4 that we drew above it. the pyramid of height 5 does NOT contain the pyramid of height 4. Our problem is therefore converted from “print a pyramid of a given height” to “print a pyramid of a given height and a given indentation”. indent + 1). That is the picture we want to draw recursively. So the lesson here is to be careful – sometimes. when in reality.1). Here is our code with the indentation modiﬁcation made: // rough draft #2 public static void DrawPyramid(int height. 2 * height . // indent the requested amount printRepeatedChar(’X’. We were close here. we need our recursive call to NOT assume the pyramid should be bordering the left-hand side of our monitor. indent). but not exact – we assumed the picture would magically be indented the right amount. you see that that triangle. the pyramid of height 5 contains this picture: X XXX XXXXX XXXXXXX If you look carefully. the computer will automatically print adjacent to the left-hand side of the monitor unless we state otherwise.println(). we can note that the two cases are the same except that the if case has a recursive call before everything else. { // indent >= 0 if (height > 1) { DrawPyramid(height . } } and as with the printing of the triangle. we can just put that one line in the conditional.println().out. so we will need to add an extra parameter in our examples – a parameter that will store the amount of indentation we want.out. the subproblem we think solves our problem isn’t really the one that solves our problem. } else // height == 1 { printRepeatedChar(’ ’. int indent) // height >= 1. The only way to send information to a method is via the parameters. 2 * height . indent). and have everything else out of the conditional entirely: .1). printRepeatedChar(’ ’. // could also hardcode 1 // for second argument System.1.283 and that being the case. System. Rather. Therefore.

// draw smaller pyramid printRepeatedChar(’ ’.1). 2 * height . { // indent >= 0 if (height > 1) DrawPyramid(height . indent + 1). } . // indent the requested amount printRepeatedChar(’X’. int indent) // height >= 1.println(). System.284 // final version public static void DrawPyramid(int height. indent).out.1.

first this line -------| | | |--. we need to decide on the actual problem: * * * * * * * * * * * * * * * * * * * * * * * * * ** * * * * * * * * If we have an ’X’ of height nine.285 If we want to draw an ’X’. ﬁrst. then recursively printing a smaller ’X’.finally this line * * * * * * * * * * * * But we will need to indent the recursive call’s output. we have a similar issue. Actually. Futhermore. so that the smaller ’X’ starts one space over to the right.this part is handled by a recursive call | | ------| <----. and stick with only odd heights and odd widths. or no? If we do. and then printing the last line of the ’X’: * * * * * <----. we’d need to match it to our choice above. so we get one of these two: * * * * * * * * * * * * * * * * * * * * * * * * * * ** ** * * * * * * * * Let’s simplify it for now. our goal is accomplished by printing the ﬁrst line of the ’X’. This gives us the following code: . do we allow an ’X’ of height ten. In that case. but the example on the right does not. meaning we will implement our ’X’ in the ﬁrst upper left example above. which of the above do we get? The example on the left has the two lines of the ’X’ cross in the middle.

That gives us the following code: . System. System.out. indent). height .2). } else { printRepeatedChar(’ ’.println(’*’). printRepeatedChar(’ ’.. } } As a ﬁnal example. height . but we’ll need to be able to indent the smaller picture when we print it. indent). printRepeatedChar(’ ’.out. System. there is a fan blade whose blade widths are each 4. System. PrintX(height . int indent) { if (height == 1) { printRepeatedChar(’ ’.out.2.out. printRepeatedChar(’ ’. System. ***** **** ------------| *** | ** | * |---** | *** | **** -----------| ***** here is a smaller fan blade So we can do the same sort of thing as in the previous two examples – we make a recursive call to print the smaller picture.println(’*’)..println(’*’).print(’*’).but within it. let us consider the drawing of a fan blade: ***** **** *** ** * ** *** **** ***** That is a fan blade whose blade widths are each 5.2). indent + 1). indent).out.print(’*’).286 public static void PrintX(int height.

System. indent).println(’*’). int indent) { if (width == 1) { printRepeatedChar(’ ’. System.out. } } . width).1). PrintFanBlade(width . System. printRepeatedChar(’ ’. indent + width . printRepeatedChar(’*’.287 public static void PrintFanBlade(int width. width). } else // width > 1 { printRepeatedChar(’ ’. printRepeatedChar(’*’. indent + 1). indent).out.out.println().println().1.

there are a number of paths to take from (curR + 1. you have two choices. Likewise. the total number of paths from (curR. For example. maxC) How many ways are there to get from (curR. So. curC) should be equal to the number of paths that begin by travelling to the right. then there are a number of paths to take from (curR. how many ways are there to get to (2. if you move down. curC + 1) . curC) to (maxR. curC + 1). however. or move down and then explore from (curR + 1. You either move right and then explore from (curR. curC) _______ | | . 3) Travelling only right or down (i. Say you have a 2-D array grid (0.. The important point. (curR. 3) _________________________ | | | | | | | | |______|________|________| | | | | | | | | |______|________|________| (2. | ___| (maxR. then you have a number of options from that point. is that those are the only two moves you can make.. maxC) travelling only right or downward? From every point. 0) (2. curC). (curR.. curC) -----------> | | | \|/ (curR + 1. curC) Once you take one of those two choices. plus the number of paths that begin by travelling down.. increasing numbers only). curC). curC + 1). 3)? More generally. 0) (0.288 Lecture 24 : Recursive Counting Let’s consider another problem: grid traversal. There are (curR. if you move right.e.

maxC). curC + 1. maxR. curC. curC. we can stop and count that as a path. when we’ve reached the destination. curC. That gives us the following so far – it’s still incomplete. int maxR. or maximum column. at best. So any point where curR > maxR or curC > maxC automatically has zero paths to the solution. that since the base case always returns zero. maxC). We’ve gone too far down. maxR. The problem is. That is. That gives us the following modiﬁcation to our code so far: public static int CountPaths(int curR. int curC. maxC) + CountPaths(curR. int maxC) { if ((curR > maxR) || (curC > maxC)) return 0. but it’s a beginning: public static int CountPaths(int curR. and thus cover all possible options. int maxR. else return CountPaths(curR + 1. the recursive calls will produce. That gives us the correct code: public static int CountPaths(int curR. maxR. curC + 1. if we always have to move to the right or downward. } So now we have a base case. else if ((curR == maxR) && (curC == maxC)) return 1. if we’ve passed up the maximum row. maxR. maxC) + CountPaths(curR. When do we actually have a correct path that we can count? And the answer there is. maxC). we can’t go back upward or to the left to reach it again. int maxR. int maxC) { return CountPaths(curR + 1. so we’ll allow ourselves a third operation as well – if we are at the destination. maxC) + CountPaths(curR. int maxC) { if ((curR > maxR) || (curC > maxC)) return 0. at that point and so the destination is now unreachable.289 no other paths to count because those two possible ﬁrst moves cover everything you can do as your ﬁrst move. zero. } . int curC. or too far to the right. we will count that as a correct path. and so the function will always return 0. maxR. maxR. } Now. curC + 1. we can never stop. int curC. else return CountPaths(curR + 1.

or exactly 6 ten dollar bills. You have the following possibilities for money: 10 dollar bills 5 dollar bills 2 dollar bills 1 dollar bills 50 cent pieces dimes nickels pennies // yes. plus 22 dollars without using 10 dollar bills tens. and as small as pennies. What is the maximum numbers of ways you can have a given quantity of money? Consider: Say you had 62 dollars. It’s possible we have 1. that means the other 52 dollars needs to come from money that is NOT ten dollar bills! Otherwise. well.290 Another example is making change. up to all combinations of 62 dollars where we have exactly 6 ten dollar bills. ﬁrst. We can’t have fewer than 0. plus all combinations that total 62 dollars where we have exactly 2 ten dollar bills. if we have one ten dollar bill. where we have exactly 1 ten dollar bill. we could say. or exactly 1. the rest of what’s left over needs to come from the rest of the list of money: 5 dollar bills 2 dollar bills 1 dollar bills 50 cent pieces dimes nickels pennies // yes. or 3. plus 32 dollars without using 10 dollar bills tens. or 4. The only problem is. where we have no 10 dollar bills. or 5. or exactly 2. plus 42 dollars without using 10 dollar bills tens. we already have 70 dollars and that is too much. plus 52 dollars without using 10 dollar bills tens. or exactly 3. But those are the only possibilities. then that total will be the total number of possible ways to have 62 dollars using the above money types – every combination has been represented exactly once in our total since every combination has either exactly 0. How do we do that? Well. and if we have more than 6. if we want to know how to make 62 dollars out of the above money. So. plus 62 dollars without using 10 dollar bills ten. such things exist! Now. if we add together all combinations that total 62 dollars. 10 dollar bills. and so on. or 6. plus 12 dollars without using 10 dollar bills tens. or exactly 5. we’d secretly be adding additional ten dollar bills! That is. or exactly 4. plus 2 dollars without using 10 dollar bills . let’s imagine we want to count all combinations that have exactly 1 ten dollar bill. You have various coin amounts. let’s ask how many 10 dollar bills we have? It’s possible we have none. okay. It’s also possible we have 2. such things exist! That gives us the following: 0 1 2 3 4 5 6 tens. and could use at most. plus all combinations that total 62 dollars.

plus 22 using at most bills worth 5 dollars tens. since if I say “make X dollars using only pennies”. Then. . such that: • every item is in at least one pile • no item is in more than one pile That is to say. In other words. In that case. plus 12 dollars using at most bills worth 5 dollars tens. the key is to divide your collection of items you are counting. into piles. plus 62 dollars using at most bills worth 5 dollars ten. and add those counting results together to get your total answer. Add up the total size of each category above and you have your answer. to guarantee we don’t accidentally add any additional 10 dollar bills. then there is exactly one way to do that – a pile of pennies large enough to total X dollars. we can say that the original problem was to “make 62 dollars using at most bills worth 10 dollars”. We decide how many 10 dollar bills we want. plus 32 dollars using at most bills worth 5 dollars tens. and then force ourselves to make the rest of the change from the lower dollar amounts. are: 0 1 2 3 4 5 6 tens. the subproblems whose solutions we add together for our answer. In both this problem and the paths problem. divide your collection into piles such that every item in the collection is in exactly one of the new piles. plus 2 dollars using at most bills worth 5 dollars Once we are down to our lowest money type – pennies in this case – we just return 1.291 That should cover every possibility. plus 52 dollars using at most bills worth 5 dollars tens. you recursively count those piles. plus 42 dollars using at most bills worth 5 dollars tens.

Since lo holds the value 0 and hi holds the value 9. on a smaller amount of data. So we are copying cells 1 through 9 of the original array. to cells 0 through 7 of an even newer array. . If we increase lo again. in the next step. How can we obtain this ”piece of the original array”? One way would be to copy parts of the original array to a new array. the goal of the recursive call would be to print part of the array. For example. If we do this. we could copy cells 1 through 9 of the original array to a new array. As with our earlier recursion examples. For example. this often means that we perform the same work on a piece of the original array. exactly the way we want. we only need to increase lo by 1. inclusive. we could copy cells 1 through 7 of that array into cells 0 through 6 of a still-newer array. And so on. Next. And then in the next step. we could increase lo by one. And then. and by changing the values of lo and hi we can change what subarray we are dealing with. we can simply use a pair of indices lo and hi to indicate what portion of the original array we are dealing with. if we had an array indexed from 0 through 9. we could copy cells 1 through 8 of that new array. Going back to the previous example. Portions of the original array – especially when lo and hi are used to indicate the low and high indices of that portion of the original array – are known as subarrays. and thus our code deals with the cells with indices 1 through 9 inclusive. So. rather than recopy all the relevant values to a new array. But this time. Our code could then be designed to deal with the cells with indices lo through hi. In the case of an array. the goal of the recursive call would be to ﬁnd the minimum value of part of the array. However. to go from one step to the next. we don’t need to create a new array or copy values from one array to the next. we can change the portion of the original array that a particular method call cares about. each step has one fewer cell than the step before it. inclusive. then we could have lo hold the value 0. lo holds the value 1 and hi still holds the value 9. to cells 0 through 8 of the new array. our code will be deal with the cells with indices 3 through 9 inclusive. the subsequent recursive call usually needs to run on a portion of the original array. to 2. if our original array were indexed from 0 through 9. Since we are copying nine cells there. this results in a lot of time spent copying and a lot of memory needed for the new array we create at each step. then code would then be dealing with the cells indexed from 0 through 9. the recursive call in these examples is solving a subproblem – performing the same work. Now. and now our code will deal with the cells with indices 2 through 9. if our goal is to print the entire array. rather than the entire original array. Each step deals with one fewer cell than the step before. we could increase lo again. inclusive. and the recursive call is designed to deal with everything but the ﬁrst cell. If our goal were to ﬁnd the minimum value in the array. When it comes time to deal with a smaller part of that array.292 Lecture 25 : Subarrays – Recursion on Data Collections When dealing with recursion on arrays. and hi hold the value 9. just as in our earlier example. and by changing lo and hi. the new array must be of size 9 – meaning it will be indexed from 0 through 8.

Thus our algorithm (minus the base case. though. starting with the lowest-indexed cell. } } If the original call to the pseudocode above had as arguments the integers 0 and 9. which we’ll add in a minute). then the next call has arguments 3 (lo + 3 is 3 if lo is 0) and 4 (hi divided by 2 is 4 if hi is 9). lo + 3. it is quicker than copying the array and easier to deal with as well. int hi) { if (recursive case condition is true) { // .. then the ﬁrst call is trying to print the values at indices 0 through 9. since the recursive call didn’t print any of those cells) Let’s build an algorithm out around the ﬁrst subproblem. int lo. the rest is handled by the recursive call) • printing the array from lo through hi-1. (Technically. i. and then // somewhere in here. looks like this: To print the array cells indexed lo through hi of an array "arr": 1) Print out arr[lo] 2) Recursively. from lo through (lo + hi)/2. some of the possible subproblems are: • printing the array from lo+1 through hi.other code here. let us consider printing an array. each of the three subproblems above could serve as the basis for an algorithm. the one we get from this subproblem will be the most straightforward.e. . (then you only need to print arr[hi] yourself. the rest is handled by the recursive call) • printing the array from lo through the middle of the subarray. And that call works by printing out arr[2] (the new value of lo in this call is 2) and then making a recursive call to print out the values at indices 3 through 9. hi / 2). we will need to have them as parameters to which we can pass arguments: ex: void DoSomething(int[] arr. (then you only need to print arr[lo] yourself. we make a recursive call DoSomething(arr. As an example.. That next call then operates on the part of the array from cell 3 through cell 4. (then you need to ﬁnd some way to print the cells with indices ((lo + hi)/2) + 1 through hi yourself. And so on.) If we want to print the cells in order by index. print the array cells indexed lo + 1 through hi of "arr" If initially lo was 0 and hi was 9.293 In order to be able to change lo and hi for each call. If we want to print the array from cell lo through hi. then we need to print arr[lo] on our own. before recursively printing everything with indices lo + 1 through hi. and does so by printing out the value at arr[0]. printing everything from lo+1 through hi. and then makes a recursive call to print out the values at indices 1 through 9. And that call works by printing out arr[1] (the new value of lo in this call is 1) and then making a recursive call to print out the values at indices 2 through 9.

} } But either one of those versions of the code will print the values with indices lo through hi inclusive.hi inclusive.. System.294 Once lo is greater than hi.out.out. // there is nothing to do and that gives us the following code: // prints all values of arr whose indices are in range lo. there are no cells in that range (i. } // else you do nothing } That’s all! If you had chosen to use lo.hi inclusive. hi).e. hi .1.... int hi) { if (lo <= hi) { print(arr. an empty subarray) and nothing left to print.hi .1).println(arr[lo]). print the array cells indexed lo + 1 through hi of "arr" } else // we have no cells in our subarray . . int lo. So that can be our base case: To print the array cells indexed lo through hi of an array "arr": if (lo <= hi) // we have at least one cell in our subarray { 1) Print out arr[lo] 2) Recursively.println(arr[hi])... int lo. then you need to make the recursive call ﬁrst (thus printing all values with indices lo through hi . lo. inclusive) and then print the value at index hi after the recursive call has completed: // prints all values of arr whose indices are in range lo. // in order from lowest to highest index public static void print(int[] arr. lo+1.1 as the subarray instead. int hi) { if (lo <= hi) { System. from the lowest-indexed value to the highest-indexed value. print(arr. // in order from lowest to highest index public static void print(int[] arr.

. perhaps the client would like to make the following method call: print(arr). arr. one possibility is to write another method: public static void print(int[] arr) { // some code goes here } to which the client could simply send the array reference as an argument.. but in both cases.1). Clients have a choice of how they want to activate the printing of their array.) directly.295 Now. Since our method needs the lo and hi parameters in order for the recursion to work. Wrapper methods are usually relatively simple. That is.1). Unfortunately. 0. Perhaps a client would simply like to say.. The “glue” between this method and the recursive one we wrote earlier. the second method call would be required. In the case of our print methods. it requires that the client send in some initial values as arguments to those parameters.. the work that gets done is to . 0 and arr. “print the array” and have it be assumed that the request means.length .length .5). 0. is nice – it means the client could also do this if they wanted to only print part of the array: // one example of an alternate method call that could be made print(arr. is that the above method can call the recursive one. situations where the client only wants to print part of the array. the call to the longer. perhaps the clients of this method don’t want to bother having to pass in indices as arguments. clients can either call the recursive print(. and only a little bit of work gets done before or after that statement. right now. arr. arr. So.1 are likely to be the most common arguments. from index 0 through the highest index of the array”.. with three arguments.) with three argumebts. That is. and not the indices as well. but even though the client could choose to pass in any pair of bounding indices as the second and third arguments.length .length .. more interesting method is generally the most important statement of the wrapper method.) with one argument. A wrapper method does exactly as the name implies – it “wraps” around an already existing method that does something similar – by making a call to that already existing method – and thus provides a diﬀerent interface to that method. the most common situation is that the client wants to print the entire array. would probably be less common. and that method can call the recursive print(.. it’s the three-parameter print(. The ﬂexibility this allows. rather than the method call: print(arr. } Such a method is known as a wrapper method..) that does most of the work. In the wrapper method above. or they can call non-recursive print(. “print the entire array. 3. with the most common arguments! public static void print(int[] arr) { print(arr.

That “set-up work” – the choosing of the initial values of lo and hi – only needs to happen once. and to calculate what the second of those two index arguments should be (i. and then once the call to the recursive method is made and returned from.1). the wrapper method might do a little bit more work before the call to the longer. to do before or after that method call. But generally.length . to calculate arr. there is only a little bit of work. which we discussed back when we introduced constructors. this wrapper method has nothing else to do.) . we do that work in a wrapper method. Instead. at most. so it should not be in the call to the recursive method itself. and it might even do some work after that method call returns as well. In other cases.296 select initial arguments to the recursive method’s lo and hi parameters. they are distinguishable by the compiler even if they have the same name. As long as the methods have diﬀerent parameter lists. Wrapper methods don’t tend to be very complex. (Note that naming both methods the same – such as print in our examples above – is ﬁne. before the call to the recursive method. more interesting method. they basically exist to help set up a situation where the more interesting method can get called.e. This is an example of method overloading.

We see that in the ﬁrst sequence above. 49. 49. the ﬁrst and last values are not the same. we want to ask if some subsequence is a palindrome. p. In the second sequence. and in fact. well. for example – 49. then our sequence matches the x. 49) is not a palindrome either. lo+1. not palindrome else return isPalindrome(arr. // if outer values are not same. 21. then x. 18 are both sequences that read the same forward and backward. If the ﬁrst and last values are the same. 18 18. Otherwise. the following three sequences are NOT palindromes: 18. is not a palindrome. then perhaps a good second question to ask is. 49 is a palindrome.. p. so you have a diﬀerent ﬁrst value when you read the sequence left-to-right versus right-to-left. 49. we have both problems – the ﬁrst and last values are not the same. 22. 49. where x is some single value. and to ask if the sequence between them is a palindrome. the second and second-to-last values are the same as well. 18 18. will be a palindrome sequence as well. A palindrome is a word or number that reads the same forward and backwards. More generally. let’s consider the detection of palindromes. 49. int hi) { if (arr[lo] != arr[hi]) return false. if p is some palindrome sequence. public static boolean isPalindrome(int[] arr. 18. For example. the ﬁrst and last values are the same. 21. 21. 49. and thus could be considered palindromes. but the middle sequence. x. we still have a palindrome. we have this: // we aren’t quite done yet. In the third sequence. 49. 19 In the ﬁrst sequence. there’s our recursion! So far. one useful question might be to ask if the same value is listed ﬁrst and last. 49 constitutes a palindrome. int lo. 49. // check inner section } If the ﬁrst and last values are not equal. 49. We can consider a sequence to potentially be a palindrome as well. Therefore the sequence is not a palindrome. since our overall intention is to ask if the entire *sequence* is a palindrome. and if we put 18 at the beginning and end of that sequence. This suggests that if you are given a sequence such as 18. these two sequences: 18. 21. 21. hi-1). So. 21. 21. we know we do not have a palindrome.. 19 18. if 49. such as RACECAR or 18481. and if everything but the ﬁrst and last values constitutes a palindrome. our answer relies on whether the middle section is a palindrome or not. 49. it would seem helpful to ask if the ﬁrst and last values of a sequence are the same. x pattern and would itself be a palindrome. 22. However. 22. If the answer to either of those questions is “no”. and as part of that task. 21. 21. just as we were discussing above. 49. and the sequence between them (49. . if everything but the ﬁrst and last values constitutes a palindrome – in this case. 22. 21. Now. If the ﬁrst and last values are the same. then we do not have a palindrome. 49.297 As another example. 21.

In the above case. the client expected the wrapper method to print a message. 0.length . 0. hi-1). the recursive method returns a value based on whether the subarray is a palindrome. arr.out. . then it’s the same sequence forwards and backwards. they still want to have a true or false returned to them. if (result == true) // could also have just said "if (result)" System. even if the client wants to call the wrapper method. For example. then it is empty whether you read it forwards or backwards. we eventually reach the point where the middle section is so small that it must be a palindrome and we don’t really need to do any processing to ﬁgure that out. so the wrapper method needs to return the value that the call to the recursive method will generate. arr. not palindrome else return isPalindrome(arr. int hi) { if (lo >= hi) return true. however..298 Of course. and the method it called. In our print(. // size 0 and 1 collections read the same both directions else if (arr[lo] != arr[hi]) return false. If instead. else System.1). // if outer values are not same.. int lo. } In this case. then of course the sequence must be a palindrome If you have just one value in a sequence. // check inner section } We could then write a wrapper method for this as follows: public static boolean isPalindrome(int[] arr) { return isPalindrome(arr. And. if you have an empty sequence. then the wrapper method could have been written like this instead: public static boolean isPalindrome(int[] arr) { boolean result = isPalindrome(arr. } So.length .1). if the sequence is of size 0 or 1. a wrapper method doesn’t even need to do exactly the same sort of thing as the method it calls. and the wrapper method instead prints a message based on whether the subarray is a palindrome.println("array does not hold a palindrome sequence"). both the wrapper method.) example. This gives us the following code: public static boolean isPalindrome(int[] arr. lo+1.println("array holds a palindrome sequence").out. printed out values that were stored in the array.

That error checking would not need to be done with each recursive call – it would only need to be done once. ”if this is not a square matrix. In the above case. we want four indices – two to indicate the low and high row indices. else return arr[loR][loC] + addDiagonal(arr. // shouldn’t read this if isSquare == false } and then our wrapper method could do this: . int loR. let’s consider adding the diagonal elements of a two-dimensional array that has the same number of rows and columns. that is a bad approach. 0. In this case. since we know that cannot be a legal sum. else return addDiagonal(arr. we’d have no way of knowing whether it was meant to signify an error. and two to indicate the low and high column indices. arr. loC+1. we might write a class as follows: public class Result { public boolean isSquare. public static int addDiagonal(int[][] arr.length-1). arr[0].length != arr[0]. } Or in other words. we know it must be an error signal.length) return -1. So. if you like. return -1. int loC. For example. otherwise return the actual sum of the diagonal”. or if instead -1 was the actual sum of a diagonal on a square array. hiR. } You can get away with fewer parameters. we might use a wrapper method to see if the matrix we are trying to add the diagonal of is actually a square matrix. hiC). if we knew our matrix would contain only positive integers – and therefore we knew that the sum could never be -1 along the diagonal – we might write a method like the following: public static int addDiagonal(int[] arr) { if (arr. we can put it in a wrapper method and then call the recursive method from there. before the recursion began. since it is just some set-up work. If the array could hold any integers. loR+1.length-1.299 As a ﬁnal example. 0. Consider that an exercise for the reader. int hiC) { if ((loR > hiR) && (loC > hiC)) return 0. If we return -1. since if we get -1 back as a return value. So in that case. int hiR. public int sumIfExists.

The client. so we will put them in a wrapper method. 0. arr. and would read the sum variable to get the diagonal sum. arr[0]. } return pair. } In the error case. not the actual recursive code where those lines would be repeated in every call. would know it was a signal that the sum doesn’t really exist. then the client would know that it was indeed a square matrix.isSquare = false. we would return a reference to a Result object where the isSquare variable is false.length) { pair.length-1). pair. However. 0. if there was a true in the isSquare variable.length-1. and would not bother reading the sum variable. if (arr. pair. upon reading that false value in that variable.sumIfExists = -1.length != arr[0].sumIfExists = addDiagonal(arr. . The point is that the setup code (the error check) and the cleanup code (taking the return value of the recursive call – if we did make the recursive call – and writing it into the Result object) each only need to be done once. // who cares? Client shouldn’t read this anyway } else { pair.300 public static Result addDiagonal(int[] arr) { Result pair = new Result().isSquare = true.

. For example: --------------------------------------A[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 lo == 0. so we again recursively check the index range lo+1.. So our next recursive step is over the index range 2. we could recursively search the rest of the array. if all we knew was that we had some collection of values to inspect. One way to choose the subproblem is to have our recursive call simply be to search everything but the ﬁrst cell for the value 39. hi == 9 So now again we check A[lo].... So. so we again recursively check the index range lo+1. and there was no information we could take advantage of to rule out any of the values.. then we’ll recursively search the rest of the array. ------------------------------A[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 lo == 2. so we will recursively check the index range lo+1.hi. and if it is not 39... This is the problem we are going to discuss now. then we would need to inspect each and every one of them. we are done. we are checking A[1]. That is. and if it is 39. or searching a list of names for a particular name.. we could check A[lo] (which is A[0] here). We’ll start with an array of integers – and thus we are searching the array for a particular integer – but later on we’ll talk about what we might have to worry about if the type were diﬀerent. hi == 9 In the example above.. let’s imagine we are searching an array for the value 39..hi we did not ﬁnd our value in A[lo]. That is.301 Lecture 26 : Searching Today.9 (we’ll indicate this in the next diagram with the shorter horizontal line above our array values).. we will start by thinking back to lecture 1. hi == 9 We check A[lo] (which this time is A[2]) and that is not equal to our value 39. where we were discussing searching for a penny under a box. we can check the ﬁrst cell.9. We had said that.9. we have not found our value yet.. given the index range lo. So.hi. we are going to search an array for a particular value. next we check the index range 1. since the only way we know how to store a collection of information is in an array. And A[1] is not our value 39. but this time since lo is 1. Since A[lo] is not 39.. That is. .hi. So our next recursive step is over the range 3. ----------------------------------A[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 lo == 1. Speciﬁcally.

302 --------------------------A[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 lo == 3, hi == 9 We check A[lo] (which this time is A[3]) and that is not equal to our value 39, so we again recursively check the index range lo+1...hi. So our next recursive step is over the range 4...9. ----------------------A[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 lo == 4, hi == 9 And now, when we check A[lo], it is equal to our value 39. We have found a cell containing 39. There is no need for further work. That is the basic idea behind our algorithm. We’ll check the ﬁrst cell of our range, and if that cell’s value is not equal to 39, then we’ll recursively check the rest of the range (everything in the range except the ﬁrst cell). This raises a number of questions, and the answers to these questions will enable us to take our rough sketch of what we want to do, and produce a complete algorithm from it, by ﬁlling in the rest of the details. • We have outlined how the recursive case should work, but what is the base case? • What should be returned by our algorithm? • How can we generalize our algorithm to search for integers other than 39? More interestingly, what if we are not searching for integers at all? What if our array contains a diﬀerent type, and we are searching for a value of that type? We will consider these questions in order. First, when dealing with the base case, we have to ask ourselves, for what size subarray do we know for sure whether the value exists or not? Well, if the subarray were of size 1, we would still need to check that cell, but if the subarray were of size 0, then we are certain our value is not there, because nothing is there. So, we could have lo > hi be the condition that would trigger the base case – the condition that indicates our subarray deﬁned by lo and hi is of size 0. Next, we need to consider what to return. We are asking if 39 is in the array or not, and so the answer is either “yes” or “no”. When you are limited to such answers, a boolean works very nicely – you return true if the value is in the array and false if it is not in the array. However, there might be a better choice. If all we care about is knowing whether the value is in the array or not, a boolean is ﬁne, but we might care about retrieving the value as well, or more speciﬁcally, retrieving the cell it is in. For example, perhaps we want to know if 39 is in the array, and, if it is, we want to later replace it with 40. In that case, we want to know not just that 39 is in the array, but also, at what index it is located. So instead of returning true or false, what we could do is return a pair of values: a boolean and an integer. This sort of return type is often useful, because there are often situations where we

303 run a method on a collection of data but we only have a meaningful return value in certain cases – such as our searching example above, where we can only return “the index where our value is located” if the value actually exists in the array. In those cases, if we have no meaningful data to send back, we have to send back some meaningless data of some kind (because something needs to be returned), so returning the boolean along with the data means you can use the boolean to check if you should bother to read your data or not. Since we can only return one value, any attempt to return a “pair” must make use of an object of some kind. For our search method, the following could be the necessary class:

304 public class ReturnPair { public boolean statusFlag; // true if found, false if not public static int index; // meaningful only if the boolean is true } // returned result is therefore one of the following: // (true, index_where_value_is_located) // (false, meaningless_integer_that_we_will_not_bother_reading) and then our search method would have a return type of ReturnPair and the method would have to allocate a new ReturnPair() from inside the method, so that there was some object to return. This is a way to get around the only-one-return-value restriction and, in doing so, return a collection of information from the method – just make your “one value” that you returned, an address of a dynamically-allocated object, rather than a value of primitive type. However, though conceptually, returning this pair of information is what we want to do, we actually can achieve this with only one integer and nothing else. We can take advantage of the fact that arrays have a limited index range – namely, 0 through length-1, where length is whatever the length of the array is. So, if you were to return a negative number, or some number greater than or equal to the length of the array, then that return value could be checked by the client of your algorithm. If the returned value is within the index range of the array, it would interpreted as a “true” – i.e. the value is in the array – and the returned integer is then assumed to be the index of the cell containing 39. On the other hand, if the returned value is NOT in the index range of the array, then that is interpreted as a “false” – i.e. the value 39 is not in the array – and the client knows not to use the returned index to access the array. We get the beneﬁts of returning a boolean (knowing whether the value is there or not) along with the beneﬁts of knowing where the value is, if it is there. And we can do this while still returning just one integer, rather than having to create a ReturnPair object and return that. Since -1 is always outside the index range of any array, it’s a nice return value to use for this purpose, and that’s what we’ll use. Finally, we are not likely to always need to search for 39. More likely, the client will have some particular value in mind to search for. And if it’s something the client speciﬁes, then probably it should be a parameter. We often refer to such a value – i.e. the value to be searched for – as a key or search key. We could then compare the value in an array cell, to the parameter, rather than to 39 speciﬁcally. Putting all of that together, leads to the algorithm expressed with the following Java code:

305 // LinearSearch // - parameters : arr - the array to search // : key - the value to search for // : lo - the low index of this subarray // : hi - the high index of this subarray // - return value : array index or -1 // - if key is in A, returns index where it // is located. Otherwise, returns -1. public static int LinearSearch(int[] arr, int key, int lo, int hi) { if (lo > hi) return -1; else if (arr[lo] == key) return lo; else return LinearSearch(arr, key, lo+1, hi); } Note that we could also write a wrapper method to call the above method, a wrapper method that would only need from the client the things the client actually cared about – namely, the array to be searched and the key to be searched for: // LinearSearch // - parameters : arr - the array to search // : key - the value to search for // - return value : array index or -1 // - if key is in A, returns index where it // is located. Otherwise, returns -1. public static int LinearSearch(int[] arr, int key) { return LinearSearch(arr, key, 0, arr.length-1); } In the recursive method, we have two base cases. If we actually ﬁnd our value, we return a real index, and if we reduce the subarray to size 0, we return a -1 to serve as a “not found” indicator. -1 is not a real index, so if our search algorithm returns it, we know it is because the key was not found in the array. But if we have not found our key and yet there is more of an array to the right of our current low cell, then we should search that array – hence the point of the recursive call. As algorithms get more complex – though admittedly, this algorithm is not terribly complex – we sometimes like to have only one return statement in our algorithm, so that there are not multiple exit points to keep track of. If we wanted to make such a modiﬁcation above, it’s a simple matter to just store the returned value in a variable and return it at the end:

306 public static int LinearSearch(int[] arr, int key, int lo, int hi) { int returnVal; if (lo > hi) returnVal = -1; else if (arr[lo] == key) returnVal = lo; else returnVal = LinearSearch(arr, key, lo+1, hi); return returnVal; } The code would be similar, but not identical, if we were dealing with diﬀerent types. For example, if we were searching for a String, we can’t use == to compare String objects for equality, but must instead use the equals instance method: public static int LinearSearch(String[] arr, String key, int lo, int hi) { if (lo > hi) return -1; else if (arr[lo].equals(key)) return lo; else return LinearSearch(arr, key, lo+1, hi); } We might also search on part of an object. For example, what if our Clock class from the objectoriented-programming lectures, had an instance method public int getHour() that returned the hour? In that case, perhaps we then wish to search a collection of Clock objects for one that is set to a particular hour. In that case, the key would be of type int, even though the array itself was of type Clock instead of int: public static int LinearSearch(Clock[] arr, int key, int lo, int hi) { if (lo > hi) return -1; else if (arr[lo].getHour() == key) return lo; else return LinearSearch(arr, key, lo+1, hi); } And if we were searching for a double, well, recall that computer arithmetic can introduce rounding errors. So, if we tried something like this:

307 public static int LinearSearch(double[] arr, double key, int lo, int hi) { if (lo > hi) return -1; else if (arr[lo] == key) return lo; else return LinearSearch(arr, key, lo+1, hi); } we might not get correct results because we might have done the computations to ﬁll the array one way, and gotten a value such as 24.839523176, and yet, when we computed our key before passing it into this method, we might have tried doing the same computation, but put the steps in a diﬀerent order, and thus had a diﬀerent sort of rounding error and ended up with 24.839523172 instead. On MP1, we said it was okay to have those sort of diﬀerences in the ﬁnal decimal place like that. But if we tried doing an exact comparison, as with the code above, well, those two numbers are, technically, diﬀerent, and thus our comparison-for-equality (==) will evaluate to false. So, if we want to compare ﬂoating-point values for equality, we generally want to see if they are equal to within some acceptable error range, rather than seeing if they are precisely equal: public static int LinearSearch(double[] arr, double key, int lo, int hi) { if (lo > hi) return -1; else if ((arr[lo] > key - 0.000000005) && (arr[lo] < key + 0.000000005)) return lo; else return LinearSearch(arr, key, lo+1, hi); } Now, we will claim we have a match, as long as A[lo] is within 0.000000005 of key. All these examples follow the same basic algorithm – we just have diﬀerent code to implement the “equality” comparison, depending on exactly what sort of comparison we are trying to do.

308 Now, this algorithm – however we implement it – doesn’t take into account the possibility that the search key occurs multiple times in the array. If there were duplicate instances of the search key – for example, if we are searching for 39 and it occurs ﬁve times in the array – what can we do about that? Well, in many cases, handling duplicates requires just a small modiﬁcation of your non-duplicate algorithm. However, there are also often a few ways that duplicates can be handled, and so before we can modify the non-duplicate algorithm to deal with duplicate-handling, we need to decide exactly how we want to handle duplicates in the ﬁrst place. A few of the possibilities are listed below: • return the index of the ﬁrst occurence (i.e. occurence with lowest index) of the key in the array (return -1 if it never appears) • return the index of the last occurence (i.e. occurence with highest index) of the key in the array (return -1 if it never appears) • return the number of times the key appears in the array • return a new array holding as its values all indices where the key appears in the original array The ﬁrst possibility is what our ﬁrst LinearSearch algorithm does already – that algorithm was designed to start returning an index as soon as it found our search key, so if there were many other instances of the search key in the array, we would never see them since we wouldn’t even inspect those cells. Likewise, the second possibility could be implemented simply by having our current LinearSearch algorithm search from hi to lo rather than from lo to hi – if we are trying to ﬁnd the last (i.e. rightmost) instance of our search key, well, that’s just the ﬁrst one we will see if we start our search from the right instead. The third possibility, though – counting the number of occurences – will require that we always look over every cell of the array. If there were some cell we did not inspect, well, our total might be wrong, depending on what our assumption was. If we assumed the value in that cell was indeed our search key, and it wasn’t, our total would be too big by 1. On the other hand, if we assumed it wasn’t equal to our search key, and it was, then our total would be too small by 1. So we need to look at every cell. So instead of just inspecting the cell, as in the regular LinearSearch, we will want to inspect the cell, and also add one to a total if the value in the cell is equal to our search key. The base case is then in charge of initializing the total, since in the base case we have no instances of the key in this subarray – it follows from that, that the base case should return 0 (the total number of occurences of our key in the subarray of size 0). public static int CountingLinearSearch(int[] arr, int key, int lo, int hi) { if (lo > hi) return 0; else if (arr[lo] == key) return 1 + CountingLinearSearch(arr, key, lo + 1, hi); else return CountingLinearSearch(arr, key, lo+1, hi); } Note that in both the case where arr[lo] is equal to our key, and the case where it isn’t, we still need to make our recursive call. This is because we need to count how many times the key occurs

309 in the other cells, regardless of whether the key is in arr[lo] or not. So, we could rearrange that code a bit so at least the same method call doesn’t appear twice in the code: public static int CountingLinearSearch(int[] arr, int key, int lo, int hi) { if (lo > hi) return 0; else { int total = CountingLinearSearch(arr, key, lo + 1, hi); if (arr[lo] == key) return 1 + total; else return total; } } Either form is acceptable. And, as with the earlier, non-duplicate version, we could also have arranged this so that we have only one return statement, if we wanted: public static int CountingLinearSearch(int[] arr, int key, int lo, int hi) { int returnValue = 0; if (lo <= hi) { returnValue = CountingLinearSearch(arr, key, lo + 1, hi); if (arr[lo] == key) returnValue = returnValue + 1; } return returnValue; } which looks much less like the original LinearSearch but is still correct. If the way we want to handle duplicates is to return an array containing the indices where the duplicates are located, then we’ve got a few issues to worry about there as well. The big conceptual issue is what to return if we have an unsuccessful search, and here we have two options. Our return type must be “integer array reference” (i.e. int[]), if we are returning an array of integers – all the array indices will be integers, so if we want to return an array of indices, we are returning an array of integers. And if our return type is an integer array reference, we can always return null if there is no array to return. The upside to that is, we don’t need to allocate an array object if there is an unsuccessful search. The downside is, we have no object returned so we need a conditional to treat the unsuccessful search as a special case:

We will leave that as an exercise for you to try. Our code below would be calling some wrappper // method around the recursive version. Sometimes.out. we’ll need to be changing the array size as we ﬁnd additional occurences of the search key.").length + " occurences of the key. So. instead of returning null: // in our hypothetical LinearSearchForDuplicates method.println("There were " + result. key).").length + " occurences of the key. or else you could write a helper method (whether recursive or not) to accomplish the same task. will always be a reference to an array. else System. not the recursive version itself. if we intend to return an array of exactly the right size. Our code below would be calling some wrappper // method around the recursive version. we can do this: // Example client code calling a hypothetical version of linear // search that returns an array of all indices where search key // is located. even in the case of an unsuccessful search. And hence.). to copy values to a new array. // with // return new int[0].println("There were 0 occurences of the key. key). Now. and not bother to continually check to see if our returned array even exists.out. the client code doesn’t need to make a special check to see if the returned reference is null – we know whatever is returned. if (result == null) System.310 // Example client code calling a hypothetical version of linear // search that returns an array of all indices where search key // is located. System. replace // return null. . int[] result = LinearSearchForDuplicates(arr. You could either put a loop inside your recursive algorithm. having to deal with the lack of an array object as a separate case can become troublesome. as we found more and more occurences of our search key. The other issue that would need dealing with would be the increasing of the size of the array to be returned. and then call that method from your algorithm. Either way.println("There were " + result. not the recursive version itself.out. int[] result = LinearSearchForDuplicates(arr. we also have the option of returning an array of size 0.

We will do things the ﬁrst way – we’ll simply deﬁne “ﬁnd the minimum” to be an operation that makes no sense on subarrays of size zero. On the other hand. so return its index. but how can you “return the minimum value of an empty collection”? There’s nothing to return! So.311 We can also consider another search problem: ﬁnding the minimum value in an array. then the minimum of the remaining cells is the minimum of all the cells. • Alternatively. } } . we could have a special case that returns -1 in cases where the subarray is of size 0. You could search an empty collection (you would not ﬁnd your value). public static int findMinimum(int[] arr. else return locOfMinOfRest. compare that result to the ﬁrst cell’s value.e. hi). int lo. and thus is the overal minimum. if the minimum of the remaining cells is also less than the ﬁrst cell. Otherwise. If the array is of size 1. More speciﬁcally. In this case. it is diﬃcult to even deﬁne the problem if the subarray we are searching is of size 0. Subarrays of size 0 won’t be allowed. since we are returning the index of the minimum. Then. design your method to assume that the parameter subarray has size at least 1. Now. ﬁnding the minimum value in an array can be solved recursively. else { int locOfMinOfRest = findMinimum(arr. then certainly that one cell is the minimum. If the ﬁrst cell’s value is lower than the minimum of the remaining cells. rather than the minimum itself. if (arr[lo] <= arr[locOfMinOfRest]) return lo. lo + 1. we want to return the index where the minimum value is located. and you could print an empty collection (you would print nothing). int hi) { if (lo == hi) return lo. then by deﬁnition it is lower than all the remaining cells. all the cells but the ﬁrst one) recursively. you have two options: • You could simply say that ﬁnding the minimum of a collection only makes sense if you actually have values in the collection – and as a result. ﬁnd the minimum of the rest of the cells (i.

.. ≤ xin Sorting is one of the most common operations done on data. from being in the wrong order to being in the correct order.. . then we will probably often need to exchange the position of two values in the array.e.. we can store arr[j] in arr[i]. sorting a collection of values means to arrange the elements in a speciﬁc order – usually in increasing order (though not always). we are merely going to look at a few sorting algorithms. so in that case we’d say that we are (usually) arranging values non-decreasing order. arr[j] = temp.. int i. xin . int i. if we are trying to rearrange values inside an array so that they are in order. Our swap(. At this point. we will pass in the array itself. More generally.. public static void swap(int[] arr. xi2 . instead. we should turn our attention to swapping. int j) { int temp = arr[i]. and ﬁnally we can write the value in the temporary variable into arr[j].. so that: xi1 ≤ xi2 ≤ xi3 ≤. then we want to arrange them in some speciﬁc order xi1 . and the two indices.. arr[i] = arr[j]. First. though. So. our swap is complete. } . arr[j] = arr[i].. Right now. and as a result. int i. . public static void swap(int[] arr. int j) It is not enough to say public static void swap(int[] arr. not perform any serious analysis of them. from a diﬀerent method. So. .. we erase the value in arr[i] by overwriting it with the value in arr[j].312 Lecture 27 : Sorting As you might expect. the end result of the above code will be that the value which is stored in arr[j] to start with gets written into arr[i] and then copied back into arr[j] as well. x2 .) method will then read the two values at those indices and write each of them into the other cell. To swap the values at two indices. We will be studying a number of sorting algorithms this semester and talking about their advantages and disadvantages. Then. x3 . we need to set up a temporary variable to store arr[i] so that we don’t lose it. i. xn . And we have lost the original arr[i] for good. xi3 .. there could be duplicate values. if our values are: x1 . it is a problem that has been exhaustively studied. } because in the ﬁrst line. int j) { arr[i] = arr[j].

Which unsorted orderings they end up in along the way doesn’t make any diﬀerence. and the easiest way to do that is to just swap it with 93. we prefer to swap. and will take longer. have been sorted yet anyway. So. we can now design an algorithm known as selection sort. you just place the sorted collection after that smallest value which you had set aside. The way we can “set the smallest value aside” once we’ve found it. we can pull 6 out of the collection by moving it to the front of all the other remaining values. if this was our array: arr[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 then findMinimum(arr. one way you might sort things in real life is to ﬁnd the smallest value in your collection and set it aside. and the findMinimum(. is to swap it to the beginning of the array. you are left with a pile of the remaining values – all of which come after that smallest value that you set aside – and you would need to sort that collection. Then. nor the values in the other cells indexed 1 through 9. For example. And that gives us the following basic outline for selection sort: . we’re going to sort them anyway. the value currently at the front of the collection: arr[i] 6 11 71 93 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 It’s not a problem to move 93 to the middle of the array. and 6. since shifting doesn’t give us any beneﬁt in this case. Once you sort the remaining values... since neither 93. What does selection sort do? Well. 0. We could also have ﬁgured out some way to shift the values between the beginning of the array..313 Given this swap(. and for no beneﬁt – once we have moved 6 to the front of the array... to make room for 6: arr[i] 6 93 11 71 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 but that can potentially take much longer (more array writes have to be done in that case). to the right. We already know how to ﬁnd the smallest value in an array – that is what our findMinimum(.) method from the last lecture.) method. we don’t care how the values in cells indexed 1 through 9 are arranged – since we don’t know for sure they are sorted.) method will do. since that is where the minimum (6) is located.. and you are done. Now. 9) would return the value 3.

314 public static void selectionSort(int[] arr. hi). this is then a useless swap. minLocation). lo. since there’s no other values with which our one value can be mis-ordered. // in that case.1). // (2) Swap minimum value into first cell of // subarray (sometimes lo will equal minLocation. } . int hi) { if (recursive case) { // (1) Find index of minimum value of subarray int minLocation = findMinimum(arr. int hi) { if (lo < hi) // subarray is at least size 2 { int minLocation = findMinimum(arr. once we have only one value left. lo + 1. there’s nothing to do. by deﬁnition.length . Any subarray of only one cell is sorted. That means the base case would simply be an empty statement. and so we don’t even need to bother coding that. // (3) recursively sort the remainder of the array selectionSort(arr. } } We could also write a selectionSort wrapper method that will take only the array as an argument: public static void selectionSort(int[] arr) { selectionSort(arr. swap(arr. lo. but // also a harmless one swap(arr. int lo. lo + 1. And similarly. an empty subarray is also sorted. lo. selectionSort(arr. well. arr. minLocation). by deﬁnition. hi). int lo. hi). That gives us our ﬁnal selection sort code: public static void selectionSort(int[] arr. hi). lo. as far as the base case goes. } else // base case // what do we do here? } Now. 0.

swap the value in cell 8 with the value at our low index – namely. which is located in cell 8. all that remains is to recursively sort the values in the subarray with indices 3 through 9. index 2 – so that the minimum is placed in the ﬁrst cell in our subarray. Now. (Note that this doesn’t really do anything. but does no harm. So.) Now. We are attempting to sort the subarray with indices 2 through 9. where we are sorting the subarray with indices 2 through 9. 0. We are attempting to sort the subarray with indices 1 through 9. index 0 – so that the minimum is placed in the ﬁrst cell in our subarray. so we would have that swap in our code. and in the cases where the miniumum happens to already be in the ﬁrst cell of our subarray. that has started oﬀ the following example. The minimum value over those indices is 6. on a sample array of size 10. So. index 1 – so that the minimum is placed in the ﬁrst cell in our subarray. arr[i] 6 11 71 93 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Start of selectionSort recursive call #2. the minimum will be somewhere other than the ﬁrst cell in our subarray. But in general. since the minimum has stayed in the same cell. swap the value in cell 1 with the value at our low index – namely. where we are sorting the subarray with indices 0 through 9. The minimum value over those indices is 23. It is assumed we have made the call selectionSort(arr. arr[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Start of selectionSort recursive call #1. all that remains is to recursively sort the values in the subarray with indices 1 through 9.315 Here is an example run of the algorithm. The minimum value over those indices is 11. the swap is useless and thus wastes a bit of time. So. which is located in cell 3. . all that remains is to recursively sort the values in the subarray with indices 2 through 9. and we will be done. arr[i] 6 11 71 93 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Start of selectionSort recursive call #3. We are attempting to sort the subarray with indices 0 through 9. where we are sorting the subarray with indices 1 through 9. Now. swap the value in cell 3 with the value at our low index – namely. and we will be done. which is located in cell 1. 9). and we will be done.

which is located in cell 9. . all that remains is to recursively sort the values in the subarray with indices 4 through 9. which is located in cell 4. and we will be done. and we will be done. where we are sorting the subarray with indices 6 through 9. The minimum value over those indices is 39. where we are sorting the subarray with indices 3 through 9. The minimum value over those indices is 67. swap the value in cell 9 with the value at our low index – namely. and we will be done. swap the value in cell 9 with the value at our low index – namely. So. The minimum value over those indices is 54. Now. swap the value in cell 6 with the value at our low index – namely. So. Now. We are attempting to sort the subarray with indices 4 through 9. arr[i] 6 11 23 39 41 67 93 88 71 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Start of selectionSort recursive call #6. arr[i] 6 11 23 39 41 54 93 88 71 67 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Start of selectionSort recursive call #7. where we are sorting the subarray with indices 5 through 9. which is located in cell 9. and we will be done. all that remains is to recursively sort the values in the subarray with indices 6 through 9. index 5 – so that the minimum is placed in the ﬁrst cell in our subarray. We are attempting to sort the subarray with indices 6 through 9. where we are sorting the subarray with indices 4 through 9. We are attempting to sort the subarray with indices 3 through 9. swap the value in cell 4 with the value at our low index – namely. index 3 – so that the minimum is placed in the ﬁrst cell in our subarray. Now. The minimum value over those indices is 41. Now. which is located in cell 6. all that remains is to recursively sort the values in the subarray with indices 5 through 9. index 4 – so that the minimum is placed in the ﬁrst cell in our subarray.316 arr[i] 6 11 23 93 39 67 41 88 71 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Start of selectionSort recursive call #4. So. arr[i] 6 11 23 39 93 67 41 88 71 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Start of selectionSort recursive call #5. all that remains is to recursively sort the values in the subarray with indices 7 through 9. So. We are attempting to sort the subarray with indices 5 through 9. index 6 – so that the minimum is placed in the ﬁrst cell in our subarray.

and we will be done. all that remains is to recursively sort the values in the subarray with indices 9 through 9. until ﬁnally call #2 returns to call #1.) Now. (Again. index 7 – so that the minimum is placed in the ﬁrst cell in our subarray. swap the value in cell 8 with the value at our low index – namely.317 arr[i] 6 11 23 39 41 54 67 88 71 93 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Start of selectionSort recursive call #8. We are attempting to sort the subarray with indices 7 through 9. which called selectionSort a second time. anyway). There is never any more work to do when we return to a previous call – the recursive call is the last work each call does – and so basically at this point call #10 just returns to call #9. We are attempting to sort the subarray with indices 9 through 9. which is located in cell 8. swap the value in cell 8 with the value at our low index – namely. the rest was just ending the methods we had started. one by one. and so on. and so on. which called selectionSort a third time. this is a swap that happens to be useless. where we are sorting the subarray with indices 7 through 9. so that we could return back to whatever method ﬁrst called selectionSort(. We are attempting to sort the subarray with indices 8 through 9. and at last.) to begin with. and we will be done.. which is located in cell 8. we actually have 10 method calls to selectionSort active. The minimum value over those indices is 88. In our selection sort code. We’ve never returned from any of those selectionSort calls. The method main() (or whatever) called selectionSort the ﬁrst time. call #1 returns back to main() (or returns back to whatever called it. So. where we are sorting the subarray with indices 9 through 9. it is in the “proper order” since there is nothing to be out of order with – and so we could simply return. our array is down to only one element. but harmless. where we are sorting the subarray with indices 8 through 9. Note that right before we return. . But now that we’ve completed our base case. all that remains is to recursively sort the values in the subarray with indices 8 through 9. arr[i] 6 11 23 39 41 54 67 71 88 93 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Start of selectionSort recursive call #10.. index 8 – so that the minimum is placed in the ﬁrst cell in our subarray. we will start to return from those calls. Now. call #9 immediately returns to call #8. Therefore. arr[i] 6 11 23 39 41 54 67 71 88 93 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Start of selectionSort recursive call #9. So. The minimum value over those indices is 71. there is nothing to sort – certainly if you have only one element. the lo < hi condition would be false in this case. call #8 immediately returns to call #7. The array was completely sorted at the end of the base case call (call #10). since the bounding indices are identical. In this case.

318 arr[i] 6 11 23 39 41 54 67 71 88 93 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Final sorted array (same as array right before returning from selectionSort call #10) .

We can assume we already have k sorted values. For example. we have k sorted values.1 of an array of size k + 1. in sorted order. stored in indices 0 through k . arr = temp.1 == 11). sorted order – into a new.length + 1]. whose length is one greater int[] temp = new int[arr.1. if we had wanted to insert 70 into our above array of size 12. temp. For example. When we are done. and k were 12. That code doesn’t play a role in what we are about to discuss.319 Next. if lo were 0. Fortunately. we need to make space for it – and thus we’d need to copy everything into an array whose length was one cell larger than our current array: // assume arr is a reference to our current array // create a new array. if this had been our initial array: arr[i] 6 11 17 23 39 41 47 54 67 71 88 93 -----------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 then the above code will expand the size of the array by one (by copying the values – in their current. // meaning that nothing will point to the old array anymore. the cell at index k – empty and available for a new value. This leaves the last cell – the new cell. // copy values from arr into temp for (int i = 0. let us consider what is involved in inserting a new value into an already sorted array. we then have room for our new value! That’s where the fun begins – let’s copy our new value into the last cell of the array – the extra cell we just created. i++) temp[i] = arr[i]. i < arr. so we don’t // lose any of our values. and then we write 70 into that new. // so the system collects it and eliminates it. then we have 12 sorted values indexed from 0 through 11 (0 + 12 . If we want to write a new value into that array. the reference arr should point to our new array. // now. indexed from lo through lo + k . // we have copied all the values to our new array. we are just reminding you how you could “add an array cell to the end of your array”. For example. larger array): arr[i] 6 11 17 23 39 41 47 54 67 71 88 93 ----------------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 12 Once we have everything copied to the larger array.length. last cell: arr[i] 6 11 17 23 39 41 47 54 67 71 88 93 70 ----------------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 12 . we ﬁrst increase the size 12 array to a size 13 array.

empty. . with one new value at the end that is not necessarily in the correct order. our subarray would be indexed from lo through hi. since right now. . by expanding the size of the sorted array by one cell. hi-1 hi and we would now like to move newValue to the correct spot in the array. that we send to recursive call: arr[i] |<---. just on smaller arrays: // for example (hi in this picture is smaller than hi in the // above picture) arr[i] |<. . .this entire section is sorted ----->| newValue ----------------------------------------------------------i lo lo+1 lo+2 . . not 0 through 12): arr[i] |<------. .this entire section is sorted ----->| newValue ----------------------------------------------------------i lo lo+1 lo+2 . So which subproblem would help? Well. hi-1 hi That is the setup we want to discuss – a sorted collection of values. . In the case of our concrete example: arr[i] 6 11 17 23 39 41 47 54 67 71 88 93 70 ----------------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 12 we would like to move 70 to its proper place between 67 and 71. So. above. . So. And that gives us the following situation (in general. now we have this situation from above: arr[i] |<------. if we want the entire array to be sorted. as we described further above. . hi-3 hi-2 hi-1 . hi-1 hi That would be the sort of problem a recursive call would solve – still inserting a new value on the right into a sorted section on the left. . that is the new. imagine smaller versions of this problem – i. . imagine the same setup. .. . we can reach this setup point.this entire section is sorted ----->| newValue ----------------------------------------------------------i lo lo+1 lo+2 . . available cell. if we want to insert a value into a sorted array.this entire section is sorted --->| newValue -------------------------------------------------------i lo lo+1 lo+2 . . and then putting the new value in that new cell at the end of the array. . But we will put the new value at the end just for a moment..e.320 That might not be where the new value belongs! Certainly.this entire section is sorted ->| newValue ------------------------------------------------i lo lo+1 lo+2 . not after 93. . 70 belongs between 67 and 71. what if the subarray you send to the recursive call is only one cell smaller? // current picture: arr[i] |<------. . hi-3 hi-2 hi-1 hi // subarray one smaller in size.only the sorted section is smaller. . And.

So why not swap them? For example... arr[i] 6 11 17 23 39 41 47 54 67 71 88 70 93 ----------------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 12 // . if this were our original problem: arr[i] 6 11 17 23 39 41 47 54 67 71 88 93 70 ----------------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 12 Then what we are looking for. we could now run that algorithm recursively. we have our basic recursive step. which we are claiming we can solve recursively. if this were our original problem: // original problem. then the cells from lo to hi-1 now form our subarray that we can send to the recursive call. then if we trust that it works.. and “insert new value at index 11 into sorted section from indices 0 through 10 inclusive”.321 If we had some way of converting the top picture into the bottom one. arr[i] 6 11 17 23 39 41 47 54 67 71 88 93 70 ----------------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 12 // . If in the top picture. in our example. We solved the original problem.. while still keeping everything from lo through hi-2 sorted. so it should be to the right of 70 anyway. For example. “insert new value at index 12 into sorted section from indices 0 through 11 inclusive”.. “insert new value at index 11 into sorted section from indices 0 through 10 inclusive”.this entire section is sorted ---->| 70 ??? ----------------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 12 |_______________________________________________| send this to recursive call How could we obtain that situation with minimal work? And what about the value to the right of 70? What should it be? Well. newValue is at cell hi.. is some way of getting this situation: arr[i] |<---.. leaving us with the problem.but 93 > 70.and now we have subarray for recursive call! arr[i] 6 11 17 23 39 41 47 54 67 71 88 70 93 ----------------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 12 |______________________________________________| Since our algorithm is supposed to “insert new value at far right into sorted section to the left”. Our overall problem is to insert 70 into the sorted section of values from 6 through 93. so swap the two values. then if we can move it to cell hi-1.. once we . by swapping the values at indices 12 and 11. 93 is greater than 70.

arr[i] 6 11 17 23 39 41 47 54 67 71 88 93 95 ----------------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 12 // ..... So something’s gone wrong here. which is the greatest value in the sorted range. Remember that everything to the left of 95 is sorted – and thus 93 is the greatest value in the sorted range. arr[i] 6 11 17 23 39 41 47 54 67 71 88 95 93 ----------------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 12 // . so swap the two values.. since 95 is greater than 93. it is 95?: // original problem. our new value.322 swap 70 and 93. If 95 is greater than that. What if instead of our new value being 70.. we know that 95 is also greater than everything that 93 is greater than. Above. we have 93 to the right and that is less than 95 – which we’ve now moved to its left – so there’s at least part of the ﬁnal array that will be out of order. And it’s easy to tell if the new value is greater than everything in the sorted range – just compare it to the value on its left. it must also be greater than everything else in the sorted range. we can make a recursive call to that would insert 70 into the sorted section of values from 6 through 88. so far. arr[i] 6 11 17 23 39 41 47 54 67 71 88 93 95 ----------------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 12 In this case. This is one of our base cases.. we can leave it right where it is. // original problem. is greater than the value to its left. since once we establish that the new value is the greatest of the values we are dealing with. we know it is correctly placed (as it is at the end) and so we don’t need to move any of the values anymore. that would not work in all cases. Now...but 93 > 70.and now we have subarray for recursive call! arr[i] 6 11 17 23 39 41 47 54 67 71 88 95 93 ----------------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 12 |______________________________________________| In this case.meaning 95 should be placed after the entire sorted range – exactly as it is right now! Or in other words – if our new value is greater than everything in the sorted range. So. we could insert 95 into the sorted section from 6 through 88... 95. we have the following code: .. 93. Let’s take a look at that original problem closely. but even once that is done.

.hi-1 is a sorted range.1] { // swap(arr. hi).. int lo.. Furthermore. If our new value is in one of the cells. This gives us our ﬁnal version of insertInOrder(.1). hi). insertInOrder(arr.1). That’s our other base case. If the sorted section to the left was completely empty.): . So we don’t need to check for lo > hi. the above code assumes that we have two values to compare.1. // 2) run the subproblem } } Note that we are assuming the array is of at least size 1. int hi) { if (arr[hi] >= arr[hi . as it is above. // 1) swap last two values insertInOrder(arr. hi . lo. It’s the “trivial” case. hi . // we don’t need to do anything.1]) // last value is greatest . and hi holds the new value public static void insertInOrder(int[] arr. note that we can eliminate the ﬁrst two cases. hi . as we said above else // arr[hi] < arr[hi ..1] { swap(arr.hi-1 is a sorted range. since they don’t actually do anything – all they have is empty statements. You’re inserting one new value into a subarray with one (empty) cell. // we don’t need to do anything else if (arr[hi] >= arr[hi . int hi) { if (lo == hi) // subarray is of size 1 . placing it in cell lo is the correct way to go..1]) // last value is greatest . where there’s nothing else there yet and thus nothing that your new value could be out of order with. // we don’t need to do anything else // lo < hi and arr[hi] < arr[hi .1. we can’t compare arr[hi] and arr[hi-1] since arr[hi-1] would not exist! // all we have here is arr[hi]. and hi holds the new value public static void insertInOrder(int[] arr. hi . // the subproblem we discussed earlier } } Now. So.. not arr[hi-1] arr[i] newValue ------------------------------------------------i lo == hi If your original sorted array was empty. lo. then no matter what the new value is.323 // assume lo. the array has to be at least size 1. let’s add that to our code: // assume lo. we might not. int lo.

hi == 11 arr[i] 6 11 17 23 39 41 47 54 67 71 88 70 93 ----------------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 12 // 70 < 88. in selection sort. it is the ﬁrst thing done. hi == 10 arr[i] 6 11 17 23 39 41 47 54 67 71 70 88 93 ----------------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 12 // 70 < 71. and recursive call has lo == 0.) calls total.. including the base case: // original problem: lo == 0. in insertion sort. called insertion sort. hi == 12 arr[i] 6 11 17 23 39 41 47 54 67 71 88 93 70 ----------------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 12 // 70 < 93. and then sorting the remainder of the unsorted values. hi). lo. so swap. and then insert the last value into the sorted collection.1. The insertion sort algorithm works in a slightly diﬀerent manner than selection sort did. do nothing } If we ran this algorithm on our original example array. and recursive call has lo == 0. // 2) run the subproblem } // else new value is already properly inserted..324 // assume lo. the recursive call is the last thing done. and recursive call has lo == 0. int hi) { if ((lo < hi) && (arr[hi] < arr[hi .1).. hi .hi-1 is a sorted range. Both sorting algorithms build an increasingly-larger sorted array as the algorithm proceeds. int lo. we’d need four insertInOrder(. and hi holds the new value public static void insertInOrder(int[] arr..1])) { swap(arr. so swap. That is. . so we’ve hit our base case and we are done We can use this algorithm as the basis for another sorting algorithm. hi == 9 arr[i] 6 11 17 23 39 41 47 54 67 70 71 88 93 ----------------------------------------------------------i 0 1 2 3 4 5 6 7 8 9 10 11 12 // 70 > 67. whereas insertion sort will sort most of the values ﬁrst. hi . so swap. // 1) swap last two values insertInOrder(arr. The diﬀerence is that selection sort builds the array by selecting a speciﬁc value (the minimum) from the unsorted values.

1). on an array of size 10 (the beginning of this gets a little repetitive. Once this is done. make two method calls – the ﬁrst. int hi) { if (lo < hi) { insertionSort(A.325 Description of insertion sort algorithm: If the array is of size 1. a recursive call to insertionSort(.. a call to the insertInOrder(.). hi). but I wanted to be complete so that everyone could see every detail if they wanted): arr[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Recursive call #1 Array as we start to sort elements at indices 0 through 9 Since the array is of size greater than 1.) method we wrote earlier: public static void insertionSort(int[] arr. we decide to sort the values at indices 0 through 7 ﬁrst. lo... into the sorted collection to its left. Otherwise recursively sort all but the last cell of this subarray. // everything from lo through hi-1 is // sorted. The code for this algorithm. we can then insert the value arr[9] into its proper place among the sorted elements in cells 0 through 8. insertInOrder(A.. and if so. int lo. Once this is done. hi . lo. we decide to sort the values at indices 0 through 8 ﬁrst. insert the value in the last cell. But ﬁrst we must recursively sort cells 0 through 7!! . and the second. do nothing. arr[hi] is "new value" } } Here is an example. But ﬁrst we must recursively sort cells 0 through 8!! arr[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Recursive call #2 Array as we start to sort elements at indices 0 through 8 Since the subarray is of size greater than 1. Then. we can then insert the value arr[8] into its proper place among the sorted elements in cells 0 through 7. basically just needs to check if the array size is greater than 1.

Once this is done. we decide to sort the values at indices 0 through 3 ﬁrst.326 arr[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Recursive call #3 Array as we start to sort elements at indices 0 through 7 Since the subarray is of size greater than 1. we decide to sort the values at indices 0 through 4 ﬁrst. we can then insert the value arr[4] into its proper place among the sorted elements in cells 0 through 3. But ﬁrst we must recursively sort cells 0 through 4!! arr[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Recursive call #6 Array as we start to sort elements at indices 0 through 4 Since the subarray is of size greater than 1. we can then insert the value arr[7] into its proper place among the sorted elements in cells 0 through 6. we decide to sort the values at indices 0 through 5 ﬁrst. Once this is done. Once this is done. But ﬁrst we must recursively sort cells 0 through 5!! arr[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Recursive call #5 Array as we start to sort elements at indices 0 through 5 Since the subarray is of size greater than 1. we can then insert the value arr[6] into its proper place among the sorted elements in cells 0 through 5. Once this is done. we decide to sort the values at indices 0 through 6 ﬁrst. But ﬁrst we must recursively sort cells 0 through 6!! arr[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Recursive call #4 Array as we start to sort elements at indices 0 through 6 Since the subarray is of size greater than 1. we can then insert the value arr[5] into its proper place among the sorted elements in cells 0 through 4. But ﬁrst we must recursively sort cells 0 through 3!! .

and return to call #9 to ﬁnish it up. For example. But now we will start to return to them one by one and do more work. and so on. We’ve never returned from any of the insertionSort calls. we decide to sort the values at indices 0 through 0 ﬁrst. Once this is done. but in a moment we will ﬁnish call #10. and from there we called insertionSort a second time.327 arr[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Recursive call #7 Array as we start to sort elements at indices 0 through 3 Since the subarray is of size greater than 1. Once this is done. Once this is done. Note that right now we have ten diﬀerent method calls active at once! From main(). And so on – one by one we will return to the previous . But ﬁrst we must recursively sort cells 0 through 0!! arr[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Recursive call #10 Array as we start to sort elements at indices 0 through 0 Now that the array is down to size 1. we are in recursive call #10 right now. we will then return to call #8 and ﬁnish it up. we called insertionSort. because the ﬁrst instruction in each call was to call insertionSort again on a smaller array. But ﬁrst we must recursively sort cells 0 through 2!! arr[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Recursive call #8 Array as we start to sort elements at indices 0 through 2 Since the subarray is of size greater than 1. and from there we called insertionSort a third time. we can then insert the value arr[1] into its proper place among the sorted elements in cells 0 through 0. we decide to sort the values at indices 0 through 1 ﬁrst. we can then insert the value arr[2] into its proper place among the sorted elements in cells 0 through 1. Once we ﬁnish call #9 up. But ﬁrst we must recursively sort cells 0 through 1!! arr[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Recursive call #9 Array as we start to sort elements at indices 0 through 1 Since the subarray is of size greater than 1. we can then insert the value arr[3] into its proper place among the sorted elements in cells 0 through 2. that is our base case. we decide to sort the values at indices 0 through 2 ﬁrst.

but note that 11 is less than 71. This results progressively swapping 11 with the element to its left until either 11 is less than the element to its left. and we need to insert the value in cell 2 into that sorted subarray in sorted order. that consists of swapping our value – 6 in this case – with the value to its left until either the element to its left is less than 6. and so we stop. since lo == hi) and we return from recursive call #10. when we return from call #9 back to call #8. also array right after returning to recursive call #7.328 recursive call and ﬁnish it. the subarray consisting of cells 0 through 1 is sorted. The subarray with indices 0 through 0 is already sorted – it is just one element and that element is of course in its proper order. we swap 71 with 93. until ﬁnally we are returning from the very ﬁrst insertionSort call back to main(). So. arr[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Array after returning to recursive call #9. Again. and then realize there are no more elements to the left of 6. note that we have sorted the elements in cells 0 through 2. Here. And now that we have ﬁnished recursive call #8. And thus we are done with recursive call #7. since there is nothing to be out of order with. Again. which sorted the elements at indices 0 through 0. And thus we are done with recursive call #8. we have ﬁnished recursive call #10. or there are no more elements to 11’s left. And now that we have ﬁnished recursive call #9. as was the point of this method call. the lo < hi condition would be false here. or there are no elements to the left. At this point. when we return from call #8 back to call #7. and then 11. the subarray consisting of cells 0 through 2 is sorted. Since arr[0] is 93. and so we don’t swap those two. note that we have sorted the elements in cells 0 through 1. as was the point of this method call. Thus. back to recursive call #9. and at that point the array will be sorted and we will be ﬁnished. Thus. and then 71. we are done with this method call (in the code. we swap 11 with 93 and then 11 is at the far left so we stop. Now. also array right after returning to recursive call #8. arr[i] 11 93 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Array right before returning from recursive call #9. we swap 6 with 93. Now. and we are going to insert the value at index 1 into the sorted subarray consisting of the elements at indices 0 through 0. and we need to insert the value in cell 3 into that sorted subarray in sorted order. This recursive call #10 that we are in now is the case where making a further recursive call would either be pointless or would not make any sense. arr[i] 11 71 93 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Array right before returning from recursive call #8. . that consists of swapping our value – 71 in this case – with the value to its left until either the element to its left is less than 71. or there are no elements to the left. Here.

that consists of swapping our value – 41 in this case – with the value to its left until either the element to its left is less than 41. the subarray consisting of cells 0 through 5 is sorted. and then 67. note that we have sorted the elements in cells 0 through 4. And thus we are done with recursive call #6. and then 71. or there are no elements to the left. Again. and then 71. Now. Again. and we need to insert the value in cell 5 into that sorted subarray in sorted order. Here. note that we have sorted the elements in cells 0 through 5. when we return from call #5 back to call #4. And thus we are done with recursive call #5. Thus. the subarray consisting of cells 0 through 3 is sorted. as was the point of this method call. and then realize that 39 is less than 67 and so we stop. or there are no elements to the left. Thus. also array right after returning to recursive call #6. Now. And thus we are done with recursive call #4. and then realize that 39 is less than 41 and so we stop. and we need to insert the value in cell 6 into that sorted subarray in sorted order. arr[i] 6 11 39 67 71 93 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Array right before returning from recursive call #5. Here. when we return from call #6 back to call #5. and we need to insert the value in cell 4 into that sorted subarray in sorted order. and then realize that 11 is less than 39 and so we stop. arr[i] 6 11 39 71 93 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Array right before returning from recursive call #6. Here. we swap 39 with 93. the subarray consisting of cells 0 through 4 is sorted. as was the point of this method call. we swap 41 with 93. or there are no elements to the left. And now that we have ﬁnished recursive call #7. note that we have sorted the elements in cells 0 through 3. Thus. And now that we have ﬁnished recursive call #5. we swap 67 with 93. that consists of swapping our value – 39 in this case – with the value to its left until either the element to its left is less than 39. also array right after returning to recursive call #4. Now. . And now that we have ﬁnished recursive call #6. as was the point of this method call. and then 71. Again. also array right after returning to recursive call #5. when we return from call #7 back to call #6.329 arr[i] 6 11 71 93 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Array right before returning from recursive call #7. that consists of swapping our value – 67 in this case – with the value to its left until either the element to its left is less than 67.

and then realize that 41 is less than 54 and so we stop. . And now that we have ﬁnished recursive call #3. as was the point of this method call. Here. we swap 88 with 93. and then ﬁnally we realize that 11 is less than 23 and so we stop. note that we have sorted the elements in cells 0 through 7. when we return from call #2 back to call #1. and then realize that 71 is less than 88 and so we stop. And thus we are done with recursive call #2. as was the point of this method call. note that we have sorted the elements in cells 0 through 6. And now that we have ﬁnished recursive call #4. And thus we are done with recursive call #1. Thus. and then 71. the subarray consisting of cells 0 through 7 is sorted. Again. when we return from call #3 back to call #2. Thus.330 arr[i] 6 11 39 41 67 71 93 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Array right before returning from recursive call #4. And thus we are done with recursive call #3. and we need to insert the value in cell 9 into that sorted subarray in sorted order. also array right after returning to recursive call #2. Now. and we need to insert the value in cell 7 into that sorted subarray in sorted order. arr[i] 6 11 39 41 67 71 88 93 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Array right before returning from recursive call #3. and then 88. note that we have sorted the elements in cells 0 through 8. Again. we swap 23 with 93. and then 67. Here. Now. or there are no elements to the left. Again. or there are no elements to the left. or there are no elements to the left. and then 88. and then 39. when we return from call #4 back to call #3. that consists of swapping our value – 23 in this case – with the value to its left until either the element to its left is less than 23. and then 71. the subarray consisting of cells 0 through 6 is sorted. and we need to insert the value in cell 8 into that sorted subarray in sorted order. Now. also array right after returning to recursive call #1. that consists of swapping our value – 54 in this case – with the value to its left until either the element to its left is less than 54. that consists of swapping our value – 88 in this case – with the value to its left until either the element to its left is less than 88. we swap 54 with 93. arr[i] 6 11 23 39 41 67 71 88 93 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Array right before returning from recursive call #2. the subarray consisting of cells 0 through 8 is sorted. and then 41. and then 67. And now that we have ﬁnished recursive call #2. Here. as was the point of this method call. Thus. also array right after returning to recursive call #3.

Then we insert a new value into that collection. And that’s insertion sort! The algorithm is named “insertion sort” because it basically consists of running an “insert new value into a sorted collection in sorted order” procedure over and over again. At this point. in sorted order. so that we have a sorted collection of size 4. for each new value we see. where we kept “selecting” the smallest value. so that we have a sorted collection of size 3. in sorted order. the array is sorted. Then we insert a new value into that collection.331 arr[i] 6 11 23 39 41 54 67 71 88 93 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Array right before returning from recursive call #1. out of the unsorted collection of values we still had left. And so on. so that we have a sorted collection of size 2. in sorted order. . We have a collection of size 1. Then we insert a new value into that collection. This is in contrast to “selection sort”. and we return from recursive call #1 back to main().

int lo. else { int locOfMaxOfRest = findMaximum(arr.1). and thus the same algorithm. and the one we’ll use for this course.. we kept selecting the minimum of what was left and moving it to cell lo. int lo.332 It works in either direction! While we have described the two sorting algorithms we have seen so far.. is the one where you select the minimum each time.. it’s still eﬀectively the same idea. we could always select the maximum of what was left.hi-1. lo. and move it to cell hi. and then sorting lo+1. int hi) { if (lo < hi) { int maxLocation = findMaximum(arr. swap(arr. hi . { if (lo == hi) return lo. For example. we could always insert a new value into the sorted range to its right. lo + if (arr[lo] >= arr[locOfMaxOfRest]) // return lo.) to assume the “new value” is at the far left. } } int hi) 1.hi. and that you are inserting it into a sorted range to the right: . in their “standard” direction. arr[lo] is max value recursive result is max value public static void selectionSort(int[] arr. rather than always insert a new value into the sorted range to its left... and then sort lo. hi. just from the other direction: public static int findMaximum(int[] arr. selectionSort(arr. hi). Instead. please note that if we had done things from the other direction. for insertion sort. It’s the same basic idea. the “oﬃcial” version of selection sort. We start by modifying insertInOrder(. for selection sort. lo. else // arr[locOfMaxOfRest] > arr[lo] --> return locOfMaxOfRest. } } However.. maxLocation). Similarly. hi).

lo... else if (arr[lo] <= arr[lo + 1]) // if arr[lo] is lowest.333 // inserts "new value" at arr[lo] into sorted range from // arr[lo+1] through arr[hi] public static void insertInOrder(int[] arr. leave it . is the one we went through in detail earlier.)... hi). lo + 1. int hi) { if (lo < hi) { insertionSort(arr. insertInOrder(arr. do nothing } public static void insertionSort(int[] arr. lo + 1.then insert ‘‘new value’’ into // smaller sorted subarray to the right } // else. hi). else // lo < hi and arr[lo] > arr[lo + 1] { swap(arr. int lo. lo + 1. insertInOrder(arr. int hi) { if ((lo < hi) && (arr[lo] > arr[lo + 1])) { swap(arr. swap. // . // everything from lo+1 through hi is // sorted.then insert ‘‘new value’’ into // smaller sorted subarray to the right } } Of course. // .. int lo. the “oﬃcial” version of insertion sort. lo). lo). that code can have the base case code eliminated.. insertInOrder(arr. and then we just rework the insertionSort(.. swap.. // arr[lo+1] is lowest. . hi). and the one we’ll use for this course. arr[lo] is "new value" } } However.. lo + 1. // arr[lo + 1] is lowest..) algorithm in the same way: // inserts "new value" at arr[lo] into sorted range from // arr[lo+1] through arr[hi] public static void insertInOrder(int[] arr. you are done . arr[lo] is where it belongs... lo + 1. int hi) { if (lo == hi) // if one cell. hi). int lo. just as with our earlier version of insertInOrder(.

. like the print(.) call returns. we still need to execute an assignment statement (to write the return value into the variable returnVal) and then we need to execute a return statement (to return the variable returnVal).. The ﬁrst is called tail recursion. if (lo > hi) returnVal = -1. Instead.hi inclusive. and thus you reach the closing curly brace of the method. to being the return value of this method. hi). We don’t actually perform any additional computation. you never need to do anything in that particular recursive call again.334 Lecture 28 : Tail Recursion and Loop Conversion What we will do today is discuss the two types of recursion. lo+1. return returnVal. into returnVal. So the above code is basically just a longer form of this code: . is our method for printing out the cells of an array: // prints all values of arr whose indices are in range lo. and thus return from the method). else returnVal = linearSearch(arr. int hi) { int returnVal.. all we do after that. key. we are returning a value of type int. That is okay. print(arr. else if (arr[lo] == key) returnVal = lo.println(arr[lo]). Many of the recursive methods we have already seen. are return with nothing. the method itself returns (the compound statement ends. or perhaps return with an already-calculated value. once the recursive call returns. once you call print(. int lo. and then we copy that value from returnVal. after the print(. are tail-recursive. } In the case of linearSearch(. Another example of a tail-recursive method would be our linearSearch(. hi). int hi) { if (lo <= hi) { System..) method did. We just copy the value from being the result of a recursive-method-call expression. So.. int lo. however. int key. lo + 1. Tail recursion is when the recursive call is the last computation you do in the recursive case of your algorithm..out. // in order from lowest to highest index public static void print(int[] arr. is copy the value into a local variable. since once the recursive call returns a value to us..) method: public static int linearSearch(int[] arr.). you do not have a return value of void..) recursively. One example... } // else you do nothing } Above. The only things you can do after your recursive call returns.. nor do we copy the value to places outside the scope of this method. and then return it.. and thus the if-statement ends. That is.

int key. in both the print(. else return linearSearch(arr. key. . is near the top of the method.. we would immediately run a return statement. we immediately turn around and return that value. and so even though the actual line of code that triggers the recursive call. no additional computation needs to be done once the recursive call returns. else return lo.) and linearSearch(. The fact that in the ﬁrst version. } else // lo > hi return -1. } In that case. as soon as the recursive call returns. as we see in the second version above. the issue is how much computation is performed once the recursive call is over. int lo.. int hi) { if (lo <= hi) { if (arr[lo] != key) return linearSearch(arr. hi).) examples. else if (arr[lo] == key) return lo. without having written it into any variables in-between. int key. doesn’t change the fact that the code is tail-recursive. int lo. we wrote the result of the recursive call into a variable before we returned it. hi).335 public static int linearSearch(int[] arr. int hi) { if (lo > hi) return -1... it would still be a tail-recursive method: // an alternate way to code linearSearch public static int linearSearch(int[] arr. you still are done with the method once that recursive call returns. key. } In that case.. lo+1.. once the recursive call returns. since the assignment statement could easily be eliminated. lo + 1. And. Where the recursive call is placed on the page doesn’t have any particular aﬀect on whether a recursive method is tail-recursive or not.) so that the cases were in a diﬀerent order. Note that even if we re-wrote linearSearch(.

} // n >= 0 Basically.336 The second kind of recursion is known as forward recursion. then it is forward-recursive. if a recursive method is not tail-recursive. so if you could somehow “end the recursive process” right at the base case. consider our print(.) method again. and vice-versa. nothing signiﬁcant gets done once you start returning from the recursive calls... An example was the factorial method from earlier. This is recursion where you do need to do computation work after you return from the recursive call. where we needed to still do a multiplication once we returned from the recursive call: public static int fac(int n) { if (n > 0) return n * fac(n . Here’s how the method calls would look. For example.1). They are of special interest to us. and so we’ve sent 0 and 3 as the initial arguments to lo and hi. because the fact that they are tail-recursive makes them very easy to convert into loop-based methods! The reason for this is. else // n == 0 return 1. as we made our way down to the base case: . Our focus today will be on tail-recursive methods. Let’s assume arr points to an array of size 4. you’d be ﬁne.

337 __________________________________________________________________ | main | print(arr. all that is left. then calls print(arr. 3). | |_________________________________________________________________ | print (arr: [pts to array]) | (#1) (lo: 0) (hi: 3) | | prints arr[0]. | |__________________________________________________________________ | print (arr: [pts to array]) | (#4) (lo: 3) (hi: 3) | | prints arr[3]. There is no more work to do. if while we are in the base case. 1. | |__________________________________________________________________ | print (arr: [pts to array]) | (#2) (lo: 1) (hi: 3) | | prints arr[1]. 4. | |__________________________________________________________________ | print (arr: [pts to array]) | (#3) (lo: 2) (hi: 3) | | prints arr[2]. 3). 3). | |__________________________________________________________________ | print (arr: [pts to array]) | (#5) (lo: 4) (hi: 3) | | lo > hi. then calls print(arr. In fact. then calls print(arr. the earlier method notecards “mysteriously vanished”: . 3). 3). then calls print(arr. so this is base case | |__________________________________________________________________ Now. is to return from each of the method calls. 0. 3. 2.

POOF | / | \ | |__________________________________________________________________ | print (arr: [pts to array]) | (#5) (lo: 4) (hi: 3) | | lo > hi. with a call to fac(4): . so this is base case | |__________________________________________________________________ it doesn’t aﬀect the running of our code at all. all the work is already done. 3). 0.338 __________________________________________________________________ | main | print(arr. and be done. We could just return from the base case back to main(). | |_________________________________________________________________ | | | \ | / | . Contrast that.

If while we are in the base case. |_________________________________________________________________ | fac (n: 4) | (#1) | | need to calculate: fac(3). in order to calculate 2 * fac(1) | |__________________________________________________________________ | fac (n: 1) | (#4) | | need to calculate: fac(0). in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | need to calculate: fac(2). in order to calculate 1 * fac(0) | |__________________________________________________________________ | fac (n: 0) | (#5) | | base case! (we have not returned yet) |__________________________________________________________________ In this case. we need to return to the previous method calls. in order to calculate 3 * fac(2) | |__________________________________________________________________ | fac (n: 2) | (#3) | | need to calculate: fac(1). the other method notecards “mysteriously vanish”: . | x = fac(4).339 __________________________________________________________________ | main | int x. in order to complete the multiplications.

once we ﬁnish the base case. and from there. The reason this is relevant. we can’t complete the multiplications.POOF | / | \ | |__________________________________________________________________ | fac (n: 0) | (#5) | | base case! (we have not returned yet) |__________________________________________________________________ then we are left only with our statement return 1. If we return from the base case. |_________________________________________________________________ | | | \ | / | ... then there is no reason to save them..) method. make the second print(. that’s not true.. | x = fac(4). and of course.. when we start the print(. directly back to main()..) call.) call: . and cannot complete the rest of the work – since we do not have the other method notecards anymore. That is the reason we care if a method is a tail-recursive method or a forward-recursive method. That is. it means we eﬀectively have ﬁnished all the work we need to do. in a forward-recursive method. then main() would think that the value of fac(4) is 1. we will still be doing work as we return from each of the other non-basecase method calls as well. is as follows: if we won’t ever need the earlier method notecards again. right now. Because there is no work done in a tail-recursive method once the recursive call is completed..340 __________________________________________________________________ | main | int x. we make the ﬁrst print(.

by increasing lo right there on the ﬁrst notecard? FROM: __________________________________________________________________ | main | print(arr. when we have this notecard sitting around for call #1 that we’ll never really need to make detailed use of anymore? Why not just convert the ﬁrst notecard into the second one. 3). 2.. 1. 3). why make an entirely new notecard for call #2. but once we do return to it. we can keep converting that notecard for each subsequent call. |__________________________________________________________________ | print (arr: [pts to array]) | (#2) (lo: 1) (hi: 3) | | prints arr[1]. then calls print(arr. |__________________________________________________________________ But if we won’t ever “need” the ﬁrst print(.) notecard again. 0. First. then calls print(arr. |_________________________________________________________________ | print (arr: [pts to array]) | (#2) (lo: 1) (hi: 3) | | prints arr[1]. |_________________________________________________________________ | print (arr: [pts to array]) | (#1) (lo: 0) (hi: 3) | | prints arr[0].341 __________________________________________________________________ | main | print(arr. 3). |_________________________________________________________________ | print (arr: [pts to array]) | (#1) (lo: 0) (hi: 3) | | prints arr[0]. 0. we make call #3 from call #2: . is so that we can eventually return to it.. then why save it? The only reason it is hanging around. 2. 3). 3). 3). then calls print(arr. 0. So. |__________________________________________________________________ And similarly. 1. we will immediately leave it and return back to main(). |__________________________________________________________________ TO: __________________________________________________________________ | main | print(arr. then calls print(arr. 3).

0.342 FROM: __________________________________________________________________ | main | print(arr. |_________________________________________________________________ | print (arr: [pts to array]) | (#2) (lo: 1) (hi: 3) | | prints arr[1]. 3). 0. then calls print(arr. 3). |__________________________________________________________________ and next we make call #4 from call #3: FROM: __________________________________________________________________ | main | print(arr. 3. 3). 3). then calls print(arr. 3. 0. 4. 3). |__________________________________________________________________ TO: __________________________________________________________________ | main | print(arr. |_________________________________________________________________ | print (arr: [pts to array]) | (#4) (lo: 3) (hi: 3) | | prints arr[3]. 0. |_________________________________________________________________ | print (arr: [pts to array]) | (#3) (lo: 2) (hi: 3) | | prints arr[2]. 3). then calls print(arr. 2. |__________________________________________________________________ . |__________________________________________________________________ TO: __________________________________________________________________ | main | print(arr. 3). then calls print(arr. 3). |_________________________________________________________________ | print (arr: [pts to array]) | (#3) (lo: 2) (hi: 3) | | prints arr[2].

rather than make a new method notecard for a new method call. |_________________________________________________________________ | print (arr: [pts to array]) | (#5) (lo: 4) (hi: 3) | | lo > hi. when each non-base-case call needs to take a returned value and perform a multiplication with it). 3). So.343 and ﬁnally. If we did need the old data again (like in fac(. 3). the above is all a loop is: you are making a recursive call.. 4. |__________________________________________________________________ TO: __________________________________________________________________ | main | print(arr. so we are done |__________________________________________________________________ At each step. 0. In the last picture above. What we have just done here is “invent” the loop! That is. But for tail-recursive methods. into the notecard we need (representing the new call). we make call #5 from call #4: FROM: __________________________________________________________________ | main | print(arr. we simply convert the notecard we already have (representing the old call). we will never need the old notecard again. This works because we never need the old notecard again.. 0.) call representing the base case of the method. and those argument values are copied into those new versions of the parameters: . the print(. could then return straight back to main(). it’s okay to write over all the data for the old notecard. and then a new notecard. with new data for the new notecard. but rather than start a new notecard underneath the old one. and our work is done. |_________________________________________________________________ | print (arr: [pts to array]) | (#4) (lo: 3) (hi: 3) | | prints arr[3]. so “erasing” it by writing over its data with data for the new notecard. is created. what a recursive call does it to calculate some new values for the parameters (the expressions that serve as the arguments are these new values). When we actually set up each notecard.. then calls print(arr..). then we can’t do this. with new versions of the parameters. is ﬁne. you simply convert the existing (old) notecard into the new one. 3).

is useless. and then repeat the algorithm (by starting the method again on a new notecard).-. copy the arguments into the existing parameters.. with these // new values stored in the parameters } . instead copy them into the versions of those parameters in this call.a and then repeat the algorithm. hi). That is. either. --. we know assigning a variable to itself. so we’ll keep it there for now and get rid of it a bit later) and then..344 call from previous method: print(arr. hi = hi. lo + 1. lo + 1. int lo. (yes.-----. \|/ \|/ \|/ public static void print(int[] arr. lo. and hi. and hi in the next call. into new versions of the parameters arr. hi). | | \ . // now we repeat everything in loop all over again. hi. rather than copy arr. . lo = lo + 1. it doesn’t do any harm. | | \ . via assignments statements: this is the call we ‘‘want to’’ make: print(arr. lo + 1. // param arr lo hi = = = = expression. int hi) { Instead. we will here. as we did with arr and hi. via a loop: loop { . we are now going to copy those arguments into the existing versions of the parameters. arr = arr. arr. lo + 1. just as we would copy the arguments into new parameters.

hi). rather than creating a new notecard to hold the parameters.println(arr[lo]). lo = lo + 1. and hi. } else // base case { base case code } } then the loop-based version would rearrange the code as follows: // copy arr. hi = hi. and repeat . and just repeat the method code via a loop.out. // param = argument. arr = arr. into current // version of parameters.println(arr[lo]). } to this: while (lo <= hi) // same condition { System. lo + 1. and hi. We want to keep repeating the method code over and over and over. If we have a recursive method organized as follows: recursiveMethod(params) { if (recursiveCase) { code we run before recursive call recursiveMethod(args). The glue that holds the loop version together is that we still need to “copy the arguments into the parameters” – we just copy the arguments into the current parameters that are already a part of our current notecard. we are changing our recursive case from this: if (lo <= hi) { System.345 That is.out. lo + 1. } That is the core idea behind converting a tail-recursive method into a loop-based method. but rather than continually making new method calls to accomplish this. lo + 1. we stay in the method call we are already in. into new // version of parameters. print(arr. and repeat // copy arr.

out. int hi) { if (lo <= hi) { System. we are writing the recursive call’s arguments (arr. } // in the base case. int hi) { while (lo <= hi) { System.out. hi). the only code in the recursive case that is performed before the recursive call. an empty statement).” is a stand-in for all the assignments you need to write each of the arguments to the recursive call. int lo. } // else you do nothing } First of all.. // params = args. So when we write our loop. And. is the printing out of arr[lo]. the “base case code” is basically nothing (or. consider the print(. That gives us the following rough draft of loop-based code: // rough draft #1 public static void print(int[] arr.” section of the code.346 loopBasedMethod(params) { while (recursiveCase) { code we run before recursive call params = args. back into the current method call’s parameters.. if you wish.println(arr[lo]). } base case code } where the line “params = args. and hi): . and hi) into the parameters (arr. lo + 1. lo. so that is the same condition that indicates we enter our loop. we’ll have nothing after the loop ﬁnishes. The condition that indicates that we should enter the recursive case is lo <= hi.println(arr[lo]).) method again: public static void print(int[] arr. print(arr. For example. we did nothing } and for the “params = args. int lo. lo + 1.

int lo.1). two of those assignments just assign a variable to itself.println(arr[lo]).). int lo. we did nothing } And ﬁnally.println(arr[lo]). respectively. we can recall our wrapper method: public static void print(int[] arr) { print(arr.out. int hi) { while (lo <= hi) { System..length . and initialize them to 0 and arr. hi = hi. int hi) { while (lo <= hi) { System. arr.347 // rough draft #2 public static void print(int[] arr. arr = arr. } rather than call a second method (our loop-based method) from the wrapper method.length . (If you still wanted the method with three parameters.out. we can simply merge the two methods together if we like. so we can get rid of those: // final loop-based version public static void print(int[] arr. then you would not want to do this. As a ﬁnal tweak.length . 0. } // in the base case. lo = lo + 1.1 to parameters lo and hi.) Rather than pass 0 and arr. we did nothing } and now we have a loop-based version of print(..1. we could simply make lo and hi local variables. . } // in the base case. lo = lo + 1.

println(arr[lo]). } } Since we never change hi.println(arr[lo]). } } That’s pretty much the standard code for printing out an array from beginning to end.1) { System.out. . while (i <= arr.out.1 for hi in the while-loop condition: public static void print(int[] arr) { int lo = 0. lo = lo + 1. while (lo <= arr. i = i + 1. lo = lo + 1.length . int hi = arr.1.length . while (lo <= hi) { System. the above code might seem a bit more familiar if we replace the variable name lo with the variable name i: public static void print(int[] arr) { int i = 0.out.println(arr[i]).length .348 public static void print(int[] arr) { // these two lines are the new ones int lo = 0.length . } } and we are done! Of course. we don’t even need that variable to begin with – we can just substitute arr.1) { System. using a while-loop.

.349 As a second example. int lo. int key. let’s rewrite the method in the form: recursiveMethod(params) { if (recursiveCase) { code we run before recursive call recursiveMethod(args). int hi) { if ((lo <= hi) && (arr[lo] != key)) return linearSearch(arr.): public static int linearSearch(int[] arr. hi). } else // base case { base case code } } that we mentioned earlier. else return linearSearch(arr. key. int lo. int hi) { if (lo > hi) return -1. int key. So.. We hit the recursive case whenever the ﬁrst two cases are both false – that is. else // lo > hi or arr[lo] == key { if (lo > hi) return -1. that’s our recursive condition: public static int linearSearch(int[] arr. lo + 1. } First of all. } } . whenever lo <= hi and arr[lo] != key. else // lo <= hi and arr[lo] == key return lo. else if (arr[lo] == key) return lo. lo + 1. key. consider linearSearch(. hi).

} } and since lo is the only one of the four parameters that actually gets reassigned. // is converted into ‘‘params = args.350 Now applying our loop-version template: loopBasedMethod(params) { while (recursiveCase) { code we run before recursive call params = args.’’ form: arr = arr. hi). else // lo <= hi and arr[lo] == key return lo. key = key. int key. we can eliminate the other three meaningless assignments: . int lo. } base case code } gives us the following: // rough draft #1 public static int linearSearch(int[] arr. } // code from base case appears below: if (lo > hi) return -1. key. lo = lo + 1. hi = hi. lo + 1. int hi) { while ((lo <= hi) && (arr[lo] != key)) { // the statement // return linearSearch(arr.

we can eliminate the compound statement curly braces as well.): public static int linearSearch(int[] arr.. 0. arr.. This would be the wrapper method for linearSearch(. int key. int hi) { while ((lo <= hi) && (arr[lo] != key)) { lo = lo + 1. we can also combine this with the wrapper method if we wanted to. // code from base case appears below: if (lo > hi) return -1.length . else // lo <= hi and arr[lo] == key return lo.351 // rough draft #2 public static int linearSearch(int[] arr. int key. int lo. key.1).). else // lo <= hi and arr[lo] == key return lo. } } And now we have our loop-based version of linear search! As with print(. int key) { return linearSearch(arr. } so by combining the two. if we want: // final loop-based version public static int linearSearch(int[] arr. we get: . int lo.. } } and thus. } // code from base case appears below: if (lo > hi) return -1.. int hi) { while ((lo <= hi) && (arr[lo] != key)) lo = lo + 1.

1). else // i <= arr.. // 2) run the subproblem } // else new value is already properly inserted.) code from the last lecture packet: // assume lo.352 public static int linearSearch(int[] arr. hi).hi-1 is a sorted range.length .1.1) return -1. lo. if (i > arr. hi . while ((lo <= hi) && (arr[lo] != key)) lo = lo + 1. we can take the insertInOrder(. // 1) swap last two values insertInOrder(arr.1 in the code..length .1])) { swap(arr. and after replacing lo with i. } } which should look familiar. and hi holds the new value public static void insertInOrder(int[] arr.. do nothing } and convert it the same way: // we ran past end of array // we found value in array . if (lo > hi) return -1. being the sort of loop-based linear search you might have written earlier.length . } } and after replacing hi with a hard-coded arr.length . int lo. else // lo <= hi and arr[lo] == key return lo.1. int hi = arr.length . int key. we get the following code: public static int LinearSearch(int[] arr. int key) { // these two lines are new int lo = 0. int lo. while ((i <= arr.1 and arr[i] == key return i.1) && (arr[i] != key)) i = i + 1. int hi) { if ((lo < hi) && (arr[hi] < arr[hi . hi .. int hi) { int i = 0. As a ﬁnal example.

1. hi . hi = hi . and hi holds the new value public static void insertInOrder(int[] arr. // 1) swap last two values arr = arr.. new value is already properly inserted.. hi). hi).hi-1 is a sorted range. and hi holds the new value public static void insertInOrder(int[] arr. int lo. } // now.1. hi .hi-1 is a sorted range. int hi) { while ((lo < hi) && (arr[hi] < arr[hi . // 1) swap last two values hi = hi .1])) { swap(arr.1. // 2) run the subproblem } // now. int lo.353 // rough draft #1 // assume lo. int hi) { while ((lo < hi) && (arr[hi] < arr[hi . we get: // final loop-based version // assume lo... do nothing } And after removing the two useless assignments. new value is already properly inserted.1.1])) { swap(arr. do nothing } . lo = lo.

the recursive call will be the ﬁnal work in the recursive case. So how did that loop-based method come about? How could we write a loop-based method if our factorial algorithm above cannot be converted into a loop? The link between the two is a type of recursion is known as accumulator recursion. n * productSoFar) (new recursive case) . However. That’s why it is known as accumulator recursion. what we want to do is to add in a parameter that will accumulate the product of the diﬀerent values of n.1 in place of the recursive call. } We can’t just convert this to a loop and say n = n . the only parameter we need for the calculation is the value of n. Accumulator recursion is a form of tail recursion. On the other hand. If we have that second parameter: public static int fac(int n. which only has n as a parameter and yet works perfectly! However. we will need to add an additional parameter that was not needed for the forward-recursive version. we need to add extra parameters that “accumulate” a result in some way so that we don’t need to do work as we return from the recursive calls. because we wrote one back in Lecture Notes #21. as proof of this.1.354 Lecture 29 : Accumulator Recursion Recall that our factorial method was forward-recursive: // Implementation #1 of the factorial algorithm // This is the algorithm in forward-recursion form public static int fac(int n) // n >= 0 { if (n > 0) return n * fac(n . In the case of the factorial method.1). If we wrote it in forward-recursive form. and thus pass that value as an argument: return n * fac(n . these parameters would not be needed. So making a recursive function of this kind into a loop is not the simple process that it was with a tail-recursive method.1) (old recursive case) -------> return fac(n . it’s tail recursion in which we have to add extra “unnecessary” parameters in order to be able to write it in tail-recursive form. because we still need that original value of n to do the multiplication once we return from the recursive call. we know a loop-based factorial method is possible. but in order to write it in tail-recursive form. if we want to write a tail-recursive method for calculating factorial. Speciﬁcally. int productSoFar) then our recursive case could multiply n by productSoFar before the recursive call is made. else // n == 0 return 1. we will thus add a second integer parameter to the factorial method. we can simply look at the code above.

but it does require passing an extra parameter to hold the product so far. we would no longer call fac(k). On the left.. but rather. } In order to correctly begin the recursion. but that the base case has not been returned from yet. productSoFar >= 1 { if (n > 0) return fac(n .355 That gives us the following code. we would initialize a product variable to 1 (just as we would generally initialize a sum variable to 0).. we see the method notecards for the accumulator-recursive version. Generally. when we reach the base case. n * productSoFar). we see the method notecards for the forward-recursive version. as well as sending the initial value we want the factorial of. We can see the diﬀerence between the two. For both examples. we need to send an initial value to productSoFar. on the right. int productSoFar) { if (n > 0) return fac(n . 1). productSoFar >= 1 What we are doing here is doing our multiplications before we make the recursive call. . n * productSoFar). fac(k.1. which is the start of a new factorial method: public static int fac(int n. Then. else // n == 0 return productSoFar. // still need base case } // n >= 0. on the table on the next page. we assume all the method calls down to the base case have been made. int productSoFar) // n >= 0.1. to n. so to calculate the factorial of k. we have the entire factorial product and can just return that: // Implementation #2 of the factorial algorithm // This is the algorithm in accumulator-recursion form // The first call to this method should send the value 1 as the second argument public static int fac(int n.

but for the accumulator-recursive method. | x = fac(4). 12) |__________________________ | fac (n: 2) | (#3) (productSoFar: 12) | | need: fac(1. and we just keep returning the same value: . 4) |_________________________ | fac (n: 3) | (#2) (productSoFar: 4) | | need: fac(2.356 Implementation #1 -------------------_________________ | main | int x. 1). 24) |__________________________ | fac (n: 0) | (#5) (product: 24) | | base case! |___________________________ As we return from the various method calls. from #5 to #4 to #3 to #2 to #1. 24) |__________________________ | fac (n: 1) | (#4) (product: 24) | | need: fac(0. | | |_________________________ | fac (n: 4) | (#1) (productSoFar: 1) | | need: fac(3. | | |________________ | fac (n: 4) | (#1) | | need: fac(3) |_________________ | fac (n: 3) | (#2) | | need: fac(2) |____________ | fac (n: 2) | (#3) | | need: fac(1) |____________ | fac (n: 1) | (#4) | | need: fac(0) |____________ | fac (n: 0) | (#5) | | base case! |____________ Implementation #2 -------------------------________________________ | main | int x. all the work is already done. | x = fac(4. the forwardrecursive version needs to do some work – multiplications – before each return (except for the return from the base case).

357 Implementation #1 -------------------_________________ | main | int x. | | [24 written into x] |________________ | fac (n: 4) | (#1) | ----> returns 24 | need: fac(3) |_________________ | fac (n: 3) | (#2) | ----> returns 6 | need: fac(2) |____________ | fac (n: 2) | (#3) | ----> returns 2 | need: fac(1) |____________ | fac (n: 1) | (#4) | ----> returns 1 | need: fac(0) |____________ | fac (n: 0) | (#5) | ----> returns 1 | base case! |____________ Implementation #2 -------------------------________________________ | main | int x. and the value of the argument n * productSoFar begin written into the parameter productSoFar of the next call. | x = fac(4. 24) ----> returns 24 |__________________________ | fac (n: 1) | (#4) (product: 24) | | need: fac(0. 12) ----> returns 24 |__________________________ | fac (n: 2) | (#3) (productSoFar: 12) | | need: fac(1. 24) ----> retursn 24 |__________________________ | fac (n: 0) | (#5) (product: 24) | | base case! ----> returns 24 |___________________________ Each recursive call in the accumulator-recursive version will result in the value of the argument n .1 being written back into the parameter n of the next call. | | [24 written into x] |_________________________ | fac (n: 4) | (#1) (productSoFar: 1) | | need: fac(3. 4) ----> returns 24 |_________________________ | fac (n: 3) | (#2) (productSoFar: 4) | | need: fac(2. | x = fac(4). 1). .

productSoFar = n * productSoFar. } return productSoFar. // now we can safely store the new value into n. productSoFar = n * productSoFar. that means that accumulatorrecursive algorithms can be converted into loops using the technique we already discussed: public static int fac(int n. we are changing n. does NOT rely on the old value of productSoFar. that multiplication expression expects the old value of n. n = tempNewN. n * productSoFar) then both arguments were evaluated. we have params = args. But now that we have two assignment statements.1. which is okay since the evaluation of the new value of n. unlike how the evaluation of the new value of productSoFar relies on the old value of n.1. int tempNewN = n . and then using n in the expression n * productSoFar. // this was originally the base case code } We actually have a slight problem here. we either need a temporary variable: public static int fac(int n. productSoFar >= 1 { while (n > 0) // while our recursive-case condition is still met { // nothing was done in the recursive case. using the old value of n. productSoFar >= 1 { while (n > 0) // while our recursive-case condition is still met { // nothing was done before the recursive call // the. When we had the line in recursive-call form: fac(n . not the new one. before the recursive call // then. the code we get in that case is as follows: . rather than the passing of two arguments to two parameters. since // we are through using the old value of n } return productSoFar. n = n . int productSoFar) // n >= 0. the eﬀects of the earlier assignments exist when we try to perform the later assignments. Unfortunately. To get around this. // this was originally the base case code } or else we can simply reorder those two assignments.358 Since accumulator recursion is just a form of tail recursion. int productSoFar) // n >= 0. we have params = args.1. So.

we can simply make it a local variable. or for the variables that are used in those computations. in that case). n = n . and our loop version of factorial. productSoFar >= 1 { while (n > 0) // while our recursive-case condition is still met { // nothing was done before the recursive call // the. } The above code is basically the same code you saw in Lecture Notes #21 when we ﬁrst presented the loop-based version of factorial! Note that this means we cannot write the loop-based version. We add the extra variable productSoFar into our computation.1. we don’t actually need to pass in a ”productSoFar” value. Converting to a loop merely reorganizes when and where certain computations get done. In order to write the algorithm in tailrecursive (accumulator-recursive) form. n = n . // this was originally the base case code } Finally. for the loop version. you could only write the algorithm in forward-recursive form. } return productSoFar. not computation – we cannot eliminate that variable entirely. while we were able to make the productSoFar variable a local variable rather than a parameter variable – a change of organization. and then to a loop-based method. to make additional method notecards unnecessary.359 public static int fac(int n. so that we can make use of the saved values in diﬀerent method notecards. and initialize it to 1 ourselves. } return productSoFar. That is the connection between our forward-recursive deﬁnition of factorial. while (n > 0) { productSoFar = n * productSoFar. it can’t eliminate the need for those computations. and now that we have access to that extra variable to use as an accumulator. So. without it. you needed the extra variable (as a parameter. // Implementation #3 of the factorial algorithm // This is the algorithm in loop-based form public static int fac(int n) // n >= 0 { int productSoFar = 1. we have params = args. And the conversion from tail-recursion to a loop-based version. to aid us in our computation. we are forced to perform our computation in a forward-recursive way. we can convert from forward-recursion to accumulator-recursion. without the use of the extra variable productSoFar. Rather than requiring that the client send in a 1 as an argument to this parameter. productSoFar = n * productSoFar. Without that extra variable. int productSoFar) // n >= 0.1. cannot get rid of needed work like continually writing into the productSoFar variable. .

and then passing that product as a third parameter to our exponentiation method: // Implementation #2 of the exponentiation algorithm // This is the algorithm in accumulator-recursive form // The first call to this method should send the value 1 as the third argument public static int pow(int base. } After we ﬁnish a recursive call. before the point where the base case returns. exp . we could try multiplying the base by an existing product. else // exp > 0 return base * pow(base. int productSoFar) { if (exp == 0) return productSoFar. else return pow(base. the multiplicaton of base and our recursive call result. int exp.1). base * productSoFar). We can try and convert this to an accumulator recursion implementation using the same technique we used for the factorial computation – instead of multiplying the base by our recursive result. that parameter’s value is our answer. this is a forward-recursive algorithm. exp . . We calculate the third argument for each call by multiplying the base by the productSoFar. int exp) { if (exp == 0) return 1. } Note that the only diﬀerences between implementation #2 and implementation #1 are: • We added a third parameter to #2 • We return that third parameter’s value instead of 1 in the base case • We have a third argument – a multiplication result – passed as the argument to that third parameter when we make our recursive call Below we have the “method notecards” generated by both our implementations. there is more work to do besides just returning a value – namely. until by the time we reach the base case. So.360 Another example would be our exponentiation algorithm: // Implementation #1 of the exponentiation algorithm // This is the algorithm in forward-recursive form public static int pow(int base.1. for each step – but the second implementation has a third parameter whose value steadily increases with each method call. Note that the ﬁrst two arguments are the same in both cases.

but merely returning the same value. then call #3. we can indicate what gets returned as the method calls above return one by one (the base case. base * productSoFar). | x = pow(3. 0) |____________ | pow (base: 3) | (#5) (exp: 0) | | base case! |____________ Implementation #2 -------------------------________________________ | main | int x. 3) |_________________ | pow (base: 3) | (#2) (exp: 3) | | need: pow(3. of course. 1). we already did the needed multiplication before making the recursive call. 1) |____________ | pow (base: 3) | (#4) (exp: 1) | | need: pow(3. 4. 3) |_________________________ | pow (base: 3) | (#2) (exp: 3) | (productSoFar: | need: pow(3. 9) |__________________________ | pow (base: 3) | (#3) (exp: 2) | (productSoFar: | need: pow(3. since the else case for Implementation #2 was: return pow(base. Note that in Implementation #1. 27) |__________________________ | pow (base: 3) | (#4) (exp: 1) | (productSoFar: | need: pow(3.1. 1.361 Implementation #1 -------------------_________________ | main | int x. exp . 81) |__________________________ | pow (base: 3) | (#5) (exp: 0) | (productSoFar: | base case! |___________________________ 1) 3) 9) 27) 81) Next. 81. | x = pow(3. 3. 4). is the ﬁrst method to return. and so for Implementation #2 we are not doing any calculations as we return. we have to do the multiplications as we complete each recursive call. that we’ve already calculated as our solution: . since the else case for Implementation #1 was: return base * pow(base. then call #4. 2. | | |________________ | pow (base: 3) | (#1) (exp: 4) | | need: pow(3. 0. and ﬁnally call #1). 2) |____________ | pow (base: 3) | (#3) (exp: 2) | | need: pow(3. but in Implementation #2. | | |_________________________ | pow (base: 3) | (#1) (exp: 4) | (productSoFar: | need: pow(3. then call #2. exp-1).

0) |____________ | pow (base: 3) | (#5) (exp: 0) | ------> return 1 | base case! |____________ Implementation #2 -------------------------________________________ | main | int x. 9) ----> return |__________________________ | pow (base: 3) | (#3) (exp: 2) | (productSoFar: 9) | need: pow(3. 1) returns. 3. | // once pow(3. | x = pow(3. 2) |____________ | pow (base: 3) | (#3) (exp: 2) | ------> return 9 | need: pow(3. 3) ----> return |_________________________ | pow (base: 3) | (#2) (exp: 3) | (productSoFar: 3) | need: pow(3. 0. 3) |_________________ | pow (base: 3) | (#2) (exp: 3) | ------> return 27 | need: pow(3. 4. | // 81 is stored in x |________________ | pow (base: 3) | (#1) (exp: 4) | ------> return 81 | need: pow(3.362 Implementation #1 -------------------_________________ | main | int x. 81) ----> return |__________________________ | pow (base: 3) | (#5) (exp: 0) | (productSoFar: 81) | base case! ----> return |___________________________ 81 81 81 81 81 . | // once pow(3. 4). | // 81 is stored in x |_________________________ | pow (base: 3) | (#1) (exp: 4) | (productSoFar: 1) | need: pow(3. 1). 4. 27) ----> return |__________________________ | pow (base: 3) | (#4) (exp: 1) | (productSoFar: 27) | need: pow(3. | x = pow(3. 1. 4) returns. 1) |____________ | pow (base: 3) | (#4) (exp: 1) | ------> return 3 | need: pow(3. 2.

1. int exp.1. int exp. } else // base case { base case code } } That is. else // exp == 0 return productSoFar. exp . int productSoFar) { if (exp > 0) return pow(base. } to this: public static int pow(int base. convert this: public static int pow(int base. Now. int productSoFar) { if (exp == 0) return productSoFar. we can use our general tail-recursion-to-loop-implementation technique to convert this into a loop implementation: . else return pow(base. base * productSoFar). exp . } by switching the if and else cases. base * productSoFar).363 To convert this to a loop implementation. let’s ﬁrst put the accumulator-recursive code into the layout we mentioned in the last lecture packet: recursiveMethod(params) { if (recursiveCase) { code we run before recursive call recursiveMethod(args).

exp = exp . or the loop version). productSoFar = base * productSoFar. exp . base * productSoFar). we are still demanding that the clients pass in a 1 as the third argument. And as a result. Consider the calculation of 34 . then .364 public static int pow(int base. they all do the exact same work. productSoFar = base * productSoFar. int productSoFar) { while (exp > 0) { // There was code before recursive call.1. int exp. or any of the three code examples (the forward-recursive version. int exp. if we wanted. we calculate an exponentiation by multiplying the base. there is an important thing to take from this example – all three of our implementations are implementing the exact same algorithm. } return productSoFar. // The statement // return pow(base.1. so we can remove that assignment: public static int pow(int base. we could make productSoFar a local variable. int productSoFar) { while (exp > 0) { exp = exp . productSoFar = base * productSoFar. in terms of everything but method calls. } Now. for example. } return productSoFar. and if not.1. } We don’t need to assign base to itself. while (exp > 0) { exp = exp . the accumulator-recursive version. In all three cases. int exp) { int productSoFar = 1. we always need to check the exponent to see if it’s zero or not. } return productSoFar. Whether you look at the mathematical formula. by an existing exponentiation with the same base and one smaller exp. // is converted into the assignments below: base = base. } And with that.1. we have a working loop implementation! Of course. in order to avoid making the clients pass in an initial value for that variable: public static int pow(int base.

and four subtractions. you will need ﬁve comparisons as you go through the calculation. and multiplications – don’t change no matter how we code the algorithm. . or any of the code examples. We will explore that idea. Furthermore.365 the recursive subproblem will have an exponent one lower in value. You will see that each time. The fundamental calculations needed – the comparisons. But the basic computation is the same as we move from recursive code to loop-based code. and then each of the three coded implementations. The base case doesn’t perform the multiplication or the subtraction. for four of those cases – all the recursive cases – you will need to do a subtraction (to get an exponent one lower than your current exponent) and a multiplication (to multiply the base by some existing product). and not just changing the way in which we coded it. four multiplications. It’s the same algorithm each time. And so we will compare the exponent to zero a total of ﬁve times – when the exponent is 4. The one big diﬀerence is that the recursive versions use the extra assistance of method calls to keep track of the values of the base and exponent. Go ahead and trace through the calculation of 34 using the pure math. a bit later in the semester. If we had a diﬀerent algorithm. so if you run the recursive case four times (when the exponent is 4. The same was true of the factorial algorithm earlier in this notes packet. and when it is 0. that could change – because then we’d be changing the fundamental computational process we were using. when it is 2. and when it is 1). subtractions. when it is 2. so the same work is being done each time. but the recursive case does. when it is 3. Whether you look at the pure math. though. you will need to perform four subtractions and four multiplications. when it is 1. when it is 3. and the non-forward-recursive versions use an extra variable to store a temporary product. we are performing ﬁve comparisons.

else return locOfMinOfRest. // Implementation #1 of the findMinimum algorithm // This is the algorithm in forward-recursion form public static int findMinimum(int[] arr.366 Lecture 30 : More Accumulator Recursion In this packet. (We don’t know what this parameter would be initialized to. but we’ll talk about that in a moment. lo + 1. int lo. hi). it is the comparison we want to move before the recursive call. That is. we present some more examples of accumulator recursion. instead of the recursive result. Our ﬁrst example will be ﬁnding the minimum of a subarray. int lo. hi). if (arr[lo] <= arr[locOfMinOfRest]) return lo. . } } If we had a parameter that held our “index of the smallest value we’ve seen so far”. and it won’t be correct again until we are ﬁnished. int hi) { if (base case) // do something else { // compare arr[lo] to something int locOfMinOfRest = findMinimum(arr. int hi) { if (lo == hi) return lo.) Note that the following code does not correctly ﬁnd the minimum! – we are now in the middle of modifying the code. we want our recursive case to be set up like this: public static int findMinimum(int[] arr. yet. } } Since it is the comparison we are doing after the recursive call. we could compare to that. else { int locOfMinOfRest = findMinimum(arr. lo + 1.

we simply returned this value. but also. int hi. in turn. return findMinimum(arr.e. latestMinIndex). and compare the value at that index – the minimum to far – to that recursive call’s arr[lo]. as our base case work. And so on. What we can do. int hi. that value had to be the minimum. In our forward-recursive method. we need to actually do something in this conditional we have written. So.367 public static int findMinimum(int[] arr. } } Now. } } We are performing the comparison. The index of the smaller of those two values. int lo. when lo == hi. lo + 1. is send either lo or indexOfMinSoFar to the recursive call. At the very end of this procedure – i. if (arr[lo] <= arr[indexOfMinSoFar]) latestMinIndex = lo. will take that parameter index. int indexOfMinSoFar) { if (base case) // do something else { if (arr[lo] <= arr[indexOfMinSoFar]) // do something with lo else // arr[lo] > arr[indexOfMinSoFar] // do something with indexOfMinSoFar int locOfMinOfRest = findMinimum(arr. as the argument for the new indexOfMinSoFar parameter we have added: public static int findMinimum(int[] arr. int lo. since if we had only one value. the value at the parameter index indexOfMinSoFar. lo == hi. hi). else // arr[lo] > arr[indexOfMinSoFar] latestMinIndex = indexOfMinSoFar.. and then passing the index of the minimum value to the next recursive call. lo + 1. when we reach the base case of the recursive method – we have only the last value left. int indexOfMinSoFar) { if (base case) // do something else { int latestMinIndex. will then be passed along to the next recursive call. But now. That is. hi. we should compare those two values – the overall minimum would be the minimum of those two values: . we have not just the value at arr[lo]. That call.

length == 1 case as well. we could do this: public static int findMinimum(int[] arr) // assume arr.length . } If we’d like the wrapper method to handle the arr. arr.368 // Implementation #2 of the findMinimum algorithm // This is the algorithm in accumulator-recursion form public static int findMinimum(int[] arr. 0). arr. int indexOfMinSoFar) { if (lo == hi) { if (arr[lo] <= arr[indexOfMinSoFar) return lo. 0). if (arr[lo] <= arr[indexOfMinSoFar]) latestMinIndex = lo. latestMinIndex). 1. // works for arr. must be the minimum else return findMinimum(arr. lo + 1. it’s necessary to have an initial value to send to indexOfMinSoFar.length >= 2 } . // only index that exists.1. so let’s send arr[lo] as the initial value. else // arr[lo] > arr[indexOfMinSoFar] latestMinIndex = indexOfMinSoFar. return findMinimum(arr. } else { int latestMinIndex. we could have a wrapper method as follows: public static int findMinimum(int[] arr) // assume arr. 1. and actually have the ﬁrst recursive call run from lo + 1 through hi.1. That is. Presumably. else return indexOfMinSoFar.length . int hi. our ﬁrst comparison would be between arr[lo] and arr[lo + 1].length >= 1 { if (arr. hi.length == 1) return 0. int lo.length >= 2 { return findMinimum(arr. } } To begin the recursion.

int lo. int hi. } and after removing the redundant assignments. if (arr[lo] <= arr[indexOfMinSoFar]) latestMinIndex = lo. else return indexOfMinSoFar. int hi. hi = hi. else return indexOfMinSoFar. we have: . int indexOfMinSoFar) { while (lo < hi) { if (arr[lo] <= arr[indexOfMinSoFar]) indexOfMinSoFar = lo. indexOfMinSoFar = indexOfMinSoFar. we can eliminate the latestMinIndex variable if we want: public static int findMinimum(int[] arr. indexOfMinSoFar = latestMinIndex. arr = arr. int indexOfMinSoFar) { while (lo < hi) { int latestMinIndex. } if (arr[lo] <= arr[indexOfMinSoFar) return lo. else // arr[lo] > arr[indexOfMinSoFar] indexOfMinSoFar = indexOfMinSoFar. hi = hi. lo = lo + 1.369 Now. lo = lo + 1. arr = arr. int lo. we can convert our accumulator-recursive version into a loop-based version: public static int findMinimum(int[] arr. } Next. else // arr[lo] > arr[indexOfMinSoFar] latestMinIndex = indexOfMinSoFar. } if (arr[lo] <= arr[indexOfMinSoFar) return lo.

lo = lo + 1. else return indexOfMinSoFar.1. } Finally. while (lo < hi) { if (arr[lo] <= arr[indexOfMinSoFar]) indexOfMinSoFar = lo. else return indexOfMinSoFar. by converting the last three parameters to local variables: public static int findMinimum(int[] arr) { int lo = 1. lo = lo + 1.length . we can combine this with the wrapper method. int indexOfMinSoFar) { while (lo < hi) { if (arr[lo] <= arr[indexOfMinSoFar]) indexOfMinSoFar = lo.370 // Implementation #3 of the findMinimum algorithm // This is the algorithm in loop-based form public static int findMinimum(int[] arr. } . int lo. int indexOfMinSoFar = 0. int hi. if we want. } if (arr[lo] <= arr[indexOfMinSoFar) return lo. } if (arr[lo] <= arr[indexOfMinSoFar) return lo. int hi = arr.

// 1) swap last two values insertInOrder(arr.1). lo. } } Both of these are “insertion sort”. hi).hi. . we could have the recursive call run on lo. ﬁrst.1). lo.. lo). // 2) run the subproblem } } public static void insertionSort(int[] arr. just from diﬀerent directions. int lo.. hi . int hi) { if (lo < hi) { insertionSort(arr. we will do the same for insertionSort. int lo. hi . lo. lo + 1.371 Next.1])) { swap(arr. int hi) { if ((lo < hi) && (arr[hi] < arr[hi . int lo. in which case. hi). hi).hi . // 1) swap first two values insertInOrder(arr. insertInOrder assumes the sorted section is to the left: public static void insertInOrder(int[] arr. insertInOrder(arr. hi . hi). lo + 1. hi).1. and for which insertInOrder assumes the sorted section is to the right: public static void insertInOrder(int[] arr. We will convert that into an accumulator-recursive version.1. insertInOrder(arr. } } But we also had a version that runs the recursive call on lo + 1. int hi) { if ((lo < hi) && (arr[lo] > arr[lo + 1])) { swap(arr. lo + 1. That said. Remember that we had two ways we could approach insertionSort. of the two recursive versions.. lo. // 2) run the subproblem } } public static void insertionSort(int[] arr.. int hi) { if (lo < hi) { insertionSort(arr. int lo. the ﬁrst is the more common one. and then into a loop.

372 We start by inserting arr[hi] somewhere other than into the sorted result of the recursive call – i.e. we move the insertInOrder idea from being after the insertionSort recursive call, to before the insertionSort recursive call: public static void insertionSort(int[] arr, int lo, int hi) { if (lo < hi) { // insert arr[hi] somewhere else, since now we haven’t // sorted the range lo...hi-1 yet insertionSort(arr, lo, hi - 1); } else // base case; do something here } Where can we insert arr[hi]? Well, as with any accumulator recursion, we’d need an extra parameter of some kind. In this case, that parameter can be some other sorted collection. (Once again, it’s not until our code is ﬁnished that it will actually work; our intermediate steps will just be pseudocode.) public static void insertionSort(int[] arr, int lo, int hi, sortedCollection) { if (lo < hi) { // insert arr[hi] into sortedCollection insertionSort(arr, lo, hi - 1, sortedCollection); } else // base case; do something here } When we reach just one value left (i.e. lo == hi), we will still have work to do. In the forwardrecursive version, we had only one value, and that was already sorted, so we just returned. However, now we have that one value, plus the sorted collection that we sent along as an argument to our new last parameter. So, what we need to do is combine our ﬁnal value, with the collection we have as a parameter. We could do this as follows: • insert last value into sorted collection as well, creating a sorted collection of all the values we’ve been worried about throughout all the recursive calls • replace the entire original subarray, with the sorted collection into which we just inserted, as the sorted collection is now the entire array that we want

373 So, we basically have this setup: // pseudocode of recursive method: public static void insertionSort(int[] arr, int lo, int hi, sortedCollection) { if (lo < hi) { // insert arr[hi] into sortedCollection insertionSort(arr, lo, hi - 1, sortedCollection); } else // base case { // insert arr[hi] (which is the same as arr[lo]) into sorted collection // make sorted collection our actual answer, i.e. copy it into this // array somehow } }

// pseudocode of wrapper method: public static void insertionSort(int[] arr) { insertionSort(arr, 0, arr.length - 1, emptyCollection); } Our initial value to the new parameter, will be an empty collection. Then, as we make our recursive calls, we will insert arr[hi] into that collection, and then arr[hi - 1], and then arr[hi - 2], and so on. The last question to resolve is how to store this parameter collection and eventually copy it back to our actual array. It might seem that we need an external array to store this collection, but in fact, we can use the same array we are already manipulating. At any point in the algorithm, we have a range lo...hi, where hi is less than or equal to our original value of hi: original subarray: <------------------------------------------> -------------------------------------------lo hi after many recursive calls: <--------------------> ???????????????????? -------------------------------------------lo current original value of value of hi hi The area marked by question marks still exists! We’ve just decremented hi enough via our various recursive calls, that our current insertionSort call doesn’t cover that area. But those cells are

374 still there. So, why not use that area for our external collection? That is, we’ve already sent arr as a parameter; let’s send our external collection simply by sending the index range of the “other” part of arr, the part that this recursive call isn’t manipulating, but that the original recursive call was manipulating?

extraLo extraHi <--------------------> ???????????????????? -------------------------------------------lo current original value of value of hi hi

Then, if we insert arr[hi] into this collection to the right, then the size of that collection grows by one, as the size of the subarray our recursive call is manipulating, shrinks by one:

extraLo extraHi <----------------> ???????????????????????? -------------------------------------------lo current original value of value of hi hi

As our algorithm proceeds, lo and extraHi won’t change, hi gets decremented once with each recursive call, and extraLo should also be decremented once with each recursive call, since it should always be the next cell over from hi. That gives us our ﬁnal accumulator-recursive version, which we see on the next page. The insertInOrder version that we use is the one that assumes the sorted range is on the right – since when we attempt to insert arr[hi] into the range indicated by question marks above, that range is to the right of arr[hi].

375 // wrapper method // the last two arguments are the lo and hi, respectively, of our // extra collection -- which are stored in the parameters extraLo and // extraHi in our recursive code. Since this extra collection should be // empty to start with, we have extraLo > extraHi public static void insertionSort(int[] arr) { insertionSort(arr, 0, arr.length - 1, arr.length, arr.length - 1); }

// this version of insertInOrder inserts the value at arr[lo] // into the sorted collection in lo+1...hi public static void insertInOrder(int[] arr, int lo, int hi) { if ((lo < hi) && (arr[lo] > arr[lo + 1])) { swap(arr, lo + 1, lo); // 1) swap first two values insertInOrder(arr, lo + 1, hi); // 2) run the subproblem } }

// Implementation #2 of the insertionSort lgorithm // This is the algorithm in accumulator-recursive form // extraLo will always be one greater than hi; technically, we can // eliminate extraLo since it’s not actually used anywhere public static void insertionSort(int[] arr, int lo, int hi, int extraLo, int extraHi) { if (lo < hi) { insertInOrder(arr, hi, extraHi); // arr[hi] is inserted into the // range hi+1...extraHi, i.e. // into the range extraLo...extraHi insertionSort(arr, lo, hi - 1, extraLo - 1, extraHi); } else // base case { insertInOrder(arr, hi, extraHi); // now the entire range lo...extraHi is sorted and that’s the // original range our array was supposed to sort } }

376 Converting this to a loop is then just a matter of using our usual technique: public static void insertionSort(int[] arr, int lo, int hi, int extraLo, int extraHi) { while (lo < hi) { insertInOrder(arr, hi, extraHi); arr = arr; lo = lo; hi = hi - 1; extraLo = extraLo - 1; extraHi = extraHi; } else // base case { insertInOrder(arr, hi, extraHi); // now the entire range lo...extraHi is sorted and that’s the // original range our array was supposed to sort } } and then eliminating the redundant assignments, public static void insertionSort(int[] arr, int lo, int hi, int extraLo, int extraHi) { while (lo < hi) { insertInOrder(arr, hi, extraHi); hi = hi - 1; extraLo = extraLo - 1; } else // base case { insertInOrder(arr, hi, extraHi); // now the entire range lo...extraHi is sorted and that’s the // original range our array was supposed to sort } }

377 Note that extraLo isn’t actually ever used anywhere, so we can get rid of that, too (we also could have gotten rid of it in the accumulator-recursive version): // Implementation #3 of the insertionSort lgorithm // This is the algorithm in loop-based form public static void insertionSort(int[] arr, int lo, int hi, int extraHi) { while (lo < hi) { insertInOrder(arr, hi, extraHi); hi = hi - 1; } else // base case { insertInOrder(arr, hi, extraHi); // now the entire range lo...extraHi is sorted and that’s the // original range our array was supposed to sort } } And you can see that the loop version, is basically just doing the same work that our “second” version of insertion sort did. The version of insertion sort where the recursive call was on the range lo+1...hi would, as the recursive calls returned, be sorting the right-hand side of the array. That’s what our loop version does above. And similarly, if you went through this process on that “second” version of insertion sort, you’d get a loop version that sorted the left-hand side of the array, just as our “ﬁrst” version of insertion sort did on the return from the recursive call on the range lo...hi-1. Or in other words, converting insertion sort from forward recursion to loop, reverses the direction of the sorting. The forward-recursive version that sorts left-to-right, becomes a loop version that sorts right-to-left, and the forward-recursive version that sorts right-to-left, becomes a loop version that sorts left-to-right. All four implementations are “insertion sort”, though for both the forwardrecursive and the loop-based versions, we tend to more commonly see the left-to-right code, than the right-to-left code.

378

**Part 4 : Algorithm Analysis
**

Generally, it is not enough to simply have correct code. For example, if you wanted a program to print the numbers from 1 to n, the following code would work: for (int i = 1; i <= n; i++) { int j = 1; while (j < i) j++; System.out.println(j); } but it is very wasteful. Why write a loop to count j upwards from 1 to i, when you can just use i and avoid the inner loop entirely? In that case, part of the code is an outright waste, so it’s easier to realize that the above code is not the best possible code. A much more diﬃcult situation to think through, is a situation where you have two well-written pieces of code that do the exact same thing in very diﬀerent ways. For example, we have seen two algorithms, selection sort and insertion sort, that both sort a subarray of integers. Which algorithm is “faster”? Even if we code both algorithms as well as we possibly can, they approach the problem in such diﬀerent ways that it can be hard to compare them and decide which algorithm is faster just by a quick glance. Deeper thought about the two algorithms is needed. In addition, it could perhaps be the case that both algorithms are good choices, just in diﬀerent circumstances. Perhaps one runs faster on large arrays, and the other one is faster for small arrays, and thus the size of the array might determine your choice of algorithm. Or perhaps one algorithm is faster but uses a lot of memory as it runs, and the other algorithm is slower but doesn’t use much memory at all, and thus how much memory you have available might determine whether you can aﬀord to use the faster algorithm or not. We want to reason through various algorithms, and be able to come up with some measurement of how much time the algorithm will take, and how much memory the algorithm will use, so that we can compare two diﬀerent algorithms in the ways we described above. In addition, ideally, these measurements would not depend on the particular machine, programming language, etc. that was being used, since if an algorithm is the best choice on today’s machine, it’s probably also the best choice if you get a machine that is 15% faster. What we want are results that describe the actual mathematical properties of the algorithms – and ways of measuring the algorithms so that we can obtain those results. For example, on the ﬁrst day of the course, we ﬁgured out that linear search on a collection of size n, would potentially need to look at all n values. That’s something that’s true no matter what machine/language/OS you are using, and thus that’s one of the results we would like to obtain about linear search and other such algorithms. That is the topic of this fourth part of the course.

379

8 seconds Now. etc. then our running time function will give us the new running time if we substitute 2n for n in the function. let’s consider comparing the speed of two algorithms. then that is more useful. we’d prefer a more mathematical way of comparing two algorithms. One thing we could do is to take two diﬀerent machines. perhaps the data above was for a set of 100 data items: .7 seconds Algorithm B on machine 2 -----------20. if we can describe the running time as a function of a general number of elements. compiler. some of those factors might be more important to us than others. If we increase our data size from n to 2n. and time the algorithms with a stopwatch. the comparison is a bit more appropriate. The problem comes when we try to deﬁne what “faster” means. we want to compare them in identical environments. how much memory it uses. at ﬁrst glance.4 seconds Algorithm B -----------20. we could still get diﬀerent results if we used a diﬀerent compiler. speed. things like processor speed and compiler optimizations (i. and those things really don’t have anything to do with the abstract description of an algorithm. the gap would not be so large. diﬀerent machine. If we do that. if we are going to compare algorithms in this manner. how good the translation from high-level code to machine code was) can aﬀect this result. the speed of the algorithm. Above. we get the following results: Algorithm A ------------10. and diﬀerent operating system. so that we can easily compare algorithms and choose the one that is best for our situation. and run algorithm A on one machine. maybe machine 1 is a seven-year-old machine. and once we run both algorithms on machine 2. etc. Perhaps if a diﬀerent machine were used. n. If we have two algorithms. etc. or other such implementation details that have nothing to do with the inherent properties of the actual solution description itself. On the other hand. This choice could be based on a number of factors – for example. So. So. requirements of an algorithm. perhaps the following are the results we get: Algorithm A on machine 1 ------------92. so that we can focus on the key details of the algorithm without considering what platform things are running on. algorithm B appears to take twice as long to run as does algorithm A. then that is one possible reason for choosing algorithm A over algorithm B.8 seconds Now. Or perhaps it would be larger. since the two algorithms are running on the same machine.e.380 Lectures 31 and 32 : Introduction to Algorithm Analysis In many situations. A and B. for solving the same problem. there are a number of algorithms we could use to solve a problem. what compiler was used. Knowing how fast we can sort an array of 10 elements isn’t particularly useful information. However. or how much data we need to process. and run algorithm B on the second machine on the same data. For any given situation. Therefore. We also have to take into account data size. we need a way to describe the time. and if algorithm A is faster than algorithm B. operating system. it might seem like Algorithm B is much faster. Even if the machine. For the moment. the same operating system. because we aren’t always going to be sorting arrays as small as 10 elements. However. were the same for the tests of algorithm A and B above. and so we need to choose which one to use. For example.

to 400 – the time the algorithm needs to run also doubles – from 10.6 seconds And with a few more data sizes.6 seconds 10.4 seconds 41.2 seconds Now. This would suggest that.4. If you were to graph the performance of these two algorithms as a function of the number of data elements. data size graph is a linear function. for large enough data sets. look at the numbers for algorithm A. to 41. then it means every time you increase the data size by a factor of k. to 100. etc. we might get: data size ----------50 elements 100 elements 200 elements 400 elements Algorithm A ------------2. Every time the data size is doubled. As the data size doubles each time – from 50. So the question is. we certainly could have two algorithms that exhibit this performance for the given data sizes.2. would get larger much faster than the running time of the algorithm with a linear order of growth – and indeed.4 seconds Algorithm B -----------20. you would get a picture with a parabola (for Algorithm A) and a straight line (for algorithm B). Now. this behavior is not strange at all. to 20. same operating system. every algorithm with a running time whose order of growth is quadratic.) If your time vs.381 data size ----------100 elements Algorithm A ------------10.) but this time we have 200 data items instead of just 100.6 seconds Algorithm B -----------20. algorithm A takes longer. we can see that exact aﬀect in our data above. Every time the data set doubles.6 seconds 166. Eventually.8. the running time of the algorithm with a quadratic order of growth. as with Algorithm A. the running time of algorithm A increases by a factor of 4. but for some other sizes algorithm B takes longer. but rather. the running time of the algorithm also doubles. then it means every time you increase the data size by a factor of k. But actually. (We can make a similar analysis for memory usage. using the same machine.6 seconds 83. data size graph is a quadratic function. . That is. why do we get this strange behavior? It seems that for some data sizes. Look closely at the numbers in the column for algorithm B. you increase the running time by a factor of k 2 .8 seconds 41. perhaps we would get the following results: data size ----------100 elements 200 elements Algorithm A ------------10. to 200.6. how quickly the performance degrades as we increase the size of the data set. to 83. will start to take longer than an algorithm with a running time whose order of growth is linear.4 seconds 20.4 seconds 41.8 seconds 41. same compiler. we would get a quadratic function and a linear function.8 seconds Perhaps if we run the same two algorithms (again.4 seconds Algorithm B -----------10. But if your time vs. That is the quality we are after. as with algorithm B. and it illustrates the important concept we want to discuss today. this is not unrealistic data. What we are discussing here is the notion of order of growth – we want to know not how fast an algorithm is on one data set. the running time also increases by a factor of k. as our data size gets larger and larger.

But. if you use the calculation above to get the address of the ﬁrst cell. or assignment would be such an operation For example. At ﬁrst. if we allocate an array of 10 ints. some parts of a function take exactly the same amount of time whether n is 10 or 1000 or 1 million. we will start by considering operations that do not depend on n. declaring a single variable i to use to run the for-loop iteration will take the same amount of time either way. • Array access is such an operation as well. the machine ﬁrst needs to move through cells 0 through 9. we said that variables of type int take up 32 bits. it is possible to hold the starting address inside the array reference. A[2] begins. And that is exactly what our calculation above tells us – that 64 bits after the starting address. The ﬁrst cell starts at some address in memory – the starting address of the array – and then the other cells are located immediately after it. the time it takes to write one object location to a reference variable (i. • A basic operation such as comparison. For example: • Declaring a variable would be such an operation. So. actually. you might not think so. and then use the calculation cell address = starting address + index * typesize to determine the starting address of the cell you want. we would see the start of A[1]. Arrays can given you (nearly) instant access to any cell because in memory the cells are arranged one after the other. In other words. it would seem that accessing a later cell takes longer than accessing an earlier one. we can get to any cell in the array via one multiplication and one . certainly that will take longer than if we declare 1. if A[0] is located at the starting address of the array and takes up 32 bits. if we declare 100 variables. The time to do one addition doesn’t suddenly increase because you plan on doing 10000 additions instead of 100. this is not the case.e. And since that takes up another 32 bits. it would follow that at bit 33 of the array. we can jump immediately to cell 2 by doing one multiplication and one addition. a logical operation. Meaning. So. we would see the start of A[2]. but if (for example) we are about to search an array of elements using a for-loop.382 So. whether we are going to search 100 elements or 1 million elements. address of A[2] = (starting address of A) + 2 * 32 bits = (starting address of A) + 64 And. But. It would seem that to get to cell (for example) 10. For example. those ten cells take up ten consecutive 32-bit cells in memory. Likewise. you would get: address of A[0] = (starting address of A) + 0 * 32 bits = (starting address of A) + 0 = starting address of A and we just said above that the ﬁrst cell was located at the start of the array. one by one. Therefore. Now. addition or subtraction. And in general. it would follow that at bit 65 of array. assignment) does not vary based on how many assignments you eventually do.

n column array to the screen. we have four times as many cells and thus four times as much printing work to do. If a quadratic-time algorithm has it’s running time graphed as a function of the data size. no matter how large the array is and no matter which particular index you are looking up. we can say that algorithms take constant memory if the amount of memory the algorithm uses. So. In general. and one addition of that bit total to the starting address tells you where the start of A[i] must therefore be in memory. respectively. that means (because we start indexing at 0) that you have to skip over i cells at (in this case) 32 bits each. a loop that counts down from n to 0 in steps of 1 would be linear – each loop pass subtracts 1 from n. The math function c*n would be a linear function (i. or triple it. because their running time is the same constant value regardless of the data size. so if you have n cells. so you’ll need n of those loop passes to reduce the number to 0. This is because array cells appear consecutively in memory. memory. Another way to view this is that you do a linear amount of work (such as printing an entire row of an array) to eliminate a constant amount of our count (one row of n). Similarly: • If the increase in the usage of a resource grows proportionally to the increase in the size of the data set. a straight line). you need to do that constant-time amount of work n times. if n is the number of rows of the array and # rows == # cols. That is. Operations like those above are said to take constant time. or quadruple it. or quadruples. you are eliminating a constant amount of data with each constant step. and so one multiplication tells you how many total bits to skip over. and so if you want A[i]. You do not need to traverse down the cells one by one – a single multiplication and addition will give you your array cell each time. That’s one way to recognize a linear function – if you are doing a constant amount of work for each step.383 addition. Printing an array is another example – each step. if the amount of a resource (time. array cell access time does not depend on the size of the array in any way. For example. This is because since you perform constant-time work to process a constant amount of the information. the graph will be a parabola – i. or triples. then we say that the order of growth of that resource usage is constant. then we say that the usage of that resource by the algorithm has an order of growth that is linear. a quadratic function. And the substraction is constant-time. if you double the amount of information. the resource usage grows by the square of that factor. What happens to the running time? If it likewise doubles. then you have a linear algorithm. whatever) that is used. A linear-time algorithm is one that grows linearly as the amount of data to process grows. do you a constant-time amount of work to print one cell. does not increase as the data size increases. • The order of growth of an algorithm’s usage of a particular resource is said to be quadratic if. so you are performing constant-time work n times. rather than growing by that same factor as a linear algorithm would do. we’ll have 9 times as much printing work to do. Similarly. An example is printing an n row. . then if n doubles. is unaﬀected by the growth of the data size. and the number of steps you do is proportional to n.e. you will be doubling the amount of times you have to run that constant-time work. another way to recognize a linear algorithm is to double the amount of data.e. If n triples. Or in other words. when the data size grows by some factor. So.

This result just gets more prominent the larger n gets. But this has nothing to do with the operating system I am using. ﬁrst for equality and then to see if the key is less than A[mid] – and those two comparisons together eliminate at least half the array from having to be examined. then I know trying to search an array about twice the size should take me about two hours.001 10. If it is taking me about an hour to search an array for an element (it’s a big array). So the next step is on at most half the original array. 4. So. All we really care about is whether the function is linear or quadratic or such. you get the graph of a logarithmic function. adding constant time to the running time (because logbase-2 of 2. as a total of the entire expression. we don’t have to concern ourselves too much with the particular details of the function. each constant amount of work eliminates an entire fraction of the data.) This information is useful because we can use it to learn what kind of eﬀect increasing our data set is likely to have.980296 . That means our analysis is completely platform-and-software-tool independent. the analysis we have done is what is known as worst-case analysis.999800 . and if you graph a logarithmic-time algorithm’s running time as the size of the data varies.998003 .384 • The order of growth of an algorithm’s usage of a particular resource is said to be logarithmic if an increase in the data size by some factor. here. 0. And the next step is on at most half of *that* subarray. Trying to search an array four times the size should take me about four hours. In fact. The number of steps needed to reduce the subarray are dealing with to size 1 is going to be a logarithmic number of steps. And so on. you can even take a quadratic function and see how little the lower order terms start to matter as n increases: n -----10 100 1000 10000 100000 n^2 + 2n + 1 ------------121 10.200. The fact that the running time is linear in the size of the array is a fundamental mathematical property of the algorithm!!.5 (n squared) and 213.201 1.001 n^2 / (n^2 + 2n + 1) -------------------. For example.332n and 1. increases the resource usage by the log of that factor. which is a fourth of the original array. we are concerned with ﬁnding out how long the algorithm takes to run in the worst . is 1). An example is binary search – you compare A[mid] to the key.2 are still both quadratic functions.002. The algorithm that is mathematically faster today will still be mathematically faster next year when processors have doubled in speed. (You’ll learn some formal notation for ignoring the lower-order terms in CS173.000. Up to this point.232n+2 are still both linear functions. because it helps us choose between algorithms when we are still in the process of designing our program. We aren’t very concerned with the lower order terms. it has nothing to do with the processor in my machine. As you can see. and because the decisions made based on such a comparison don’t change when a new processor comes along because the result of the comparison of two algorithms using mathematical analysis is completely independent of the processor you used. That is the best way to compare algorithms.001 100. even when n is 100. and such. the quadratic term accounts for almost the entire value of the expression. and constant factors.826446 .e.999980 The column at the far right measures what percentage the quadratic term is. so if you double the amount of work.020. you are adding only one more operation total – i.234 (n squared) + 3n . in the case of logarithmic running time. and it has nothing to do with the compiler I used. In this kind of analysis.

. On the other hand. ”What happens in the most ideal situation?” is what we are asking here. but “hit a snag” in certain situations and spent an extra hour processing before piping in oxygen.. In fact. Sometimes – though not usually – we also discuss the ”best case”. then we might ﬁnd that acceptable. but we *do* care if they end up worse than we expect. in such a case it would be important to know the absolute longest that your algorithm could take. we don’t care too much about what the possible best we can expect to see it. you are designing software to pipe extra oxygen into the space shuttle living area for our astronauts in the event that their existing oxygen all leaks out somehow. and also what is most likely. even in the worst case.385 possible situation.so while we care about what the worst we can expect to see would be. often what we are more concerned with is what usually occurs. even if occasionally we end up with a slower case as a result. For example. That is the idea of worst-case. We don’t *care* if things work out better than we expect. Sometimes we will prefer to focus on what “usually” happens. and pick an algorithm that “usually” runs very fast. This notion of “usual” is known as the average case. The astronauts would die waiting for your algorithm to ﬁnish!! So. you’d want to make sure that the algorithm you chose to make this oxygen delivery system work was very very fast. if it usually printed right away. and only took 10 minutes every once in a long while due to some quirk in certain types of ﬁles. but that was what “usually” happened as well. we might prefer that to a situation where the worst case was only 5 minutes. But. We usually don’t care about this case because it’s not interesting. . We care about the worst possible situation. It would do you no good if it “usually” worked quickly. no matter what input the algorithm is given. and attempts to analyze an algorithm with the average case in mind are known as average case analysis (in contrast to worst case analysis). We might not like using a computer system that took 10 minutes to send a ﬁle to the printer each time we wanted to print.

386 Lecture 33 : Selection Sort Analysis Consider our selection sort code: public static int findMinimum(int[] arr. arr[j] = temp. This is because every time we hit the recursive case of selectionSort. on a subarray one smaller in size than before.) from main(. If our subarray is initially size n when we make our ﬁrst call to selectionSort. lo. lo + 1. else return indexOfMinOfRest. else { int indexOfMinOfRest = findMinimum(arr. if (arr[lo] <= arr[indexOfMinOfRest]) return lo. we will make another selectionSort call. int lo... plus the n-1 more calls to get down to a subarray of size 1 – making n calls total to selectionSort. we would end up with n total calls to selectionSort. int i. swap(arr. int lo. we will need to make n-1 more calls before the subarray size has reached 1.). int j) { int temp = arr[i]. lo. Thus. int hi) // lo <= hi { if (lo == hi) return lo.. hi). lo + 1. on a subarray of size n. we have our ﬁrst call (the one from main(. } } public static void swap(int[] arr. } public static void selectionSort(int[] arr. } } If we imagine calling selectionSort(. that is the base case and so there would not be any additional calls to selectionSort. hi). int hi) { if (lo < hi) { int indexOfMin = findMinimum(arr. then since each recursive call decreases the subarray size by one. ... arr[i] = arr[j]. And when the subarray size reaches 1. selectionSort(arr.. hi). indexOfMin).).

. every time you are in the recursive case.. you’ll be at the recursive case n-1 of thoses times.. and you call findMinimum(...) from selectionSort(. you will start – and later return from – n separate calls to selectionSort(. n-1 times... We are not speaking here of all the work that goes on while you are on that notecard. how often does each of those things happen? • Each of the n calls to selectionSort(.). which means creating a new method notecard and copying the argument values onto that notecard. • You will need to make a call to findMinimum(.). what work gets done by selectionSort as we go through those n calls? Well: • You will sometimes need to evaluate the base-case condition to see if you are in a recursive case or a base case. lo < hi • You will sometimes need to make a call to findMinimum • You will sometimes need to perform a swap • You will sometimes need to add 1 to the parameter lo in preparation for a recursive call (i. so you’ll make n-1 calls to findMinimum(. and add 1 to lo.. i. as the array grows bigger and bigger? .. call swap(.) must check once to see if you are at the base case or the recursive case.. so you’ll add 1 to lo.. one of those times.) every time you are in the recursive case.. and at the base case.).).e. is the eventual destruction of that notecard when that method call has completed.. So..). • And as we’ve already explained.) from selectionSort(.)... you’ll need to evaluate the recursive call arguments that need evaluating) • You will sometimes start a new method. what is the order of growth of the time the computer needs to do each one of those steps once.).. so you’ll make n-1 calls to swap(. Finally. • You will need to make a call to swap(. So.) every time you are in the recursive case.. the work involved in permanently destroying that notecard and returning to the previous method call.e. of the work involved in creating the notecard..387 So. But you evaluate the lo < hi condition within selectionSort(.. n times.. n diﬀerent times in all. and some time later. Along with that work. We are only speaking here. you evaluate the base-case condition and make/return from a method call.. n-1 separate times from within selectionSort(... nor are we speaking of the work that gets done on elsewhere whenever you leave that notecard temporarily. • You will need to add 1 to the parameter lo in preparation for a recursive call.

. • A call to swap(. as an argument. it is a linear function. plus four array accesses and three assignments.e. • The method call set-up/destroy overhead will not depend on the size of the array. We will call this constant. So. And that leaves us with: (cbase ∗ n) + (cswap ∗ (n − 1)) + (caddition ∗ (n − 1)) + (cmethodcall ∗ n) which. since no matter how large the array is. We will call this constant. .388 • The base-case condition check: evaluating a “less than” comparison of two integers is a simple machine instruction and thus is constant time.) for just a moment. after running some algebra. • Let’s ignore findMinimum(. We will call this constant caddition . so it is constant time.e. as we’ve already discussed. if the array becomes ten times as large. becomes: (cbase + cswap + caddition + cmethodcall ) ∗ n + (cswap + caddition ) ∗ −1 If we condense our constants by creating a new constant cW to be the sum of four of these constants: cW = cbase + cswap + caddition + cmethodcall and another new constant cX to be the sum of two of those terms: cX = cswap + caddition then our algebra becomes cW ∗ n − cX i. And so is method call overhead. cswap . An array access is constant time. the whole package adds up to constant time for one swap operation. We will call this constant. we are only passing a single reference to the array. i. This does not take longer as the array grows larger. cmethodcall .. So is an assignment. • Adding 1 to lo is a simple machine operation.. this cost will also be constant time. cbase ..) consists of the overhead to start and return from one method. we’ll deal with that on its own after we ﬁgure everything else out. it doesn’t change the time needed to compare two integers to each other once. So. a single addition implemented by a single addition instruction..

is constant.e. We’ll call that constant cmin−compare . • Every one of the n-1 recursive cases needs to add 1 to lo.) on a subarray of size n? well. it is a linear function.389 And that brings us to findMinimum(. runs in constant time. and eventually. return from n diﬀerent findMinimum(. having more array cells won’t make it take longer to run one return statement. We’ll call that cassign . Each such call/return cost is a constant.. . The time to evaluate the (very simple) expression inside the return statement. and the comparison is constant time.. becomes: (cequality−comp +cadd−in−f ind−min +cassign +cmin−compare +creturn +cf ind−min−call )∗n+(cadd−in−f ind−min + cassign + cmin−compare ) ∗ −1 If we condense our constants by creating a new constant cY to be th e sum of these six of these constants: cY = cequality−comp + cadd−in−f ind−min + cassign + cmin−compare + creturn + cf ind−min−call and another new constant cZ to be the sum of three of those terms: cZ = cadd−in−f ind−min + cassign + cmin−compare then our algebra becomes cY ∗ n − cZ i. What work gets done by findMinimum(. That is two arrays accesses and a comparison... and to run the return statement. • We are actually returning a value in every case of this method. • Every one of the n-1 recursive cases will need to perform an assignment to write the value of the recursive acll into a variable. We’ll call this constant creturn . Let’s call this constant.) calls. • Every one of the n-1 recursive cases will need to compare arr[lo] to arr[indexOfMinOfRest]..). That said: • Checking the lo == hi condition will happen in every one of the n method calls. We’ll call that cadd−in−f ind−min . A single addition. there will be n-1 recursive calls and 1 base case. and three constants added together still result in a constant. since having more array cells won’t change the time it takes to read two array cells and perform one comparison. Each array access is constant time. and it takes constant time. as we have already discussed. runs in constant time. after running some algebra. together. that work is still constant time. We’ll call this constant cf ind−min−call .. cequality−comp . And that gives us: (cequality−comp ∗ n) + (cadd−in−f ind−min ∗ (n − 1)) + (cassign ∗ (n − 1)) + (cmin−compare ∗ (n − 1)) + (creturn ∗ n) + (cf ind−min−call ∗ n) which. A single asignment. • We have to start..

. with substitution.. .cZ cY * (n-2) . we get: ((cY /2) ∗ n2 ) + (((cY /2) − cZ + cW ) ∗ n) + (cZ − cX ) which is still quadratic. and then add those costs together: selection sort step # ----------1 2 3 4 5 6 . .... + 4 + 3 + 2 + 1 == n(n+1)/2 (You’ll probably prove this in CS173 using mathematical induction.cZ cY * (n-1) . n-4 n-3 n-2 n-1 running time of findMinimum(. . . cY * (n-k+1) ..) cost of each step of selectionSort(. Even if we add the earlier work.cZ cY * (n-3) . it turns out that: n + (n-1) + (n-2) + (n-3) + .).. we need to list the findMinimum(.) is called once in each of the recursive-case method calls of selectionSort(. . .390 Now. So to get a true assessment of the situation.) call runs on is diﬀerent each time.cZ cY * (n-4) ....) in this step -----------------------------cY * n . k . the one problem here is.). ..cZ cY * (n-5) .. And that sum is just: cY * (sum of numbers from 2 through n) .. ...(n-1)*cZ Now.) over the lifetime of the selectionSort(.) So..cZ cY * 3 .cZ If you add up the second column. this becomes: cY ∗ ((n ∗ (n + 1)/2) − 1) − (n − 1) ∗ cZ which if simpliﬁed.) algorithm. becomes: ((cY /2) ∗ n2 ) + (((cY /2) − cZ ) ∗ n) + cZ and that’s a quadratic function. the “n” that the findMinimum(.cZ . that’s the work for findMinimum(.. cY * 5 .cZ cY * 4 .cZ ..cZ cY * 2 . even though findMinimum(. .

) costs. and even the best-case analysis – since findMinimum(. But it’s useful to have gone through at least one very detailed analysis.. and thus when you add the findMinimum(.. What we just did was the worst-case analysis. there is no diﬀerence between any of the cases. you don’t need to always indicate every little constant the way we did here. Normally you can just reason through the summary like that... and thus linear total. the non-findMinimum(. you see that it is quadratic...) is constant per step..391 A quick way to summarize this is: • All the work with the exception of findMinimum(.e.. just so you see all the constants and how they would add up. • The findMinimum(..) doesn’t run any faster when the minimum is the ﬁrst cell of the array. All of them have running times whose orders of growth are quadratic.. selectionSort(.).) costs. . So... add up to a quadratic function.. • A linear plus a quadratic is a quadratic.) costs for each step together. i. but note that it is also the average-case.) won’t run any faster even if you pass a perfectly-sorted array to selectionSort(. plus the findMinimum(..) result is linear per step...

that is constant time per step and thus linear overall. and as we saw above for SelectionSort. so we have a table much like for FindMinimum. then in InsertInOrder. worst case is about (n squared)/2. The primary work here is InsertInOrder – the rest is just starting and returning from InsertionSort recursive calls. For InsertionSort. though they are both quadratic. the average case would take only about half as long as the worst case. so the average case would involve multiplying each of the second-column values above by one-half. then compares A[3] to A[2]. k . . rather than the entire array. .e. Or in other words. . . and so on. i. That’s just constant time. ¡p¿ InsertInOrder’s worst case is when A[hi] needs to be shifted all the way to the front of the array – then *every* value needs to be moved to a diﬀerent cell. n-4 n-3 n-2 n-1 n (i. the size of the subarray that InsertInOrder works on is diﬀerent each step. all InsertionSort basically does is to compare A[2] to A[1]. . then compares A[4] to A[3]. Remember that the ﬁrst call InsertionSort has. This trend continues – if you had an already sorted array. base case) # of cells InsertInOrder moves in worst case in this call -----------------------------n n-1 n-2 n-3 n-4 n-5 .392 Lecture 34 : Insertion Sort Analysis Analyzing InsertionSort The worst case here is much like SelectionSort.. 5 4 3 2 0 So again. Note that for SelectionSort. . on an already-sorted array. we might expect to have to shift A[hi] down half the array. since we don’t run InsertInOrder in the ﬁrst InsertionSort call. .. So. n values need to be moved. if you were to time them. until we make the second InsertionSort call and return from it: InsertionSort call ----------1 2 3 4 5 6 . A[hi] would need to be compared to A[hi-1]. but no shifting would be needed and the method would end there. the average case would be about (n squared)/4. the worst and average cases were the same. n-(k-1) == n-k+1 . .so over the life of the algorithm – n steps – we spend constant time on each step so it’s linear total.e. On average. But of course. . is the *last* time InsertInORder runs. up to comparing A[hi] to A[hi-1]. That’s n-1 comparisons (there’s no comparison in the base case) so linear time.

that gives us: 221 = 2 ∗ 210 ∗ 210 If we wanted to calcualte 222 . and then square it (a 6th multplication) to get 210 . In the implementations we were just discussing. In our ﬁrst exponentiation algorithm. and then square that result (a 7th multiplication) to get 220 . if we are trying to calculate 220 . That’s only 11 multiplications total! In fact. which is much faster than using 15 multiplications to get there. We already know that 220 = 210 ∗ 210 . mathematically. if for no other reason than. we will instead rely on the following recursive idea instead: baseexp = baseexp/2 ∗ baseexp/2 Now. we would have needed 15 multiplications to go from 25 to 220 . but by squaring 25 . and then square the “result so far” to get 220 . how does knowing the value of 210 help us? And the answer is: 220 = 210 ∗ 210 So. so we could perform 5 multiplications to obtain 25 . and then squaring that square. 210 is 25 ∗ 25 . For example. let’s just do 10 multiplications to get 210 . Consider what would happen if we still kept the base the same. our important recursive idea was: baseexp = base ∗ baseexp−1 now. this doesn’t quite work for exponents that are odd numbers. let’s try this: 220 x = 210 =x∗x That is. we could get away with even fewer multiplications. we divided it by 2. and we only need to multiply 220 by 2 to get 221 . instead of doing 20 multiplications to calculate 220 . and we are trying to stay with integer exponents. that would just be multiplying by 2 one more time: 222 = 2 ∗ 2 ∗ 210 ∗ 210 but that’s just the equivalent of: 222 = 211 ∗ 211 . So.393 Lecture 35 : Exponentiation and Searching A second and third algorithm for exponentiation The subproblem we chose for our ﬁrst exponentiation algorithm – reducing the exponent by one and keeping the base the same – is not the only subproblem that works for the exponentiation problem. we can go from 25 to 220 in just two multiplications. exp/2 is not an integer if exp is odd. but instead of subtracting 1 from the exponent. But our solution to this problem can be seen by considering the calculation of 221 . since we could calculate 210 in this manner as well.

though.base * base | | if exp > 0 and exp is even | | | (exp-1)/2 (exp-1)/2 |_____ base * base * base if exp > 0 and exp is odd This is a fundamentally diﬀerent way of performing the exponentiation than the previous algorithm was. if exp > 0 and |______ exp is odd If we actually implement the above in the way the formula implies – by calculating your recursive call once and then squaring the result – then this third algorithm will use fewer multiplications . However. is that in both recursive cases. where x = base . If we save the result the ﬁrst time. we have the following algorithm as a second algorithm for calculating exponentiation.x * x.394 So. as long as we have an even exponent. if exp > 0 and exp is even | | | | (exp-1)/2 | base * x * x. we can just use it again instead of recalculating it from scratch. What we might note. we will still perform the same number of multiplications in the end. where we express baseexp in terms of baseexp/2 // second algorithm for exponentiation ______ 1 if exp > 0 | | exp | exp/2 exp/2 base -----|----. and for an odd exponent. we are performing the same recursive calculation twice. we can just use the baseexp = baseexp/2 ∗ baseexp/2 pattern. where x = base . And this will give us a third algorithm for computing exponentiation: // third algorithm for exponentiation ______ 1 if exp > 0 | | exp | exp/2 base -----|----. we’ll use this pattern instead: baseexp = base ∗ base(exp−1)/2 ∗ base(exp−1)/2 That is.

and we haven’t even written any code yet! Saving intermediate results is sometimes helpful.1)/2) * base. else // exp > 0 { if (exp % 2 == 0) return pow(base. were all implementations of the same algorithm. you will explore the concept of dynamic programming much more in a later course. 1) exp/2) * pow(base. The three code results we looked at for the ﬁrst algorithm. Note that. (exp . But this is not simply a diﬀerent way to code the same work.) For example. just organized in diﬀerent ways (some of those organizations needing many method calls. exp/2). and a completely diﬀerent algorithm entirely. exp >= 0 exp/2) * pow(base. but we are already talking above about how we can save so many multplications using this approach. } } base. int exp) // base >= 1. we are calling pow twice with the same arguments. exp/2). (exp . If we instead write code for our third exponentiation algorithm. (This saving of intermediate results is called dynamic programming. because some recursive algorithms can repeat subproblems and we’d rather not have to perform the same calculation many times. else // (exp % 2 == return pow(base. this is a completely diﬀerent computational process in the ﬁrst place. we get the following: . 1) (exp . exp/2) * base. In both those cases. and others not needing those calls). leading to the following alteration to the above code: public static int pow(int { if (exp == 0) return 1. There is a diﬀerence between coding the same algorithm in a slightly diﬀerent way.1)/2) * pow(base. else // (exp % 2 == return pow(base. consider the algorithm above that was labelled our “second exponentiation algorithm”. And that is why all three code examples for algorithm 1 all performed the same number of comparisons and the same number of multiplications and the same number of subtractions. we can code it as follows: public static int pow(int { if (exp == 0) return 1.1) / 2 is equal to (exp / 2). } } base. thus repeating work. else // exp > 0 { if (exp % 2 == 0) return pow(base. if exp is odd.395 than the previous algorithms did. exp >= 0 exp/2) * pow(base. int exp) // base >= 1. and thus did the same calculation work.

.396 public static int pow(int base. exp/2). else { int intermediateResult = pow(base. if (exp % 2 == 0) return intermediateResult * intermediateResult. exp >= 0 { if (exp == 0) return 1. we avoided having to run that entire recursive calculation a second time. int exp) // base >= 1. } } By saving the value of our recursive call. else return intermediateResult * intermediateResult * base.

397 Binary Seach If our linear search is unsuccessful. if we have access to additional information about our collection or the items inside it. we need to ﬁnd a value larger than X. So. and that wouldn’t be any better than linear search. However. we will only need to search the subarray to the left of X. suppose the value X. the items in our array cells increase as we move from arr[lo] to arr[hi] (or if we will allow duplicate items. How? Well. we are looking for either X itself. sometimes we ﬁnish our search quickly. and everything greater than X will be be to the right of X. we will end up having searched every cell in the range lo through hi – there is no other way to be sure every cell is unequal to the search key. we will consider the problem of having a very particular piece of extra information – knowledge that the array is sorted. Knowing that the array is sorted allows us to improve our search algorithm tremendously. inspecting the ﬁrst cell of the array ﬁrst is not the nicest decision. then the items in the array are non-decreasing as we move from arr[lo] to arr[hi]). nothing in the subarray to the left of X can be greater than X. then if we’re really lucky. so that the next subarray we search is of size 0 (since if X is the ﬁrst value in the array. so the subarray to the left of X can be ignored entirely. And we can use a similar reasoning if what we are looking for is greater than X – in that case. we are looking for an item less than X. and in that case. That is. pick some value in the array to examine. and so we’re not really taking advantage of our knowledge that the array is sorted. then we only need to search one side of the value. as we said back in lecture 1. we have the entire rest of the array to inspect. For example. Obviously. But if in that case – where X is the ﬁrst value in the array – if we are unlucky. Now. So. we’ll be able to eliminate about half the array from needing to be searched: . or the other. nothing to the right of X is less than X. if X were the ﬁrst value in the array. or if not. is the initial value we inspect: _____________________________________________________________ | | | | | values < X | X | values > X | |________________________|_______|___________________________| indices < i i indices > i Everything less than X will be to the left of X. Sure. but other times. so the entire subarray to the right of X can be ignored. then we are done! But if it is not our key. we can only rule out the cell containing X (once we inspect that cell) but still have the entire rest of the array to the right of X to search: ____________________________________ | | | | X | all other values are > X | |_______|___________________________| lo indices > lo no values < X no indices < lo So. whether we are searching for a value less than X or greater than X. if that value turns out to be our key. if the item we are looking for is less than X. there is nothing to its left). It would be nicer to inspect the middle cell of the array ﬁrst – that way. at index i. we can sometimes use that additional information to rule out certain items without having to inspect them.

hi). we’ll take the left of the two middle cells. and if the middle value was not our search key. and we could take either 4 or 5 as the middle cell. then you have no values to explore and should return -1. return the index. you recursively search the subarray to the left of the middle index. Call the middle index mid. We won’t need to make both recursive calls. in our actual code. int lo.5.. if what we are searching for is less than the value at mid. int hi) As with linear search.hi. So. then (lo + hi)/2 will end up truncating a division. then the index 4 will be the easiest choice for the middle index.. ﬁnd the middle index of the sorted array and call it mid. (lo + hi)/2 gives us 4. if what we are searching for is greater than the value at mid. since we have an even number of cells. . It cannot be both less than and greater than the middle element. For example. when we have an even number of cells in our subarray. But since.. If what you are searching for is at that index. that makes no sense. In that case. if lo > hi.1). We’ll start with the same parameters we had for linear search... our subproblem could be to search on half the array. then we recursively search the subarray with index range (lo. If you have an odd number of cells in the range lo.398 _____________________________________________________________ | | | | | values < X | X | values > X | |________________________|_______|___________________________| indices < middle middle indices > middle (about half of the (about half of the total number of indices) total number of indices) So. the low index of our new subarray will be the smallest index that is still to the right of mid – namely. the low index is still lo. mathematically. or we recursively search the subarray to the right of the middle value. mid + 1 – and therefore the subarray we search recursively has the index range (mid + 1. Since we’re looking to the left. Otherwise. then (lo + hi)/2 would be an integer.1.. and the same return type: public static int binarySearch(int[] arr. If you have an even number of cells in the range lo. the index mid . if what you are searching for is less than that value. The middle index will be the average of the lowest and highest indices. rather than the ﬁrst value. since the indices are all consecutive. We could check the middle value of the array. Otherwise.mid . Likewise. only one. and an existing index. (lo + hi)/2 will be truncated to 4. This is because the search key – assuming it’s not equal to the middle value – can only be less than that middle element or greater than the middle value. but the high index is now the highest index that is still to the left of mid – namely.hi. That is. There is no one clear middle cell.. int key.. then we either recursively search the subarray to the left of the middle value. as the middle cell we use in our algorithm. then we want to search the subarray to the right of mid. if you had the indices 0 through 9: 0 1 2 3 4 5 ----6 7 8 9 then 4 and 5 are the cells in the middle.

. and 71 is equal to arr[6].9: ------------------arr[i] 3 11 31 46 59 67 71 78 93 94 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 lo == 5. and 71 is greater than arr[4] == 59. so we recursively search the subarray 5.. 72 is greater than 59. we end up shrinking our subarray down to size 0. mid == (6 + 6)/2 == 6. But in the fourth step.. so we recursively search the subarray 5. mid == (5 + 9)/2 == 7. hi == 6 Now. we instead have a search key greater than the current middle value: . hi == 9 Our mid index is 4. instead of having found our value. if we were searching for 72 instead of 71. the index where that value is located. so we have found our value and we return 6. as we did above. For example. and greater than 67. and 71 is greater than arr[5] == 67.6: -----arr[i] 3 11 31 46 59 67 71 78 93 94 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 lo == 5.. mid == (5 + 6)/2 == 5. less than 78. hi == 9 Now. If our search turns out to be unsuccessful.. suppose we are searching for 71 in the array below: --------------------------------------arr[i] 3 11 31 46 59 67 71 78 93 94 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 lo == 0.6: -arr[i] 3 11 31 46 59 67 71 78 93 94 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 lo == 6. so we recursively search the subarray 6. and 71 is less than arr[7] == 78. the ﬁrst three of the four steps above would be the same.399 For example. hi == 6 Now..

hi recursively. we’d search the range mid + 1. but for now we can at least note that binary search. indicating an unsuccessful search. hi == 6 And as previously stated.. the maximum number of cells it will need to look at will be much less than the maximum number of cells that linear search will need to look at. We’ll quantify this diﬀerence in a moment. And. each of our two search algorithms ﬁnds some values faster – linear search works better if our value is at the ﬁrst index..400 -arr[i] 3 11 31 46 59 67 71 78 93 94 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 lo == 6. in that case. Certainly. so now we are searching the range 7. so we can’t really select an algorithm based on where the value is. whereas binary search is continually discarding half the remaining array without even having to inspect the cells in that half of the array... binary search wins out (assuming we can use it to begin with. and binary search works better if our value is at the middle index. But one thing we can do is to select an algorithm based on the maximum number of comparisons we might need to make before we know our return value for certain. assuming the array is sorted) – linear search potentially searches every cell in the array. mid + 1 == 7.e.subarray of size 0 | arr[i] 3 11 31 46 59 67 71 78 93 94 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 lo == 7. Since there are so many cells that binary search won’t need to look at. and 72 is greater than arr[6] == 71 So. in that case we would return -1.. at worst.. .i. for example. inpects signiﬁcantly fewer cells than the worst possible case of linear search. Except. hi == 6 mid == (6 + 6)/2 == 6. The catch is that we don’t know where the value is ahead of time.6 and we have lo > hi: | <----.

So. For exponentiation. int hi) { int returnVal. the running time grows logarithmically with the change in the array size. and likewise. if (arr[mid] == key) returnVal = mid. int key. else // key > arr[mid] returnVal = binarySearch(arr. key. } return returnVal. arr.401 Our code that implements this algorithm appears below: public static int binarySearch(int[] arr. the question is. this new exponentiation algorithm’s running time grows logarithmically with the change in exponent. then your algorithm will take log2 n time. if you have constant time per step. if (lo > hi) returnVal = -1. int lo. else // lo <= hi { int mid = (lo + hi)/2. for binary search. key.1). we’d probably have a wrapper method around that as well. hi). Similarly. it takes constant time to go through the call once. and log2 n steps. So we’ve managed to improve the order of growth of the running time. . The exponentiation algorithm divides the exponent in half each time. } Of course. key. of both exponentiation.length-1). how many recursive calls are started? If you divide the value in half each time. } In both of these algorithms. but for binary search. and the binary search algorithm recursively searches on half the remaining array each time. the number of times you can divide n in half before you reach 1 is log2 n. mid + 1. int key) { return binarySearch(arr. So. and the searching of a sorted array. logarithmic time. lo. the recursive call operates on a collection that is half the size of the original collection. as we did for linearSearch: public static int binarySearch(int[] arr. that is. the running time for linear search grew linearly with the change in the array size. 0. else if (key < arr[mid]) returnVal = binarySearch(arr. mid . The ﬁrst exponentiation algorithm’s running time grew linearly with the change in exponent. it takes constant time to go through the call once. how many times can you do that before the problem can no longer be divided in half? The answer is.

b1 is the minimum of that collection by deﬁnition. Mergesort will make recursive calls to sort both the ﬁrst half. and to ﬁnd out which of the two is the overall minimum.402 Lectures 36 and 37 : Mergesort Mergesort is related to InsertionSort in the same way that Quicksort was related to SelectionSort. we know it can’t be any of a2. Mergesort: 1) sort the first half of the array (recursively) 2) sort the second half of the array (recursively) 3) now that you have two sorted subarrays.) Likewise. and you get the rest of the sorted collection by merging the remaining two sorted collections recursively: . So. That is to say. What does Merge do? Well. SelectionSort has one recursive call as the last work that needs to be done. even of other values are tied with it. and then. once the array was fully partitioned. well. we will have a Merge algorithm in Mergesort that will handle most of the work for this algorithm. that’s the ﬁrst element of your new sorted collection. and a4 aren’t even the minimum of their own collection. InsertionSort began with one recursive call and then did other work. just as InsertInOrder was where most of the work was done in InsertionSort. (If we had duplicates. b3. MergeSort will begin with two recursive calls and then do other work. a3. And if we want to ﬁnd the overall minimum. of the array – and so the work that remains after that is to merge the two sorted halves of the array – hence the name of the algorithm. a1 still has to be the smallest value of its collection. because for any two sorted collections. and Quicksort has two recursive calls as the last work that needs to be done. the overall minimum has to be either a1 or b1. This can actually be done in one pass down the data. we just compare them and select the smaller of the two values. in the second collection. and just as Partition and PartitionWrapper were where most of the work was done in Quicksort. and so there is no way b2. merge them into one sorted array So. Quicksort did a bunch of partitioning work. or a4. and so if a2. The recursive calls are all going to make Merge calls themselves. it takes two sorted collections and builds a single sorted collection out of them. made two recursive calls to sort the two halves of the partition. or b4 can be the overall minimum since they aren’t even the minimum of their own collection. they certainly can’t be the overall minimum. and then the second half. it’s easy to ﬁnd the minimum value: a1 b1 a2 b2 a3 b3 a4 b4 (a1 < a2 < a3 < a4. and b1 < b2 < b3 < b4) Above are two sorted collections of values. just as FindMinimum was where most of the work was done in SelectionSort. In the event that it is a1. since it is sorted. a3. and over the lifetime of the algorithm. the primary work on MergeSort occurs after we’ve already made two recursive calls to sort two parts of the array recursively. most of the time will be spent in Merge. because the ﬁrst collection is sorted so a1 is smaller than all of them.

10 6 20 18 23 26 25 50 Start of merge call #3: 6 1 2 10 18 20 26 23 50 25 6 < 10. . For example: Start of merge call #1: 2 1 6 10 18 20 26 23 50 25 1 < 2. and b1 < b2 < b3 < b4) And if instead b1.. so.. you would then just automatically pick the minimum value from the other collection for your ﬁrst element.. then you have no elements and just return. Start of merge call #2: 2 1 2 < 10... and b2 < b3 < b4) If you reach the point where one collection is completely empty.403 a1 then merge these: a2 b1 a3 b2 a4 b3 b4 (a2 < a3 < a4.. and you get the rest of the sorted collection by merging the remaining two sorted collections recursively: b1 then merge these: a1 b2 a2 b3 a3 b4 a4 (a1 < a2 < a3 < a4. so. then that’s the ﬁrst element of your new sorted collection. If both collections are completely empty. so.

. so. . Start of merge call #7: 26 1 2 6 10 18 20 23 50 25 23 < 26. Start of merge call #8: 26 1 2 6 10 18 20 23 25 50 25 < 26. so.. Start of merge call #5: 18 1 2 6 10 20 26 23 50 25 18 < 20..404 Start of merge call #4: 18 1 2 6 10 26 20 50 23 25 10 < 18.. so. so.. so... Start of merge call #6: 26 1 2 6 10 18 20 50 23 25 20 < 26....

Start of merge call #11: 1 2 6 10 18 20 23 25 26 50 no collections are left. we are done. the entire ﬁrst collection – half the cells – needs to be shifted to the right one cell to make room for 1 to go at the front. If we did that shifting of the ﬁrst collection repeatedly. So instead. and min of that is 50. we see the two sorted collections we just merged in our example. When we’ve ﬁnished the merge. start returning from recursive method calls The one sticking point here is that we can’t do this eﬃciently in place: 2 6 18 26 50 ____________________ first sorted half 1 10 20 23 25 _________________ second sorted half Above. since it is less than 2. so. 1 is the minimum of the entire collection. we copy everything from the temporary array back into the original array. We keep track of the lo and hi of each of the two collections.405 Start of merge call #9: 26 1 2 6 10 18 20 23 25 50 first collection is all that’s left. if we try to move 1 to the front of the array. This means we’ll need to keep track of the next spot to write into in the temporary array as well. However. but have a temporary array that we write the next minimum into. Start of merge call #10: 50 1 2 6 10 18 20 23 25 26 first collection is all that’s left. again. we use a temporary array... so. For example: . In this case. that cost will add up.. stored side-by-side in an array as they would be after MergeSort’s two recursive calls. cell by cell.. and min of that is 26.

.. so.. so... Start of merge call #2: 2 loA 6 18 26 50 hiA 1 10 loB 20 23 25 hiB 1 cur 2 < 10. so. Start of merge call #3: 2 6 loA 18 26 50 hiA 1 10 loB 20 23 25 hiB 1 2 cur 6 < 10. .406 Start of merge call #1: 2 loA 6 18 26 50 hiA 1 loB 10 20 23 25 hiB cur // temp values written here // index to temp array 1 < 2..

so.. You know the ﬁrst collection is empty when loA > hiA. mid. and you know the second collection is empty when loB > hiB. int[] temp = new int[hi-lo+1]. int hi) { if (lo < hi) { int mid = (lo + hi)/2.. Copy(temp. Merge(A. 0).. MergeSort(A. temp. int lo. // copy temp array back to A } } . lo. hi). mid). Start of merge call #5: 2 6 18 loA 26 50 hiA 1 10 20 loB 23 25 hiB 1 2 6 10 cur . 0). hi.. A.407 Start of merge call #4: 2 6 18 loA 26 50 hiA 1 10 loB 20 23 25 hiB 1 2 6 cur 10 < 18. lo. hi. mid+1. and so on. MergeSort(A. lo. mid+1. This gives us the following code: public static void MergeSort(int[] A.

cur1+!. end1. end1. cur1. end1. temp. end1. } } /* you could also combine those conditions. int end1. end1. temp. cur3+1). cur2. end1. end2. } else if (cur2 > end2) { temp[cur3] = A[cur1]. cur1+1. Merge(A. int[] temp. } else // ((cur2 > end2) || ((cur1 <= end1) && (A[cur1] < A[cur2]))) { temp[cur3] = A[cur1]. cur1. int cur1. cur2. temp. cur1+1. Merge(A. cur1. } */ . } else if (A[cur1] < A[cur2]) { temp[cur3] = A[cur1]. } else // (A[cur2] <= A[cur1] { temp[cur3] = A[cur2]. if (( cur1 > end1) && (cur2 > end2)) return. Merge(A. temp.408 public static void Merge(int[] A. end2. cur3+1). i. temp. int end2. end2. cur3+1). else if (cur1 > end1) { temp[cur3] = A[cur2]. end2. cur3+1). Merge(A. int cur2.e. cur2+1. cur2+1. int cur3) { if ((cur1 > end1) && (cur2 > end2)) return. cur2. else if ((cur1 > end1) || ((cur2 <= end2) && (A[cur2] <= A[cur1]))) { temp[cur3] = A[cur2]. end2. cur3+1). end2. Merge(A. temp. Merge(A. cur2+1. cur3+1).

} } . lo. int lo.409 public static void Copy(int[] temp. index+1). Copy(temp. A. int index) { if (index <= hi-lo) { A[lo+index] = temp[index]. int[] A. hi. int hi.

410 Lectures 38 and 39 : Quicksort Quicksort is the best sorting algorithm known which is still comparison-based. which are all less than 5. Other sorting algorithms which take advantage of other speciﬁc properties of your data (for example. Quicksort relies only on being able to say whether a given element of a given type is less than. choose a pivot value. This will force the pivot value.| | (elements 6-8 4 take up | | take up cells cells 0-3) | 5 | 5-7) -------------------------------------i 0 1 2 3 4 5 6 7 Above.e. partition it – so that all values less than 5 are to the left of 5. value 5 will be in cell 4. what we want to is arrange the array – i. 67. As with the previous two sorting algorithms. | | (elements 41. A[i] 11. which means we will be passing the lower and upper array bounds to our recursive function in addition to the array itself. which are all greater than 5. 88 and 93 here) ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 A[i] . all the elements greater than the pivot. if you are sorting integers you can use your data as array indices and count how many times each integer occurs) can be faster. For now. (We will discuss in just a bit how we would go about choosing a pivot. 5. The values 6-8. the values 1-4. are to the right of ﬁve.) With 5 selected as the pivot. The algorithm works as follows. Partition the array so that you have on one side. and on the other side. For example. Given an array. but those algorithms are topics for a more advanced course than this. never mind that and assume we end up with 5 as the pivot. (elements 1. If you had the array A[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 and chose 39 as the pivot then the properly partitioned array would be: (elements 6. and in between them the pivot itself (which would by deﬁnition be in its correct ﬁnal location). if we had the array: A[i] 2 6 8 1 7 4 3 5 -------------------------------------i 0 1 2 3 4 5 6 7 and let’s suppose we chose 5 as our pivot value. or greater than another element of the same type. when the array is fully sorted. Then. but that is also where it already is right now).e. into its correct location (i. recursively sort the two sides. and all values greater than 5 are to the right of 5. are to the left of 5. 23 here)| 39 | 71. all the elements less than the pivot. equal to. 54. Quicksort will operate on subarrays. A comparison-based sorting algorithm sorts using only comparisons on the elements.

411 Again. then the left half consists of the cells lo. we get the following: Array after partitioning: (elements 1.m-1 and the right half consists of the cells m+1. We said we should “recursively sort the halves”.. there are three things we still don’t really know.e. (As you can see. and we are going to look at those next: 1. but that is a little vague.) Once you have partitioned the array.hi. everything less than the pivot ends up to the left of the pivot. If your array runs from lower bound lo to upper bound hi. how exactly does the array get partitioned into “less than pivot” and “greater than pivot” sections? It’s all well and good to say “all these values end up in this section”. 39 goes in cell 3. When are those halves too small? i.... everything greater than the pivot ends up to the right of the pivot. though we are calling the two sides “halves”. then you simply call Quicksort recursively on the two halves. Precisely what algorithm gets used to do this partitioning work? 3.| | (elements 6-8 4 take up | | take up cells cells 0-3) | 5 | 5-7) -------------------------------------i 0 1 2 3 4 5 6 7 Array after recursively sorting left half: A[i] | | (elements 6-8 | | take up cells 1 2 3 4 | 5 | 5-7) -------------------------------------i 0 1 2 3 4 5 6 7 A[i] Array after recursively sorting right half: A[i] | | | | 1 2 3 4 | 5 | 6 7 8 -------------------------------------i 0 1 2 3 4 5 6 7 and after that second recursive call returns. How is the pivot selected? 2. we are done! So. and the pivot ends up in its correct location (in the fully sorted array. the are not always exactly the same size. and your pivot ends up at index m. More on that later. since we can assume the recursive call works correctly. You don’t need to include the pivot cell in either of the recursive calls because it is already placed correctly. Once the pivot is selected.. And. what is the base case? . and that is where it is already).

but pick the leftmost element instead of the rightmost element for the pivot. we have to do the best we can to get a decent pivot. This will result in quadratic behavior. we can’t go in search of the exact median. However. So. Quicksort will take linear time to ﬁnd the pivot and partition at each step (we’ll see why shortly). If sometimes. one of the two “halves” won’t exist at all. Instead. that is not a problem. Quicksort runs in quadratic time in the worst case. The problem is that trying to ﬁnd the exact median element will take far too long for our purposes (take our word for this. What we want to avoid is getting a bad pivot. we will have all values less than 2 to the left of 2. In those situations. most of the time.412 Selecting the pivot The key to making Quicksort run quickly is to get the two halves to actually be as close to real halves as possible. we are okay. the proof of it is beyond the scope of this course). When we get a bad pivot almost all of the time. and yet each step will result in trying to recursively sort an array only one cell less in length – meaning we have n nested recursive calls. how can we hit the average case as often as possible? How can we make sure our pivot selection is as good as we can make it? . almost all of the time. So. and all values greater than 2 to the right of 2: A[i] 1 2 (everything > 2 here) -------------------------------------i 0 1 2 3 4 5 6 7 2 (at index 1) is the pivot As you can see. and indeed. after we partition. that is our worst-case behavior. The danger of allowing otherwise can be seen if we go back to our ﬁrst example array. But. That is. A[i] 2 6 8 1 7 4 3 5 -------------------------------------i 0 1 2 3 4 5 6 7 2 will be the pivot In this case. “linear times logarithmic”. Sorting two arrays half the size of the original array actually turns out to take less time than it takes to sort one array only one cell less in length than the original array. so we would prefer to have a pivot location close to the middle so that we can divide our array into two equal-size subarrays rather than one non-existent subarray and one subarray nearly the same size as the overall array. and the right subarray is only two cells less than the overall array. but smaller function – and thus a shorter (faster) running time – than quadratic functions. if our pivot ends up at index m. we would like m to be the middle index of the array. As long as we get a decent pivot. the worst situation is when we have the minimum or maximum as the pivot. and so we are also concerned with the average case. we would like m to be as close to the middle index of the array as possible. the left subarray has only one element. And in the average case. This is a larger function – and thus a longer (worse) running time – than linear functions. and the other will be only one cell shorter than the overall array. In fact. we generally get good pivots most of the time. it turns out this is okay. we get bad pivot. and barring that. Quicksort runs in time proportional to n log n – that is. In that case.

This process is known as median-of-three pivot selection. the middle cell of the subarray. • One other alternative – the one we are going to use. because it will make each recursive call take too much time. We want to partition quickly. a middle element. actually searching for the ideal pivot is not practical. But (good) random number generation can take a bit of time. But. and a high element. The three values we read are the ones in the second cell of the subarray. since we are selecting the pivot by taking the median of three diﬀerent values. It would make it unlikely that we would consistently select a pivot value very close to the minimum or very close to the maximum. • We could randomly select an index and use that value as the pivot. it makes it even less likely that we get a bad pivot. and the last cell of the subarray. since we’d now have to pick two values that were both close-to-the-end on the same side of the array – something which is even less likely than getting one close-to-the-end value. this is likely to give us a low element.413 • As already stated. so picking the ﬁrst or last index is more likely to result in a bad pivot choice than would a purely random selection. This should be the equivalent of making a random selection of index. It is still constant time. • If the array is arranged randomly. It makes a consistently bad pivot selection unlikely enough that we can consider the worst case to be quite unlikely. it tends to get us a better pivot in practice. we could just grab the ﬁrst or last element in the array (or subarray). but none with a constant as low as Quicksort’s). Even if the data is partially sorted. that makes Quicksort a very appealing choice for many sorting problems. And. So. while avoiding the random number generation time. in the real world (as we mentioned when discussing the running time of InsertionSort) data is often partially sorted. . since the average case – which is much more likely than the worst case – is the fastest known for a comparison-based sorting algorithm (there are other n log n sorting algorithms. and at any rate. and choosing their median. just not as low of a constant as you might think. this is the more appealing choice. which makes the algorithm run faster overall. But. This method exchanges the random number generation time for three comparisons among the three values at the three cells. by the way – involves reading three diﬀerent values.

1. The second cell is A[1]. the median of those values is 5.414 Example 1: A[i] 2 6 8 1 7 4 3 5 -------------------------------------i 0 1 2 3 4 5 6 7 In the above array. Example 2: A[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 In example 2. 4. the last cell is A[7]. and thus 39 would be the pivot value selected for the above array by the median-of-three pivot selection algorithm. middle. Therefore 5 would be the pivot value selected for the above array by the median-of-three pivot selection algorithm. and 5 respectively. .) The values in the second. above). (With arrays of even length. we must (as stated above) look at the second cell. and the middle cell is A[(0 + 7)/2] == A[3]. middle. and last cells are those at indices 1. The median of those three values is 39. And. and last cells are 6. and 9 respectively. the middle cell. if we want to perform median-of-three pivot selection. there are technically two middle elements. 39. The values at those cells are 11. but typically the one on the left gets chosen because typically integer division rounds down (to 3. above) instead of up (to 4. the second. and the last cell. and 54 respectively.

In other words. or it might be in the second cell. So.which means we need some knowledge of which cells constitute the ‘‘right side’’ so we know where to move 86 to. Values that are less than the pivot will go to the left side of the array. how can we partition the rest of this array – from the second cell through the last cell – into a “less than pivot portion” and a “greater than pivot” portion? We will partition the array by inspecting the values in the array one by one. The problem is. if a value belongs on the right side of the array. once we read a value and we know it belongs on the left side of the array. that pivot is just getting in the way. what we are going to do is get it out of the way. No matter which of the three cells – second. However. Likewise. by moving the pivot to the ﬁrst cell. we want to make sure we don’t deal with a value once it has been inspected. so we know where to move 26 to. there is no need to keep track of all of those cells. or last – the pivot was found in. we then need to partition the array. and for the moment. the pivot might be in the last cell.. just as we said we needed to do above. The solution is to keep track of which cells constitute the “left side” and the “right side”. Rather. or it might be in the middle cell. Now.415 Partitioning the array Once we have chosen our pivot value using median-of-three pivot selection. we need only keep track of the rightmost value of the left side (anything to its left is also part of the left side and less than the pivot) and the leftmost value of the right side (anything to its right is also part of the right side and greater than the pivot).. we would like to put it on the left side of the array and know we’ll never run into it again for the rest of the partitioning process. and values that are greater than the pivot will go to the right side of the array. For example. we would like to put it on the right side of the array and know we’ll never run into it again for the rest of the partitioning process. A[i] p=39 26 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 ^ 39 is pivot | | We’d like to know we can move 26 to the ‘‘left side’’ and not deal with it any longer.which means we need some knowledge of which cells constitute the ‘‘left side’’. middle. we’ll swap it with the value in the ﬁrst cell and now we can safely traverse from the second cell through the last cell without running into the pivot. A[i] p=39 86 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 ^ 39 is pivot | | We’d like to know we can move 86 to the ‘‘right side’’ and not deal with it any longer. Our partition algorithm is going to want to traverse down the array trying to rearrange values... we will have “side . However. the question is.

| -----------|---------------|-------1 2 3 | 4 5 6 7 | 8 9 | | A[i] pivot=39 ----------i 0 . left right bound bound confirmed | unknown | confirmed < pivot | | > pivot .. And now that the left bound has moved to the right one location. So.416 bounds”.| -----------|---------------|-------1 2 3 | 4 5 6 7 | 8 9 | | | | |____________________________________| | portion of array that we are partitioning A[i] pivot=39 ----------i 0 As we discover a new value that belongs on the left.| ---------------|-----------|-------1 2 3 4 | 5 6 7 | 8 9 | | A[i] pivot=39 ----------i 0 ..| ..and then put X on the left side of the left bound. If X < pivot left right bound bound confirmed | unknown | confirmed < pivot | | > pivot X | . And we next check cell 5. which will move inwards as we learn about the values in the “unknown” region.| X .. the less-than-pivot region is one cell larger and the unknown region is one cell smaller. we just keep X exactly where it is but move the left bound over to the right one location. the cell which is now just to the right of the left bound. we’ll move the left bound over to the right one spot to open up a space to the left of the left bound. Eﬀectively. the next value we check will always be the value in the cell just to the right of the left bound. If X < pivot left right bound bound confirmed | unknown | confirmed < pivot |---> | > pivot .

Y is where X was. we want to move that value to the right side of the right bound. And then when we move X into that newly-added-to-the-right-side cell (i. since the swap has placed a new value (Y) there. an example will help. will be: • Check cell i. and X is on the right side of the right bound.| X -----------|-----------|-----------1 2 3 | 4 5 6 | 7 8 9 | | A[i] pivot=39 ----------i 0 X is where Y was. move the left bound one position to the right (so cell i is now to the left of the left bound) and check cell i+1. • If the value we encounter at cell i is less than the pivot. this means that we need to move the right bound over to the left one location. we will write over Y.417 Likewise. . the cell we check is always the one just to the right of the left bound. move the right bound one position to the left so that X is just to the right of the right bound. If X > pivot left right bound bound confirmed | unknown | confirmed < pivot | | > pivot . X is just to the left of the right bound. and so it is okay to inspect that cell a second time (since it contains a new value). Whenever we don’t move the left bound. that moves Y into the right side when we haven’t even checked it yet. if we come across a value that belongs on the right side of the right bound. The solution here is to swap X and Y before we move the right bound over one location.| X Y | -----------|---------------|-------1 2 3 | 4 5 6 7 | 8 9 | | A[i] pivot=39 ----------i 0 But if we move the right bound over. cell 7). At this point. and then we will re-check cell 4 because we have moved a new value there – again. But. and that means a new value got swapped into the cell (A[4] above) that we just inspected.| Y . swap that value and the value just to the left of the right bound (call it Y). • If the value we encounter at cell i (call it X) is greater than the pivot. again. which is also bad. which will always be the cell just to the right of the left bound.e. so that there is an additional space to the right of the right bound where we can store X. Our rules. Then. and Y is just to the right of the left bound. and then re-check cell i. it is because we moved the right bound instead. Now. If X > pivot left right bound bound confirmed | unknown | confirmed < pivot | <---| > pivot .

so swap with value to left of Right Bound and move Right Bound over one location. so here you just move Left Bound over one location. Now 6 is on ‘‘right side’’.418 Example 1. 6 > pivot. where 5 was the pivot: Original array: A[i] 2 6 8 1 7 4 3 5 -------------------------------------i 0 1 2 3 4 5 6 7 Swap pivot with first cell to get it out of the way: A[i] 5 6 8 1 7 4 3 2 -------------------------------------i 0 1 2 3 4 5 6 7 Set up bounds marked with L and R: L R | | A[i] 5 | 6 8 1 7 4 3 2 | ----------|---------------------------| i 0 | 1 2 3 4 5 6 7 | | | Inspect cell just to right of Left Bound. Now. which here is A[1]. L R | | A[i] 5 2 | 8 1 7 4 3 | 6 --------------|-------------------|--i 0 1 | 2 3 4 5 6 | 7 | | . 2 < pivot. L R | | A[i] 5 | 2 8 1 7 4 3 | 6 ----------|-----------------------|--i 0 | 1 2 3 4 5 6 | 7 | | Inspect cell just to right of Left Bound. 2 is on ‘‘ left side’’. which here is A[1].

8 > pivot. 3 is on ‘‘ left side’’. which here is A[3]. so swap with value to left of Right Bound and move Right Bound over one location. 1 < pivot. which here is A[2]. L R | | A[i] 5 2 3 1 | 7 4 | 8 6 ----------------------|-------|------i 0 1 2 3 | 4 5 | 6 7 | | . Now.419 Inspect cell just to right of Left Bound. 1 is on ‘‘ left side’’. Now. which here is A[2]. L R | | A[i] 5 2 3 | 1 7 4 | 8 6 ------------------|-----------|------i 0 1 2 | 3 4 5 | 6 7 | | Inspect cell just to right of Left Bound. so here you just move Left Bound over one location. Now 8 is on ‘‘right side’’. so here you just move Left Bound over one location. 3 < pivot. L R | | A[i] 5 2 | 3 1 7 4 | 8 6 --------------|---------------|------i 0 1 | 2 3 4 5 | 6 7 | | Inspect cell just to right of Left Bound.

so swap with value to left of Right Bound and move Right Bound over one location. What happens when we have only one cell left above? As far as the partitioning goes. So. So. as appropriate: Our leftover value is 4: L R | | A[i] 5 2 3 1 | 4 | 7 8 6 ----------------------|---|----------i 0 1 2 3 | 4 | 5 6 7 | < pivot | > pivot | |______________|___________| But if it had happened to be 9 instead. and our “greater than” bound will decrease. 7 > pivot. We know that. And. our “less than” bound will increase. now we come to the ﬁnal idea behind our partitioning. except for the pivot itself. L R | | A[i] 5 2 3 1 | 4 | 7 8 6 ----------------------|---|----------i 0 1 2 3 | 4 | 5 6 7 | | As we continue with the partitioning. Now 7 is on ‘‘right side’’. everything in cells to the left of cell i is less than the pivot.420 Inspect cell just to right of Left Bound. we also know that everything to the right of cell i is greater than the pivot. which here is A[4]. there isn’t really any problem anymore. Say that last cell is cell i. thus closing in a smaller and smaller area until we have only one cell left. we’d simply view it as being part of the right side instead of as part of the left side: L R | | A[i] 5 2 3 1 | 9 | 7 8 6 ----------------------|---|----------i 0 1 2 3 | 4 | 5 6 7 | < pivot | > pivot | |__________|___ ___________| . we can picture our value at i with one side or the other.

so really. if that value belongs with the left side then it is sitting where the pivot should go. since the array is correct in that regard either way. the problem isn’t partitioning this last element. Our leftover value is 4: L R | | A[i] 5 2 3 1 | 4 | 7 8 6 ----------------------|---|----------i 0 1 2 3 | 4 | 5 6 7 | | < pivot | > pivot | | |______________|___________| | |_________________^ pivot goes in between the sides. since the pivot gets placed right after it. when we are partitioning. pivot should be at cell 4 and the ‘‘< pivot’’ stuff should be in cells 0-3 But if A[4] had happened to be 9 instead. so really.421 So. pivot should be at cell 3 and the ‘‘< pivot’’ stuff should be in cells 0-2. But they are quite diﬀerent from the standpoint of “placing the pivot”. and if it belongs . clearly. And. In other words. the size of the left side determines where the pivot gets placed. Which of the two situations we have above doesn’t matter from the standpoint of “ﬁnding sides”. we’d simply view it as being part of the right side instead of as part of the left side: L R | | A[i] 5 2 3 1 | 9 | 7 8 6 ----------------------|---|----------i 0 1 2 3 | 4 | 5 6 7 | | < pivot | > pivot | | |__________|___ ___________| | |_____________^ pivot goes in between the sides. and we end up down to one last value. The problem is in re-placing the pivot at its correct location. We are ﬁne as far as rearranging the elements goes.

then all the “less than pivot” elements are now to the left of the pivot. is that we can complete our partition by swapping the pivot with the rightmost value of the left side. the rightmost cell of the left side will always be sitting where the pivot should be. pivot should be at cell 4 and the ‘‘< pivot’’ stuff should be in cells 0-3 So swap pivot 5 (A[0] == 5) and our one remaining ‘‘unknown’’ value (A[4] == 4) to fix this problem. or the cell to its left (the second example in the example pairs above). however. as we can see by expanding on our two examples above.422 to the right side. and the rightmost value of the left side can go where the pivot currently is. then it is the ﬁrst value in the right side and the pivot should be placed just to the left of it. it starts at cell 1. but that left side doesn’t get to start at 0. What this means. since it is less than the pivot and thus can end up anywhere on the left side after this partition operation is complete. Once we make that swap. since the pivot is in the way. the last cell of the left side is sitting where the pivot should go. since the pivot is supposed to go in that rightmost cell of the left side. then since the rightmost value of the left side is taking up cell c instead of cell c-1 due to the pivot forcing the left side to start at cell 1 instead of cell 0. it ends up at cell c (after taking up cells 1 through c). and we get the array as shown below: . Why do things work this way? Why is the last cell of the left side always where the pivot should go? Well. leaving cell c free for the pivot. this is because the left side needs to take up c cells. But in either case. and therefore instead of ending up at cell c-1 (after taking up cells 0 through c-1). instead. Since the c values less than the pivot should be taking up cells 0 through c-1. whether that last cell is the “last unknown cell” (the ﬁrst example in the example pairs above). so really. So. The ﬁrst example. where the last value to be handled by the partition was less than the pivot: Our leftover value is 4: L R | | A[i] 5 2 3 1 | 4 | 7 8 6 ----------------------|---|----------i 0 1 2 3 | 4 | 5 6 7 | < pivot | > pivot | |______________|___________| pivot goes in between the sides.

which will be the spot that our last ‘‘unknown’’ value was located in. . pivot should be at cell 3 and the ‘‘< pivot’’ stuff should be in cells 1-2. and we get the array as shown below. so really. So swap pivot 5 (A[0] == 5) and the cell just to the left of our last ‘‘unknown’’cell (A[3] == 1) to fix this problem. But if A[4] had happened to be 9 instead.423 L R | | A[i] 4 2 3 1 | 5 | 7 8 6 ----------------------|---|----------i 0 1 2 3 | 4 | 5 6 7 | < pivot | | > pivot | |______________| m |___________| And we should return the index m. we’d simply view it as being part of the right side instead of as part of the left side: L R | | A[i] 5 2 3 1 | 9 | 7 8 6 ----------------------|---|----------i 0 1 2 3 | 4 | 5 6 7 | | < pivot | > pivot | | |__________|_______________| | |_____________^ pivot goes in between the sides.

. and likewise when the “right bound moves”. A[1] is less than pivot so (left bound case) partition range 2.. we are just calling on the subarray with bounds lo and hi-1.9. where 39 was the pivot: A[i] 93 11 71 6 39 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Swap pivot to side and out of way. which will be just to the left of the spot that our last ‘‘unknown’’ value was located in... and when the “left bound moves” we are just calling on the subarray with bounds lo+1 and hi.. Each call attempts to partition an array with bounds lo and hi..9 L R A[i] 39 11 71 6 93 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Partitioning range 2.9 L R A[i] 39 11 71 6 93 67 41 88 23 54 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Partitioning range 1.. Please take note of one last thing – our “wall” or bounds idea is really just a set of recursive calls.424 L R | | A[i] 1 2 3 5 | 9 | 7 8 6 ----------------------|---|----------i 0 1 2 3 | 4 | 5 6 7 | < pivot | | > pivot | |__________| m |_______________| And we should return the index m. Example 2..9. A[2] is greater than pivot so (right bound case) swap with A[9] and then partition range 2.8 L R A[i] 39 11 54 6 93 67 41 88 23 71 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 ... Then Partition the range 1.

..7.425 Partitioning range 2.. A[2] is greater than pivot so (right bound case) swap with A[8] and then partition range 2..7.5... A[3] is less than pivot so (left bound case) partition range 4. L R A[i] 39 11 23 6 93 67 41 88 54 71 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Partitioning range 4..5.....8... L R A[i] 39 11 23 6 93 67 41 88 54 71 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Partitioning range 3.6.. A[4] is greater than pivot so (right bound case) swap with A[6] and partition range 4. A[4] is greater than pivot so (right bound case) swap with A[7] and partition range 4.4..7...6...7. L R A[i] 39 11 23 6 88 67 41 93 54 71 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Partitioning range 4.7 L R A[i] 39 11 23 6 93 67 41 88 54 71 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Partitioning range 2...7... A[2] is less than pivot so (left bound case) partition range 3. A[4] is greater than pivot so (right bound case) swap with A[5] and partition range 4. L R A[i] 39 11 23 6 67 41 88 93 54 71 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 . L R A[i] 39 11 23 6 41 67 88 93 54 71 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Partitioning range 4..

so actual partitioning is completed. 67 is greater than pivot... Low and high bounds are equal. so pivot should go to its left.426 Partitioning range 4. L R A[i] 39 11 23 6 67 41 88 93 54 71 ----------------------------------------------i 0 1 2 3 4 5 6 7 8 9 Done! < pivot | m | > pivot A[i] 6 11 23| 39 |67 41 88 93 54 71 ------------------|----|----------------------i 0 1 2 | 3 |4 5 6 7 8 9 .4. and it is simply a matter of knowing where pivot should go. Here. Eventually we swap A[0] and A[3]. into cell 3.

we will need more than one comparison to learn the correct sorted order. or 2. There. and so those situations will not be part of the base case. Essentially. swap the two if it is not. then that means we have passed bounds that don’t make sense. • If the array size is 2. The same rule applies here. or else we swap the two elements to put it in order. and recursive calls as we have already discussed. or 2. There is no subarray. but will instead be handled by the recursive case. though other methods are likely to be a bit more eﬃcient overall. if the array size is 0. but will instead handle those cases with non-recursive code speciﬁcally designed for those small-array-size cases. partitioning. and then return. we knew that we had made a recursive call on an “empty” or “non-existent” subarray. and that the recursive case is that the array cannot be sorted in one comparison or less.427 Quicksort base cases There are a number of ways we could handle the base case for Quicksort. But. and we could simply return without doing anything. then we will not do those things. In other words. so it cannot possibly be “out of order”. once lo was greater than hi. So. So. you only need to return. . • If the array size is 1. The way we will use for this class is one of the most easily understandable. Our base case will be the situation where the array size is 0. What do we do in those three cases? • If the array size is zero. we can check to make sure the lower-cell element is less than the higher-cell element. if the array size is 3 or more. we are saying that our base case is that the array can be sorted in one comparison or less. This is similar to what happened with BinarySearch. In any case where the array size is 3 or more. then there is nothing to sort – there is only one element. then go ahead and perform pivot selection. so just return. 1. 1. then either the 2-element array is in order.

0.. A[i] = A[j]. then you can have a public Quicksort that accepts only the array. a few quick preliminaries. we’ll reproduce it here: public static void Swap(int[] A. int j) { int temp = A[i]. public static void Quicksort(int[] A) { Quicksort(A.1). So. } . and then that that method can be implemented with the one-line call to the private version of Quicksort which does take array bounds as parameters and does all the real work of the algorithm.428 Implementing Quicksort in Java First. We ﬁrst looked at this method when discussing SelectionSort. and the SelectionSort notes go over the development of Swap. just as some of the other recursive algorithms we’ve looked at have done. A. once again. But as a reminder of what the code looks like.length . if you’d prefer that the user just send in the array to be sorted and not worry about having to pass in array bounds to start the recursive calls with. } 2) Quicksort needs to recursively call itself on subarrays.. int i. 1) We are going to need the Swap method again. A[j] = temp.

1). lo. It really is useful to view things this way. The cases for subarrays of size 2. private static void Quicksort(int[] A. we need to add the base case to the recursive case above. int hi) { if (hi > lo + 1) // size is 3 or more { int m = PartitionWrapper(A. This method will be responsible for returning the index m where the pivot eventually ends up. Quicksort(A. and so we are going to view them that way – by having a separate method that takes care of pivot selection and partitioning together.. or 2 // handle cases } . then the size of the subarray is 2 or less. hi). 1. because the partitioning algorithm as we described it way above needs the actual low and high bounds of the subarray in order to work.1 (lo is just to right of hi) in which case ---> hi < lo in which case ---> hi < lo + 1 So. private static void Quicksort(int[] A. so we know which subarray constitutes the “left half” and which subarray constitutes the “right half”.. } We need to pass in our array bounds to PartitionWrapper. } else // size is 0. All we need is the index m where the pivot is located. lo.1) // sort (m + 1.. We said our base case would be that the subarray size was 2 or less. lo. all details of pivot selection and partitioning can be hidden away in that method call. m . hi). but otherwise. int hi) { int m = PartitionWrapper(A. hi). Quicksort(A. which will make the recursive Quicksort method a lot cleaner.m . hi). and 0 are as follows: size == 2: size == 1: size == 0: hi == lo + 1 (lo is just to left of hi) hi == lo (lo and hi are same cell) in which case ---> hi < lo + 1 hi == lo . it appears we can say that if hi <= lo + 1.1). Quicksort(A. m + 1.. and by contrast.429 On to the Quicksort implementation. int lo.. 1.hi) Note that we have chosen to describe the pivot selection and partitioning as one procedure. Before we move on. if hi > lo + 1. m + 1. lo.. The core of the Quicksort method is the recursive structure: // choose pivot and partition the subarray. then we have 3 or more elements in our subarray. m . Quicksort(A. int lo. with pivot at cell m // sort (lo. But. we don’t need to care about the details of PartitionWrapper in this Quicksort method.

For 0 and 1. m + 1. hi). or 2 if ((hi == lo + 1) && (A[lo] > A[hi])) Swap(A. m . hi).430 The last thing we have to handle here is what to actually do for our base cases. .1). } So. Quicksort(A. above is the Quicksort method. we don’t need to do anything. Now we only need to write PartitionWrapper and we are done. if we have the 2-element case and the lower element is already less than the higer element. lo. The only time we need to do anything is when we have the two-element case and the lower element is greater than the higher element. Quicksort(A. private static void Quicksort(int[] A. lo. hi). lo. } else // size is 0. in which case we swap them and then we are done. And. 1. int lo. we still don’t need to do anything. int hi) { if (hi > lo + 1) // size is 3 or more { int m = PartitionWrapper(A.

and the second index. // 3) Partition the remainder of the subarray. just to organize things a little bit better. private static int PartitionWrapper(int[] A. what order A[i]. lo+1. j. . and will take care of the steps one at a time. // 2) Swap pivot and first element. and therefore which of i. and middle index to MedianOfThree. hi. (lo+hi)/2 ). int lo. // 2) Swap pivot and first element. and k is the index of the median.431 Implementing PartitionWrapper We start with the following framework. Speciﬁcally. A[j]. int lo. int hi) { // 1) Choose pivot. given an array A and three indices i. int hi) { int currentPivotLocation = MedianOfThree(A. and which was the second one. last index. the method needs to decide. We’ll pull that selection into a separate method. } Notice that we are passing the array. // 3) Partition the remainder of the subarray. our pivot selection will be done via the median-of-three pivot selection algorithm. // 5) Return index where pivot is currently stored. and A[k] belong in. and therefore which of the three is the median. which was the last one. The method itself won’t care which index was the middle one. What it will care about is the values at these indices. j. private static int PartitionWrapper(int[] A. // 4) Put pivot in its appropriate spot between the halves. } 1) Choosing the pivot: As stated above. and k. // 4) Put pivot in its appropriate spot between the halves. // 5) Return index where pivot is currently stored.

int k) { if (A[i] <= A[j]) if (A[j] <= A[k]) return j. else // A[j] < A[i] if (A[i] <= A[k]) return i. } . private static int MedianLocation(int[] A. int j. I went over this in class. else if (A[i] <= A[k]) return k. else return j. else if (A[j] <= A[k]) return k. else return i. int i.432 We have six possibilities for the ordering of the three values at those three indices: A[i] A[i] A[k] A[j] A[j] A[k] <= <= <= <= <= <= A[j] A[k] A[i] A[i] A[k] A[j] <= <= <= <= <= <= A[k] A[j] A[j] A[k] A[i] A[i] // // // // // // return return return return return return j k i i k j And so all we really need to do is compare between the three values to determine the median. but it is straightforward enough that I’ll just put the resultant method here and leave it to you to understand why it is written the way it is. and then return the appropriate index.

int lo. we really don’t care where we found the pivot except that we need the index of that median-of-3 element in order to make the “swap pivot out of the way” work. . so that the cells from the second index through the last index can be part of the partitioning. int hi) { Swap(A. // 5) Return index where pivot is currently stored. lo+1. So. So. the pivot will either be sitting at A[lo] or much later. // 3) Partition the remainder of the subarray. lo. which are the bounds of the remainder of this array. hi. // 5) Return index where pivot is currently stored. and PartitionWrapper does the setup work that is needed before Parition runs (such as selecting the pivot) and the clean-up work that is needed after Partition runs. lo. private static int PartitionWrapper(int[] A. while lo + 1 and hi are only indices. Swap(A. Since the pivot is sitting at A[lo]. // 4) Put pivot in its appropriate spot between the halves.433 2) Swapping the pivot and the ﬁrst element: With MedianOfThree completed. We will also pass in the pivot value. we will never use currentPivotLocation after this swap – from now on. int lo. we would only control the swap of pivot to the leftmost cell (step 2) and then the swap of pivot to it’s proper location later on (step 4). Partition will be the recursive algorithm that repeats the partitioning step over and over. // 3) Partition the remainder of the subarray. int hi) { int currentPivotLocation = MedianOfThree(A. But there’s no need to store the currentPivotLocation in a separate variable because it will never be needed again after this line of code. private static int PartitionWrapper(int[] A. hi. (lo+hi)/2)). That is. as “partition this subarray” becomes a matter of “deal witht the leftmost element and then partition a smaller portion of this subarray”. } 3) Partitioning the remainder of the array: As we said way above. currentPivotLocation). Because of this. we now need to swap the pivot out of the way. we could write a recursive version of Partition which would only take care of that actual partitioning work. All we need returned from that recursive version of Partition is the index m where the pivot should swap to in the end. into the ﬁrst cell in the array. the partitioning is essentially handled recursively. (lo+hi)/2 ). } Actually. MedianLocation(A. // 4) Put pivot in its appropriate spot between the halves. we can write the index directly into the swap by just passing the method call as an argument to swap. lo+1. and from this non-recursive PartitionWrapper. we will move the pivot to its correct location in the array. we pass the recursive partitioning algorithm the bounds lo + 1 and hi.

lo+1. so we simply return the index m since that is the index that our Quicksort method is waiting for in order to perform its own recursive calls. lo. } So. m). int lo. Swap(A. and we swap the pivot into that cell and return that index back to Quicksort. We then call the recursive Partition to partition the rest of the array. the pivot is stored at index m. A[lo]). we run median-of-three to get the pivot location and swap the pivot with the far left cell. lo+1. A[lo]). MedianLocation(A. } 4) Putting pivot in its appropriate place between the halves: Now that you know which index m is. } 5) Return index where pivot is currently stored: Once the above swap is complete. lo. int lo. // 5) Return index where pivot is currently stored. int hi) { Swap(A. Swap(A. lo+1.434 private static int PartitionWrapper(int[] A. hi. int m = Partition(A. // 4) Put pivot in its appropriate spot between the halves. int lo. MedianLocation(A. lo+1. int hi) { Swap(A. above. // 5) Return index where pivot is currently stored. lo+1. hi. hi. It returns the index where the pivot should go. int m = Partition(A. m). (lo+hi)/2)). (lo+hi)/2)). private static int PartitionWrapper(int[] A. lo. lo. MedianLocation(A. well. A[lo]). . int hi) { Swap(A. int m = Partition(A. lo. hi. we know we are suppposed to put the pivot there. return m. lo+1. which means it is time for another swap (where we swap the pivot value in A[0] with the m generated by the recursive Partition algorithm. // final version of the PartitionWrapper algorithm private static int PartitionWrapper(int[] A. hi. hi. (lo+hi)/2)).

Recursive case 1: A[lo] is less than pivot. Recursive case 2: A[lo] is greater than pivot. lo+1. with index lo . lo. In that case. ... we just consider the cases we discussed when going over two examples of partitioning way above: We are partitioning the subarray lo. } Base case: The base case occured when we were down to only one element in our subarray – i. We start by inspecting A[lo]... pivot). If instead.hi.. int lo.e. lo. In that case. which assumes the pivot is already out of the way and just worries about the actual rest of the array. we need to write the recursive version of Partition. we simply want to return which index the pivot should end up in. hi-1. hi..435 Finally. And. and then recursively partitioned the subarray lo. hi). return Partition(A. and we said it would either be the location of this element in the partition of size 1 (i. private static int Partition(int[] A. the way we made that choice was.1). and we re-checked A[lo] since we had just swapped a new value into that position: if (A[lo] > pivot) { Swap(A. then we wanted to swap pivot with the element to the left of that instead – in both cases. we just left A[lo] where it was and recursively partitioned the subarray lo+1. we returned the location of the element in the partition of size 1 if that element was less than the pivot and should be swapped into pivot’s location on the left. when lo == hi. the element belonged to the right side. the index lo == hi) or else it would be the cell to the left of that (i. pivot).hi.e. int hi. the rightmost element in the left side. the “greater than pivot” side. int pivot) To do this. we swapped A[lo] with A[hi].e. In this case. if (A[lo] <= pivot) return Partition(A.hi-1 (meaning the right bound moved left one position.

else { Swap(A. hi. int pivot) { if (hi == lo) if (A[lo] < pivot) return lo. } } .. else return lo-1. else if (A[lo] <= pivot) return Partition(A. lo. hi). lo+1..436 if (hi == lo) if (A[lo] <= pivot) return lo. int lo. return Partition(A. else return lo-1. int hi. pivot). Putting it all together. pivot). private static int Partition(int[] A. hi-1. lo.

437 Lecture 40 : Advanced Sorting Analysis Analyzing MergeSort Mergesort can be described in this manner: time to sort n values = time to sort left half + time to sort right half + time to merge sorted halves which is commonly written in the form of a recurrence relation. linear time. it simply makes for a convenient notation for the mergesort running time. if we have n total values among the two sorted collections we are merging. For the moment. etc. when we write the temporary array back into the original array. eventually. and we spend constant time on each step to write one of those values into the temporary array. Now. the recurrence relation looks like this: T(n) = 2 * T(n/2) + linear T(1) = 1 That describes mergesort – two recursive calls on an array half the size. consider the following diagram. Each step breaks the array in half. before we get our ﬁnal result. So. what is the running time for merge? Well. you don’t have the mathematical tools to solve that yet. likewise. So. i.) is the number of cells in the array at that level of recursion: . you’ll learn all about solving recurrence relations. n/2. we can still analyze Mergesort – we just will use a diﬀerent technique than the recurrence relation. then we have linear time total. Now. and learn how to solve them. for a given step of MergeSort. that takes constant time to write one value on each step. so that is likewise. So if we start out with n elements. where the number we list (n. the merge work is linear. overall. plus linear work to merge them. and then we sort each of those halves before trying to merge the entire array. And then.e. each value is written to the temporary array once and then written back once. a recursive mathematical formula: T(n) is the time to run MergeSort on n values T(n) = T(n/2) + T(n/2) + time to merge T(1) = 1 / base case is constant time or after doing some algebra: T(n) = 2 * T(n/2) T(1) = 1 + time to merge You’ll learn about recurrence relations in a diﬀerent course. But in this case.

On the second level.. This turns out to be exactly the result we’d get if we mathematically solved the recurrence relation we listed earlier. or even best. but we double that time because we have two such recursive calls. It means the algorithm takes longer than linear time – which makes sense. So it’s n *lg n for any case – worst. we end up with two recursive calls on an array one fourth the size of the original. linear is less than n * lg n is less than quadratic). The next level involves the third level of recursion – the calls we make. then the result is that the running time for Mergesort has an order of growth equal to: linear times logarithmic OR n * log-base-2 of n OR n * lg n // lg is a shorthand for log-base-2 \ right of left n/4 / \ n/8 n/8 \ right side n/2 / left of right n/4 / \ n/8 n/8 \ right of right n/4 / \ n/8 n/8 but ”linear times logarithmic” is good enough for our purposes here. And so on. The ﬁrst level is indicating the Merge of the entire array that we do at the end. from the calls we made. Notice that each level adds up to n. likewise n*constant is less than n*lg n is less than n * n. so again. Each row will total to n. Our diagram above was just an alternate way of arriving at the same result. from our ﬁrst call.e. Since we had already recursively called on half the array on the level above. and we have four such calls. to the level below it. too. since we keep cutting the array size in half as we move from one level. etc.438 n / left side n/2 / left of left n/4 / \ n/8 n/8 . in terms of time. So the Merge for such a call takes time n/4. average. since the last merge *alone* is linear time – but it’s not quite as long as quadratic time. So that level. And we have a logarithmic number of rows. And note Mergesort does not take any advantage of the array being already sorted – the same work gets done regardless. adds up to n. when we made two calls on half of THAT array. the total time for all the 3-levels-deep recursive calls is n.. we have two recursive calls (the two we made from our ﬁrst call to Mergesort). i. (just as constant is less than logarithmic is less than linear. Each one eventually runs Merge on an array of size n/2. So if each row has time n. . and there are a logarithmic number of rows.

and in that case.) The worst case for quicksort is when the pivot is always as low as possible. In that case.. . basically everything.439 Analyzing Quicksort Partition is linear – each step takes constant time to place one value beyond the ”left wall” or ”right wall” so to partition an array of n values takes time that is linear in n. of course. but the proof that the average case is basically equal to the best case is beyond this course. in the best case. I just want to give you a gut feeling that it’s true. So. so we can expect the average case to be similar to the best case. and is thus quadratic. we get the best possible pivot for quicksort and the two halves of the array are equal in size. and thus to *also* be n*lg n.. So each recursive call to sort the right side. is basically only sorting only one or two fewer cells than the previous call. or everything but one value.so this is basically the same as SelectionSort at that point. And on average. ends up on the right side of the pivot. so that you remember it better. in the partition. we have the same recurrence relation as for Mergesort: T(n) = linear + 2 * T(n/2) T(1) = 1 So quicksort in the best case is n*lg n. (That’s not a proof. we expect quicksort to get a good pivot most of the time.

. Also.e. the only faster algorithms we know of are designed for speciﬁc kinds of data. quicksort’s constants are lower.this is especially a problem if you have a VERY large array to sort.. it’s not better than quadratic unless the array *is* sorted a lot already • SelectionSort – best case: quadratic – average case: quadratic – worst case: quadratic – advantage: fewest number of swaps. i. when you consider the constant factors (since they both have n * lg n order of growth) • InsertionSort – best case: linear – average case: quadratic.440 Summary: • Quicksort – best case: n * lg n – average case: n * lg n – worst case: quadratic – advantage: on average. . not quite as fast as quicksort’s average case. even worst case – disadvantage: uses linear extra memory (the temporary array needed by merge). fastest known comparison-based sorting algorithm. i. – disadvantage: the quadratic worst case. but in terms of constant factors of that n * lg n term. It’s very unlikely to occur but it *can* happen.in situations (sorting integers is not one of them) where swapping our data can take a very long time due to very large amounts of it to move back and forth. In that case. making quicksort not the best choice if you *need* to be at n * lg n time. though about half the time of worst case – worst case: quadratic – advantage: works well for partially sorted or completely sorted arrays. swapping might have such a LARGE constant on it that it would overshadow everything else. rather than working on anything we can compare. • Mergesort – best case: n * lg n – average case : n * lg n – worst case: n * lg n – advangtage: n * lg n in all cases. we might want selection sort. Both quicksort and mergesort have the same order of growth.. also good for small arrays since quicksort and mergesort tend to be overkill for such arrays – disadvantage: quadratic for any randomly arranged array..e. since you might not even *have* enough memory left over to create another array that size.

can’t even be improved upon if the array is partially sorted.441 – disadvantage: quadratic in all cases.. SelectionSort is not generally useful.so in general.. . It’s only useful in the one case listed as an advantage above. Works okay for small arrays but insertionsort works better for those arrays.

- MACF - Memory Address Content Forgery (Eric A. Schulman)
- What is Computer
- CMP 101 Set 1 Intro
- Components of Computer
- Algorithm Keeps Data Safe-6715768
- Computers & Equipment
- Ch 5 Computer Architecture
- Svp
- LOm1
- Lecture 02
- Lecture 1 Introduction to Computers
- Computer Fundamentals and Operations
- 2nd Quarter 2011
- Chapter 1
- Infrastructure Components
- R100-02
- Paper 15
- Lecture 5
- Planning for the 1980's / Proceedings of the First Annual Computer Users Conference, April 1981
- ITM 9
- B06XR1SWB5
- Exam (First Grading)- 10
- mbah
- it syllabus class ix 2017
- computer programming lecture 1
- BBA-II-Intro To Computers.pptx
- Automation Manual
- Us 8429162
- 16 Marks Answer
- M11 Techical Guide

Read Free for 30 Days

Cancel anytime.

Close Dialog## Are you sure?

This action might not be possible to undo. Are you sure you want to continue?

Loading