Topics covered: Operators and operands; statements; branching, conditionals, and iteration
Instructor: Prof. Eric Grimson, Prof. John Guttag
The following content is provided under a Creative Commons license. Your support will help MIT OpenCourseware continue to offer high-quality educational resources for free. To make a donation, or view additional materials from hundreds of MIT courses, visit MIT OpenCourseware, at ocw.mit.edu.
PROFESSOR JIM ERICSON: OK, to work. A word of warning: fasten your seat belts. Or, another way of saying it is, I'm going to open up the fire hose a little bit today.
Last lecture, you might have thought this was a SHASS class, it's not like a philosophy class, and it was important to set the stage for what we're going to talk about, but we talked about very high level things. The notion of recipes, the notion of computation, why you want to do this, what you're going to learn.
Today we're going to dive into the nitty-gritty, the nuts and bolts of the basics of computation, and in particular, what I'm going to do today is, I'm going to talk about operators and operands, which we did a little bit real last time, in particular how to create expressions, I'm going to talk about statements as the key building blocks for writing code, and I'm going to introduce simple sets of programs, in particular I'm going to talk about branching, conditionals, and iteration. So, a lot to do. OK? So, let me jump straight to it.
At the end of last lecture, we started introducing some of the pieces you want to do. And I want to remind you of our goal. We're trying to describe processes. We want to have things that deduce new kinds of information. So we want to write programs to do that.
If we're going to write programs, we need at least two things: we need some representation for fundamental data. And we saw last time two examples of that. And the second thing we're going to need, is we're going to need a way to give instructions to the computer to manipulate that data. We need to give it a description of the recipe.
In terms of primitive data, what we saw were two kinds: Right? Numbers and strings. A little later on in the lecture we're going to introduce a third kind of value, but what we're going to see throughout the term is, no matter how complex a data structure we create, and we're going to create a variety of data structures, fundamentally all of them have their basis, their atomic level if you like, are going to be some combinations of numbers, of strings, and the third type, which are booleans, which I'm going to introduce a little later on in this lecture.
And that kind of makes sense right? Numbers are there to do numeric things, strings are our fundamental way of representing textual information. And so we're going to see how to combine those things as we go along.
Second thing we saw was, we saw that associated with every primitive value was a type. And these are kind of obvious, right? Strings are strings. For numbers, we had some variations; we had integers, we had floats. We'll introduce a few more as we go along. But those types are important, because they tell us something about what we want to do when we want to put them together.
OK, but nonetheless, I want to stress we have both a value, yeah, and a type. All right. Once we have them, we want to start making combinations out of them. We want to put pieces together. And for that, we combine things in expressions. And what we saw as expressions are formed of operands and operators. And the simple things we did were the sort of things you'd expect from numerical things.
Now I want to stress one other nuance here. Which is, and we're going to do some examples of this, initially we just typed in expressions into the interpreter; that is, directly into Python. And as I suggested last time, the interpreter is actually a program inside of the machine that is basically following the rules we're describing here to deduce the value and print it up. And if we type directly into the interpreter, it essentially does an eval and a print. It evaluates, and it prints. Most of the time, we're going to be doing expressions inside of some piece of code, inside of a script, which is the Python word for program. In there, I want to make this distinction, this nuance: the evaluator is still going to be taking those expressions and using its rules to get a value, but it's not going to print them back out.
Why? Because typically, you're doing that to use it somewhere else in the program. It's going to be stored away in a variable. It's going to be stuck in a data structure. It's going to be used for a side effect. So, inside of code, or inside of a script, there's no print, unless we make it explicit. And that's a little bit down in the weeds, it's a detail, but one I want to stress. You need to, if you want something to be printed out inside your code, you need to tell the machine to do that.
OK. So let's do some simple examples. We've already seen somebody's. I just want to remind you, if I wanted to, for example, type in an expression like that, notice the syntactical form, it's an expression, a number, followed by an operand, followed by another expression. And of course I get out the value I'd like there.
Yes sir. Oh, you don't like leaning that far to the left? OK, if you're a Republican I'll be happy to shift this over a little bit. Wow, John, I got a laugh for a political joke, I'm in big trouble. That better? Oh damn, all right, I'll have to do it even more. OK, here we go, here we go, you see, I'm doing it down here, I can't see it, does that-- ah, I hear his sighs of relief, OK, good. There we go. Better. All right.
One of the other things we showed last time is that operators are overloaded. And this is where you heard John and I disagree. I don't happen to like this, but he thinks it's an ok thing. In a particular-- if we, whoa, we don't do that, we do this-- that is, give a combination of a number multiplication in a string, this will in fact give us back a new string with that many replicas, if you like, of the string concatenated together. All right? And if you want to do other things, for example, we can take two strings and add-- whoops, sorry-- and add them together, we will get out, again, a concatenation of that string. And these will, we'll let you work through the variations, but these are the simple expressions we can use. Now, sometimes things get a little interesting. All right? What's the value of that expression? What do you think should happen if I evaluate that expression? Somebody with a hand up, so I can see it. What's going to happen?
PROFESSOR JIM ERICSON: An error? Why?
PROFESSOR JIM ERICSON: Great. OK. That means, let's check it. It certainly is. We bribe people. So I, ah, by the way, John's a Yankees fan, he throws like Johnny Damon, I'm a Red Sox fan, so we'll see if I, how about that? And I almost hit John along the way, great. My third right, exactly, what can I say? All right, so we're into bribing you as we go along here, and all right? You'll be badly overweight by the end of the term.
Right, it's a syntactic error, because it doesn't know how to deal with this. But there's an important thing going on here, if I in fact wanted to combine those into a string, I should have told the machine to do that, and I can do that, by explicitly saying, take that, which is a number, and convert it into a string, and then-- bleah, I keep doing that-- then add it to that string.
OK, so there's an important point here. We've got what's called type conversion. That is, if I want to combine two things together in a particular way, I need to make sure that I give it the kind of operand it expects. So STR, which I just typed up there, takes in parens, some input, and it converts it into a string, so that now I can use that where I was expecting a string. John.
PROFESSOR JOHN GUTTAG: You've got a static semantic error in your syntax.
PROFESSOR JIM ERICSON: Thank you. And I was going to come to that in a second, but thank you, John, for pointing it out.
All right. Why is it a static semantic error? The syntax is OK in the sense of, it is an operand, an operator, an operand, so syntactically it's OK. The semantics was what caused the problem, because the operator was expecting a particular kind of structure there.
There's a second thing going on here that I want to highlight, because it's really important. Yes indeed. OK, there we go. The second thing I want to highlight is, that what's going on, is that Python is doing some type checking. It caught the error, because it checked the types of the operands before it applied things, and it says, I'm going to stop.
Now, you might have said, gee, why didn't it just assume that I wanted to in fact treat these as strings, and combine them together? Sounds like a reasonable thing to do. But it's a dangerous thing. Because in doing that, Python would then have a value that it could pass on into some other part of a computation, and if it wasn't what I wanted, I might be a long ways downstream in the computation before I actually hit some result that makes no sense. And tracing back where it came from can be really hard. So I actually want to have type checking as much as I can early on.
And in fact, under type checking, different languages sometimes fall on a spectrum from weak to strong typing. Which basically says, how much type checking do they do? Now, you're going to hear John and I go back and forth a lot, as I said I'm an old time-- well I'm certainly old time, but I'm also an old time Lisp programmer. I love Lisp, but Lisp is certainly in the category of a very weakly typed language. It does not check the types of its arguments at all. Python is, I wouldn't say completely strong, but it's much closer to the strong end of the spectrum. It's going to do a lot of type checking for you before it actually passes things back. Nonetheless, I'm also going to argue that it's probably not as strongly typed as we might like.
So, for example, there's an expression. Now, less than is just, if you haven't used it before, it's just the operator you'd expect, it's comparing two things and it's going to return either true or false depending on whether the first argument is less than the second argument. What's going to happen here? Again, I need a hand so I can know where to throw candy. I've also got on my reading glasses on, I can't see anything. Anybody. TAs don't count, they get their own candy. When it, yep.
PROFESSOR JIM ERICSON: Good question. Sounds like a reasonable guess, right? How in the world am I going to compare a string to a number? So, see how good my aim is, ah, not bad. All right. A good quest-- sorry, a good thought, but in fact, son of a gun. Or as my younger son would say, fudge knuckle. Yeah. All right? So, what in the world's going on here? This is a place-- I don't know about you, John, I think this is actually really not good, because right, what this is doing is, it's allowing-- sorry, let me back up and say it-- it's got an overload on the less-than that allows you to compare basically the lexicographic ordering, or this sequence of ordering of symbols, including numbers inside of the machine. And this, in my mind, should have been an error. Why in the world would you want to compare that?
Just to give you an example of that, for instance, I can do the following: all right, the number four is less than the string three, whereas the string four, oops, is not less than the string three. And this is a place where it's comparing strings and numbers in a strange way.
So why am I showing you this? Partly to show you that it's kind of weird, but also to tell you that one of the things you want to do is exercise what I'm going to call some type discipline. Meaning, when you write code, you want to get into the habit of A, checking out operators or procedures to see what they do under different circumstances, either check them out or read the specifications of it, and two, when you write your own code, you want to be disciplined about what types of arguments or operands you apply to operators. Because this is something that could certainly have screwed you up if you didn't realize it did it, and you need to have that discipline to make sure it's doing the right thing.
OK. One of the other things you're going to see is that some of the operators have odd meanings. And again, I think we looked-- Yup?
STUDENT: So, the string A is less than three, is false because they're comparing like ASCII values?
PROFESSOR JIM ERICSON: Yes. I mean, I'm sorry. The answer is, I don't know if it's ASCII. John, do you know, are they doing ASCII encoding inside of here? I'm assuming so. Right. So, in case you didn't understand what the the question was, basically every symbol gets translated into a particular encoding, a string of bit, if you like, inside the machine, there's a particular one called ASCII, which is, if you like, an ordering of that, and that's what the machine's actually comparing inside of here, which is why in under ASCII encoding the numbers are going to appear after the characters, and you get the strange kind of thing going on.
All right. I want a couple of other things, just to quickly remind you, and one of them is, remember, the operators do look at the types, so division, for example nine divided by five is one, because this is integer division, that is, it's the largest number of integer multiples of five to go into nine, and there would be a remainder associated with it, which is in fact four. And again, you've got to be careful about how you use the operators.
Right, having done that, we can certainly get to more complicated things, so for example, suppose I look at that expression. Three plus four times five. All right. Now. There are two possible values here, I think. One is 23, the other's 35. Because this could be three plus four, times five, or it could be three, plus four times five. And of course, you know, when you look at code it doesn't pause in between them. But what I do? I just separated, do I do the addition first or do the multiplication first? Anybody know what happens In this case? Yeah, way up, oh God I'm going to have a hell of time throwing up there, way up at the back.
STUDENT: Standard order of operations, I guess take the multiplication first, and add the three.
PROFESSOR JIM ERICSON: Right. I'm going to try, if I don't make it, you know, just get somebody to pass back, whoa! I just hit somebody in the head. Thank you. Please pass it back to that guy. If you want candy, sit much closer down, and that way we can film you as well as we go along. Right.
So the point is, there is something here called operator precedence, which is what the gentleman said. I'm not going to say much more about it, but basically what it says is, with all other things being equal, things like exponentiation are done before you do multiplication or division, which are done before you do things like addition and subtraction. And so, in fact, if I wanted the other version of it, in fact, if I do this right, it's going to give me 23 because it does the multiplication first, if I wanted the other version of it, I need to tell it that, by using, excuse me, parentheses. And in general, what I would say is, when in doubt, use parens. OK.
Now, that just gives us expressions. We can start having complex expressions, you can imagine we can have things are lots of parens, and all sorts of things in it. Yes, question.
STUDENT: What does it mean, the operator used, when you were calculating the remainder between nine and five?
PROFESSOR JIM ERICSON: It's the percent sign. If you can't read it, I guess I'm going to have to blow that font up, aren't I, next time around. Yeah, it's a percent, so this percent sign will give you the remainder.
OK. Second thing I need to do, though, is I need to, when I get those values, I want to hang on to them. I'd like to give them a name, so I can refer to them in other places. And so we saw that as well, the last piece we had here is the ability to create variables, which have their own values, and that's done using an assignment statement. So in particular, that is an assignment statement. It says, take the name x and create a binding for that name to the value of the sub-expression and in fact to do this, to stress a point, let's do that. It's not just a number, it's any expression. What Python will do, is it will evaluate that expression using the kinds of rules we talked about, and then it creates a binding for x to that value. And I want to stress this, we're going to come back to it later on in the term, so the way I'd like you to think about it for now, is that somewhere in the machine, there's a big space that contains all possible values. Right. It's a slight lie, it doesn't have all possible values, but you get the idea. It has, if you like, intellectually, all possible values. And when I create a binding, I'm taking a variable name, in this case x, stored somewhere in a table, and I'm creating a link or a pointer from that name to that value. This is a nuance. It's going to make a lot more sense later on when we introduce mutation into our language, but I want you to start thinking of it that way. Don't think of it as a specific box into which we're putting things; think of it as a link to a value.
I could have, for example, another assignment statement, and that creates a binding from y into that same value, and one of the things as a conservist I can do is, I could have a statement like, let z be bound to the value of x. And I said it deliberately that way. That statement says, get the value of x, which is this link, and give z a pointer to the same place. To the value, not to x. OK, and we'll just plant that idea, we're going to come back to later on, as we carry on.
OK. So if we have variables, one of the questions we can ask is, what's the type of the variable. And the answer is, it inherits it from its value. OK. Yes. So if somewhere in my code, I have that statement, that assignment statement, x now is a variable whose value is an integer. Unfortunately, at least in my mind, in Python, these variable bindings are dynamic, or the type, rather, is dynamic. Meaning, it changes depending on what the current value is. Or said a different way, if somewhere later on in the program I do this, x now has changed its type from INT to string. Now why should you care? OK, my view is, I don't like it. Especially in the presence of operator overload. Because I might have written some code in which I'm expecting that particular variable to have an integer value. If somewhere later on in the code it shifts to string, I might not be manipulating that and getting actual values out, but not what I wanted, and it's going to be really hard for me to chase it back. So one of the things I would like to suggest is that you develop some good style here, and in particular, don't change types arbitrarily. I can't spell today. Meaning, sometimes you need to do this, but in general there's-- at least in my view and I don't, John, would you agree?-- you just don't want to do this. You don't want to make those changes. It just leads to trouble down the road.
OK. Now, last thing about variables, and then we're going to start pushing on this, is where can you use them? And the answer is, you can use a variable anywhere you can use the value. So, any place it's legal to use the value.
OK. Now. This is just sort of bringing us back up to speed and adding a few more details in. What we really want to do now though is start using this stuff. So, operands. Let us take expressions, get values out, we can store them away in places, but ultimately we want to do something with them, so we need to now start talking about what are the things we can do inside of Python, or any programming language, to manipulate them. And for that, we're going to have statements.
Statements are basically, if you want to think about it, legal, and I was about to use the word expression except I've misused that elsewhere, so legal commands that Python can interpret. You've already seen a couple of them. Print, assignment, certainly two obvious statements, they're commands to do something. Assignment is binding a name to a value, print is saying put it back out in the screen. Obviously if you have print as a way of putting things out, we expect to have ways of getting input in, we're going to see an example that in the second. And as we go through the next few lectures, we're going to add in more and more of these statements.
But let's look at what we could do with this, OK? And to do this, I'm going to use some code that I've already typed in. So I'm hoping you can read that, and it's also in your handout. This is a little file I created, all right, and I'm going to start with a sequence of these things and walk them along, again I invite you to put comments on that handout so that you can follow what we're going to do. All right?
So let's look at the first part of this. Right now, this is just a text file. OK. And I've highlighted in blue up there one of the pieces I'm going to start with. And what do I have? I have a sequence of commands; I've got an assignment statement, I've got another assignment statement, I've got a print statement, I've got an input statement, which we'll come back to in a second. And I want to basically try and use these things to do something with them.
Second thing I want to note is, the little hash mark or the pound sign, that's identifying a comment. So what's a comment? It's words to you, or to the reader of the code, that are telling you what's going on inside of this code. OK?
Now, these comments, frankly, are brain-damaged, or computationally challenged if you prefer. Meaning, why in the world do I have to tell the reader that I'm binding x to the value three? All right? I'm putting them in there to make a point. In general, good programming style says you put in comments that are going to be valuable in helping you as a reader understand what's going on inside of the code. It could be, what's the intuition behind this piece of code. It could be, preconditions I want to have on input. It could be, explanations of specific things you're doing. But you need to have those comments there.
Now, this becomes a little bit of one of those motherhood and apple pie kinds of lectures. You know, your mother always told you to eat brussels sprouts because it was good for you. Well this is a brussels sprouts comment. everybody goes yeah, yeah, yeah, comments, of course. Of course we're going to do comments. And they never do. So my challenge to you, and I know Professor Guttag can do this, my challenge to you is, a year from now, come back and look at code you wrote here. Can you still understand what it was you were trying to do? I don't know, John, if you'd agree, right? If you can read the code a year later, even code you wrote yourself, it's a good sign that you put good comments in, right?
Second good piece of style here is choice of variable names. These are lousy. Deliberately. OK? I'm just using simple things like x and y and z because I want to make it, just get through the lecture if you like. But in general, the choice of variable name is a great way of commenting your code. Use variable names that make sense. That little problem set zero that you did. You read in a couple of values, you probably stored them away. My bet is, you used simple names like x and y. A much better name would have been firstname, lastname, as the name of the variable to tell you what you were trying to capture there.
OK. The other piece I want to say about variable names is, once I have that choice of variable name, I can use it, but in fact there are a few things that I can't use in terms of variable names. So, these are an important way of documenting, but there're some things excluded. And in particular, there are some key words that Python is going to use that have to be excluded. Let me highlight that. As I said, right now that's just text file. I'm going to save this away-- yeah, not that way, I'm going to save this away-- with the subscript, or the suffix rather, py to make it a Python file. Yeah, and I know it's already there but I'm going to do it, and I get some wonderful colors. But these are important, OK? So notice what I have up there now. Comments appear in red. I can see those. There's a keyword, which I'm going to highlight right up here, print, which is in, I don't know what that color is, orange? There's a function in purple, there's a string in green, and in black I have the assignment statements.
That print is a keyword. It's a command to Python to do something. As a consequence, I can't use it as a variable name. All right, think about it for a second. If I wanted to use print as a variable name, how do I get the system to decide gee, do I want print as a value for something, or do I want print as a command? So there's a sequence of these that are blocked out, and I-- John, I think there are what, twenty-eight? Something like that, TAs, is that right? Twenty-eight keywords that are blocked? We'll find them as we go along--
OK. Having done this now, I can simply go ahead and run this, and in fact if I go up here to run, you'll see I've got both an option to check the module, though in this case I'm just going to run it. Oh, notice what happened. It ran through that sequence of instructions, in particular it bound x to the value three, and then it took x times x, got the value of x multipied by x, which of course is nine, bound that to the value of x, and then it printed out the value, and now it's sitting here waiting for an input. You notice what it did, it printed out that little, right up here I'd said enter a number and that's what it's printed out, so I can enter a number and it prints it out. Great.
Let's run it again. Actually for that, I can just use, if I'm lucky, function F5, which didn't work, so let me try it again., here we go. We're going to run that module. OK.
Whoa. What happened? I said enter a number. I didn't. I gave it a string. And it still took it. And printed it up. Well, this is one of the places where I want to come back to that highlighting of what do things do? Even though my statement said enter a number, in particular, raw input here simply takes in a set of characters and treats it as a string. And then prints it back out. So if in fact I wanted to make sure this was a number, I should have done something like either try and convert it to a number, which of course failed here, or put in a check to say where it is. So it's a way of reminding you, I've got to be careful about the types of things that I put in.
OK. This is still boring, so let's step on the accelerator. What I have now is the following: I can write expressions, do combinations of things to get out values, I can store them away, I can print them up. But literally all I can do at this stage is write what we would call a straight-line program, that is, a program in which we execute in which we execute the sequence of instructions one by one. Simply walk down that list. That's what we just did there, right? We just walked through that list. This is boring. In fact, you can do some nice things to prove what is the class of functions you can compute with straight-line programs, and what you'd see if you did that is, it's not particularly interesting.
OK. Let's go back and think about our recipes. What we use as our motivation here. Even in real recipes, you have things like, if needed, add sugar. That's a decision. That's a conditional. That's a branch. That says, if something is true, do something. Otherwise, do something different. So to really add to this, we need to have branching programs. What I mean by that is, a branching program is something that can change the order of instructions based on some test. And that test is usually a value of a variable. OK. And these get a whole lot more interesting.
So let's look at a little example, and this is going to, excuse me, both allow us introduce the syntax as well as what we want to have as the flow of control inside of here. So let me go back up here, and I'm going to comment out that region, and let's uncomment this region. I want to write a little piece of code. It's going to print out even or odd, depending on whether the value I put in, which is x in this case, is even or odd.
Think about that. That says, if this thing has some particular value, I want to do one thing; otherwise, I want to do something different. And let's look at the syntax of this. This is the first of the conditionals that we're going to see. Notice the format. I'm going to go up there. The first statement right here, that's just an assignment statement, I'm giving some value to x. We could make it something different. And then, notice the structure here. The next three statements.
First of all, IF is a keyword. which makes sense. It is followed, as you can see there, by some expression, followed by a colon. And in fact, that colon is important, so let me stress this over here. The colon is important It's defining the beginning of a block of instructions. Yes sir.
PROFESSOR JIM ERICSON: Based on a test. Usually the value of a variable.
OK, so let me go back to where I am. I'm looking at that piece of code. What that colon is saying is, I'm about to begin a sequence of instructions that I want to treat as a block. So it identifies a block of instructions. It's, and in particular, the colon is the start, and the carriage return is the end.
Now what in the world does that mean? I'm doing a lot of words here, let me try and say this a little bit better. That code says the following: the IF says, I've got an expression, I'm going to evaluate it. If that value is true, I want to do a set of things. And that set of things is identified by the sequence of commands that are indented in, this one right here, following the colon but before I get back to the same place in terms of the indentation. If that test is not true, I want to skip this instruction, and there's a second keyword else, followed by a colon, and that tells me the thing I want to do in the case that it's false.
So in fact if I run this, ah, and save it, and it prints out odd. So, what happened here? Well, let's look at the code. Right? x is initially bound to fifteen. I get to the IF. The IF says, evaluate that next expression. In that next expression, I'm actually taking advantage of the fact that I'm doing integer multiplication and division here. Right, that divide is, if x is an integer and two is an integer, what's it going to do? If x was even, x divided by two is going to be actually the half of x, right? If x is odd, that integer division is going to give me the number of multiples of two, that go into x, plus a remainder, which I'm going to throw away. In either case, I take that value and multiply back by two, if it was even I get back the original number, if it was odd, I'm not going to get back the original number, so I can just check to see if they're the same.
OK, so a little nuance that I'm using there. So, the first thing that IF does, bleah that IF says is, evaluate that expression and if it's true, do the next thing, the thing after the colon. In this case it's not true, so it's going to skip down and evaluate the thing printed up the odd.
OK. What-- yes.
PROFESSOR JIM ERICSON: Thank you. I was hoping somebody would ask that question. The question was, if you didn't hear, why do I have two equal signs? It's like I'm doing this, right? Anybody have a se--%uFFFD why don't I just use an equal sign? I want to know if something's equal to something. Yeah.
PROFESSOR JIM ERICSON: Absolutely. The equal sign is going to bind-- Nice catch. John, this is so much fun, throwing candy. I've got to say, we've got to do this more often-- Right. Let me, let me get to the point. What does an equal sign do? It is an assignment. It says, take this thing on the left and use it as a name to bind to the value on the right. It's not what I want here. Having already chosen to use equal as an assignment, I need something else to do comparison. And that's why I use double equals. Those two equal signs are saying, is this thing equal to, in value, the thing on the other side?
OK. Now, having done that, again I want to stress this idea and I'm going to write it out one more time, that there's a particular format here. So we have if, and that is followed by, I'm going to use angle braces here just to indicates something goes in here, some test followed by a colon. That is followed by a block of instructions. And we have an ELSE, followed by a colon in some other block of instructions. And I want you to get used to this, that colon is important, it identifies the start, and then the set of indented things identify all the things at the same level, and when we reset back to the previous level, that's when we go back to where we were.
OK. Now, that's a nice simple little test, let's look at a couple of other examples to get a sense of what this will do. OK, let me comment this out, and let's look at this next little piece of code. All right. I'm binding a z to be some value, and then I'm going to run this. Well, let's just run it and see what it does.
Nothing. OK, so why? Well, let's look at it. I'm doing a test there to say, if the string x is less than the value of b, and x does not appear before b as strings, then I was going to do, oh, a couple of things, because they're at the same block level. Given that that wasn't true, it did nothing.
Now, wait a minute, you say, where's the ELSE clause? And the answer is, I don't need one. All right, if this is purely a test of, if this is true do this otherwise I don't care, I don't need the ELSE clause in there to identify it. All right?
Second thing I want to look at is, suppose I compare that the one below it. Oops, that I don't want to do. Comment that out, and let's uncomment this. Yeah, I've still got a binding for z and I'm giving it the same test, but notice now I've got the two same commands but they have different indentation. In this case, in fact I do get a different behavior.
Why? Because that block identifies a set of things that I'm going to do if the test is true. If the test was not true, notice that that last command for print Mon is now back at the same level as the IF, so what this says is the IF does the test, having done the test, it decides I'm not going to do anything in the block below it, I'm going to skip down therefore to the next instruction at the same level as the IF, which gets me to the second print statement.
OK. So now we're seeing some of these variations, let's see what else can we do here. So let me just to try something a little more interesting, and then we'll get to writing some simple programs. So I'm going to comment those out, and let's go down to this piece of code, and uncomment it. Ooh yes, that was brilliant. Let's try this again. And uncomment that, and uncomment it again. Right, so here's a little piece of code that's going to print out the smallest value of three. And notice what this is showing is that the IFs can be nested. All right, it's so if I looked at it, it's going to say that IF x is y-- sorry, IF x is less than y, THEN check to see IF x is less than z, and if that's true, print out x is the smallest. And notice the structure of it, if it's not true I'm going to go to that next ELSE, and print out that z is smallest. If the first test wasn't true, I'm going to skip that whole block and just go down and print out that y was smallest. So notice the nesting, I can flow my way through how those tests are actually going to take place.
All right, so let's run this and see what happens. Great. y is smallest. OK. Is that code correct? Is that a tentative hand back there? Yeah.
STUDENT: Let me compare y to [INAUDIBLE]
PROFESSOR JIM ERICSON: Yeah, it's not doing all of the comparisons. All right, and let's just check this out, because I want to make a point of this, let's go back and do the following. Let's take y, change it to thirteen, let's run it, hmm.
So what did I miss here? Two important points. First one, when I write a piece of code, especially code that has branches in it, when I design test cases for that piece of code, I should try and have a specific test case for each possible path through the code. And by just doing that, I just spotted, there's a bug here. And the bug was in my thinking, I did not look for all of the tests. So the way I can fix that, is, let me comment that out, and keep doing that, comment that out, let's uncomment this, notice the structure here. I now have multiple tests. So actually, let's just run it and then we'll talk about what it does.
I run this, yeah, I have a syntax error, yes indeed, because I forgot to comment that one out, all right, and cue, we'll try it again. Ah-ha!
And let's quickly look at the structure of this. This now has, gee, a funny thing, it says IF x is less than y AND x is less than z, then do something. And then it has a strange thing called ELIF, which is simply short for else/if in a second test. So the way to think about this in terms of flow is, it starts with that if and it says, check both of those things.
And that fact that both of those things is the fact that we're using Boolean combination here. It is to say, we can take any logical expressions, combine them together with AND, OR, or NOT to make a complex expression, and use the value of that expression as my test. And that's literally what I've done there, right, I've got x less than y, that's a test, it returns a Boolean, which by the way is the other type, at least I would include here, it has only two values, which are true and false, and what that code says, if x is less than y, AND, logically, whatever I put up there, x is less than z, then the combination is true, and therefore I'm going to do something.
So AND is if both arguments are true it's true, OR is if either argument it's true it's true, NOT is if the argument is not true it's true, and then the last piece, as I said is, I can now have a sequence of things I want to do. So if this is true do something else, otherwise test and see if this is true, do something else, as many as I like followed by the end. And ELSE, it says, here's what I want to do.
OK. Now. Having added this in, I have branching instructions. I have simple branching programs. These are still awfully simple. OK? And they're awfully simple because, all I can do now, is decide whether to execute some piece of code or another. Said a different way, in the case of the straight-line programs, how long would it take to run a program? Well, basically, however many instructions I have, because I've got to do each one in order. With simple branching, how long is it going to take to run a piece of code? Well at most, I'm going to execute each instruction once. All right? Because the IFs are saying, if it's true do this, otherwise skip on it.
Therefore, for simple branching programs, the length of time, the complexity the code, is what we would call constant. That is, it's at most the length of the actual number of instructions. It doesn't depend on the input. Real simple programs.
Let's take another simple example. Suppose I want to compute the average age of all the MIT faculty. There's about a thousand of us. However I do that, I know that should inherently take more time than it takes to compute the average age of all the EECS faculty. There's only 125 of us. And that should take more time than what it takes to compute the average of John's and my ages, instructors in 600, because there's only two of us.
All right, those pieces of code inherently involved something that does depend on the size of the input, or on the particular input. And that is a preface to an idea of computational complexity we're going to come back to. One of the things we want to help you do is identify the different classes of algorithms, what their costs are, and how you can map problems into the most efficient class to do the computation.
OK. Now. Think for a second about computing the average age of the faculty. You can already kind of see what I want to do. I, somehow if, I want to walk through some sequence of data structures, gathering up or doing the same thing, adding ages in until I get a total age and then divide by the number faculty.
How do I write a piece of code for that? Well, let's go back up to our original starting point of recipes. And I'm sure you don't remember, but one of the things I had in my recipe, is beat egg whites until stiff. OK. That until is an important word. It's actually defining a test. Let me rephrase it into garbled English that'll lead more naturally into what I want to do. While the egg whites are not stiff, beat them. That is a different kind of structure. It has a test in it, which is that while, while something is true, do something, but I want to keep doing it.
And so for that, we need to add one last thing. Which is iteration. Or loops. We're going to see variations of this, we're going to see a variation of it called recursion, a little later on, but for now we're just going to talk about how do we do iterations. And I want to show you an example of this, to lead to both the syntax, and to the semantics. And let me comment that out, and let's go to this one.
All right. What does this piece of code say? Not, what does it do, but what does it say. Well, the first three statements are just assignment statements. I'm binding x, y, and iters left to some values. And then notice the structure, I got a keyword WHILE, there's that color identifying it, and in parentheses I have a test. I'm expecting the value that test to be a Boolean. Followed by a colon. The colon's identifying a block of code. And what this is saying is, gee. Check to see if the variable iters left has a value greater than zero. If it does, then execute each of the instructions in that block.
So I'm going to have an assignment of y, I'm going to have an assignment of iters left, I've got a comment that I had borrowed in order to do some debugging, and then what do I want it to do? I want it to go back around to the test. Once again, say, is that test true? If it is true, execute the sequence of instructions. So in fact we can block this out and see what it does. If I make a little chart here, I've got x, y, and iters left. x starts off as I think I set it up as, here I can't even read it, is x is three, y is zero, iters left is three. I can hand-simulate it. It says, is the value of iters left greater than zero? Yes it is. So, execute those two instructions. It says, take value of y and value of x, add them together, and create that as the new value of y. All right. That's the assigned statement. It says, take iters left, subtract one from it, and bind that as the new value of iters left.
Having reached the end of the block, go back up and check the test. Is iters left greater than zero? Yes it is. So, evaluate the same sequence of instructions again. y plus x is six, that's my new value of y, two minus one is one, that's my new value of iters left, go back up. Is iters left greater than zero? Yes it is. So once more, thank God I didn't take 47 as an example, x plus y, subtract one from iters left, go back up to the test. Is iters left's value greater than zero? No, it is not. Therefore, skip the rest of that block of code and go to the next instruction, which is, ah, print out y.
In fact, if we test this, son of a gun. Got a simple square procedure,. Right, It's just squaring an integer, is what it's doing.
But notice the structure. Now I have the ability to create a loop, that is, to reuse the same pieces of code over and over again as I go around. And this adds, now, a lot of power to the kinds of code I can write.
Notice some other things I want to highlight on this. All right? The first one is, that test has to involve-- shouldn't have to, but almost always is going to involve-- the value of some variable. What if I don't change the value of that variable inside of the code? Or, another way of saying it is, what if I did this?
Comment it up. What happens if I run this sucker?
PROFESSOR JIM ERICSON: Yeah. It'll go forever. Absolutely, right? It's going to loop into an infinite loop-- I think I can hit this close, ah, no I can't, boy what a terrible aim-- All right, what has [UNINTELLIGIBLE PHRASE] try again, the point I'm trying to make here-- thank God we're at the end of this lecture, my tongue is falling apart-- the point I'm trying to make is, that test needs to involve some loop variable that is changing. Otherwise the test is always going to be true, we're going to go off here, but this would loop forever if I did that.
All right. Second question: or maybe a better way of saying this, and the general format you're likely to see here is, a test involving a variable name, which must be initialized outside of the loop, and which interior to the loop gets changed, so that the test is going to change. Here's the second question. What value of inputs, what values of x will this run correctly for? Probably should be integers, right? Otherwise, this is going to be doing something strange, but all integers?
All right, suppose I do this. It's my last example. Yeah, how about that, right? We knew this was trying to do squaring, so intellectually we know we can square -4, it ought to be 16, but what happens here? Double fudge knuckle. All right? It's going to run through the loop, accumulating the answers, but because I'm subtracting, it's just going to keep making x more and more negative as it goes along, again it's off into an infinite loop. Which is a way of reminding you that I need to think as I write the code about what are my expectations from the input, and how might I enforce them. In this case, I probably want to make sure I use absolute value of x before I carry it on. Yes ma'am.
PROFESSOR JIM ERICSON: You're absolutely right, because I bind iters left to, um, yeah. Yes. Thank you. Boy, two candy for you. You caught me making an error. Yes. The point is, it's not going to work, and she caught both of them, impressive, it's not going to work because iters left is already negative, it's just going to skip the whole loop, and I'm in trouble, so thank you for catching that.
All right. I was going to do one more example, but I've run you up to the end of the time. I'll leave the example on the handout, it shows you another version that we'll come back to next time. The key thing to notice is, I now have the ability to create iterations, which extends well I can do. And we'll see you next time.