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:

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

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

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

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

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

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

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

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

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

by supplying it with some huge sequence of bits that encodes our instructions to the machine. it can be stored in one memory cell (since each cell can hold up to 8 bits). because our programs are encoded into bits just as our data is. all our instructions to the processor. just like larger bit strings representing programs. and one related to data. The second important idea is that any given data value is also stored as a sequence of bits. are encoded as bit strings and stored in memory That is. So there are two key concepts here. were broken up into 8-bit pieces and stored in consecutive memory cells.14 1. specifying our program. If our data value is only 8 bits long. could also be used to store our programs instead. Larger bit strings get broken up into 8-bit pieces and stored in consecutive memory cells. is broken into 8-bit pieces and stored in consecutive memory cells. not only is our data stored in bit string form. That is the essential idea behind a stored-program computer – that the same hardware that is used to store our data. one related to instructions. The ﬁrst important idea is that whatever task we want a computer to do. and then encoding those instructions into a form such that the program can be stored in the computer’s memory. 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. Our large sequence of bits. 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 write a term paper.) Preciseness is very important when writing computer programs. we have forgotten the ‘c’ in “static”: public class HelloWorld { public stati void main(String[] args) { System. } } 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. or change an existing program) 2.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 just composed of text characters. in the following text. However.println("Hello World!"). but in addition. it renders the program completely incorrect.out. but many will. 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.out.println("Hello World!"). run program through tests to see how well it works 4. and can continue reading your paper. edit (either write a new program. one important diﬀerence between writing a computer program and writing a term paper. Developing a program is a four-step cycle: 1. the paper will still be readable – a human reading the paper can ﬁgure out that that is just a typo. . 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. and accidentally type “the” as “teh”. compile (encode program in bit string from) 3. However. is that the text of the computer program must be exact. (Not every typo will have that eﬀect. in this case it renders the program not even legitimate at all. if you have a typo in a computer program.

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

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

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

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

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

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

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

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

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

In addition.1 -3. a value of type int is 32 bits. If the ﬁrst 8 of those bits are stored at. all our data is really stored as bit strings in the computer’s memory cells.40292347E38 to 3. rather than thinking of it as a bit string spread across one or more consecutive memory cells. etc.e. For that reason. then the next 8 bits are at address a49.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. . -(2^7) to (2^7) -1 -32768 to 32767 i.79769313486231570E308 short 16 int long float 32 64 32 double 64 Values in the machine As we have previously discussed. which means it needs four 8-bit cells to hold all 32 bits. we mentioned that a value that needs more than one cell will use consecutive cells.79769313486231570E308 to 1. address a48. For example. the next 8 bits are at address a50.) handle for you the particulars of where a variable’s value should be stored in memory.40292347E38 -1. and let the environment (compiler. and the last 8 bits are in address a51. 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. say. operating system. -(2^15) to (2^15) .1 -2^(63) to (2^63) .1 -2^31 to (2^31) .e. Variables are abstractions of this idea – they allow us to think of our value as an item in a “labelled box”.

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

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

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

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

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

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

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

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

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

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

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

The operator + in an int + int expression has a diﬀerent meaning than the operator + in a String + int expression.println("Sum is " + sum). System.println(exam2). System. sum = exam1 + exam2. 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. just as in our earlier type expression examples. exam2. and that expression evaluates to the String value Sum is 130 (which is the String value that gets printed to the screen). exam2 = 70. in the expression: System. • In the last line.out.out. . again. } } • When there is nothing at all in the parenthesis. we have a String + int operation. 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. all that is printed is a blank line. that operand’s value is automatically converted to a String. exam1 = 60. the meaning of an operator depends on the type of the operand(s). System. • For example. and that means that the + is a concatentation.println("Sum is: " + 20).out.out. and that means the value of the int should be converted to a String automatically and then the concatenation done.out.println(). the type of the expression in parenthesis is String.37 String concatenation • Concatenation is the merging of two Strings into one by placing them one after the other. System. • "Sum is: " + 20 -> "Sum is: " + "20" -> "Sum is: 20" • So.println(exam1). sum.

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

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

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

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

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).out. and tab characters (collectively known as “whitespace”) are discarded by the compiler. all the names you use get traded in by the compiler for actual memory locations. you can use all the whitespace you want. blank lines. Doing this makes your code more readable and understandable. which is very important. it is also important to write your code using good programming style.42 Programming Style While certainly it is most important to write a program that actually works. } } 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.java. and was written as follows: public class Foo { public static void main(String[] x) { System. and all spaces. (This will be even more important when we starting adding other methods besides main to a class. Those things only serve to help you (and others) read your program.println("Hello World!").println("Hello World!"). Wrong: public class HelloWorld { public static void main(String[] args) { System.) . There are various “indentation standards” which vary slightly. The compiler reads these characters in one by one. So. Likewise. and becomes even more important as your programs get larger. }} That is legal! The compiler can understand this! But you cannot. as we have stated. Imagine if our “Hello World” program was stored in a ﬁle named Foo. Style component #1: Indentation Indenting your source code properly is very important.out. 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.

} } The second thing to realize is that. . To give another example. 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.out. some prefer ﬁve. Some people think this tradeoﬀ is worth it and others (myself among them) don’t. and what code is inside it. } } Since the word public lines up with the closing brace. It’s a bit harder to see it. some people indent three spaces. Again. } } Now. you still have the same kind of visual structure.println("Hello World!").43 Better. what code is inside it. it is very easy to visually pick out with one glance where the class begins and ends. since the code is crushed together a bit more. the more code you have.println("Hello World!"). That is what was meant by “indentation standards” – there are slightly diﬀerent ways of doing things.println("Hello World!"). the more important this becomes.out. likewise. 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. You can make your own choice. you can ﬁt more code in the viewing window at once.out. but on the other hand. Correct: public class HelloWorld { public static void main(String[] args) { System. You can use whichever variation you prefer – just be consistent and keep your line lengths at about 80 characters or so. 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. but still not ideal: public class HelloWorld { public static void main(String[] args) { System. and where main begins and ends.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

out. and we then put that compound statement within a conditional statement. } In both cases. And. none of the smaller statements within the compound statement 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 three lines inside the curly braces are run. all three lines inside the curly braces are run. as with the compund statement. we simply need to enclose each of the three assignment statements. or else none of those 1000 lines would run – in either case.58 Conditionals with Compound Statements Just as the compound statement had other statements nested within it.println(a). to be the statement nested within an if-statement. b = b + 3. as the result of evaluating a single condition. For example. 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. Similarly. and if the condition is false. then those 1000 lines together would either all get run. For example. likewise the if-statement has another statement nested within it. a = 2. . if we chose our example compound statement from the previous page. System. all the smaller statements within the compound statement are run. if the condition is true. to solve the problem we introduced at the start of this packet. } In the above example. if the condition is true. together inside a compound statement: if (x > 5) { a = a + 2. and if the condition is false. c = c + 1. then we might get the following: if (daysInAttendance > 200) { int a.

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

since the increment that happpened to the variable i within the compound statement. its memory is now free for some other variable to use. 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. are still in eﬀect when the nested block ends and we return to the outer block. permanently aﬀects i. inside a block that is nested in main(). the variable you declared inside the inner block has gone out of scope and cannot be used by the outer block. As a result. And so. At that point in the code. and after the compound statement is over. variables cannot be used before they are declared. (In our example. There are other advantages as well. 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.60 But. The rule is as follows: • As always. • Variables declared in outer blocks can be used inside any block nested inside that outer block. because that variable went out of scope when the nested block ended and thus no longer exists. . 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.e.) • Variables declared inside an inner block are not accesssible to the outer block – i. once you leave the inner block and you are back in the outer block inside which the inner block was nested. The rule is that a variable declared inside a block goes out of scope at the end of the block. the variable i is usable anywhere inside that compound statement. the variable x is declared inside a block that is inside main(). (In our example.) One advantage of this is that. nested block. there isn’t a variable x. the program would not compile if we uncommented the output line that is currently commented out. the variable x is destroyed at the close of the compound statement and does not exist past that point. or. more formally. the variable i holds the value 1. The value of i does NOT reset to 0 when the compound statement ends. So x and any value it was holding eﬀectively vanish once the end of the nested block is reached. if a variable is out of scope. and any changes made to the variable inside that inner.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

128. Likewise.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 . 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. 99. 0. -10.

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

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

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

single-dimensional arrays. These arrays are by far the most common type of array. if you would ﬁnd it useful to have an array with both rows and columns (i. it is possible to have arrays of multiple dimensions as well. and so often we just say “array” when we mean this kind of array.e. For example. 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.82 Lecture 9 : Multi-Dimensional Arrays Multi-dimensional arrays Up to now we have been talking about one-dimensional arrays (or alternatively.

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

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

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

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

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

.88 The use of . to see how we can view each row above as a one-dimensional array of size 5. 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. or 3 of an array of size 4. 1.length in multi-dimensional arrays Imagine we created a two-dimensional array as in our earlier examples: int[][] theMatrix = new int[4][5]. and the second dimension indexes the columns. 2. and then there is another array holding all of those rows as its items.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. Imagine it as an array of arrays – where each row is a one-dimensional array holding the items in that row. imagine we have written values into all twenty cells. Furthermore. and then look at the picture on the next page. It’s a good way to think about a two-dimensional array. being stored in either cell 0. Look at the above picture. in which the ﬁrst dimension indexes the rows.

imagine for a moment that the name of the two-dimensional array. If that is the case.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. And.e. this expression will return the item at cell 1 of the array drawn vertically in the picture above. is instead the name of the one-dimensional array of size 4 that is drawn vertically in the above picture. what is at cell 1 of that array? Another 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. then we can consider an array access such as: theMatrix[1][3] to be composed of two parts. theMatrix. The ﬁrst part is selecting index 1 from the ﬁrst dimension – i.

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

. we’d use: arr[3] and if we instead wanted the length of the array. we are now using: theMatrix[1] to access the one-dimensional array representing the row with index 1.. and index 3. respectively.91 theMatrix[1] in a sense gave us the particular row at index 1. instead of arr being the name of an array. instead of accessing the cell at index 3 of that row. . and index 3. index 0. so each of the four expressions involving . all those rows have a length of 5.length will return the lengths of the one-dimensional arrays representing the rows at index 0.length theMatrix[3].then the expressions: theMatrix[0]. and we wanted to access cell 3. in fact. why not try and obtain the length of the row instead? If we had the variable arr which was the name of an array. And. index 0. index 1.length above. index 1. of course. 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 to get the length of that row? And.length theMatrix[2].length theMatrix[1]. 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.length Well. evaluates to 5. so why not use: theMatrix[1]. we’d use: arr. respectively.

second. And then likewise. inclusive. in the four row. and third dimensions.length and the length of the second dimension would be given by twoDarray[0]. for a three-dimensional array threeDarray. the following expressions: threeDarray. respectively – due to much the same reasoning as we used earlier to explain the two-dimensional array example. ﬁve column array of our earlier example).92 So.length or by any similar expression. that is how you obtain the length of the ﬁrst dimension and second dimension of some two-dimensional array twoDarray. The length of the ﬁrst dimension is given by: twoDarray. 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. .length threeDarray[0][0].length would give you the lengths of the ﬁrst.length threeDarray[0].

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

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

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.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. 0) c is incremented to 1 c is compared to 4 enter body of loop write 37 into (0... 3) c is incremented to 4 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. 1) (and so on. 2) c is incremented to 3 c is compared to 4 enter body of loop write 37 into (0.) . 1) c is incremented to 2 c is compared to 4 enter body of loop write 37 into (0.

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

The variable counter was declared outside the outer for-loop... 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. 3) c is incremented to 4 c is compared to 4 enter body of loop counter is incremented by 3 write 15 into (0. Note that when we start writing values into the row with index 1.and so on. 2) c is incremented to 3 c is compared to 4 enter body of loop counter is incremented by 3 write 12 into (0.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. 0) c is incremented to 1 c is compared to 4 enter body of loop counter is incremented by 3 write 21 into (1. 0) . 1) c is incremented to 2 c is compared to 4 enter body of loop counter is incremented by 3 write 9 into (0. so the changes we make to it inside . 1) counter is incremented by 3 write 3 into (0.

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

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

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

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

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

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

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 <------.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) [I calculate] I have: 21 .1)) / 6) + 7 -------> I send FriendA: (13 .117 I have: (((2 + 5) * ((3 + 10) .1) FriendA has: (13 .1)) / 6) + 7 [I calculate] I have: ((7 * (13 .

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

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

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

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

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

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

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

not FriendC. since I have no idea what FriendB did to get this result..answer from FriendC 14 Column 3 Column 4 Note that I got my result from FriendB... 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) ....1)) / 6) + 7 12 <--.answer from FriendB Column 2 Column 3 Column 4 And ﬁnally..1)) / 6) + 7 12 <--..answer from FriendA ((7 * 12) / 6) + 7 Column 2 ((7 * 12) / 6) . First..answer from FriendA ((7 * 12) / 6) + 7 14 <--.some work done.1)) / 6) + 7 . (84 / 6) 14 <--.1)) / 6) + 7 .answer from FriendB Column 2 ((7 * 12) / 6) . Second.125 answer that FriendB needed: Column 1 (((2 + 5) * ((3 + 10) . In fact.some work done. (84 / 6) 14 <--..answer from FriendA ((7 * 12) / 6) + 7 14 <--. FriendB hands the scratch paper back to me. I don’t even have any idea FriendC was ever involved.1)) / 6) + 7 12 <--. FriendB tells me what the result is: Column 1 (((2 + 5) * ((3 + 10) . and I can resume my own work where I .some work done.. FriendB erases column 2...some work done.. ((7 * (13 ..answer from FriendC 14 Column 3 Column 4 And now FriendB can prepare to return back to me.. ((7 * (13 .. ((7 * (13 ..some work done...1)) / 6) + 7 .

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

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. .127 to the right. to the closest-available empty area and use that. And when any set of instructions has ﬁnished.

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

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

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

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

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

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

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

you must list the class name with the method name. if from main you invoke Add3. in place of the method call we do have. Methods work in a very similar manner.readInt() – the method is called readInt() (you will ﬁnd it if you look inside the Keyboard. followed by a dot. This is done via the following syntax: className. both of the methods were inside the same class. main was able to invoke Add3 just by using the name of that method. followed by the method name (readInt).argN).135 Scope in methods When we went over loops we talked a little about the scope of variables. • Other variables in other methods are not accessible from that method. In the code above. If you want to use methods from another class inside the class you are in. Using methods from other classes In our program from the last lecture (repeated above).. 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. followed by the method call as usual. you list the class. you cannot access main’s local variables from Add3.Add3(a. c). but it is not necessary.methodName(arg1. the method scope rule is: • Variables declared in a method are only accessible in that method. Thus. and in no other methods anywhere else in the program.. . That is.java ﬁle). 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. followed by a “dot”.. • Variables declared within a method have as their scope the life of the method. since Add3 and main are within the same class.. So. 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. Speciﬁcally. we could have written: d = Program. so that the system knows in which class to ﬁnd the method you want. and since it is in the class Keyboard. you access it by listing the class (Keyboard). So. they vanish when the method ends. b. This is the idea behind the call to Keyboard.

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

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

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

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

. _______________________________________ | | | main | | | | a: 2 b: 1 c: 5 d: 8 | | | |_____________________________________| And now. the return value gets used in the assignment statement. .140 _______________________________________ | main | return | a: 2 b: 1 c: 5 d: ? | value: 8 | ___________________________________|______ | |\ / \ / \ / \ / | |__| \dd/ x:\2 / y: \ / z: 5\ / | | \/ \/ \/ \/ | | / \sum: 8 / \ / \ / \ | |/___\_____/___\____/___\_____/___\______| And ﬁnally. However. until the assignment statement was over). Two diﬀerent method calls really are two separate. 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). and 2.after which the return value itself vanishes (it only gets stored temorarily. distinct processing segments. _______________________________________ | | | main | | | | a: 2 b: 1 c: 5 d: 8 | | | |_____________________________________| return value: 8 .. the calling method can send data to the called method by passing arguments obtained in the calling method to the called method’s parameters. The only ways that they can communicate are: 1. we are back 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. instead of a value we calculated directly in main.

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

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

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

we’ll assume reference variables need 32 bits as well. | . | . Likewise. ..e. the actual value 2 (or rather. | . 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).144 | . |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ | . The way these variables diﬀer is in the data they store. | . When you execute the assignment statement b = 2. four 8-bit memory cells. so the variable b needs four 8-bit memory cells to store its value. i. | . so therefore the reference variable x would also need 32 bits. 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.

we would use an expression such as the following to create an array and return the address of that array: . | . | . The question is. how can we obtain memory addresses to actually store in our reference variables? And the answer is. So. We want to have an assignment statement such as x = ?. those exact same kinds of labels – or rather. So. we have this reference variable. | .145 | . just as we’ve labelled our memory locations with addresses like a5 and a7. remember that in an assignment statement. What values can be written into reference variables? And the answer is: memory addresses. our variable type and value type had to match. You could only assign boolean values to boolean variables. You could only assign char values to char variables. That is. 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. we do this by using the keyword new. The data that gets stored inside reference variables are bit strings that tell the machine to go to particular memory locations. together. but so far we have not discussed what we would replace the question mark with. | encode the integer 2 |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ | . | . | . their bit string encodings – are what get stored in our reference variables. |__________________________ | | 32 bits that. x. And so on.

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

. . together. and when the expression new int[3] evaluates to a100. that address would be what is stored in the reference variable x: . So. 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. we would have used this expression as follows: x = new int[3]. | encode the integer 2 |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ | .147 | . |__________________________ | | 32 bits that. beyond the | edge of our picture). | . | | . | . | . | .

| . So. | encode the memory | address a100 |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ | .148 | . | . since a100 is cell 0. together. and then step (2) says 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. that tells the machine to (1) read the memory address stored in x. |__________________________ | | 32 bits that. step (1) would read the value a100 from x. a100 + 2 * sizeOfInt = a100 + 2 * 4 memory cells . and then (2) taking that location as the start of the array. which uses the square brackets. | . when you have a statement such as: x[2] = 17. | encode the integer 2 |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ | 32 bits that. jump to the start of cell 2 of the array. together.

|__________________________ | | 32 bits that. a108. that is the location into which 17 is written: | . Finally. together. | . | encode the integer 2 |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ | 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) This memory that we request from the system using new. Without the .149 = a108 must be the address of cell 2. together. and we call that memory an object – i. we say that we allocate that memory from the heap. Note that objects do not have names!!!!. | . This is why reference variables are so important. together. so go to a108. | encode the integer 17 |__________________________ |__________________________ | . we declare variables. but we allocate objects. 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. | encode the memory | address a100 |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ |__________________________ | | 32 bits that. Therefore. | . since that memory location. we call it dynamic memory. is x[2]. | .e.

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

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

.| 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. 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. • 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. it still exists. all variables have names – Reference variable – yes. 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. for now. Once no reference points to the object. the object is gone. the only object we know is arrays and they store many items of the same type) . you cannot use objects directly. there is no name. as we study classes.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.

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

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

i++) System. } public static void readData(int[] arr) { for (int i = 0.length).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. printData(scores).println(scores.print("Enter score for index #" + i + ": "). } } public static void printData(int[] arr) { for (int i = 0.out. int[] scores. public class Lecture14 { public static void main(String[] args) { int numberOfScores.out. arr[i] = Keyboard. System. i < arr.out.length). i++) { System.println(scores.out.readInt(). readData(scores). } .println("Score at index #" + i + " is " + arr[i] + ". numberOfScores = Keyboard.").length.readInt().println(scores[2]). scores = getArray(4). System. System. System.out. badInit(scores).out.length.out.print("How many scores are there?: "). i < arr. System. scores = new int[numberOfScores].println(scores[2]).

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

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

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

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

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

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

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

... since your program is no longer using it: . When we return from a method. 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. 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 there are no longer any references in the program to the array object at a5000.164 the body of the loop will run three times..) back to main(). Thus. the local variables and parameter variables of that method go out of scope. with the for-loop complete.) goes out of scope. we can return from the method badInit(. the variable arr in badInit(. the system will reclaim that memory.

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

and write the memory address of that array object into the reference variable temp. we ﬁrst declare a local reference variable of type int[]. this method call is a bit more straightforward than the previous three. since the one parameter is an integer. just to pick something for our example. (We’ll assume that the array object is allocated at memory address a7000.166 As far as passing parameters goes. 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. As with any reference variable declared via a variable declaration statement.) . 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.

the method getArray(. Remember that in any return statement of the form: return expr. and the method returns a7000 as its return value. As we have already discussed. a memory address. is a memory address. the value of any reference type. i. and thus temp and n go out of scope. This matches our return type. So. (Remember.. we evaluate the expression. reference variables evaluate to the memory addresses they hold.) So. our expression temp in our return statement. the return type is int[] and our return value is a value of type int[].e. the last statement of the method: return temp.) now ends. and return that value as our return value.. and that is what we return. is the interesting part.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. . evaluates to a7000.

scores now points to the array object that was allocated in the getArray(. and so that value is what the assignment statement writes into the reference variable scores. 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)...) method. 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... and there are no longer any references holding a8000. a7000 ____________________ | | | | | | | | | | |____|____|____|____| 0 1 2 3 evaluates to a7000. since no reference variable in the program is pointing to it anymore: .168 ________________________________ | _______ | | main | | | | scores |a8000 -|-----------> | |_______| | | | | | | | |______________________________| a8000 _______________________________ | | | | | | | | 56 | -9 | 22 | 83 | 43 | 71 | |____|____|____|____|____|____| 0 1 2 3 4 5 /|\ | | the value a7000 is returned by getArray(.

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

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

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

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

though both phrases mean the same thing). 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 ﬁrst line below will generate a compiler error. 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. instead of saying “reference variable of type Clock”. Clock objects – or objects of any other type (i. the reference variable serves the same purpose – it will hold the address of a memory location. Similarly. For example. As with array objects. In both cases. we can use the expression “new Clock()” to create an object of type Clock. We access the individual variables of the object by using the same dot syntax we used for arrays. objects in general do not have names. Likewise. just as we used the expression “new int[6]” to create an object of type “int array”. 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. Once the object of type Clock is created. Similarly. Just as length was a variable built into every array object.e. In both cases. the reference variable holds the address of the memory location where the new object is stored in memory.173 Reference variables and objects We can declare a reference variable of type Clock. we can say “Clock reference variable” or “Clock reference”. 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. and we can only access them by having reference variables refer to those objects and then working through the reference variables. just as we said was the case with array objects. but the second one will not: int[] arr = new Clock(). . just as we declared a reference variable of type “int array”. minutes. we can refer to them as “Clock objects” (a shorter phrase to say than “objects of type Clock”. likewise hour. Clock c1 = new Clock(). A quick terminology issue: when we create objects of type Clock in this manner. and AM are variables built into every Clock object. The same applies for any other type of reference variable or object.

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

out.print("Time is " + c.out.").AM = true.minutes = 14.AM = false.println("PM. Clock office. // 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. home.out. // create/initialize "office" clock object office = new Clock(). } } // end of class . } // end main // This method now has a Clock reference for a parameter public static void printClock(Clock c) { // print variables for clock System.out. home."). office.AM == true) System. // object created office. System. if (c.print(c.out.hour + ":"). else // AM == false System.print("0").println("AM. office.minutes + " ").minutes < 10) System.hour = 2. // object created home. if (c. // create/initialize "home" clock object home = new Clock(). // Prints the "office" clock printClock(office).minutes = 15.hour = 7.

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

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

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

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

.minutes = theMinutes. 15..180 Each of the four arguments in that method call are evaluated. c. and control is transferred to the setTime(. and true. also writing into the instance variables of the object that home points to. since c and home point to the same object: . c.hour = theHour. when we run the three lines of the setTime method: c.. to obtain the four values a5000.) 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. we are writing into the instance variables of the object that the reference variable c points to.. Those four values are then copied into the four parameters of the setTime(. and thus.) method. 2.AM = theAM.

) back to main(). the object home points to has been initialized: . once we return from setTime(..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..

false).) method. and control is transferred to the setTime(... Each of the four arguments in that method call are evaluated. 7.. to obtain the four values a7800. 14.) method: ...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(. 14. Those four values are then copied into the four parameters of the setTime(.): setTime(office.. and false. 7.

when we run the three lines of the setTime method: c.minutes = theMinutes. c. since c and office now point to the same object.. and thus. we are writing into the instance variables of the object that the reference variable c points to.) the ﬁrst time: .. c.AM = theAM.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. Our second method call has written a diﬀerent value into the parameter c. and so by writing to the instance variables of what c now points to.hour = theHour. we are writing to a diﬀerent object than we were writing to when we called setTime(. also writing into the instance variables of the object that office points to.

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(. the object office points to has also been initialized: .) back to main()...

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. we gain the ability for other methods to read and write those variables freely. So. we can read or write the same object from many diﬀerent methods. We could not have written a separate method to initialize local variables within main(). but we can write a separate method to initialize the instance variables of some object. by moving the variables from being local to main(). to being in an object. . into main().

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

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

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

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

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

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

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

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

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

or reference variables stored in the cells of an array. when dealing with references – whether local reference variables.195 ________ | | ___________ data . .| --|------> | | |______| | 1 | examNumber |_________| | | | 80. and 2 at that object: ________ | | ___________ data ..scores does indeed point to an array object. 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. since now data. before you can use the “dot syntax” or the “bracket syntax” on that reference.3 | average |_________| | | | | | scores |_____|___| | | ______________________ | | | | | |---------> | | | | |______|______|______| 0 1 2 and thus we can run the last three lines as well. 1. and we can write the cells indexed 0. or parameter reference variables.| --|------> | | |______| | 1 | examNumber |_________| | | | 80.

7."). int theHour. // set the times on the clocks setTime(home."). 14.196 Lecture 17 : Instance Methods During the last lecture. office = new Clock(). if (c. } // end of class } . 15.println("AM.print("0").out.minutes + " ").print(c. // print the clocks printClock(home). setTime(office. if (c. int theMinutes.hour + ":"). // allocate objects and assign values to reference variables home = new Clock().out. 2.out.out. c.out.AM = theAM. false). boolean theAM) { c.println("PM. } // end main public static void setTime(Clock c. Clock office.AM == true) System. } public static void printClock(Clock c) { // print variables for clock System. else // AM == false System.minutes = theMinutes.hour = theHour. printClock(office). 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.minutes < 10) System. System. true). c.

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

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

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

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

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

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

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

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

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

The class method version of this program worked the same way. and that machine address is copied into this. except that (1) we called the parameter in printClock(. this points to the same object that home points to. and (2) we actually sent home as an argument in that case. Of course. instead of having our argument in front of the method call and our parameter automatically created by the compiler. in these examples. . the this in each instance method would be of type String.. our memory looks something like this: _____________________________ | | | main | _______ | | | | | home ----------|--------> |Clock | | | |object| | | |______| | | ^ | ______________________|_______ | | | | | | | printClock this -----|-----| |_____| | | | | | |____________________________| That is. 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).) “c” instead of “this”. to an actual parameter c.. this is of type Clock only because the instance method is in the Clock class.e. 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). If the instance methods were in the String class. matches the class the instance method is in).206 _____________________________ | | | main | | | | home --------|--------> | | | | | | | | |___________________________| ________ | | |Clock | |object| |______| Once the method call actually begins.

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

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

So the compiler. then your instance variable that is four cells from the start. So. and that is the only information we are missing! . If the object begins at a60. when compiling the code for the method. deﬁnes all the accessing of an object in terms of the starting address of the object. you simply move four more cells downward and there is the particular instance variable you are looking for. the starting address of the object is what is located in the reference variable. for objects of type Clock) is always four cells from the beginning 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. as we have discussed. instead begins at a10088. when once you know the starting address of the object. we know where the overall object begins. If your object instead begins at a10084.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. then your instance variable (such as minutes) is at a64.printClock(). When you have a line of code such as: home.

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

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

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

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

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

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

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

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

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

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

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

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

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

minutes = 0.AM = c. private int minutes.AM = theAM. } . this.AM = true. int theMinutes. this. // a constructor public Clock() { this. this.hour = 12.minutes = theMinutes.minutes.hour = theHour.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.AM. } // another constructor public Clock(int theHour. boolean theAM) { this. } // a third constructor public Clock(Clock c) { this. this.hour.hour = c. this. this.minutes = c. private boolean AM.

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

like this: . if we had wanted that. } If you write out a constructor to initialize a copy. } and thus assigned the object that this pointed to (which in the end. private double average. the constructor argument. Instead. You did not have two references pointing to the same object.hour = c. we used the following line: car = new Clock(home). the object that car pointed to.minutes = c.minutes. to hold the same values as the object that c points to (which is the same object that home.hour. that after the assignment of car was completed. If you are making a copy of an object like this. You have two diﬀerent objects.225 We pointed out at the time. 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.AM. which called the following constructor: // a third constructor public Clock(Clock c) { this. this. For example. we would have assigned car as follows: car = home. was a copy of the object that home pointed to. this. points to.AM = c. you need to be a bit more careful when reference variables come into play. is the same object that car points to). which hold the same values. private int[] scores.

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

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

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

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

230 .

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

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

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

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

n The general rule is that if we are n steps away.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. . and we need to know how to start the solution. and applying our rule all over again. 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. . since that is now our situation. by running the solution appropriate for being (n-1) steps away. we can ﬁnish solving the problem. . we don’t have to have our code consider every detail from the start to the ﬁnish of the problem. the solution to our problem is to ﬁrst take one step. by reducing the problem to a smaller subproblem – the same problem. just on fewer data values or smaller data values) – and then running the solution to that subproblem. . leaving us (n-1) steps away. 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 need to know how to ﬁnish the solution once we are zero steps away. Then. 1) take a step 2) run (n-1)-steps-away solution 2 3 . We only need the ﬁrst step and the last step – that is. In every situation where we’re a positive number of steps away from the door. Thus to write the solution to this problem. . thanks to us having just taken one step.

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

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

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

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

so we can use its value to | complete the multiplication in the | expression | | n * fac(n . as with any method. we set up a “notecard” (some area of memory inside the machine) to store the parameters and local variables for that method call.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. // we call the fac method. 3!. i. | // set up the "fac" notecard below: |____________________________________________________________________ | fac (n: 4) | (#1) | | need to calculate: fac(3).e.e. // we call the fac method. 4 was passed to this method as an | // argument. We start with a main() method that makes a call to fac(4): _____________________________________________________________________ | main | int x. and in doing so. _____________________________________________________________________ | main | int x. 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. we will take the recursive case. | x = fac(4). Since n is not zero. | x = fac(4). | x = fac(4). which requires we calculate (n − 1)!.1) | i. and in doing so. // we call the fac method | |____________________________________________________________________ Now. | 4 * fac(3) |__________________________________________________________________ . when we call that method. we run the fac method on the data in the fac notecard. It’s no diﬀerent with recursive methods: _____________________________________________________________________ | main | int x.

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

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

| 1 * fac(0) |__________________________________________________________________ From within the fac(1) call. so we make that call: . in order to calculate 3 * fac(2) | |__________________________________________________________________ | fac (n: 2) | (#3) | | need to calculate: fac(1). in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | need to calculate: fac(2).e. so we can use its value to | complete the multiplication in the | expression | | n * fac(n . | x = fac(4).1) | i. we will need a fac(0) call.243 __________________________________________________________________ | main | int x. |_________________________________________________________________ | 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 3 * fac(2) | |__________________________________________________________________ | fac (n: 2) | (#3) | | need to calculate: fac(1). in order to calculate 4 * fac(3) | |__________________________________________________________________ | fac (n: 3) | (#2) | | need to calculate: fac(2).244 __________________________________________________________________ | main | int x. . ﬁnally making our ﬁfth call which is our 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 1 * fac(0) | |__________________________________________________________________ | fac (n: 0) | (#5) | | base case! (we have not returned yet) |__________________________________________________________________ We make and pause four separate calls to fac. | x = fac(4).

|_________________________________________________________________ | fac (n: 4) | (#1) | | need: fac(3). we can start returning from there. since the base case doesn’t need to make any further recursive calls. 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).245 Then. 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). | x = fac(4). The base case itself returns 1: __________________________________________________________________ | main | int x.

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

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

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

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

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

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

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

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

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

when you make a recursive method call. your calculation cannot proceed until that method call is ﬁnished. .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.

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

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

and try and ﬁgure out what subproblem solution might help us calculate 35 . In the case of exponentation. 35 .) However. even though that formula works in most cases. the one that could help the most would be to decrease the exponent but not the base. The recursive case tells us how to solve any general problem in terms of a subproblem.e. since you’d already found a way to use the result of that subproblem! So. (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. for recursion. we only need to multiply it by the base (3) again to get 35 . we want to calculate the values of expressions such as 25 or 137 or 821 . That is.e. Of those subproblems above. Trying to calculate 35 if given the value of 25 . so our base case could be that when the exponent is 0. or 23 wouldn’t be anywhere near as simple. 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. and therefore that the “lower both the base and the exponent by one” subproblem wasn’t much help. More generally. we’ll assume that exp ≥ 0. you would likely conclude pretty quickly that knowing 24 didn’t seem to be much help in calculating 35 . if you were to take the value 34 . but let us at least consider the ones above for a moment. i. If we have 34 . i. reduce exponent by one • 24 . we can calculate baseexp with the formula base ∗ baseexp−1 .e. Some possible subproblems are: • 25 . 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. i. We might consider. and try and use that value to help calculate 35 . and the base case tells us when to stop breaking the problem into yet another subproblem. for example. say. if we generalize our result above. reduce base by one • 34 . 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 need a base case as well. if exp > 0 . That gives us the following mathematical formula: exp base = (base >= 1. reduce base and exponent by one • 23 . If you were to take the value 24 . reduce base by one and reduce exponent by two There are other possibilities as well. of course. if exp == 0 exp-1 base * (base ). and we’ll assume that base ≥ 1 so that we avoid having to deal with 00 in our algorithm. so that we don’t have to deal with fractional results.e. Further more. exp >= 0) ____________ | | | |___________ 1. But on the other hand.258 The ﬁrst example we will look at in depth is exponentiation. we are trying to calculate: baseexp We’ll assume for this problem that base and exp are integers. 24 . we return 1. it’s not all we need. i.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

i. 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 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. so the sum-of-digits of that number is just itself.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: . and none of them seem particularly promising – all of them have a greater sum of all digits than the original sum. Imagine we are trying to ﬁnd the sum of the digits in the integer 51460 (which is 16. The above looks nice.e. but we’ll pretend we don’t know that yet).276 Another example we can consider. i. is adding together the digits of a non-negative integer. once again.e. what smaller subproblem helps us out? • 51459. we only have one digit. i. (number − 3) 22 • 25730. (number − 1) 24 • 51457. If our number is 51460. (number/2) 17 Those are three possibilities. but how can be extract individual digits from a number? Well. let’s try a concrete example.

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

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

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

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

not twice. the pyramid of height 5 was just a pyramid of height 4: X XXX XXXXX XXXXXXX followed by drawing the bottom line. and so we should only count that once. the base of the pyramid of height 5. 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. are also a pyramid – the shape is the same. X XXX XXXXX XXXXXXX And similarly. Note also that the top four lines of that picture.1. minus one. For example. 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 . the pyramid’s base – which is twice the triangle base. just as we did for the triangle. we would like to describe a pyramid of height n. for that pyramid of height 4. with a one-line base. its top three lines form a pyramid of height 3: X XXX XXXXX In general.281 X XXX XXXXX XXXXXXX XXXXXXXXX Note that the picture is basically just two triangles facing in opposite directions. as being a pyramid of height n-1. minus one – is equal to 2 * height . We subtract one because the vertical edges overlap. the picture is merely smaller. Since the base of our triangle picture is equal to the height of our triangle picture. is equal to twice the base of a triangle of the same height.

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

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

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

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

out. height . indent).println(’*’). System. printRepeatedChar(’ ’. int indent) { if (height == 1) { printRepeatedChar(’ ’.out.2). printRepeatedChar(’ ’. System.out. PrintX(height . printRepeatedChar(’ ’.out. System.println(’*’). System. indent).print(’*’).2). ***** **** ------------| *** | ** | * |---** | *** | **** -----------| ***** 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. let us consider the drawing of a fan blade: ***** **** *** ** * ** *** **** ***** That is a fan blade whose blade widths are each 5. That gives us the following code: . } else { printRepeatedChar(’ ’.286 public static void PrintX(int height.println(’*’). System.but within it. } } As a ﬁnal example. height . indent + 1).out.2.print(’*’). but we’ll need to be able to indent the smaller picture when we print it.. there is a fan blade whose blade widths are each 4.. indent).

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

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

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

and could use at most. It’s possible we have 1. where we have no 10 dollar bills. It’s also possible we have 2. we already have 70 dollars and that is too much.290 Another example is making change. or exactly 4. or exactly 3. and so on. if we have one ten dollar bill. We can’t have fewer than 0. or 6. 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. let’s ask how many 10 dollar bills we have? It’s possible we have none. 10 dollar bills. or 3. So. we could say. What is the maximum numbers of ways you can have a given quantity of money? Consider: Say you had 62 dollars. or exactly 1. up to all combinations of 62 dollars where we have exactly 6 ten dollar bills. that means the other 52 dollars needs to come from money that is NOT ten dollar bills! Otherwise. or 4. The only problem is. plus all combinations that total 62 dollars. or exactly 2. plus 32 dollars without using 10 dollar bills tens. plus 2 dollars without using 10 dollar bills . well. ﬁrst. such things exist! Now. we’d secretly be adding additional ten dollar bills! That is. let’s imagine we want to count all combinations that have exactly 1 ten dollar bill. such things exist! That gives us the following: 0 1 2 3 4 5 6 tens. 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. You have various coin amounts. and as small as pennies. plus 52 dollars without using 10 dollar bills tens. and if we have more than 6. How do we do that? Well. 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. where we have exactly 1 ten dollar bill. if we add together all combinations that total 62 dollars. if we want to know how to make 62 dollars out of the above money. okay. or exactly 6 ten dollar bills. plus 12 dollars without using 10 dollar bills tens. plus 42 dollars without using 10 dollar bills tens. plus all combinations that total 62 dollars where we have exactly 2 ten dollar bills. or 5. plus 22 dollars without using 10 dollar bills tens. But those are the only possibilities. plus 62 dollars without using 10 dollar bills ten. or exactly 5.

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

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

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

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

it requires that the client send in some initial values as arguments to those parameters. the second method call would be required.length .) with three argumebts. the call to the longer. 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.. perhaps the client would like to make the following method call: print(arr).5). The ﬂexibility this allows. arr.. That is. Perhaps a client would simply like to say.length . 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. perhaps the clients of this method don’t want to bother having to pass in indices as arguments. the most common situation is that the client wants to print the entire array.length .1 are likely to be the most common arguments. clients can either call the recursive print(. more interesting method is generally the most important statement of the wrapper method. 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. 0.length .. arr. or they can call non-recursive print(.. the work that gets done is to . right now. would probably be less common. The “glue” between this method and the recursive one we wrote earlier. That is.... Unfortunately. situations where the client only wants to print part of the array.1). In the case of our print methods.. Clients have a choice of how they want to activate the printing of their array.295 Now. but in both cases. and only a little bit of work gets done before or after that statement.) directly.1). In the wrapper method above. Wrapper methods are usually relatively simple. 0. } Such a method is known as a wrapper method. and not the indices as well. Since our method needs the lo and hi parameters in order for the recursion to work. and that method can call the recursive print(. but even though the client could choose to pass in any pair of bounding indices as the second and third arguments. with the most common arguments! public static void print(int[] arr) { print(arr. 0 and arr. 3. with three arguments. rather than the method call: print(arr. from index 0 through the highest index of the array”. “print the array” and have it be assumed that the request means. So. arr. it’s the three-parameter print(. is that the above method can call the recursive one. “print the entire array.) with one argument.) that does most of the work.

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

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

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

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

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

.. if all we knew was that we had some collection of values to inspect. we are going to search an array for a particular value. Since A[lo] is not 39. So our next recursive step is over the index range 2.9 (we’ll indicate this in the next diagram with the shorter horizontal line above our array values).hi.hi. And A[1] is not our value 39. we can check the ﬁrst cell. That is. and if it is 39. we are done... hi == 9 So now again we check A[lo]..hi.hi we did not ﬁnd our value in A[lo].. then we would need to inspect each and every one of them.. So. since the only way we know how to store a collection of information is in an array. 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. and if it is not 39. ----------------------------------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. we will start by thinking back to lecture 1. .. Speciﬁcally. so we will recursively check the index range lo+1. so we again recursively check the index range lo+1. So our next recursive step is over the range 3. We had said that. we could recursively search the rest of the array. where we were discussing searching for a penny under a box. So. then we’ll recursively search the rest of the array. we have not found our value yet.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 == 2. let’s imagine we are searching an array for the value 39.. That is. 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. we are checking A[1].. but this time since lo is 1. given the index range lo. next we check the index range 1. That is. and there was no information we could take advantage of to rule out any of the values. 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.9. This is the problem we are going to discuss now..301 Lecture 26 : Searching Today. so we again recursively check the index range lo+1.. we could check A[lo] (which is A[0] here).. or searching a list of names for a particular name. hi == 9 In the example above. hi == 9 We check A[lo] (which this time is A[2]) and that is not equal to our value 39.

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:

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

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

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

) method. since shifting doesn’t give us any beneﬁt in this case. Which unsorted orderings they end up in along the way doesn’t make any diﬀerence. we’re going to sort them anyway. 0.. What does selection sort do? Well.. we can pull 6 out of the collection by moving it to the front of all the other remaining values.. and the findMinimum(. Once you sort the remaining values. and for no beneﬁt – once we have moved 6 to the front of the array. nor the values in the other cells indexed 1 through 9. 9) would return the value 3. 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 from the last lecture.. 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. We could also have ﬁgured out some way to shift the values between the beginning of the array. For example. have been sorted yet anyway. Then. and you are done. you just place the sorted collection after that smallest value which you had set aside. And that gives us the following basic outline for selection sort: . 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. 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.) method will do. and 6. one way you might sort things in real life is to ﬁnd the smallest value in your collection and set it aside. and will take longer. So.313 Given this swap(. since that is where the minimum (6) is located. 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 can now design an algorithm known as selection sort. since neither 93. is to swap it to the beginning of the array. Now. we prefer to swap. The way we can “set the smallest value aside” once we’ve found it.. We already know how to ﬁnd the smallest value in an array – that is what our findMinimum(. and the easiest way to do that is to just swap it with 93.

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

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

Now. where we are sorting the subarray with indices 6 through 9. We are attempting to sort the subarray with indices 5 through 9. and we will be done.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. index 6 – so that the minimum is placed in the ﬁrst cell in our subarray. We are attempting to sort the subarray with indices 3 through 9. The minimum value over those indices is 39. all that remains is to recursively sort the values in the subarray with indices 7 through 9. Now. index 3 – so that the minimum is placed in the ﬁrst cell in our subarray. which is located in cell 4. all that remains is to recursively sort the values in the subarray with indices 4 through 9. Now. index 5 – so that the minimum is placed in the ﬁrst cell in our subarray. So. Now. where we are sorting the subarray with indices 5 through 9. swap the value in cell 9 with the value at our low index – namely. So. index 4 – so that the minimum is placed in the ﬁrst cell in our subarray. all that remains is to recursively sort the values in the subarray with indices 5 through 9. and we will be done. where we are sorting the subarray with indices 4 through 9. which is located in cell 6. where we are sorting the subarray with indices 3 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. We are attempting to sort the subarray with indices 6 through 9. swap the value in cell 9 with the value at our low index – namely. swap the value in cell 4 with the value at our low index – namely. The minimum value over those indices is 67. So. 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. . and we will be done. which is located in cell 9. which is located in cell 9. and we will be done. The minimum value over those indices is 54. We are attempting to sort the subarray with indices 4 through 9. 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. So. swap the value in cell 6 with the value at our low index – namely. all that remains is to recursively sort the values in the subarray with indices 6 through 9. The minimum value over those indices is 41.

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

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) .

For example. temp. i++) temp[i] = arr[i].1. For example. stored in indices 0 through k . // copy values from arr into temp for (int i = 0. This leaves the last cell – the new cell. // we have copied all the values to our new array. // meaning that nothing will point to the old array anymore. // now.1 of an array of size k + 1. then we have 12 sorted values indexed from 0 through 11 (0 + 12 . 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. the reference arr should point to our new array. When we are done. // so the system collects it and eliminates it. we have k sorted values. 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. if we had wanted to insert 70 into our above array of size 12. in sorted order. let us consider what is involved in inserting a new value into an already sorted array. whose length is one greater int[] temp = new int[arr. 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 . and k were 12. Fortunately. if lo were 0.length. i < arr. That code doesn’t play a role in what we are about to discuss.length + 1]. If we want to write a new value into that array. We can assume we already have k sorted values. we are just reminding you how you could “add an array cell to the end of your 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. indexed from lo through lo + k . sorted order – into a new.319 Next. arr = temp. 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. and then we write 70 into that new. we ﬁrst increase the size 12 array to a size 13 array. so we don’t // lose any of our values. the cell at index k – empty and available for a new value.1 == 11). For example.

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

. If in the top picture. 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. So why not swap them? For example. by swapping the values at indices 12 and 11. if this were our original problem: // original problem. For example..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”. “insert new value at index 12 into sorted section from indices 0 through 11 inclusive”... once we .. 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 // . then if we can move it to cell hi-1. while still keeping everything from lo through hi-2 sorted.. then if we trust that it works.but 93 > 70. so swap the two values. in our example. We solved the original problem. is some way of getting this situation: arr[i] |<---. “insert new value at index 11 into sorted section from indices 0 through 10 inclusive”. so it should be to the right of 70 anyway. newValue is at cell hi. then the cells from lo to hi-1 now form our subarray that we can send to the recursive call.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. we could now run that algorithm recursively. we have our basic recursive step.. 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 // . 93 is greater than 70. which we are claiming we can solve recursively. and “insert new value at index 11 into sorted section from indices 0 through 10 inclusive”. leaving us with the problem..321 If we had some way of converting the top picture into the bottom one. Our overall problem is to insert 70 into the sorted section of values from 6 through 93.

. Above. Now. If 95 is greater than that. but even once that is done.. Let’s take a look at that original problem closely. 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. we have the following code: .. 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.322 swap 70 and 93. 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 // .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. since once we establish that the new value is the greatest of the values we are dealing with. 93... So. is greater than the value to its left. This is one of our base cases. What if instead of our new value being 70.. our new value. 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 // . we can make a recursive call to that would insert 70 into the sorted section of values from 6 through 88.but 93 > 70. since 95 is greater than 93. we can leave it right where it is. so far. 95. So something’s gone wrong here. which is the greatest value in the sorted range. that would not work in all cases.. 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..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.... Remember that everything to the left of 95 is sorted – and thus 93 is the greatest value in the sorted range. we know that 95 is also greater than everything that 93 is greater than. 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. it is 95?: // original problem. it must also be greater than everything else in the sorted range. // original problem.. we could insert 95 into the sorted section from 6 through 88. so swap the two values.

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

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

325 Description of insertion sort algorithm: If the array is of size 1. int hi) { if (lo < hi) { insertionSort(A.. we decide to sort the values at indices 0 through 7 ﬁrst. int lo. But ﬁrst we must recursively sort cells 0 through 7!! . lo.. basically just needs to check if the array size is greater than 1. 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. into the sorted collection to its left. do nothing. Once this is done. a recursive call to insertionSort(. // everything from lo through hi-1 is // sorted. lo. insertInOrder(A.. insert the value in the last cell. Once this is done. hi . a call to the insertInOrder(. make two method calls – the ﬁrst. Otherwise recursively sort all but the last cell of this subarray. we can then insert the value arr[8] into its proper place among the sorted elements in cells 0 through 7.). we can then insert the value arr[9] into its proper place among the sorted elements in cells 0 through 8. Then. 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. on an array of size 10 (the beginning of this gets a little repetitive. and the second. The code for this algorithm. hi).) method we wrote earlier: public static void insertionSort(int[] arr.. and if so.1). we decide to sort the values at indices 0 through 8 ﬁrst.

Once this is done. we decide to sort the values at indices 0 through 4 ﬁrst. Once this is done. 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 decide to sort the values at indices 0 through 3 ﬁrst. we decide to sort the values at indices 0 through 6 ﬁrst. Once this is done. 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[4] into its proper place among the sorted elements in cells 0 through 3. we can then insert the value arr[6] into its proper place among the sorted elements in cells 0 through 5.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 can then insert the value arr[7] into its proper place among the sorted elements in cells 0 through 6. 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[5] into its proper place among the sorted elements in cells 0 through 4. But ﬁrst we must recursively sort cells 0 through 3!! . we decide to sort the values at indices 0 through 5 ﬁrst. Once this is done.

We’ve never returned from any of the insertionSort calls. we called insertionSort. 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. that is our base case. we can then insert the value arr[3] into its proper place among the sorted elements in cells 0 through 2. 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. 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. because the ﬁrst instruction in each call was to call insertionSort again on a smaller array. and return to call #9 to ﬁnish it up. and so on. we decide to sort the values at indices 0 through 0 ﬁrst. But now we will start to return to them one by one and do more work. and from there we called insertionSort a second time. we will then return to call #8 and ﬁnish it up. we decide to sort the values at indices 0 through 2 ﬁrst. Once this is done. And so on – one by one we will return to the previous . Note that right now we have ten diﬀerent method calls active at once! From main(). and from there we called insertionSort a third time. Once this is done. Once we ﬁnish call #9 up.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. For example. Once this is done. we can then insert the value arr[2] into its proper place among the sorted elements in cells 0 through 1. 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 are in recursive call #10 right now. but in a moment we will ﬁnish call #10.

since there is nothing to be out of order with. the subarray consisting of cells 0 through 1 is sorted. Again. or there are no elements to the left. So. and at that point the array will be sorted and we will be ﬁnished. which sorted the elements at indices 0 through 0. as was the point of this method call. Here. as was the point of this method call. and then 71. and we need to insert the value in cell 2 into that sorted subarray in sorted order. And now that we have ﬁnished recursive call #8. and we are going to insert the value at index 1 into the sorted subarray consisting of the elements at indices 0 through 0. At this point. This results progressively swapping 11 with the element to its left until either 11 is less than the element to its left. 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. 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. Thus. or there are no elements to the left. we swap 11 with 93 and then 11 is at the far left so we stop. 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.328 recursive call and ﬁnish it. also array right after returning to recursive call #7. 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. 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 thus we are done with recursive call #7. Since arr[0] is 93. and then realize there are no more elements to the left of 6. we swap 71 with 93. but note that 11 is less than 71. note that we have sorted the elements in cells 0 through 1. since lo == hi) and we return from recursive call #10. when we return from call #8 back to call #7. and then 11. we swap 6 with 93. we are done with this method call (in the code. Again. the lo < hi condition would be false here. or there are no more elements to 11’s left. 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. Now. . Here. and so we don’t swap those two. Now. and we need to insert the value in cell 3 into that sorted subarray in sorted order. the subarray consisting of cells 0 through 2 is sorted. 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. and so we stop. note that we have sorted the elements in cells 0 through 2. we have ﬁnished recursive call #10. also array right after returning to recursive call #8. back to recursive call #9. until ﬁnally we are returning from the very ﬁrst insertionSort call back to main(). when we return from call #9 back to call #8. Thus. And now that we have ﬁnished recursive call #9. And thus we are done with recursive call #8.

and we need to insert the value in cell 4 into that sorted subarray in sorted order. Again. 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. 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. the subarray consisting of cells 0 through 3 is sorted. And now that we have ﬁnished recursive call #7. and then 67. or there are no elements to the left. 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 thus we are done with recursive call #5. and then 71. Again. . Thus. Here. when we return from call #6 back to call #5. Here. we swap 41 with 93. And now that we have ﬁnished recursive call #5. we swap 67 with 93. and then realize that 39 is less than 67 and so we stop. Thus. also array right after returning to recursive call #6. 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. and we need to insert the value in cell 5 into that sorted subarray in sorted order. when we return from call #5 back to call #4. Now. Thus. Again. and then realize that 11 is less than 39 and so we stop. the subarray consisting of cells 0 through 4 is sorted. and we need to insert the value in cell 6 into that sorted subarray in sorted order. as was the point of this method call. note that we have sorted the elements in cells 0 through 3. as was the point of this method call. also array right after returning to recursive call #5. And thus we are done with recursive call #4. Now. and then 71. when we return from call #7 back to call #6. we swap 39 with 93. Now. and then realize that 39 is less than 41 and so we stop. And thus we are done with recursive call #6. And now that we have ﬁnished recursive call #6. as was the point of this method call.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. 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. or there are no elements to the left. or there are no elements to the left. also array right after returning to recursive call #4. note that we have sorted the elements in cells 0 through 4. and then 71. the subarray consisting of cells 0 through 5 is sorted. note that we have sorted the elements in cells 0 through 5.

also array right after returning to recursive call #2. also array right after returning to recursive call #1. Again. when we return from call #4 back to call #3. Here. 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. note that we have sorted the elements in cells 0 through 8. Thus. when we return from call #2 back to call #1. and then 39. or there are no elements to the left. the subarray consisting of cells 0 through 7 is sorted. when we return from call #3 back to call #2. 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. as was the point of this method call. Now. And thus we are done with recursive call #2. we swap 54 with 93. or there are no elements to the left. And thus we are done with recursive call #1. Now. and then 88. Here. And now that we have ﬁnished recursive call #2. as was the point of this method call. and then realize that 41 is less than 54 and so we stop. 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. Again. 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. 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. note that we have sorted the elements in cells 0 through 7. we swap 23 with 93. the subarray consisting of cells 0 through 8 is sorted. as was the point of this method call. and we need to insert the value in cell 9 into that sorted subarray in sorted order. note that we have sorted the elements in cells 0 through 6. Thus. and then 71. and we need to insert the value in cell 7 into that sorted subarray in sorted order. and then 71. and then realize that 71 is less than 88 and so we stop. And thus we are done with recursive call #3. and then 41. the subarray consisting of cells 0 through 6 is sorted. And now that we have ﬁnished recursive call #4. and then 67. also array right after returning to recursive call #3. and we need to insert the value in cell 8 into that sorted subarray in sorted order. . and then 88. we swap 88 with 93. And now that we have ﬁnished recursive call #3. or there are no elements to the left. Here. Now. and then 67. Again. 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 ﬁnally we realize that 11 is less than 23 and so we stop.

so that we have a sorted collection of size 2. At this point.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. Then we insert a new value into that collection. and we return from recursive call #1 back to main(). so that we have a sorted collection of size 4. the array is sorted. Then we insert a new value into that collection. This is in contrast to “selection sort”. where we kept “selecting” the smallest value. for each new value we see. And so on. Then we insert a new value into that collection. out of the unsorted collection of values we still had left. in sorted order. . so that we have a sorted collection of size 3. We have a collection of size 1. in sorted order. 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. in sorted order.

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

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

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

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

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

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

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

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

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

is so that we can eventually return to it. we make call #3 from call #2: . 1. 3). 0. |__________________________________________________________________ And similarly. |_________________________________________________________________ | print (arr: [pts to array]) | (#1) (lo: 0) (hi: 3) | | prints arr[0]. 2. then why save it? The only reason it is hanging around. |_________________________________________________________________ | print (arr: [pts to array]) | (#2) (lo: 1) (hi: 3) | | prints arr[1]. |__________________________________________________________________ | print (arr: [pts to array]) | (#2) (lo: 1) (hi: 3) | | prints arr[1]. So.341 __________________________________________________________________ | main | print(arr. then calls print(arr. 1. 3). 0. by increasing lo right there on the ﬁrst notecard? FROM: __________________________________________________________________ | main | print(arr.. 3). we can keep converting that notecard for each subsequent call. 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. |_________________________________________________________________ | print (arr: [pts to array]) | (#1) (lo: 0) (hi: 3) | | prints arr[0]. 3). 3). we will immediately leave it and return back to main().. 3). why make an entirely new notecard for call #2. but once we do return to it. |__________________________________________________________________ But if we won’t ever “need” the ﬁrst print(. 2. |__________________________________________________________________ TO: __________________________________________________________________ | main | print(arr. then calls print(arr. then calls print(arr. 0. First.) notecard again. then calls print(arr. 3).

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

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

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

print(arr. We want to keep repeating the method code over and over and over. and just repeat the method code via a loop. and hi.out. we stay in the method call we are already in. into current // version of parameters. into new // version of parameters. // param = argument. lo = lo + 1. } else // base case { base case code } } then the loop-based version would rearrange the code as follows: // copy arr.out. arr = arr. 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. rather than creating a new notecard to hold the parameters. but rather than continually making new method calls to accomplish this. } to this: while (lo <= hi) // same condition { System.345 That is. we are changing our recursive case from this: if (lo <= hi) { System. lo + 1. and repeat // copy arr. hi). and repeat . lo + 1. and hi. lo + 1.println(arr[lo]). hi = hi.println(arr[lo]). } That is the core idea behind converting a tail-recursive method into a loop-based method.

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

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

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

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

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

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

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

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

we will need to add an additional parameter that was not needed for the forward-recursive version.1) (old recursive case) -------> return fac(n . On the other hand. In the case of the factorial method. and thus pass that value as an argument: return n * fac(n . } We can’t just convert this to a loop and say n = n . Speciﬁcally. the recursive call will be the ﬁnal work in the recursive case. what we want to do is to add in a parameter that will accumulate the product of the diﬀerent values of n. but in order to write it in tail-recursive form. the only parameter we need for the calculation is the value of n. int productSoFar) then our recursive case could multiply n by productSoFar before the recursive call is made. If we wrote it in forward-recursive form. as proof of this. else // n == 0 return 1. That’s why it is known as accumulator recursion. So making a recursive function of this kind into a loop is not the simple process that it was with a tail-recursive method. 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. we will thus add a second integer parameter to the factorial method.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 . we know a loop-based factorial method is possible. because we still need that original value of n to do the multiplication once we return from the recursive call. because we wrote one back in Lecture Notes #21. n * productSoFar) (new 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. which only has n as a parameter and yet works perfectly! However. If we have that second parameter: public static int fac(int n.1). we can simply look at the code above. Accumulator recursion is a form of tail recursion. these parameters would not be needed. However. 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. if we want to write a tail-recursive method for calculating factorial.1.1 in place of the recursive call.

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

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

. | | [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. 24) ----> returns 24 |__________________________ | fac (n: 1) | (#4) (product: 24) | | need: fac(0. | x = fac(4). 4) ----> returns 24 |_________________________ | fac (n: 3) | (#2) (productSoFar: 4) | | need: fac(2. 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 . | x = fac(4. 12) ----> returns 24 |__________________________ | fac (n: 2) | (#3) (productSoFar: 12) | | need: fac(1.357 Implementation #1 -------------------_________________ | main | int x. 1). | | [24 written into x] |_________________________ | fac (n: 4) | (#1) (productSoFar: 1) | | need: fac(3.1 being written back into the parameter n of the next call. and the value of the argument n * productSoFar begin written into the parameter productSoFar of the next call.

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

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

else // exp > 0 return base * pow(base. 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. int exp.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. base * productSoFar). else return pow(base. for each step – but the second implementation has a third parameter whose value steadily increases with each method call. that parameter’s value is our answer. Note that the ﬁrst two arguments are the same in both cases. We calculate the third argument for each call by multiplying the base by the productSoFar. we could try multiplying the base by an existing product. } 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. the multiplicaton of base and our recursive call result. } After we ﬁnish a recursive call. there is more work to do besides just returning a value – namely.1). exp . int exp) { if (exp == 0) return 1. int productSoFar) { if (exp == 0) return 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. this is a forward-recursive algorithm. exp . until by the time we reach the base case. So. before the point where the base case returns. .

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

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

int exp. int productSoFar) { if (exp == 0) return productSoFar.1. } else // base case { base case code } } That is. else // exp == 0 return productSoFar. exp . base * productSoFar). int productSoFar) { if (exp > 0) return pow(base.363 To convert this to a loop implementation. int exp. we can use our general tail-recursion-to-loop-implementation technique to convert this into a loop implementation: . base * productSoFar). Now. else return pow(base. } by switching the if and else cases. 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). convert this: public static int pow(int base.1. exp . } to this: public static int pow(int base.

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

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

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

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

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

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

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

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

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

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

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

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

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

then I know trying to search an array about twice the size should take me about two hours. because it helps us choose between algorithms when we are still in the process of designing our program. you are adding only one more operation total – i. In this kind of analysis.234 (n squared) + 3n .201 1. so if you double the amount of work. And so on. This result just gets more prominent the larger n gets. ﬁ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. The number of steps needed to reduce the subarray are dealing with to size 1 is going to be a logarithmic number of steps.980296 . you get the graph of a logarithmic function.5 (n squared) and 213. we don’t have to concern ourselves too much with the particular details of the function. the analysis we have done is what is known as worst-case analysis. The algorithm that is mathematically faster today will still be mathematically faster next year when processors have doubled in speed. That means our analysis is completely platform-and-software-tool independent. and constant factors. We aren’t very concerned with the lower order terms. In fact. here.001 10.232n+2 are still both linear functions. and if you graph a logarithmic-time algorithm’s running time as the size of the data varies.826446 . the quadratic term accounts for almost the entire value of the expression. 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.002.2 are still both quadratic functions. even when n is 100. But this has nothing to do with the operating system I am using. 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. And the next step is on at most half of *that* subarray. That is the best way to compare algorithms.e. Trying to search an array four times the size should take me about four hours.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. 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.020. So. Up to this point. For example. 0. adding constant time to the running time (because logbase-2 of 2. (You’ll learn some formal notation for ignoring the lower-order terms in CS173.999800 . and such. increases the resource usage by the log of that factor.000.001 100. which is a fourth of the original array. it has nothing to do with the processor in my machine.001 n^2 / (n^2 + 2n + 1) -------------------. All we really care about is whether the function is linear or quadratic or such. An example is binary search – you compare A[mid] to the key.200.999980 The column at the far right measures what percentage the quadratic term is. 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. as a total of the entire expression. 4. The fact that the running time is linear in the size of the array is a fundamental mathematical property of the algorithm!!. and it has nothing to do with the compiler I used.998003 . in the case of logarithmic running time. is 1). we are concerned with ﬁnding out how long the algorithm takes to run in the worst .332n and 1.

we might prefer that to a situation where the worst case was only 5 minutes.. and attempts to analyze an algorithm with the average case in mind are known as average case analysis (in contrast to worst case analysis). but “hit a snag” in certain situations and spent an extra hour processing before piping in oxygen. we don’t care too much about what the possible best we can expect to see it. in such a case it would be important to know the absolute longest that your algorithm could take. The astronauts would die waiting for your algorithm to ﬁnish!! So.385 possible situation. But. 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. In fact. 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. It would do you no good if it “usually” worked quickly. On the other hand. That is the idea of worst-case. 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. even if occasionally we end up with a slower case as a result. Sometimes we will prefer to focus on what “usually” happens.so while we care about what the worst we can expect to see would be. . Sometimes – though not usually – we also discuss the ”best case”. then we might ﬁnd that acceptable. even in the worst case. We care about the worst possible situation. For example. We usually don’t care about this case because it’s not interesting. often what we are more concerned with is what usually occurs.. This notion of “usual” is known as the average case. ”What happens in the most ideal situation?” is what we are asking here. and pick an algorithm that “usually” runs very fast. and also what is most likely. you’d want to make sure that the algorithm you chose to make this oxygen delivery system work was very very fast. We don’t *care* if things work out better than we expect. but we *do* care if they end up worse than we expect. no matter what input the algorithm is given. if it usually printed right away.

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

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

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

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

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

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

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

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

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. and for an odd exponent. we have the following algorithm as a second algorithm for calculating exponentiation. where x = base . is that in both recursive cases. as long as we have an even exponent. though. If we save the result the ﬁrst time. And this will give us a third algorithm for computing exponentiation: // third algorithm for exponentiation ______ 1 if exp > 0 | | exp | exp/2 base -----|----. we will still perform the same number of multiplications in the end. where x = base .x * x. where we express baseexp in terms of baseexp/2 // second algorithm for exponentiation ______ 1 if exp > 0 | | exp | exp/2 exp/2 base -----|----. 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. we are performing the same recursive calculation twice. However.394 So. we’ll use this pattern instead: baseexp = base ∗ base(exp−1)/2 ∗ base(exp−1)/2 That is. 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 . we can just use the baseexp = baseexp/2 ∗ baseexp/2 pattern. What we might note.

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

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

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

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

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. and greater than 67. the index where that value is located. and 71 is less than arr[7] == 78. so we recursively search the subarray 5. mid == (5 + 9)/2 == 7. less than 78.399 For example. and 71 is greater than arr[5] == 67. so we recursively search the subarray 6.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. hi == 6 Now. as we did above. hi == 9 Now. instead of having found our value. so we recursively search the subarray 5. hi == 9 Our mid index is 4.. the ﬁrst three of the four steps above would be the same. if we were searching for 72 instead of 71. hi == 6 Now.. mid == (6 + 6)/2 == 6. so we have found our value and we return 6. 72 is greater than 59. mid == (5 + 6)/2 == 5. and 71 is equal to arr[6].. we end up shrinking our subarray down to size 0.. But in the fourth step. we instead have a search key greater than the current middle value: . 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. and 71 is greater than arr[4] == 59. For example..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. If our search turns out to be unsuccessful..

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

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

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

. and b1 < b2 < b3 < b4) And if instead b1. 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. then that’s the ﬁrst element of your new sorted collection. 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. For example: Start of merge call #1: 2 1 6 10 18 20 26 23 50 25 1 < 2.. so. If both collections are completely empty.. 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. Start of merge call #2: 2 1 2 < 10...403 a1 then merge these: a2 b1 a3 b2 a4 b3 b4 (a2 < a3 < a4. and b2 < b3 < b4) If you reach the point where one collection is completely empty. so.

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

and min of that is 50. and min of that is 26. Start of merge call #11: 1 2 6 10 18 20 23 25 26 50 no collections are left.405 Start of merge call #9: 26 1 2 6 10 18 20 23 25 50 first collection is all that’s left... However. We keep track of the lo and hi of each of the two collections. In this case. if we try to move 1 to the front of the array. we see the two sorted collections we just merged in our example. that cost will add up. we copy everything from the temporary array back into the original array. but have a temporary array that we write the next minimum into. When we’ve ﬁnished the merge. so. This means we’ll need to keep track of the next spot to write into in the temporary array as well. Start of merge call #10: 50 1 2 6 10 18 20 23 25 26 first collection is all that’s left. we are done. we use a temporary array.. since it is less than 2. 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. So instead. cell by cell. stored side-by-side in an array as they would be after MergeSort’s two recursive calls. For example: . 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.. 1 is the minimum of the entire collection. If we did that shifting of the ﬁrst collection repeatedly. so. again.

so. so. .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. Start of merge call #2: 2 loA 6 18 26 50 hiA 1 10 loB 20 23 25 hiB 1 cur 2 < 10... Start of merge call #3: 2 6 loA 18 26 50 hiA 1 10 loB 20 23 25 hiB 1 2 cur 6 < 10... so...

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

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

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

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

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

“linear times logarithmic”. we get bad pivot. if our pivot ends up at index m. Quicksort runs in quadratic time in the worst case. most of the time. we would like m to be the middle index of the array. In fact. And in the average case. we have to do the best we can to get a decent pivot. Instead. As long as we get a decent pivot. 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? . and the right subarray is only two cells less than the overall array. In that case. and the other will be only one cell shorter than the overall array. Quicksort will take linear time to ﬁnd the pivot and partition at each step (we’ll see why shortly). Quicksort runs in time proportional to n log n – that is. When we get a bad pivot almost all of the time. However. 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. one of the two “halves” won’t exist at all. the proof of it is beyond the scope of this course). we generally get good pivots most of the time. after we partition. it turns out this is okay. 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. 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. the left subarray has only one element. This will result in quadratic behavior. almost all of the time. So. The danger of allowing otherwise can be seen if we go back to our ﬁrst example array. But. and so we are also concerned with the average case. but smaller function – and thus a shorter (faster) running time – than quadratic functions. we can’t go in search of the exact median. What we want to avoid is getting a bad pivot. So. but pick the leftmost element instead of the rightmost element for the pivot. and barring that. the worst situation is when we have the minimum or maximum as the pivot. This is a larger function – and thus a longer (worse) running time – than linear functions. The problem is that trying to ﬁnd the exact median element will take far too long for our purposes (take our word for this.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 will have all values less than 2 to the left of 2. 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. that is not a problem. In those situations. we would like m to be as close to the middle index of the array as possible. That is. 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. and indeed. we are okay. that is our worst-case behavior. If sometimes.

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

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

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. 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). Our partition algorithm is going to want to traverse down the array trying to rearrange values. 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. or last – the pivot was found in. The problem is. by moving the pivot to the ﬁrst cell. Likewise. that pivot is just getting in the way. and values that are greater than the pivot will go to the right side of the array. we want to make sure we don’t deal with a value once it has been inspected. middle. The solution is to keep track of which cells constitute the “left side” and the “right side”. 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. Rather. However. what we are going to do is get it out of the way. so we know where to move 26 to. No matter which of the three cells – second. Now. 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. and for the moment. or it might be in the second cell. just as we said we needed to do above.which means we need some knowledge of which cells constitute the ‘‘left side’’. 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. the pivot might be in the last cell. we then need to partition the array. once we read a value and we know it belongs on the left side of the array.. we will have “side ... if a value belongs on the right side of the array.415 Partitioning the array Once we have chosen our pivot value using median-of-three pivot selection. In other words. So. However.. there is no need to keep track of all of those cells. the question is. or it might be in the middle cell.which means we need some knowledge of which cells constitute the ‘‘right side’’ so we know where to move 86 to. For example. Values that are less than the pivot will go to the left side of the array. 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.

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

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

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

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

our “less than” bound will increase. we also know that everything to the right of cell i is greater than the pivot. What happens when we have only one cell left above? As far as the partitioning goes. 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. we can picture our value at i with one side or the other. except for the pivot itself. there isn’t really any problem anymore. which here is A[4]. thus closing in a smaller and smaller area until we have only one cell left. everything in cells to the left of cell i is less than the pivot. Say that last cell is cell i. and our “greater than” bound will decrease. so swap with value to left of Right Bound and move Right Bound over one location. 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. So. We know that.420 Inspect cell just to right of Left Bound. Now 7 is on ‘‘right side’’. So. now we come to the ﬁnal idea behind our partitioning. 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 | |__________|___ ___________| . 7 > pivot. And.

pivot should be at cell 3 and the ‘‘< pivot’’ stuff should be in cells 0-2. the problem isn’t partitioning this last element. And. since the pivot gets placed right after it. so really. clearly. In other words. 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. But they are quite diﬀerent from the standpoint of “placing the pivot”. and if it belongs . when we are partitioning. The problem is in re-placing the pivot at its correct location. We are ﬁne as far as rearranging the elements goes. the size of the left side determines where the pivot gets placed. 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. if that value belongs with the left side then it is sitting where the pivot should go. 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. so really. Which of the two situations we have above doesn’t matter from the standpoint of “ﬁnding sides”. and we end up down to one last value.421 So. since the array is correct in that regard either way.

and the rightmost value of the left side can go where the pivot currently is. the rightmost cell of the left side will always be sitting where the pivot should be. this is because the left side needs to take up c cells. but that left side doesn’t get to start at 0. 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. since the pivot is supposed to go in that rightmost cell of the left side. Since the c values less than the pivot should be taking up cells 0 through c-1. instead.422 to the right side. But in either case. leaving cell c free for the pivot. whether that last cell is the “last unknown cell” (the ﬁrst example in the example pairs above). since it is less than the pivot and thus can end up anywhere on the left side after this partition operation is complete. however. Why do things work this way? Why is the last cell of the left side always where the pivot should go? Well. and therefore instead of ending up at cell c-1 (after taking up cells 0 through c-1). 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. is that we can complete our partition by swapping the pivot with the rightmost value of the left side. so really. then all the “less than pivot” elements are now to the left of the pivot. What this means. or the cell to its left (the second example in the example pairs above). it starts at cell 1. then it is the ﬁrst value in the right side and the pivot should be placed just to the left of it. Once we make that swap. So. it ends up at cell c (after taking up cells 1 through c). the last cell of the left side is sitting where the pivot should go. since the pivot is in the way. 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. and we get the array as shown below: . The ﬁrst example. as we can see by expanding on our two examples above.

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. . But if A[4] had happened to be 9 instead. so really. which will be the spot that our last ‘‘unknown’’ value was located in. 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. 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. pivot should be at cell 3 and the ‘‘< pivot’’ stuff should be in cells 1-2. and we get the array as shown below.

which will be just to the left of the spot that our last ‘‘unknown’’ value was located in.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 .9. Then Partition the range 1.. Please take note of one last thing – our “wall” or bounds idea is really just a set of recursive calls.. A[2] is greater than pivot so (right bound case) swap with A[9] and then partition range 2.. we are just calling on the subarray with bounds lo and hi-1. A[1] is less than pivot so (left bound case) partition range 2...9.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.. and when the “left bound moves” we are just calling on the subarray with bounds lo+1 and hi.. 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.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. and likewise when the “right bound moves”.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. Each call attempts to partition an array with bounds lo and hi....

... A[4] is greater than pivot so (right bound case) swap with A[7] and partition range 4..7.7.7.6... A[2] is greater than pivot so (right bound case) swap with A[8] and then partition range 2. A[4] is greater than pivot so (right bound case) swap with A[5] and partition range 4.425 Partitioning range 2.. A[3] is less than pivot so (left bound case) partition range 4.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.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.. A[2] is less than pivot so (left bound case) partition range 3...5.7. 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..... A[4] is greater than pivot so (right bound case) swap with A[6] and partition range 4.8.6.. 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 ....5..... 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..

. 67 is greater than pivot.426 Partitioning range 4. so actual partitioning is completed.4.. Eventually we swap A[0] and A[3]. and it is simply a matter of knowing where pivot should go. Here. into cell 3. Low and high bounds are equal. 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 . so pivot should go to its left.

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

A[j] = temp..1). just as some of the other recursive algorithms we’ve looked at have done. once again. We ﬁrst looked at this method when discussing SelectionSort. 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. int i.. public static void Quicksort(int[] A) { Quicksort(A. a few quick preliminaries. then you can have a public Quicksort that accepts only the array.428 Implementing Quicksort in Java First. A[i] = A[j].length . 1) We are going to need the Swap method again. int j) { int temp = A[i]. 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. we’ll reproduce it here: public static void Swap(int[] A. and the SelectionSort notes go over the development of Swap. So. } 2) Quicksort needs to recursively call itself on subarrays. 0. A. } . But as a reminder of what the code looks like.

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

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

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

int j. } . 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. else return j. else if (A[j] <= A[k]) return k. else if (A[i] <= A[k]) return k.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. I went over this in class. and then return the appropriate index. int k) { if (A[i] <= A[j]) if (A[j] <= A[k]) return j. else return i. else // A[j] < A[i] if (A[i] <= A[k]) return i. private static int MedianLocation(int[] A. int i.

the pivot will either be sitting at A[lo] or much later. int hi) { int currentPivotLocation = MedianOfThree(A. which are the bounds of the remainder of this array. That is. // 5) Return index where pivot is currently stored. (lo+hi)/2 ). Because of this. we pass the recursive partitioning algorithm the bounds lo + 1 and hi. MedianLocation(A. into the ﬁrst cell in the array. } 3) Partitioning the remainder of the array: As we said way above. // 5) Return index where pivot is currently stored. So. int hi) { Swap(A. hi. hi. we now need to swap the pivot out of the way. private static int PartitionWrapper(int[] A.433 2) Swapping the pivot and the ﬁrst element: With MedianOfThree completed. lo+1. lo. we will move the pivot to its correct location in the array. // 3) Partition the remainder of the subarray. we will never use currentPivotLocation after this swap – from now on. private static int PartitionWrapper(int[] A. } Actually. // 3) Partition the remainder of the subarray. and from this non-recursive PartitionWrapper. 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. int lo. 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. // 4) Put pivot in its appropriate spot between the halves. We will also pass in the pivot value. So. 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. so that the cells from the second index through the last index can be part of the partitioning. . int lo. // 4) Put pivot in its appropriate spot between the halves. the partitioning is essentially handled recursively. Partition will be the recursive algorithm that repeats the partitioning step over and over. as “partition this subarray” becomes a matter of “deal witht the leftmost element and then partition a smaller portion of this subarray”. currentPivotLocation). All we need returned from that recursive version of Partition is the index m where the pivot should swap to in the end. while lo + 1 and hi are only indices. we can write the index directly into the swap by just passing the method call as an argument to swap. 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). we could write a recursive version of Partition which would only take care of that actual partitioning work. lo. (lo+hi)/2)). Since the pivot is sitting at A[lo]. Swap(A. lo+1.

int m = Partition(A. m). Swap(A. lo+1. A[lo]). hi. int lo. the pivot is stored at index m. int hi) { Swap(A. lo+1. int hi) { Swap(A. // final version of the PartitionWrapper algorithm private static int PartitionWrapper(int[] A. hi. and we swap the pivot into that cell and return that index back to Quicksort. // 5) Return index where pivot is currently stored. hi. lo. private static int PartitionWrapper(int[] A. lo+1. m). MedianLocation(A. return m. MedianLocation(A. we know we are suppposed to put the pivot there. lo+1. (lo+hi)/2)). // 4) Put pivot in its appropriate spot between the halves. A[lo]). lo+1. hi. MedianLocation(A. lo. we run median-of-three to get the pivot location and swap the pivot with the far left cell. int m = Partition(A.434 private static int PartitionWrapper(int[] A. It returns the index where the pivot should go. } 5) Return index where pivot is currently stored: Once the above swap is complete. Swap(A. hi. 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. int lo. int hi) { Swap(A. int m = Partition(A. 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. above. lo. We then call the recursive Partition to partition the rest of the array. lo. . } 4) Putting pivot in its appropriate place between the halves: Now that you know which index m is. } So. (lo+hi)/2)). A[lo]). lo+1. hi. (lo+hi)/2)). int lo. lo. well. // 5) Return index where pivot is currently stored.

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

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

eventually. we can still analyze Mergesort – we just will use a diﬀerent technique than the recurrence relation. what is the running time for merge? Well. that takes constant time to write one value on each step. if we have n total values among the two sorted collections we are merging. when we write the temporary array back into the original array.e. so that is likewise. the merge work is linear. consider the following diagram. n/2. For the moment. likewise. where the number we list (n. And then. and we spend constant time on each step to write one of those values into the temporary array. for a given step of MergeSort. linear time. 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. then we have linear time total. 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. it simply makes for a convenient notation for the mergesort running time. Each step breaks the array in half. So. you’ll learn all about solving recurrence relations.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. and then we sort each of those halves before trying to merge the entire array. each value is written to the temporary array once and then written back once. Now. etc. So. plus linear work to merge them. and learn how to solve them. So if we start out with n elements.) is the number of cells in the array at that level of recursion: . before we get our ﬁnal result. But in this case. overall. you don’t have the mathematical tools to solve that yet. Now. i.

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

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.so this is basically the same as SelectionSort at that point. I just want to give you a gut feeling that it’s true. (That’s not a proof. basically everything. . is basically only sorting only one or two fewer cells than the previous call. And on average. So. or everything but one value. so we can expect the average case to be similar to the best case. So each recursive call to sort the right side. so that you remember it better. we get the best possible pivot for quicksort and the two halves of the array are equal in size. we expect quicksort to get a good pivot most of the time.. in the partition. In that case. of course.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. in the best case. but the proof that the average case is basically equal to the best case is beyond this course. ends up on the right side of the pivot.. and is thus quadratic. and thus to *also* be n*lg n.) The worst case for quicksort is when the pivot is always as low as possible. and in that case.

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

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

Sign up to vote on this title

UsefulNot useful- Computer Application in Businessby NainaJames
- Computer Applicationby Arjun Sanal
- Connect Plus - Test Centre Technical Guide - V1.6by francisco.zuniga.l
- <HTML><HEAD><TITLE>Access Denied</TITLE></HEAD> <BODY>Your page is blocked due to a KIET IT security policy that prohibits access to Category default <BR> </BODY></HTML>by akttripathi

- CS Unplugged en 10
- Siemens S7 Libraries
- Computer Systems Update--November 1994 (166159340)
- Computer Architecture
- Software Engineer or UI designer
- c100 lec
- IT3104(Case Study)
- PC AT Technical Reference Mar84 3 of 3
- v4m1pt
- BSC IT.docx
- Chapter 1 - Introduction to Computer
- Chapter 1
- bms1.7
- Very Impora
- 01Whatisacomputer(June07)
- The Virtualisation of Art Practice
- Online Treasure Hunt 01
- Cyber Law of It act 2000 Frm Section 43 to 71
- Ibps Computers
- BChronicleJan
- Computer Application in Business
- Computer Application
- Connect Plus - Test Centre Technical Guide - V1.6
- <HTML><HEAD><TITLE>Access Denied</TITLE></HEAD> <BODY>Your page is blocked due to a KIET IT security policy that prohibits access to Category default <BR> </BODY></HTML>
- Computer Masti Information Brochure 2011
- Erio Takdir Pane(CV)
- hw st
- Chapter 21-C Language1
- my computing portfolio
- Computers - Boon to Human Life
- entire

Are you sure?

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

We've moved you to where you read on your other device.

Get the full title to continue

Get the full title to continue reading from where you left off, or restart the preview.