Puzzle 1: You Will All Conform

Flash and JavaScript are required for this feature.

Download the video from Internet Archive.

SRINI DEVADAS: Morning, everyone. Welcome to 6.S095. Thanks for registering for this class. I hope you like it.

My name is Srini Devadas. I'm a professor of computer science at MIT, obviously, and I've been teaching at MIT for 30 years. So I've been at it for a while.

The reason I'm teaching this IAP class is because I want to try out this way of teaching programming that starts with the recreational world of mathematical puzzles, algorithmic puzzles, and I want to connect it up to writing programs. And I have taught this material before in 6.009 that I think some of you are registered for, but I wanted to teach it in a more interactive, informal setting.

And I should mention that I wrote a book on this, which is a book that's recommended, though not required, for 6.009. And so you might see this in the MIT Press Bookstore and other places. And most of the material I'm going to cover here is from the book.

But I don't want you to read the book because I want you to listen to my explanations for the puzzles and try and think of the solutions yourself and then obviously do some amount of coding not during lecture but offline. And I'll show you some solutions to the puzzles, and there will be some exercises.

So I don't want to spend a whole lot of time on logistics. This is an IAP class, as I mentioned. I'd like this to be informal and relaxed. So we meet every day for two weeks. We have the holiday coming up on Monday, so we'll have nine total lectures roughly for an hour.

It might spill over, but hopefully, you'll be OK with it because you don't have another class to go to at noon. And if you do, well, let me know, and I'll give you that five minutes of overflow personally at a time of your choosing. But hopefully, I'll keep on time.

And in terms of work, this is a pass/fail. It's a for-credit class. I know some of you may be listeners, but some of you are registered for it. So I'll have to assign you a grade, so there will be some exercises. And I'm going to make it very simple.

There'll be exercises up on the website. They'll be due two days from when I put them up. And you'll have options in terms of whether to do a really simple exercise that's probably going to take about five minutes or do something harder. And for each of the puzzles, there are two or three exercises, and you get to do all of them if you want. You can choose to do one or the other.

I'm going to have office hours every day from 1:00 to 2:00. I don't plan on putting solutions up to the exercises. The solutions to the puzzles themselves, they'll be shown mostly in lecture and put up on the Stellar website, but the solutions to the exercises, I'm not going to put up. But if you ever get stuck, well, that's what office hours are for. And if you can't solve the exercises, I'll help to solve them.

So with that, without further ado, let's dive in and talk about puzzles and solving puzzles using algorithms and then programs. I'd really like this to be interactive, so please ask questions and suggest solutions that I haven't thought of. That'd be great. In my second edition, whenever that is, you'll get credit for that.

So the first puzzle, I tried to have, I don't know, funny titles for all of these puzzles-- intriguing. And so the first puzzle is called "You Will All Conform." This is really a warm-up puzzle. As you'll see-- this is true for any class-- things are going to get more complicated as we go along. So we'll probably do 10 or 12 puzzles in the next two weeks.

And by the end, you're going to see things like memoization and dynamic programming and things like that towards the end of next week. And if you don't know what those mean, well, that's great because hopefully, you will know by the end of the two weeks. And a lot of these things are algorithmic insights, are insights that will help you in Course 6 classes, for sure, and perhaps even if you follow a career outside of Course 6.

So this particular puzzle is, as I said, a fairly simple puzzle, at least to describe. And the setting is as follows. So you're a gatekeeper at a baseball game. We'll just call it a baseball game because baseball players wear caps, and most people are watching games with caps on.

And so you're a gatekeeper, and there are a bunch of people standing in line. Let's just say each of them knows their number. And this is Python, so the number starts at 0 as opposed to 1. And there are a whole bunch of people standing in line waiting to get in, and they all have caps.

And some of them are wearing their caps forward, so with the-- I guess, what do you call it? What's the front of the cap called? The shade-- the thing that gives you shade up front. And so we just say-- let me try and draw this out.

So this person is wearing his cap like that, and this lady here is wearing her cap like this. So that's forwards, and that's backwards. So your job here is straightforward. You can only let all of these people into line-- sorry-- into the stadium who are in line if they all conform.

So they all have to wear their caps in a particular orientation, and you don't particularly care which one. And so they all have to be forwards, all, let's say, 13 of them. Or they all have to be backwards.

Now, all of them know their position on the line. So this person knows that his position is 0, this lady here knows that her position is 1, et cetera, et cetera. And so obviously, you'd like to minimize the amount of work that you do, and you could certainly choose-- let's say you want everyone to be forwards. You could say, hey, lady in position 1, flip your cap.

Oh, I should mention something that is important. So people have a different definition of forwards and backwards. It's sort of what's natural and what's unnatural to them. So we think that this person has his cap on forwards.

But for all you know, he thinks he's got his cap on backwards or vice versa. So you can't assume that the definition of "forwards" and "backwards" is identical for all of the people in line and really is the same as your definition. It seems kind of unreasonable. Hey, this is a puzzle, so let's not argue about that. We can argue about other things, like how to write Python programs properly.

So what you have to do now is you have to call out commands that correspond to saying, person in position 0, flip your cap. That's all you can say. You can just say, flip your cap. You can obviously see how these caps are oriented, and you can ask-- let's say that you had something like that. I'm just making this up as I go along, in terms of I'm not going to draw all the caps here.

But this is what it looks like. And so according to you, you see F, B, B, in terms of Forwards and Backwards. And let's say you decide that you want everyone to have their cap on forwards. Then you could say, person in position 1, flip your cap. Person in position 2, flip your cap. And then person in position 4, 7, et cetera.

And you want to make it easier on yourself because people do know their indices, their positions in line. And so to save yourself some trouble, you could say, people in positions 1 through 2-- and the implication is that it's inclusive-- flip your caps. And so that takes care of these two. And perhaps 8 here and 9 had their caps on backwards, and you could say, people in positions 7 through 9, flip your caps.

So in this particular example, assuming that things ended with 10 people in line, 0 through 9, you could get away with three commands. You could say, 1 through 2-- and the implication, as I said, is this is inclusive-- flip. And then you could say, 4, flip. And then you could say, 7 through 9, flip.

And if you did it the other way, you could also say, person at 0, flip, and so this would go backwards. Person at 3, flip. Obviously, you can't say, 0 through 3 because that would be wrong. And then you could say 5 through 6 here. So you would get three commands in that case, as well.

But if in fact, for argument's sake, let's just say that I'm going to just turn this into 10 and put an F down here. Then these three commands would still work if you wanted to focus on the B's, on the Backwards people, and make them make them go forwards. But if you wanted to make all of the forward people go backwards, you would need 1, 2, 3, 4, four commands. So in this particular case, you're better off doing that as opposed to doing four commands.

So first straightforward question is-- and I'm not talking about code yet-- algorithmically, just in terms of pseudocode-- and I'm happy with English-- how would you determine the minimum set of commands given a particular set of people? There's an arbitrary set of people.

You want to make sure that you have the absolute minimum set of commands, and you're going to have to use these intervals. So you definitely want to look for all the contiguous intervals because obviously, if you say 1 followed and then that's a separate command, then you say 2 and that's a separate command, you end up having more commands than necessary.

So anyone want to tell me I guess roughly, in terms of pseudocode, how this would work? Yeah, go ahead.

AUDIENCE: Well, you could go down the line and look at the groups. So start with the 0. You've got to write 0. Group them. And then whatever the second one is-- so whatever the second group is, if it's F or B, those are the ones that need to be flipped because that's going to be the minimum number of commands. And then so from what you pass through, whenever you get to those, you just tell them to flip.

SRINI DEVADAS: Sounds good. Do you have anything to add to that?

AUDIENCE: No. I was going to say, you find the backwards interval or forward interval. And whichever is the lower, then--

SRINI DEVADAS: Lower. That's good. Good. Excellent. And so both of you had it right. So let me explain what these two gentlemen-- what was your name?

AUDIENCE: Ganatra.

SRINI DEVADAS: Ganatra? Yours?

AUDIENCE: Fadi.

SRINI DEVADAS: Fadi?

AUDIENCE: Yeah.

SRINI DEVADAS: So Ganatra and Fadi said-- so basically, this is, as I said, the natural algorithm that you would use. The natural algorithm would be that you walk through this list, and you start computing what I would call-- you called it "groups," Ganatra. But I'd call them "intervals," and an interval is something that is unbroken.

It's contiguous. It's a contiguous set of people who all have their caps in the same orientation, be it forward or backwards. And what you would do in this case is you would compute the-- you would say, I want to look at the forward intervals. And the forward intervals would be 0, 0 because that's essentially what you have in here.

And going through that list, you would also say, I'm going to be computing the backward intervals. And in this case, you discover 1, 2 as the backwards interval. So I'm going to write them out like that. And in general, in the mathematical notation, when you put square brackets, these are closed intervals. So 0 is included.

And we're going to see a puzzle where you might see something a little bit different, so I wanted to point that out. So 1, 2 are both inside this interval. And these two you would generate as you would go through the list, and then you would generate 3, 3 over here.

And then you would generate 4-- yeah, that's right, just 4-- I'm sorry, 4, 4 over here, if you want to call it an interval. You could represent this differently. There's nothing that stopping you in Python to have these slightly more complicated representations where you just have 0 here as a number. And if it's just a number, then it represents an interval of length 1, but let's just be uniform about this.

Usually, when you have special cases and if you do things heterogeneously, then you have more code to write. It might get more efficient, but there's usually more code to write. So you do 4, 4 here. And then you'd go up, and then you'd have 5 comma 6. And then you would do 7 comma-- I'm sorry, 7 comma 9.

So this is the first time you actually have something interesting, in the sense that the interval has a representation that only has two numbers in it, whereas you actually have three people in that interval. And obviously, this could be arbitrarily large in the context of people getting into a baseball game-- and so 7 through 9 and then finally here 10 through 10.

And so now you go ahead, and you count and you realize that there's four intervals here and three intervals here. And then you say, I'm going to go ahead and go with the backwards and asking the people who are-- well, according to you-- backwards to flip their caps so we're all good.

So I'm going to show you code that implements exactly this algorithm. And while you see the code, I want you to think about this harder question. This code is going to do exactly what we describe. It's going to go compute all of these things, and as you can imagine, he's going to make a pass through this entire Python list, this group of people, this queue of people, and is going to do this computation.

Then it's going to say, 4 is greater than 3. And then it's going to go through and start calling out commands for each of the backward intervals. So in some sense, it's going to make two passes over the list.

So the first pass is to get all of the intervals together. Then there's a check. And then the second pass is to take those backward intervals, in this case, and call out those commands. So that's what I mean by two passes. Yeah, please.

AUDIENCE: You don't necessarily need to count them, the 4 and 3 because whichever orientation interval comes second is always going--

SRINI DEVADAS: Brilliant. Brilliant. So I was going to say-- if you didn't understand what Ganatra said, wonderful because then you can think about the question I'm going to ask and ignore what he said. And then we'll get to, I guess, the more efficient code.

But what I was going to ask was, is there a way that I'm looking at this, and I'm just going-- and I'm going to just start calling out by-- the moment I see that there's an interval, the moment I see-- this is an interval. And I'm going to make a call with respect to whether I'm going to call out a command or not.

And I'm going to do this in one pass through this array. I want to do this in one pass through the array. I want to look at this, and I say, I see the interval 0, 0. Is that going to be something that I need to worry about in terms of that person having to flip his cap?

And then I see B, B. And I say, oh, the interval is 1 comma 2. Is that something I'm going to have to deal with in terms of a command? And so someone else other than Ganatra, tell me if there's a one-pass algorithm. And explain to me why you can do this in one pass through the array.

And just in terms of code, I'll preview this. The first algorithm needs-- according to my coding, which, isn't great-- 26 lines of code. The second one needs eight lines of code. So yeah, go ahead.

AUDIENCE: So after you see the first interval, you know that the second interval at the very least is going to have the same number of commands as the first one. So you might as well always go with the second one. So once you've identified what type the first interval is, you iterate through the list seeing-- and once you identify the first and second intervals, you iterate through every type of the second interval and do a command for that.

SRINI DEVADAS: That's absolutely right. What was your name?

AUDIENCE: Kevin.

SRINI DEVADAS: Kevin? So you had it right. And Kevin has it right, as well. So the observation here is actually-- it was said a little bit differently by Kevin, but I'm going to say it a little bit differently. The observation is simply that if you look at the very first orientation, the very first orientation is something which, at best, is going to be a tie.

So if you had it ending with 10 people, 0 through 9, then the number of forward intervals is the same as the number of backwards intervals. And if you went here, obviously, the number of forward intervals is greater than the number of backwards intervals. So you really only have to look at the first person in line not to determine the intervals because when you see the first person in line, you have no idea what the intervals are that are coming after.

You have to generate those intervals, so you have to make your pass through the array. But the first person in line gives it away in terms of what the final result is going to be with respect to your decision as to what set of commands you're going to call up. And so because this is an F, you basically say, I'm going to go, and I'm going to make the people with the B's flip their caps.

And that's a small observation in terms of its insight. But obviously, it's potent, in the sense that it's going to give you a one-pass algorithm versus a two-pass algorithm. And as I mentioned, it's going to give you a substantial reduction in the amount of code that you write.

So I'm going to show you a bunch of code now. So this is usually how this is going to go, by the way, this entire class. And this is kind of my way of teaching, at least this type of material that's algorithmic and has data structures and so on. I like explaining to people what they're going to see in the code without having to deal with Python syntax and worrying about whether it's while loops or for loops and things like that and tuples or arrays and things like that.

So let's get into that. I don't need this. Ah, good.

So the naive algorithm first-- so what you see here is an input. So "caps" is simply an input Python list that has, as you can see from our examples, the F's and the B's. And so F is Forwards, and B is Backwards. And there are a couple of different Python lists so you can run them with different things. And all of this hopefully is not at all surprising to you because we went through all of this in terms of the pseudocode.

So the first initialization-- and I can just highlight that to make it easy. So that's initialization. And this part here-- the comment sort of gives it away-- is you're making a pass through the array, computing the intervals. And so how do you actually get the intervals? And there's one line of code that does that.

And oh, by the way, if there are any questions about Python syntax, just don't feel shy. Just tell me. Ask me a question, and I'm happy to explain it.

I don't want to explain things that you all know, obviously. But if any one of you doesn't know any particular thing, please don't feel shy. Syntax is something that sometimes you even forget. I tend to forget things.

So this thing here is essentially something that decides on what the intervals are. And the insight here is simple. You know that this interval ended when you see something here that is different from what the previous one is. So that's when you know. So you generate the interval when you go and find something that is different from the current location that you're pointing to.

So that's really what's going on out here. In this line, you say, you're setting a "start" here. "Start" is your counter, and remember, "start" is getting set again here. Initially, it was set to 0. And if you ever get to the point where you're looking at "i," it's the next thing you're looking at, and if "caps start," which is the start of the interval, is different from "i," it meant that the interval that began with "start" ended.

And so you essentially say, well, that needs to end at i minus 1 because obviously, "caps start" is different from "caps size." So this is the end of the interval right here. And this particular thing, our interval, is something that includes both these two numbers, so it's not exactly that.

But it also includes-- where did my chalk go? It also includes whether this is an F or not. So each of these things has-- I'm not going to write it out for all of those things-- but has the orientation inside of it.

So that's what you have out here. That's why we have three of these in this tuple. This is a tuple because it's got round brackets around it. I could have made it a list if I wanted. And so that's essentially what we have here.

And now, here's what happens. In this particular piece of code, when you get to the end and you fall off the end-- so if you just end, then this code essentially skips over and doesn't actually put in the last interval because you don't see something. When you come up here and you're off the end of the array, you fall off the edge of the array, you don't see a B that is different from F.

So if you just wrote that code-- so it's something that I'd say many people would have a quote, "bug," where they wouldn't add this part of the code here because they didn't realize this particular point that I made. And so that last interval doesn't get added in. And so you have a bit more code here that decides on the last interval.

And then you count, and then you go ahead and call the commands. So this all looks good? Yeah, we're good with this?

Is there a way that I could somehow eliminate these four lines of code which are a little bit annoying and just do things in the array? How could I, with a little trick, eliminate those four lines of code? I might need a little bit more in, quote, "pre-processing," but how would I eliminate that?

Someone else? I'll get to you. Yeah. Go ahead.

AUDIENCE: One way is you can check thoroughly you've reached the end of the-- the forward is equal to the backwards.

SRINI DEVADAS: You could have an extra check. What's your name?

AUDIENCE: Kanishka.

SRINI DEVADAS: Kanishka? So Kanishka says that before you reach the end of the array or when you reach the end of the array, you go ahead and do a check that you've reached the end and generate the interval inside of it, which is kind of like moving this code inside of it.

So that's reasonable. I'm not sure that's going to be as good as the way that I want and which I coded. Think about pre-processing. Think about taking the array-- yeah?

AUDIENCE: I guess you could add a wrong interval at the end.

SRINI DEVADAS: You could add on a wrong interval. What was your name?

AUDIENCE: Kevin.

SRINI DEVADAS: Kevin, too? Just how many Kevins do we have here? So you could do this. So let me show you something that's a little bit more-- so that was 26 lines of code.

And so this says-- this is the line that's interesting. So I had F and B, and so I don't want to add F or B there. But what is convenient to do, as it turns out, is to just have something that essentially says, there's an ending point. So you have an explicit end to the array that is actually not something that would crash if you tried to access it.

You had n people in line, and now you have effectively n plus 1 people in line. But that n plus one person is a dummy, or you can call them "end." And then if you do that, then that's it. The previous code works. You can just remove those four lines, and it'll all just work because your F or your B is not equal to "end."

So whatever you had at the end-- I'm sorry. I'm overloading terms here. Whatever you had at the last element-- whether it was a B because it ended here or F-- when you equate that to "end," you're going to get an inequality. And so that last interval gets generated-- so small optimization.

Tony Hoare was a very famous computer scientist-- said that "inside every program, there's a smaller program waiting to get out." So this is kind of one of the themes that I'd like to harp on in this class, which is it's nice to write compact code. Usually, if you can write compact code, there are fewer bugs in it.

And you don't want to go overboard. You don't want too many subtleties, but it's nice to write compact code. And so this is a little bit more compact.

And then finally, here's the one-pass algorithm. So the whole thing, the "pleaseConformonepass," is the smarter algorithm that essentially says, look, if I just look at the very first thing, I'm going to be able to skip that and then move on. So this is fairly complicated.

You know the algorithm. Trying to map that to this code is non-trivial, so we'll spend a minute or two on it. But here's how this works. And by the way, just to make sure-- so I've repeated things here.

So the optimized algorithm for that particular example produced these first three commands. And then the one-pass algorithm produced exactly the same commands. So this is good to do. It's always nice to have a couple of different ways of solving a problem because then you can verify things.

Anyway, so this piece of code is essentially something that does a similar trick to what we had before, in terms of adding to the original list, adding an element. But we're not using "end" here. We're actually saying, what we want to do because we're going to go ahead and skip this, anyway-- we know we're going to skip the F.

So whatever that is, we could just sort of add up here. And so that makes it easy. You don't have to worry about what the actual characters are and cooking up this "end," which is different from the F or the B because hey, it's possible that people might use whatever they want to represent a forward cap or a backward cap.

So this is very clean. You go ahead and put exactly that over here. You know you're going to skip that interval because you're going to skip the first interval, effectively, because that is what the algorithm allows you to do. And then this part here is doing what we had before.

We're not even generating the intervals. We're just directly making the commands. So there are no tuples in terms of the tuples and the appending of the intervals data structure that we had. It's all gone. I'm just going to go ahead and fire off these commands. And those commands are written in kind of a funny way because I'm going to go ahead and if I wanted to print these things out, you'd have to do this in this funny way.

If you're willing to wait and collect up the intervals, you can certainly do that. But this is absolutely the most compact code that I could think of, and I'm printing a command in two different parts. I'm printing the first part of the command that says, people in positions. And that's the start of the interval "i," and I'm just using a Python construct.

This "end" here is something that the print command can recognize. And this simply says, don't give me a new line. I want to print it exactly. As you saw from when I ran the code, the printing was exactly the same as the original algorithm.

And so this "end" simply says, don't print a new line at the end of people in positions, whatever this number is. Call it 7. And you don't print the new line.

And then you have a space here because you didn't print a new line or a space. And you go through i minus 1, which is exactly the same as we had before. You only discover that the end of an interval after you see the person that comes after the interval. So you have to go back to i minus 1.

So this makes sense? Any questions about this code? Yeah, go ahead.

AUDIENCE: So the first line, like you said, if we're going to move the first--

SRINI DEVADAS: Not move, duplicate--

AUDIENCE: Duplicate the first--

SRINI DEVADAS: Yeah. So the plus there is a concatenation, and you have two lists. "Caps" is a list. And when you use a plus operator in Python, you can only add things that are of the same type.

And so "caps" is a list, and "cap 0" is an element of the list. And so you need to make it a list in order to use the plus operator. There are also other things you could do using a pen, for example. So you could take that line, and you could do "caps start" at "pen" "cap 0." And you wouldn't need all of the square brackets, but that's just neither here nor there. But I'm obviously not answering your question, so go ahead. Yeah.

AUDIENCE: So I was going to say that we skip the first interval. And then we flip all the second-type orientation.

SRINI DEVADAS: That's right.

AUDIENCE: But here, we're focusing on the first element. Why--

SRINI DEVADAS: Ah, but that's OK because remember, the if statement is going to skip this one, too. So if I had two F's in the beginning-- I think your question is, what would happen if I had two F's or three F's in the beginning? So I'm going to go ahead, and first thing I'm going to do is I'm going to put an F at the end here, and then maybe there's a bunch of stuff.

But then the nice thing is that I'll skip this F, too, because the not equal to is not going to fire. The next line, the "caps i not equal to i minus 1," this is going to be equal to. So I'm going to go ahead and go to the next iteration of the loop.

That makes sense? Did people understand Fadi's question? It was a good question. So the question was, you skipped the first one.

But what is making you skip this one because you want to skip the first interval? And so that if statement is making you do that. Good.

So as you can see, even a fairly straightforward puzzle-- this is the simplest puzzle we're going to do here is the first one. And there are nuances associated with how you solve it but also how you code it. And this is kind of-- like I said, this was my eureka moment a few years ago, where I said, I think the way you want to teach programming, at least some aspects of programming, is in this fashion.

Anyway, good. So we're done with this. Any questions about this puzzle? All right. Good.

Free Downloads

Video


Caption

  • English-US (SRT)