If you want to learn about computer science and the art
of programming, this course is where to start. CS50 is considered by many to be one of the
best computer science courses in the world. This is a Harvard University course
taught by Dr. David Malan and we are proud to bring it to
the freeCodeCamp YouTube channel. Throughout a series of lectures, Dr. Malan will teach you
how to think algorithmically and solve problems efficiently. And make sure to check the description for a lot of
extra resources that go along with the course. [MUSIC PLAYING] DAVID MALAN: All right, this is CS50,
Harvard University's introduction to the intellectual
enterprises of computer science and the art of programming, back here
on campus in beautiful Sanders Theatre for the first time in quite a while. So welcome to the class. My name is David-- OK. [CHEERING AND APPLAUSE] So my name is David Malan. And I took this class myself
some time ago, but almost didn't. It was sophomore fall and I
was sitting in on the class. And I was a little curious
but, eh, it didn't really feel like the field for me. I was definitely a computer
person, but computer science felt like something altogether. And I only got up the
nerve to take the class, ultimately, because the professor
at the time, Brian Kernighan, allowed me to take the
class pass/fail, initially. And that is what made
all the difference. I quickly found that
computer science is not just about programming and working
in isolation on your computer. It's really about problem
solving more generally. And there was something
about homework, frankly, that was, like, actually fun for perhaps
the first time in, what, 19 years. And there was something
about this ability that I discovered, along
with all of my classmates, to actually create something and bring
a computer to life to solve a problem, and sort of bring to bear something
that I'd been using every day but didn't really know how to harness,
that's been gratifying ever since, and definitely challenging
and frustrating. Like, to this day,
all these years later, you're going to run up against
mistakes, otherwise known as bugs, in programming, that
just drive you nuts. And you feel like you've hit a wall. But the trick really is
to give it enough time, to take a step back, take
a break when you need to. And there's nothing better, I daresay,
than that sense of gratification and pride, really,
when you get something to work, and in a class like
this, present, ultimately, at term's end, something like
your very own final project. Now, this isn't to say that
I took to it 100% perfectly. In fact, just this past week, I looked
in my old CS50 binder, which I still have from some 25 years
ago, and took a photo of what was apparently the very first
program that I wrote and submitted, and quickly received minus 2 points on. But this is a program that we'll
soon see in the coming days that does something quite simply like
print "Hello, CS50," in this case, to the screen. And to be fair, I
technically hadn't really followed the directions, which is
why I lost those couple of points. But if you just look at this, especially
if you've never programmed before, you might have heard
about programming language but you've never typed
something like this out, undoubtedly it's going to look cryptic. But unlike human
languages, frankly, which were a lot more sophisticated, a
lot more vocabulary, a lot more grammatical rules, programming, once
you start to wrap your mind around what it is and how it works and what these
various languages are, it's so easy, you'll see, after a few
months of a class like this, to start teaching
yourself, subsequently, other languages, as they may
come, in the coming years as well. So what ultimately matters
in this particular course is not so much where you end
up relative to your classmates but where you end up relative
to yourself when you began. And indeed, you'll begin today. And the only experience that matters
ultimately in this class is your own. And so, consider where you are today. Consider, perhaps, just how
cryptic something like that looked a few seconds ago. And take comfort in knowing just
some months from now all of that will be within your own grasp. And if you're thinking that, OK, surely
the person in front of me, to the left, to the right, behind me, knows more than
me, that's statistically not the case. 2/3 of CS50 students have never taken
a CS course before, which is to say, you're in very good company
throughout this whole term. So then, what is computer science? I claim that it's problem solving. And the upside of that is
that problem solving is something we sort of do all the time. But a computer science
class, learning to program, I think kind of cleans up your thoughts. It helps you learn how to think more
methodically, more carefully, more correctly, more precisely. Because, honestly, the
computer is not going to do what you want unless you are
correct and precise and methodical. And so, as such, there's
these fringe benefits of just learning to think like a
computer scientist and a programmer. And it doesn't take all
that much to start doing so. This, for instance, is perhaps the
simplest picture of computer science, sure, but really problem
solving in general. Problems are all about taking input,
like the problem you want to solve. You want to get the solution, a.k.a. output. And so, something interesting
has got to be happening in here, in here, when you're trying to
get from those inputs to outputs. Now, in the world of
computers specifically, we need to decide in advance how we
represent these inputs and outputs. We all just need to decide, whether
it's Macs or PCs or phones or something else, that we're all going to speak
some common language, irrespective of our human languages as well. And you may very well know that
computers tend to speak only what language, so to speak? Assembly, one, but binary,
two, might be your go-to. And binary, by implying two,
means that the world of computers has just two digits at
its disposal, 0 and 1. And indeed, we humans have many more
than that, certainly not just zeros and ones alone. But a computer indeed
only has zeros and ones. And yet, somehow they can do so much. They can crunch numbers in
Excel, send text messages, create images and artwork
and movies and more. And so, how do you get from something
as simple as a few zeros, a few ones, to all of the stuff
that we're doing today in our pockets and laptops and desktops? Well, it turns out that
we can start quite simply. If a computer were to want to do
something as simple as count, well, what could it do? Well, in our human world,
we might count doing this, like 1, 2, 3, 4, 5, using so-called
unitary notation, literally the digits on your fingers where one finger
represents one person in the room, if I'm, for instance, taking attendance. Now, we humans would typically
actually count 1, 2, 3, 4, 5, 6. And we'd go past just those five
digits and count much higher, using zeros through nines. But computers, somehow, only
have these zeros and ones. So if a computer only somehow
speaks binary, zeros and ones, how does it even count
past the number 1? Well, here are 3 zeros, of course. And if you translate this
number in binary, 000, to a more familiar number in decimal,
we would just call this zero. Enough said. If we were to represent, with
a computer, the number 1, it would actually be 001,
which, not surprisingly, is exactly the same as we
might do in our human world, but we might not bother writing
out the two zeros at the beginning. But a computer, now, if it
wants to count as high as two, it doesn't have the digit 2. And so it has to use a different
pattern of zeros and ones. And that happens to be 010. So this is not 10 with
a zero in front of it. It's indeed zero one zero
in the context of binary. And if we want to count
higher now than two, we're going to have to tweak these
zeros and ones further to get 3. And then if we want 4
or 5 or 6 or 7, we're just kind of toggling these
zeros and ones, a.k.a. bits, for binary digits that represent,
via these different patterns, different numbers that you
and I, as humans, know, of course, as the so-called
decimal system, 0 through 9, dec implying 10, 10 digits,
those zeros through nine. So why that particular pattern? And why these particular zeros and ones? Well, it turns out that
representing one thing or the other is just really simple for a computer. Why? At the end of the day, they're
powered by electricity. And it's a really simple thing to
just either store some electricity or don't store some electricity. Like, that's as simple as
the world can get, on or off. 1 or 0, so to speak. So, in fact, inside of a
computer, a phone, anything these days that's
electronic, pretty much, is some number of switches,
otherwise known as transistors. And they're tiny. You've got thousands, millions of them
in your Mac or PC or phone these days. And these are just tiny little switches
that can get turned on and off. And by turning those things
on and off in patterns, a computer can count from 0 on up
to 7, and even higher than that. And so these switches, really, you
can think of being as like switches like this. Let me just borrow one of
our little stage lights here. Here's a light bulb. It's currently off. And so, I could just think
of this as representing, in my laptop, a transistor,
a switch, representing 0. But if I allow some electricity
to flow, now I, in fact, have a 1. Well, how do I count higher than 1? I, of course, need another light bulb. So let me grab another one here. And if I put it in that same kind of
pattern, I don't want to just do this. That's sort of the old finger
counting way of unary, just 1, 2. I want to actually take
into account the pattern of these things being on and off. So if this was one a moment ago, what I
think I did earlier was I turned it off and let the next one over be on, a.k.a. 010. And let me get us a
third bit, if you will. And that feels like enough. Here is that same pattern now,
starting at the beginning with 3. So here is 000. Here is 001. Here is 010, a.k.a., in our
human world of decimal, 2. And then we could, of course,
keep counting further. This now would be 3 and dot dot dot. If this other bulb now goes
on, and that switch is turned and all three stay on--
this, again, was what number? AUDIENCE: Seven. DAVID MALAN: OK, so, seven. So it's just as simple,
relatively, as that, if you will. But how is it that these
patterns came to be? Well, these patterns actually
follow something very familiar. You and I don't really
think about it at this level anymore because we've probably been
doing math and numbers since grade school or whatnot. But if we consider something in
decimal, like the number 123, I immediately jump to that. This looks like 123 in decimal. But why? It's really just three symbols,
a 1, a 2 with a bit of curve, a 3 with a couple of curves, that
you and I now instinctively just assign meaning to. But if we do rewind a few years,
that is one hundred twenty-three because you're assigning meaning
to each of these columns. The 3 is in the so-called ones place. The 2 is in the so-called tens place. And the 1 is in the
so-called hundreds place. And then the math ensues
quickly in your head. This is technically 100 times 1, plus
10 times 2, plus 1 times 3, a.k.a. 100 plus 20 plus 3. And there we get the sort of
mathematical notion we know as 123. Well, nicely enough, in binary,
it's actually the same thing. It's just these columns mean
a little something different. If you use three digits in decimal,
and you have the ones place, the tens place, and the hundreds place,
well, why was that 1, 10, and 100? They're technically just powers of 10. So 10 to the 0, 10 to
the 1, 10 to the 2. Why 10? Decimal system, "dec" meaning 10. You have 8 and 10 digits, 0 through 9. In the binary system, if you're
going to use three digits, just change the bases if you're
using only zeros and ones. So now it's powers of 2, 2 to the
0, 2 to the 1, 2 to the 2, a.k.a. 1 and 2 and 4, respectively. And if you keep going, it's going
to be 8s column, 16s column, 32, 64, and so forth. So, why did we get these
patterns that we did? Here's your 000 because it's 4 times
0, 2 times 0, 1 times 0, obviously 0. This is why we got the
decimal number 1 in binary. This is why we got the number 2
in binary, because it's 4 times 0, plus 2 times 1, plus 1 times 0, and
now 3, and now 4, and now 5, and now 6, and now 7. And, of course, if you wanted to
count as high as 8, to be clear, what do you have to do? What does a computer need to
do to count even higher than 7? AUDIENCE: Add a bit. DAVID MALAN: Add a bit. Add another light bulb, another switch. And, indeed, computers
have standardized just how many zeros and ones,
or bits or switches, they throw at these kinds of problems. And, in fact, most computers would
typically use at least eight at a time. And even if you're only counting
as high as three or seven, you would still use eight and
have a whole bunch of zeros. But that's OK, because the
computers these days certainly have so many more, thousands,
millions of transistors and switches that that's quite OK. All right, so, with that said, if
we can now count as high as seven or, frankly, as high
as we want, that only seems to make computers
useful for things like Excel, like number crunching. But computers, of course,
let you send text messages, write documents, and so much more. So how would a computer represent
something like a letter, like the letter A of the English
alphabet, if, at the end of the day, all they have is switches? Any thoughts? Yeah. AUDIENCE: You can represent
letters in numbers. DAVID MALAN: OK, so we could
represent letters using numbers. OK, so what's a proposal? What number should represent what? AUDIENCE: Say if you were starting
at the beginning of the alphabet, you could say 1 is A, 2 is B, 3 is C. DAVID MALAN: Perfect. Yeah, we just all have to agree
somehow that one number is going to represent one letter. So 1 is A, 2 is B, 3 is
C, Z is 26, and so forth. Maybe we can even take into
account uppercase and lowercase. We just have to agree and sort of
write it down in some global standard. And humans, indeed, did just that. They didn't use 1, 2, 3. It turns out they started
a little higher up. Capital A has been
standardized as the number 65. And capital B has been
standardized as the number 66. And you can kind of imagine
how it goes up from there. And that's because whatever
you're representing, ultimately, can only be stored, at
the end of the day, as zeros and ones. And so, some humans in a room before,
decided that capital A shall be 65, or, really, this pattern of zeros
and ones inside of every computer in the world, 01000001. So if that pattern of zeros and
ones ever appears in a computer, it might be interpreted then as indeed
a capital letter A, eight of those bits at a time. But I worry, just to be clear, we
might have now created a problem. It might seem, if I play
this naively, that, OK, how do I now actually do
math with the number 65? If now Excel displays 65 is
an A, let alone Bs and Cs. So how might a computer
do as you've proposed, have this mapping from numbers to
letters, but still support numbers? It feels like we've given something up. Yeah? AUDIENCE: By having
a prefix for letters? DAVID MALAN: By having a prefix? AUDIENCE: You could have
prefixes and suffixes. DAVID MALAN: OK, so we could
perhaps have some kind of prefix, like some pattern of zeros and ones-- I like this-- that
indicates to the computer here comes another pattern
that represents a letter. Here comes another pattern that
represents a number or a letter. So, not bad. I like that. Other thoughts? How might a computer
distinguish these two? Yeah. AUDIENCE: Have a
different file format, so, like, odd text or just
check the graphic or-- DAVID MALAN: Indeed, and that's spot-on. Nothing wrong with what you suggested,
but the world generally does just that. The reason we have all of these
different file formats in the world, like JPEG and GIF and PNGs
and Word documents, .docx, and Excel files and so forth, is
because a bunch of humans got in a room and decided, well, in the context
of this type of file, or really, more specifically, in the
context of this type of program, Excel versus Photoshop versus
Google Docs or the like, we shall interpret any patterns of
zeros and ones as being maybe numbers for Excel, maybe letters in, like, a
text messaging program or Google Docs, or maybe even colors of the rainbow
in something like Photoshop and more. So it's context dependent. And we'll see, when we
ourselves start programming, you the programmer
will ultimately provide some hints to the computer that tells
the computer, interpret it as follows. So, similar in spirit to that, but
not quite a standardized with these prefixes. So this system here actually has a
name ASCII, the American Standard Code for Information Interchange. And indeed, it began here
in the US, and that's why it's actually a little
biased toward A's through Z's and a bit of punctuation as well. And that quickly became a problem. But if we start simply now,
in English, the mapping itself is fairly straightforward. So if A is 65, B it 66,
and dot dot dot, suppose that you received a text
message, an email, from a friend, and underneath the hood,
so to speak, if you kind of looked inside the computer, what you
technically received in this text or this email happened to
be the numbers 72, 73, 33, or, really, the underlying
pattern of zeros and ones. What might your friend have sent you
as a message, if it's 72, 73, 33? AUDIENCE: Hey. DAVID MALAN: Hey? Close. AUDIENCE: Hi. DAVID MALAN: Hi. It's, indeed, hi. Why? Well, apparently, according to this
little cheat sheet, H is 72, I is 73. It's not obvious from
this chart what the 33 is, but indeed, this
pattern represents "hi." And anyone want to guess,
or if you know, what 33 is? AUDIENCE: Exclamation point. DAVID MALAN: Exclamation point. And this is, frankly, not the
kind of thing most people know. But it's easily accessible by a
nice user-friendly chart like this. So this is an ASCII chart. When I said that we just need to
write down this mapping earlier, this is what people did. They wrote it down in
a book or in a chart. And, for instance, here is our
72 for H, here is our 73 for I, and here is our 33
for exclamation point. And computers, Macs, PCs,
iPhones, Android devices, just know this mapping
by heart, if you will. They've been designed to
understand those letters. So here, I might have received "hi." Technically, what I've received is
these patterns of zeros and ones. But it's important to note that when
you get these patterns of zeros and ones in any format, be it
email or text or a file, they do tend to come
in standard lengths, with a certain number of
zeros and ones altogether. And this happens to be 8 plus 8, plus 8. So just to get the message
"hi, exclamation point," you would have received at least,
it would seem, some 24 bits. But frankly, bits are so tiny,
literally and mathematically, that we don't tend to think or
talk, generally, in terms of bits. You're probably more
familiar with bytes. B-Y-T-E-S is a byte,
is a byte, is a byte. A byte is just 8 bits. And even those, frankly, aren't
that useful if we do out the math. How high can you count
if you have eight bits? Anyone know? Say it again? Higher than that. Unless you want to go
negative, that's fine. 256, technically 255. Long story short, if we actually got
into the weeds of all of these zeros and ones, and we figured out what
11111111 mathematically adds up to in decimal, it would
indeed be 255, or less if you want to represent
negative numbers as well. So this is useful because now we can
speak, not just in terms of bytes but, if the files are bigger,
kilobytes is thousands of bytes, megabytes is millions of bytes,
gigabytes is billions of bytes, terabytes are trillions
of bytes, and so forth. We have a vocabulary for these
increasingly large quantities of data. The problem is that, if you're using
ASCII and, therefore, eight bits or one byte per character, and
originally, only seven, you can only represent 255 characters. And that's actually 256 total
characters, including zero. And that's fine if you're using
literally English, in this case, plus a bunch of punctuation. But there's many human
languages in the world that need many more symbols
and, therefore, many more bits. So, thankfully, the world
decided that we'll indeed support not just the US
English keyboard, but all of the accented characters that
you might want for some languages. And heck, if we use enough
bits, zeros and ones, not only can we represent all
human languages in written form, as well as some emotions
along the way, we can capture the latter with
these things called emojis. And indeed, these are very
much in vogue these days. You probably send and/or receive
many of these things any given day. These are just characters, like
letters of an alphabet, patterns of zeros and ones that you're receiving,
that the world has also standardized. For instance, there
are certain emojis that are represented with
certain patterns of bits. And when you receive them, your
phone, your laptop, your desktop, displays them as such. And this newer standard
is called Unicode. So it's a superset of
what we called ASCII. And Unicode is just a mapping of many
more numbers to many more letters or characters, more
generally, that might use eight bits for
backwards compatibility with the old way of doing things with
ASCII, but they might also use 16 bits. And if you have 16
bits, you can actually represent more than
65,000 possible letters. And that's getting up there. And heck, Unicode might even use 32
bits to represent letters and numbers and punctuation symbols and emojis. And that would give you up
to 4 billion possibilities. And, I daresay, one of the reasons we
see so many emojis these days is we have so much room. I mean, we've got room for
billions more, literally. So, in fact, just as a
little bit of trivia, has anyone ever received this decimal
number, or if you prefer binary now, has anyone ever received this pattern
of zeros and ones on your phone, in a text or an email,
perhaps this past year? Well, if you actually look this up,
this esoteric sequence of zeros and ones happens to represent
face with medical mask. And notice that if you've got
an iPhone or an Android device, you might be seeing different things. In fact, this is the Android
version of this, most recently. This is the iOS version
of it, most recently. And there's bunches of other
interpretations by other companies as well. So Unicode, as a
consortium, if you will, has standardized the descriptions
of what these things are. But the companies themselves,
manufacturers out there, have generally interpreted
it as you see fit. And this can lead to some
human miscommunications. In fact, for like, literally,
embarrassingly, like a year or two, I started being in the habit of
using the emoji that kind of looks like this because I thought it was
like woo, happy face, or whatever. I didn't realize this
is the emoji for hug because whatever device I was using
sort of looks like this, not like this. And that's because of their
interpretation of the data. This has happened too when
what was a gun became a water pistol in some manufacturers' eyes. And so it's an interesting dichotomy
between what information we all want to represent and how we
choose, ultimately, to represent it. Questions, then, on these
representations of formats, be it numbers or letters, or soon more. Yeah? AUDIENCE: Why is decimal
popular for a computer if binary is the basis for everything? DAVID MALAN: Sorry,
why is what so popular? AUDIENCE: Why is the decimal popular
if binary is the fundamental-- DAVID MALAN: Yeah, so we'll come
back to this in a few weeks, in fact. There are other ways
to represent numbers. Binary is one. Decimal is another. Unary is another. And hexadecimal is yet a fourth that
uses 16 total digits, literally 0 through 9 plus A, B, C,
D, E, F. And somehow, you can similarly count
even higher with those. We'll see in a few weeks
why this is compelling. But hexadecimal, long story
short, uses four bits per digit. And so, four bits, if you have two
digits in hex, that gives you eight. And it's just a very
convenient unit of measure. And it's also human convention in
the world of files and other things. But we'll come back to that soon. Other questions? AUDIENCE: Do the lights on the
stage supposedly say that-- DAVID MALAN: Do the lights on the
stage supposedly say anything? Well, if we had thought in advance
to use maybe 64 light bulbs, that would seem to give us 8
total bytes on stage, 8 times 8, giving us just that. Maybe. Good question. Other questions on 0's and 1's? It's a little bright in here. No? Oh, yes? Where everyone's pointing
somewhere specific. There we go. Sorry. Very bright in this corner. AUDIENCE: I was just going
to ask about the 255 bits, like with the maximum characters. [INAUDIBLE] DAVID MALAN: Ah, sure, and we'll
come back to this, in some form, in the coming days too,
at a slower pace too, we have, with eight bits, two
possible values for the first and then two for the next, two
for the next, and so forth. So that's 2 times 2 times 2. That's 2 to the eighth
power total, which means you can have 256 total
possible patterns of zeros and ones. But as we'll see soon computer
scientists, programmers, software often starts counting at 0 by
convention and if you use one of those patterns, 00000000 to represent
the decimal number we know is zero, you only have 255 other patterns left
to count as high as therefore 255. That's all. Good question. All right, so what then might we
have besides these emojis and letters and numbers? Well, we of course have things
like colors and programs like Photoshop and pictures and photos. Well let me ask the question again. How might a computer, do you think,
knowing what you know now, represents something like a color? Like what are our options if all we've
got are zeros and ones and switches? Yeah? AUDIENCE: RGB DAVID MALAN: RGB. RGB indeed is this acronym that
represents some amount of red and some amount of green and
blue and indeed computers can represent colors by just doing that. Remembering, for instance, this dot. This yellow dot on the screen that
might be part of any of those emojis these days, well that's some amount
of red, some amount of green, some amount of blue. And if you sort of mix
those colors together, you can indeed get a very specific one. And we'll see you in
just a moment just that. So indeed earlier on, humans
only used seven bits total. And it was only once they decided,
well, let's add an eighth bit that they got extended ASCII and
that was initially in part a solution to the same problem of
not having enough room, if you will, in those patterns of zeros and ones
to represent all of the characters that you might want. But even that wasn't enough and that's
why we've now gone up to 16 and 32 and long past 7. So if we come back now to
this one particular color. RGB was proposed as a scheme,
but how might this work? Well, consider for instance this. If we do indeed decide as a group to
represent any color of the rainbow with some mixture of some red,
some green, and some blue, we have to decide how to represent
the amount of red and green and blue. Well, it turns out if all we have
are zeros and ones, ergo numbers, let's do just that. For instance, suppose a computer we're
using, these three numbers 72, 73, 33, no longer in the context of
an email or a text message, but now in the context of something
like Photoshop, a program for editing and creating graphical files,
maybe this first number could be interpreted as representing
some amount of red, green, and blue, respectively. And that's exactly what happens. You can think of the first digit as
red, second as green, third as blue. And so ultimately when you combine that
amount of red, that amount of green, that amount of blue, it turns out it's
going to resemble the shade of yellow. And indeed, you can come up
with a numbers between 0 and 255 for each of those colors to mix any
other color that you might want. And you can actually
see this in practice. Even though our screens,
admittedly, are getting really good on our phones and laptops such that you
barely see the dots, they are there. You might have heard
the term pixel before. Pixel's just a dot on
the screen and you've got thousands, millions of them these
days horizontally and vertically. If I take even this
emoji, which again happens to be one company's interpretation
of a face with medical mask and zoom in a bit, maybe
zoom in a bit more, you can actually start
to see these pixels. Things get pixelated
because what you're seeing is each of the individual dots
that compose this particular image. And apparently each of
these individual dots are probably using 24 bits, eight bits
for red, eight bits for green, eight bits for blue, in some pattern. This program or some other like
Photoshop is interpreting one pattern and it's white or yellow or
black or some brown in between. So if you look sort of awkwardly, but
up close to your phone or your laptop or maybe your TV, you can
see exactly this, too. All right, well, what
about things that we also watch every day on YouTube or the like? Things like videos. How would a computer,
knowing what we know now, represent something like a video? How might you represent a video
using only zeros and ones? Yeah? AUDIENCE: As we can see here,
they represent images, right? [INAUDIBLE] sounds of
the 0 and 1s as well. [INAUDIBLE] DAVID MALAN: Yeah, exactly. To summarize, what video really
adds is just some notion of time. It's not just one image, it's
not just one letter or a number, it's presumably some kind of
sequence because time is passing. So with a whole bunch of images,
maybe 24 maybe 30 per second, if you fly them by the
human's eyes, we can interpret them using our eyes
and brain that there is now movement and therefore video. Similarly with audio or music. If we just came up with some convention
for representing those same notes on a musical instrument, could we have
the computer synthesize them, too? And this might be
actually pretty familiar. Let me pull up a quick video here,
which happens to be an old school version of the same idea. You might remember from childhood. [MUSIC PLAYING] [CLICKING] So granted that particular
video is an actual video of a paper-based animation, but
indeed, that's really all you need, is some sequence of these images,
which themselves of course are just zeros and ones because they're
just this grid of these pixels or dots. Now something like musical notes like
these, those of you who are musicians might just naturally play
these on physical devices, but computers can certainly
represent those sounds, too. For instance, a popular
format for audio is called MIDI and MIDI
might just represent each note that you saw a moment ago
essentially as a sequence of numbers. But more generally, you might
think about music as having notes, for instance, A through G, maybe
some flats and some sharps, you might have the duration like how
long is the note being heard or played on a piano or some
other device, and then just the volume like how hard
does a human in the real world press down on that key and
therefore how loud is that sound? It would seem that just remembering
little details like that quantitatively we can then represent really all of
these otherwise analog human realities. So that then is really
a laundry list of ways that we can just represent information. Again, computers or digital have
all of these different formats, but at the end of the day and as
fancy as those devices in years are, it's just zeros and ones, tiny
little switches or light bulbs, if you will, represented in some
way and it's up to the software that you and I and others
write to use those zeros and ones in ways we want to get
the computers to do something more powerfully. Questions, then, on this representation
of information, which I daresay is ultimately what problem solving
is all about, taking in information and producing new via
some process in between. Any questions there? Yeah, in back. AUDIENCE: Yeah, so we talked about how
different file formats kind of allow you to interpret information. How does a file format like .mp4
discriminate between audio and video within itself as a value? DAVID MALAN: So a really good question. There are many other
file formats out there. You allude to MP4 for video
and more generally the use are these things called
codecs and containers. It's not quite as simple when
using larger files, for instance, in more modern formats that a
video is just a sequence of images, for instance. Why? If you stored that many images
for like a Hollywood movie, like 24 or 30 of them per second,
that's a huge number of images. And if you've ever taken
photos on your phone, you might know how many megabytes or
larger even individual photographs might be. So humans have developed over
the years a fancier software that uses much more math to represent
the same information more minimally just using somehow shorter
patterns of zeros and ones than are most simplistic
representation here. And they use what might
be called compression. If you've ever used a zip
file or something else, somehow your computer is
using fewer zeros and ones to represent the same
amount of information, ideally without losing any information. In the world of multimedia, which we'll
touch on a little bit in a few weeks, there are both lossy and
lossless formats out there. Lossless means you lose
no information whatsoever. But more commonly as you're alluding
to one is lossy compression, L-O-S-S-Y, where you're actually throwing
away some amount of quality. You're getting some amount
of pixelation that might not look perfect to the human, but heck
it's a lot cheaper and a lot easier to distribute. And in the world of multimedia,
you have containers like QuickTime and other MPEG containers
that can combine different formats of video, different
formats of audio in one file, but there, too, do
designers have discretion. So more in a few weeks, too. Other questions, then, on
information here as well? Yeah? AUDIENCE: So I know
computers used to be very big and taking up like a
whole room and stuff. Is the reason they've gotten
smaller because we can store this information piecemeal or what? DAVID MALAN: Exactly. I mean, back in the day you might
have heard of the expression a vacuum tube, which is like some
physically large device that might have only stored some 0 or 1. Yes, it is the miniaturization
of hardware these days that has allowed us to store as many
and many more zeros and ones much more closely together. And as we've built more
fancy machines that can sort of design this hardware
at an even smaller scale, we're just packing more and
more into these devices. But there, too, is a trade off. For instance, you might know by
using your phone or your laptop for quite a while, maybe on
your lap, starts to get warm. So there are these literal
physical side effects of this where now some
of our devices run hot. This is why like a data
center in the real world might need more air conditioning
than a typical place, because there are these
physical artifacts as well. In fact, if you'd like to see one of
the earliest computers from decades ago, across the river here in now Allston
in the new engineering building is the Harvard Mark 1 computer that
will give you a much better mental model of just that. Well if we come back now
to this first picture being computer science or
really problem solving, I daresay we have more
than enough ways now to represent information, input and
output, so long as we all just agree on something and thankfully all
of those before us have given us things like ASCII and Unicode. Not to mention MP4s, word
documents, and the like. But what's inside of this proverbial
black box into which these inputs are going in the outputs are coming? Well that's where we get this
term you might have heard, too. An algorithm, which is just step-by-step
instructions for solving some problem incarnated in the world
of computers by software. When you write software
aka programs, you are implementing one or more algorithms,
one or more sets of instructions for solving some problem, and maybe
you're using this language or that, but at the end of the day,
no matter the language you use the computer is going
to represent what you type using just zeros and ones. So what might be a
representative algorithm? Nowadays you might use
your phone quite a bit to make calls or send texts
or emails and therefore you have a whole bunch of
contacts in your address book. Nowadays, of course,
this is very digital, but whether on iOS or
Android or the like, you might have a whole
bunch of names, first name and/or last, as well as numbers
and emails and the like. You might be in the habit of like
scrolling through on your phone all of those names to find
the person you want to call. It's probably sorted alphabetically by
first name or last name, A through Z, or some other symbol. This is frankly quite the same as
we used to do back in my day, CS50, when we just used a physical book. In this physical book
might be a whole bunch of names alphabetically
sorted from left to right corresponding to a
whole bunch of numbers. So suppose that in this
old Harvard phone book we want to search for John Harvard. We might of course start
quite simply at the beginning here, looking at one page at a
time, and this is an algorithm. This is like literally step-by-step
looking for the solution to this problem. In that sense, if John
Harvard's in the phone book, is this algorithm page-by-page
correct, would you say? AUDIENCE: Yes. DAVID MALAN: Yes. Like if John Harvard's
in the phone book, obviously I'm eventually going to get to
him, so that's what we mean by correct. Is it efficient? Is it well designed, would you say? No. I mean this is going to take forever
even just to get to the Js or the Hs, depending how this thing's sorted. All right, well let
me go a little faster. I'll start like two pages at a time. 2, 4, 6, 8, 10, 12, and so forth. Sounds faster, is faster, is it correct? AUDIENCE: No. DAVID MALAN: OK, why is it not correct? Yeah? AUDIENCE: So if you're
starting on page 1, you're only going odd number of pages,
so if it's on an even number page, you'll miss it. DAVID MALAN: Exactly. If I start on an odd number of
pages and I'm going two at a time I might miss pages in between. And if I therefore conclude
when I get to the back of the book there was no John
Harvard, I might have just errored. This would be again one of these bugs. But if I try a little harder,
I feel like there's a solution. We don't have to completely
throw out this algorithm. I think we can probably go
roughly twice as fast still. But what should we do
instead to fix this? Yeah, in back. AUDIENCE: [INAUDIBLE] DAVID MALAN: Nice. So I think what many of us, most of us,
if we even use this technology any more these days, we might go roughly
to the middle of the phone book just to kind of get us started. And now I'm looking down, I'm looking
for J, assuming first name, J Harvard, and it looks like I'm in the M section. So just to be clear,
what should I do next? AUDIENCE: [INAUDIBLE] DAVID MALAN: OK, and
presumably it is John Harvard would be to the left of this. So here's an opportunity to
figuratively and literally tear this particular problem in half,
throw half of the problem away. It's actually pretty easy
if you just do it that way. The hard way is this way. But I've now just decreased the
size of this problem really in half. So if I started with 1,000 pages
of phone numbers and names, now I'm down to 500. And already we haven't
found John Harvard, but that's a big bite
out of this problem. I do think it's correct because if
J is to the left of M, of course, he's definitely not
going to be over there. I think if I repeat this again
dividing and conquering, if you will, here I might have gone a little too far. Now I'm in like the E section. So let me tear the problem in half
again, throw another 250 pages away, and again repeat, dividing
and dividing and conquering until finally, presumably, I end
up with just one page of a phone book on which John Harvard's
name either is or is not, but because of the algorithm
you proposed, step by step, I know that he's not in
anything I discarded. So traumatic is that
might have been made out to be, it's actually just harnessing
pretty good human intuition. Indeed, this is what
programming is all about, too. It's not about learning
a completely new world, but really just how to harness intuition
and ideas that you might already have and take naturally
but learning how to express them now more succinctly,
more precisely, using things called
programming languages. Why is an algorithm like that if I found
John Harvard better than, ultimately, just doing the first
one or even the second and maybe doubling back
to check those even pages? Well let's just look
at little charts here. Again, we don't have to get
into the nuances of numbers, but if we've got like a chart
here, xy plot, on the x-axis here I claim as the size of the problem. So measured in the numbers
of pages in the phone book. So the farther you go out here, the
more pages are in the phone book. And here we have time
to solve on the y-axis. So the higher you go
up, the more time it's going to be taking to solve
that particular problem. So let's just arbitrarily say that
the first algorithm, involving like n pages, might be
represented graphically like this. No matter the slope,
it's a straight line because there's presumably
a one to one relationship between numbers of pages and number
of seconds or number of page turns. Why? If the phone company adds
another page next year because some new people
move to town, that's going to require one
additional page for me. One to one. If, though, we use the second
algorithm, flawed though it was, unless we double back a little bit
to fix someone being in between, that's too going to be a
straight line, but it's going to be a different slope because
now there's a 2 to 1 or a 1 to 2 relationship because I'm
going to pages at a time. So if the phone company adds
another page or another two pages, that still only just one more step. You can see the difference
if I kind of draw this. If this is the phone book in
question, this number of pages, it might take this many
seconds on the yellow line to represent or to find
someone like John Harvard. But of course on the first
algorithm, the red line, it's literally going to
take twice as many steps. And what do the n here mean?
n is the go-to variable for computer scientist or programmer
just generically representing a number. So if the number of pages
in the phone book is n, the number of steps the second
algorithm would have taken would be in the worst case n over 2. Half as many because
you're going twice as fast. But the third algorithm, actually
if you recall your logarithms, looks a little something like this. There's a fundamentally
different relationship between the size of the problem and
the amount of time required to solve it that technically is
log-based, too, again, but it's really the
shape that's different. The implication there is that if,
for instance, Cambridge and Allston, two different towns here in
Massachusetts, merge next year and there's just one phone
book that's twice as big, no big deal for that
third and final algorithm. Why? You just tear the problem
one more time in half, taking one more byte,
that's it, not another 1,000 bytes just to get to the solution. Put another way, you
can walk it way, way, way out here to a much bigger
phone book and ultimately that green line is barely
going to have budged. So this then is just a way of
now formalizing and thinking about what the performance or
quality of these algorithms might be. Before we now make one more
formalization of the algorithm itself, any questions then on this notion of
efficiency or now performance of ideas? Yeah. AUDIENCE: How many phone
books have you got? DAVID MALAN: (LAUGHING) A lot
of phone books over the years and if you or your parents have any
more still somewhere we could definitely use them because they're hard to find. Other questions? But thanks. Other questions here, too? No. Oh, was that a murmur? Yes, over here. AUDIENCE: You could get Harry
Potter as a guest speaker. DAVID MALAN: Sorry, say again. AUDIENCE: You could get Harry
Potter as a guest speaker. DAVID MALAN: (LAUGHING) Oh, yeah. Hopefully. Then we'd have a little
something more to use here. So now if we want to formalize
further what it is we just did, we can go ahead and introduce this. A form of code aka pseudocode. Pseudocode is not a specific
language, it's not like something we're about to start coding in, it's
just a way of expressing yourself in English or any human
language succinctly correctly toward an end of getting
your idea for an algorithm across. So for instance, here
might be how we could formalize the code, the pseudocode
for that same algorithm. Step one was pick up the
phone book, as I did. Step two might be open to
the middle of the phone book, as you proposed that we do first. Step three was probably to
look down at the pages, I did. And step four gets a
little more interesting because I had to quickly make a
decision and ask myself a question. If person is on page, then
I should probably just go ahead and call that person. But that probably wasn't the
case at least for John Harvard, and I opened the M section. So there's this other
question I should now ask else if the person
is earlier in the book, then I should tear the problem
in half as I did but go left, so to speak, and then not just open to the
middle of the left half of the book, but really just go back to
step three, repeat myself. Why? Because I can just repeat what I
just did, but with a smaller problem having taken this big bite. But, if the person
was later in the book, as might have happened with a
different person than John Harvard, then I should open to the middle
of the right half of the book, again go back to line
three, but again, I'm not going to get sucked doing
something forever like this because I keep shrinking
the size of the problem. Lastly, the only
possible scenario that's left, if John Harvard is not on
the page and he's not to the left and he's not to the right,
what should our conclusion be? AUDIENCE: He's not there. DAVID MALAN: He's not there. He's not listed. So we need to quit in some other form. Now as an aside, it's kind of deliberate
that I buried that last question at the end because this is what
happens all too often in programming, whether you're new at
it or professional, just not considering all possible
cases, corner cases if you will, that might not happen that often,
but if you don't anticipate them in your own code,
pseudocode or otherwise, this is when and why
programs might crash or you might say stupid little
spinning beach balls or hourglasses or your computer might reboot. Why? It's doing something
sort of unpredictable if a human, maybe myself,
didn't anticipate this. Like what does this program do if
John Harvard is not in the phone book if I had omitted lines 12 and 13? I don't know. Maybe it would behave
differently on a Mac or PC because it's sort of undefined behavior. These are the kinds of omissions
that frankly you're invariably going to make, bugs
you're going to introduce, mistakes you're going to make early
on, and me, too, 25 years later. But you'll get better at
thinking about those corner cases and handling anything that can
possibly go wrong and as a result, your code will be all the better for it. Now the problem ultimately
with learning how to program, especially if you've
never had experience or even if you do but you
learned one language only, is that they all look a little
cryptic at first glance. But they do share certain commonalities. In fact, we'll use this
pseudocode to define those first. Highlighted in yellow
here are what henceforth we're going to start calling functions. Lots of different programming
languages exist, but most of them have what we might call
functions, which are actions or verbs that solve
some smaller problem. That is to say, you might use
a whole bunch of functions to solve a bigger problem
because each function tends to do something very specific or precise. These then in English might be
translated in code, actual computer code, to these things called functions. Highlighted in yellow now are
what we might call conditionals. Conditionals are things
that you do conditionally based on the answer to some question. You can think of them kind
of like forks in the road. Do you go left or go right
or some other direction based on the answer to some question? Well, what are those questions? Highlighted now in yellow or what we
would call Boolean expressions, named after a mathematician last name Bool,
that simply have yes no answers. Or, if you prefer, true or false answers
or, heck, if you prefer 1 or 0 answers. We just need to distinguish
one scenario from another. The last thing manifests
in this pseudocode is what I might highlight
now and call loops. Some kind of cycle, some kind of
directive that tells us to do something again and again so that I don't
need a 1,000-line program to search a 1,000-page phone book, I can get
away with a 13-line program but sort of repeat myself inherently in order to
solve some problem until I get to that last step. So this then is what we
might call pseudocode and indeed there are
other characteristics of programs that we'll touch on before
long, things like arguments and return values, variables, and more, but
unfortunately in most languages, including some we will very
deliberately use in this class and that everyone in the real
world these days still uses, its programs tend to look like this. This for instance, is a distillation
of that very first program I wrote in 1996 in CS50 itself just
to print something on the screen. In fact, this version here just tries
to print quote unquote, "Hello, world." Which is, dare say, the most
canonical first thing that most any programmer ever gets a
computer to say just because, but look at this mess. I mean, there's a hash symbol,
these angled brackets, parentheses, words like int, curly braces, quotes,
parentheses, semicolons, and back slashes. I mean there's more overhead
and more syntax and clutter than there is an actual idea. Now that's not to say that you won't
be able to understand this before long, because honestly there's not that many
patterns, indeed programming languages have typically a much smaller vocabulary
than any actual human language, but at first it might
indeed look quite cryptic. But you can perhaps infer I have no
idea what these other lines do yet, but "Hello, world." is
presumably quote unquote what will be printed on the screen. But what we'll do today,
after a short break, and set the stage for
next week is introduce these exact same ideas in
just a bit using Scratch, something that you
yourselves might have used when you're quite younger but
without the same vocabulary applied to those ideas. The upside of what we'll soon do using
Scratch, this graphical programming language from our friends down the
road at MIT, it'll let us today start to drag and drop things that
look like puzzle pieces that interlock together if it makes
logical sense to do so, but without the distraction
of hashes, parentheses, curly braces, angle brackets,
semicolons, and things that are quite beside the point. But for now, let's go ahead
and take a 10 minute break here and when we resume, we
will start programming. So this on the screen
is a language called C something that will dive
into next week and thankfully this now on the screen is
another language called Python that we'll also take a look at
in a few weeks before long along with other languages along the way. Today though, and for this first
week, week zero, so to speak, we use Scratch because
again it will allow us to explore some of those
programming fundamentals that will be in C and in Python and in
JavaScript and other languages, too, but in a way where we don't have to
worry about the distractions of syntax. So the world of Scratch looks like this. It's a web-based or downloadable
programming environment that has this layout here
by default. On the left here we'll soon see is a palette of puzzle
pieces, programming blocks that represent all of those
ideas we just discussed. And by dragging and
dropping these puzzle pieces or blocks over this big area
and connecting them together, if it makes logical
sense to do so, we'll start programming in this environment. The environment allows you to have
multiple sprites, so to speak. Multiple characters, things
like a cat or anything else, and those sprites exist
in this rectangular world up here that you can full screen to
make bigger and this here by default is Scratch, who can move up, down, left,
right and do many more things, too. Within its Scratch's
world you can think of it as perhaps a familiar
coordinate system with Xs and Ys which is helpful only when it comes
time to position things on the screen. Right now Scratch is at the default,
0,0, where x equals 0 and y equals 0. If you were to move the cat way
up to the top, x would stay zero, y would be positive 180. If you move the cat all the way
to the bottom, x would stay zero, but y would now be negative 180. And if you went left, x would become
negative 240 but y would stay 0, or to the right x would be
240 and y would stay zero. So those numbers generally don't
so much matter because you can just move relatively in this
world up, down, left, right, but when it comes time
to precisely position some of these sprites
or other imagery, it'll be helpful just to have that mental
model off up, down, left, and right. Well let's go ahead and make perhaps
the simplest of programs here. I'm going to switch over to the
same programming environment now for a tour of the left hand side. So by default selected here are
the category in blue motion, which has a whole bunch of puzzle
pieces or blocks that relate to motion. And whereas Scratch as
a graphical language categorizes things by the type
of things that these pieces do, we'll see that throughout
this whole palette we'll have functions and
variables and conditionals and Boolean expressions and more
each in a different color and shape. So for instance, moving 10 steps
or turning one way or the other would be functions categorized
here as things like motion. Under looks in purple, you
might have speech bubbles that you can create by
dragging and dropping these that might say "hello" or
whatever for some number of seconds. Or you could switch costumes, change
the cat to look like a dog or a bird or anything else in between. Sounds, too. You can play sounds like "meow" or
anything you might import or record, yourself. Then there's these things Scratch calls
events and the most important of these is the first, when green flag clicked. Because if we look over to the
right of Scratch's world here, this rectangular region has
this green flag and red stop sign up above, one of which is
for Play one of which is for Stop and so that's going to allow us to
start and stop our actual programs when that green flag
is initially clicked. But you can listen for other types of
events when the spacebar is pressed or something else, when this sprite
is clicked or something else. Here you already see like a
programmer's incarnation of things you and I take for granted like
every day now on our phones. Any time you tap an icon or drag your
finger or hit a button on the side. These are what a programmer
would call events, things that happen and
are often triggered by us humans and things that a program
be it in Scratch or Python or C or anything else can
listen for and respond to. Indeed, that's why when you tap
the phone icon on your phone, the phone application
starts up because someone wrote software that's listening for a
finger press on that particular icon. So Scratch has these same things, too. Under Control in orange,
you can see that we can wait for one second
or repeat something some number of times,
10 by default, but we can change anything in these
white circles to anything else. There's another puzzle
piece here forever, which implies some kind of loop where
we can do something again and again. Even though it seems a
little tight, there's not much room to fit
something there, Scratch is going to have these
things grow and shrink however we want to fill
similarly shaped pieces. Here are those conditionals. If something is true or false,
then do this next thing. And that's how we can put in
this little trapezoid-like shape. Some form of Boolean expression, a
question with a yes/no, true/false, or one/zero answer and decide
whether to do something or not. You can combine these things, too. If something is true, do this,
else do this other thing. And you can even tuck
one inside of the other if you want to ask three
or four or more questions. Sensing, too, is going to be a thing. You can ask questions aka Boolean
expressions like is the sprite touching the mouse pointer, the
arrow on the screen? So that you can start to
interact with these programs. What is the distance between
a sprite and a mouse pointer? You can do simple calculations
just to figure out maybe if the enemy is getting
close to the cat. Under Operator some lower level
stuff like math, but also the ability to pick random numbers,
which for a game is great because then you can kind
of vary the difficulty or what's happening in a game
without the same game playing the same way every time. And you can combine ideas. Something and something must be true
in order to make that kind of decision before. Or we can even join two words together. Says apple and banana by default,
but you can type in or drag and drop whatever you want there to
combine multiple words into full, larger sentences. Then lastly down here, there's in
orange things called variables. In math we've obviously
got x and y and whatnot. In programming we'll
have the same ability to store in these named symbols,
x or y, values that we care about. Numbers or letters or words or
colors or anything, ultimately. But in programming you'll see that
it's much more conventional not to just use simple letters like x
and y and z, but to actually give variables full singular or plural
words to describe what they are. Then lastly, if this isn't
enough color blocks for you, you can create your own blocks. Indeed, this is going to be a
programming principle we'll apply today and with the first problem set whereby
once you start to assemble these puzzle pieces and you realize, oh, would have
been nice if those several pieces could have just been replaced by one
had MIT thought to give me that one puzzle piece, you yourself
can make your own blocks by connecting these all together,
giving them a name, and boom, a new puzzle piece will exist. So let's do the simplest,
most canonical programs here, starting up with
control, and I'm going to click and drag and drop this
thing here when green flag clicked. Then I'm going to grab one
more, for instance under Looks, and under Looks I'm going
to go ahead and just say something like initially not
just Hello but the more canonical Hello comma world. Now you might guess that in
this programming environment, I can go over here now and
click the green flag and voila, Hello comma world. So that's my first
program and obviously much more user friendly than typing out
the much more cryptic text that we saw on the screen that you,
too, will type out next week. But for now, we'll just focus on
these ideas, in this case, a function. So what it is that just happened? This purple block here is
Say, that's the function, and it seems to take some form of input
in the white oval, specifically Hello comma world. Well this actually fits
the paradigm that we looked at earlier of just inputs and outputs. So if I may, if you consider
what this puzzle piece is doing, it actually fits this model. The input in this case is going
to be Hello comma world in white. The algorithm is going to be implemented
as a function by MIT called Say and the output of that is going
to be some kind of side effect, like the cat and the speech
bubble are saying Hello, world. So already even that
simple drag and drop mimics exactly this relatively
simple mental model. So let's take things further. Let's go ahead now and make the
program a little more interactive so that it says something like
Hello, David, or Hello, Carter, or Hello to you specifically. And for this, I'm going
to go under Sensing. And you might have to poke around
to find these things the first time around, but I've done this a few times
so I kind of know where things are and what color. There's this function here. Ask what's your name,
but that's in white, so we can change the
question to anything we want, and it's going to wait for
the human to type in their answer. This function called Ask
is a little different from the Say block, which just had
this side effect of printing a speech bubble to the screen. The ask function is even more powerful
in that after it asks the human to type something in. This function is going
to hand you back what they typed in in the form of
what's called a return value, which is stored ultimately and by
default this thing called Answer. This little blue oval here
called Answer is again one of these variables
that in math would be called just x or y but in
programming we're saying what it does. So I'm going to go ahead and do this. Let me go ahead and
drag and drop this block and I want to ask the question
before saying anything, but you'll notice that
Scratch is smart and it's going to realize I want to
insert something in between and it's just going to
move things up and down. I'm going to let go and ask the
default question, what's your name? And now if I want to go ahead
and say hello, David or Carter, let's just do Hello
comma, because I obviously don't know when I'm writing the
program who's going to use it. So let me now grab another looks block
up here, say something again, and now let me go back to Sensing and now
grab the return value, represented by this other puzzle piece, and
let me just drag and drop it here. Notice it's the same shape, even
if it's not quite the same size. Things will grow or shrink as needed. All right, so let's now zoom out. Let me go and stop the old version
because I don't want to say Hello, world anymore. Let me hit the green
flag and what's my name? All right, David. Enter. Huh. All right, maybe I just wasn't
paying close enough attention. Let me try it again. Green flag, D-A-V-I-D, Enter. This seems like a bug. What's the bug or
mistake might you think? Yeah? AUDIENCE: Do you need to somehow add
them together in the same text box? DAVID MALAN: Yeah, we kind of want
to combine them in the same text box. And it's technically a bug because
this just looks kind of stupid. It's just saying David
after I asked for my name. I'd like it to say
maybe Hello then David, but it's just blowing past
the Hello and printing David. But let's put our finger
on why this is happening. You're right for the solution, but
what's the actual fundamental problem? In back. AUDIENCE: So it says hello,
but it gets to that last step so quickly you can't see it. DAVID MALAN: Perfect. I mean, computers are
really darn fast these days. It is saying Hello, all of us
are just too slow in this room to even see it because it's then saying
David on the screen so fast as well. So there's a couple of solutions
here, and yours is spot on, but just to poke around,
you'll see the first example of how many ways in programming be
it Scratch or C or Python or anything else, that there are going
to be to solve problems? We'll teach you over the
course of these weeks, sometimes some ways are
better relatively than others, but rarely is there a
best way necessarily, because again reasonable
people will disagree. And what we'll try to teach
you over the coming weeks is how to kind of think
through those nuances. And it's not going to be
obvious at first glance, but the more programs you
write, the more feedback you get, the more bugs
that you introduce, the more you'll get your footing with
exactly this kind of problem solving. So let me try this in a couple of ways. Up here would be one
solution to the problem. MIT anticipated this kind of issue,
especially with first-time programmers, and I could just use a
puzzle piece that says say the following for
two seconds or one second or whatever, then do the
same with the next word and it might be kind
of a bit of a pause, Hello, one second, two seconds, David,
one second, two seconds, but at least it would look a little
more grammatically correct. But I can do it a little more
elegantly, as you've proposed. Let me go ahead and throw
away one of these blocks, and you can just drag and let
go and it'll delete itself. Let me go down to Operators because
this Join block here is the right shape. So even if you're not sure what goes
where, just focus on the shapes first. Let me drag this over here. It grew to fill that. Let me go ahead and
say hello comma space. Now it could just say by
default Hello, banana, but let me go back to
Sensing, Drag answer, and that's going to drag and drop there. So now notice we're sort of stacking
or nesting one block on another so that the output of one becomes the
input to another, but that's OK here. Let me go ahead and zoom
out, hit Stop, and hit Play. All right, what's your name? D-A-V-I-D, Enter, and voila. Now it's presumably
as we first intended. [APPLAUSE] (LAUGHING) Oh, thank you. Thank you. No minus 2 this time. So consider that even with
this additional example, it still fits the same mental model,
but in a little more interesting way. Here's that new function
Ask something and wait. And notice that in this case too
there's an input, otherwise known henceforth as an argument
or a parameter, programming speak for just an input in
the context of a function. If we use our drawing as before
to represent this thing here, we'll see that the input now is going
to be quote unquote "What's your name?" The algorithm is going to be implemented
by way of this new puzzle piece, the function called Ask, and the
output of that thing this time is not going to be the
cat saying anything yet, but rather it's going
to be the actual answer. So instead of the visual side effect
of the speech bubble appearing, now nothing visible is happening yet. Thanks to this function it's sort of
handing me back like a scrap of paper with whatever I typed in written on it
so I can reuse D-A-V-I-D one or more times even like I did. Now what did I then do with that value? Well consider that with
the subsequent function we had this Say block,
too, combined with a join. So we have this variable
called Answer, we're joining it with that first argument, Hello. So already we see that
some functions like Join can take not one but two arguments,
or inputs, and that's fine. The output of Join is presumably going
to be Hello, David or Hello, Carter or whatever the human typed in. That output notice is essentially
becoming the input to another function, Say, just because we've
kind of stacked things or nested them on top of one another. But methodically, it's
really the same idea. The input now are two things,
Hello comma and the return value from the previous Ask function. The function now is going to be Join,
the output is going to be Hello, David. But that Hello, David
output is now going to become the input to another function,
namely that first block called Say, and that's then going to have the side
effect of printing out Hello, David on the screen. So again as sort of sophisticated
as ours as yours as others programs are going to get, they really do
fit this very simple mental model of inputs and outputs and you just have
to learn to recognize the vocabulary and to know what kinds of puzzle
pieces or concepts ultimately to apply. But you can ultimately really
kind of spice these things up. Let me go back to my
program here that just is using the speech bubble at the moment. Scratch's inside has some pretty
fancy interactive features, too. I click the Extensions button
in the bottom left corner. And let me go ahead and choose
the Text to Speech extension. This is using a Cloud service, so
if you have an internet connection it can actually talk to the
Cloud or a third party service, and this one is going to give me a
few new green puzzle pieces, namely the ability to speak
something from my speakers instead of just saying it textually. So let me go ahead and drag this. Now notice I don't have to interlock
them if I'm just kind of playing around and I want to move some things around. I just want to use this as
like a canvas temporarily. Let me go ahead and
steal the Join from here, put it there, let me throw away
the Say block by just moving it left and letting go, and
now let me join this in so I've now changed my program
to be a little more interesting. So now let me stop the old version. Let me start the new. What's your name? Type in David. And voila: PROGRAM: Hello, banana. DAVID MALAN: (LAUGHING)
OK, minus 2 for real. All right, so what I accidentally
threw away there, intentionally for instructional purposes,
was the actual answer that came back from the ask block. That's embarrassing. So now if I play this again,
let's click the green icon. What's your name? David. And now: PROGRAM: Hello, David. DAVID MALAN: There we go. Hello, David. All right, thank you. [APPLAUSE] OK, so we have these functions then
in place, but what more can we do? Well what about those conditionals
and loops and other constructs? How can we bring these programs
to life so it's not just clicking a button and voila,
something's happening? Let's go ahead and make this
now even more interactive. Let me go ahead and throw
away most of these pieces and let me just spice things up
with some more audio under Sound. I'm going to go to Play
Sound Meow until done. Here we go, green flag. [MEOW] OK, it's a little loud, but it
did exactly do what it said. Let's hear it again. [QUIETER MEOW] OK. It's kind of an underwhelming
program eventually since you'd like to think that the
cat would just meow on its own, but. [MEOW] I have to keep hitting the button. Well this seems like an opportunity
for doing something again and again. So all right, well if I
wanted to meow, meow, meow, let me just grab a few of these, or you
can even right click or Control click and you can Copy Paste
even in code here. Let me play this now. [THREE MEOWS] All right, so now like
it's not really emoting happiness in quite the same way. It might be hungry or upset. So let's slow it down. Let me go to Control, wait
one second in between, which might be a little less worrisome. Here we go, Play. [THREE SLOWER MEOWS] OK, so if my goal was to make
the cat meow three times, I dare say this code or
algorithm is correct. But let's now critique its design. Is this well-designed? And if not, why not? What are your thoughts here? Yeah? AUDIENCE: You could use the forever
or a repeat to make it more-- DAVID MALAN: Yeah, so yeah, agreed. I could use forever or repeat,
but let me push a little harder. But why? Like this works, I'm kind of done with
the assignments, what's bad about it? AUDIENCE: There's too much repetition. DAVID MALAN: Yeah, there's
too much repetition, right? If I wanted to change the
sound that the cat is making to a different variant of meow or
have it bark instead like a dog, I could change it from the
dropdown here apparently, but then I'd have to change it here
and then I'd have to change it here, and God, if this were even longer
that just gets tedious quickly and you're probably
increasing the probability that you're going to
screw up and you're going to miss one of the dropdowns or
something stupid and introduce a bug. Or, if you wanted to change the
number of seconds you're waiting, you've got to change it in
two, maybe even more places. Again, you're just
creating risk for yourself and potential bugs in the program. So I do like the repeat or the forever
idea so that I don't repeat myself. And indeed, what I
alluded to being possible, copy pasting earlier, doesn't
mean it's a good thing. And in code, generally
speaking, when you start to copy and paste puzzle
pieces or text next week, you're probably not doing
something quite well. So let me go ahead and throw away most
of these to get rid of the duplication, keeping just two of the
blocks that I care about. Let me grab the Repeat block for now,
let me move this inside of the Repeat block, it's going to grow to fit
it, let me reconnect all this and change the 10 just
to a 3, and now, Play. [THREE SLOW MEOWS] So, better. It's the same thing. It's still correct, but
now I've set the stage to let the cat meow, for instance,
four times by changing one thing, 40 times by changing one thing, or
it could just use the Forever block and just walk away and it
will meow forever instead. If that's your goal,
that would be better. A better design but still correct. But you know what? Now that I have a
program that's designed to have a cat meow, wow like why? I mean, MIT invented
Scratch, Scratch as a cat, why is there no puzzle
piece called Meow? This feels like a missed opportunity. Now to be fair, they gave
us all the building blocks with which we could implement that
idea, but a principle of programming and really computer science
is to leverage what we're going to now start calling Abstraction. We have step-by-step instructions
here, the Repeat, the Play, and the Wait that collectively
implements this idea that we humans would call meowing. Wouldn't it be nice to abstract
away those several puzzle pieces into just one that literally
just says what it does, meow? Well here's where we
can make our own blocks. Let me go over here to Scratch
under the pink block category here and let me click Make a Block. Here I see a slightly
different interface where I can choose a name for it
and I'm going to call it Meow. I'm going to keep it simple. That's it. No inputs to meow yet. I'm just going to click OK. Now I'm just going to
clean this up a bit here. Let me drag and drop Play
Sound and Wait over here. And you know what? I'm just going to drag this
way down here, way down here because now that I'm
done implementing Meow, I'm going to literally abstract
it away, sort of out of sight, out of mind, because now notice at
top left there is a new pink puzzle piece called Meow. So at this point, I'd argue it doesn't
really matter how Meow is implemented. Frankly, I don't know how Ask
or Say was implemented by MIT. They abstracted those
things away for us. Now I have a brand new puzzle
piece that just says what it is. And this is now still correct,
but arguably better design. Why? Because it's just more
readable to me, to you, it's more maintainable
when you look at your code a year from now for the first time
because you're sort of finally looking back at the very first
program you wrote. It says what it does. The function itself has semantics,
which conveys what's going on. If you really care about
how Meow is implemented, you could scroll down and start
to tinker with the underlying implementation details, but otherwise
you don't need to care anymore. Now I feel like there's an
even additional opportunity here for abstraction and to factor
out some of this functionality. It's kind of lame that I
have this Repeat block that lets me call the Meow function,
so to speak, use the Meow function three times. Wouldn't it be nice if I could
just call them Meow function, aka use the Meow function, and pass
it in input that tells the puzzle piece how many times I want it to meow? Well let me go ahead and
zoom out and scroll down. Let me right click or Control click on
the pink piece here and choose Edit, or I could just start from scratch,
no pun intended, with a new one. Now here, rather than just give this
thing a name Meow, let me go ahead and add an input here. I'm going to go ahead and
type in, for instance, n, for number of times to
meow, and just to make this even more user friendly
and self descriptive, I'm going to add a label,
which has no functional impact, it's just an aesthetic,
and I'm just going to say Times, just to make
it read more like English in this case that tells me
what the puzzle piece does. Now I'm going to click OK. And now I need to refine
this a little bit. Let me go ahead and grab
under Control a repeat block, let me move the Play, Sound,
and Wait, into the repeat block. I don't want 10 and I
also don't want 3 here. What I want now is this n that is
my actual variable that Scratch is creating for me that represents
whatever input the human programmer provides. Notice that snaps right in place. Let me connect this and now voila, I
have an even fancier version of Meow that is parameterized. It takes input that affects
its behavior accordingly. Now I'm going to scroll back up,
because out of sight, out of mind, I just care that Meow exists. Now I can tighten up my code, so
to speak, use even fewer lines to do the same thing by
throwing away the Repeat block, reconnecting this new puzzle piece here
that takes an input like 3 and voila, now we're really programming, right? We've not made any forward
progress functionally. The thing just mouse three times. But it's a better design. As you program more and
more, these are the kinds of instincts still start
to acquire so that one, you can start to take a big assignment,
a big problem set, something for homework even, that feels kind of
overwhelming at first, like, oh my God where do I even begin? But if you start to identify what are
the subproblems of a bigger problem? Then you can start making progress. I do this to this day where if I have to
tackle some programming-related project it's so easy to drag my feet and ugh,
it's going to take forever to start, until I just start writing
down like a to do list and I start to modularize the
program and say, all right, well what do I want this thing to do? Meowing. What's that mean? I've got to have it say
something on the screen. All right, I need to have it
say something on the screen some number of times. Like literally a mental or written
checklist, or pseudocode code, if you will, in English on a
piece of paper or text file, and then you can decide,
OK, the first thing I need to do for homework to
solve this real world problem, I just need a Meow function. I need to use a bunch
of other code, too, but I need to create a
Meow function and boom, now you have a piece of the problem
solved not unlike we did with the phone book there, but in this case, we'll
have presumably other problems to solve. All right, so what more can we do? Let's add a few more
pieces to the puzzle here. Let's actually interact
with the cat now. Let me go ahead and now when the
green flag is clicked, let me go ahead and ask a question using an event here. Let me go ahead and
say, let's see, I want to do something like implement
the notion of petting the cat. So if the cursor is touching the
cat like here, something like this, it'd be cute if the cat meows
like you're petting a cat. So I'm going to ask the question,
when the green flag is clicked, if let's see I think I need Sensing. So if touching mouse
pointer, this is way too big but again the shape is
fine, so there goes. Grew to fill. And then if it's touching
the mouse pointer, that is if the cat to whom
this script or this program, any time I attach puzzle
pieces MIT calls them a script or like a program, if you will, let
me go ahead then and choose a sound and say play sound meow until done. All right, so here it is to be clear. When the green flag is
clicked, ask the question, if the cat is touching the mouse
pointer then place sound meow. Here we go. Play. [SILENCE] All right, let's try again. Play. [SILENCE] Huh. I'm worried it's not Scratch's
fault. Feels like mine. What's the bug here? Why doesn't this work? Yeah, in back, who just turned. AUDIENCE: [INAUDIBLE] DAVID MALAN: Yeah, the problem is
the moment I click that green flag, Scratch asks the question, is the
cat touching the mouse pointer? And obviously it's not because the
cursor was like up there a moment ago and it's not down there. It's fine if I move the cursor
down there, but too late. The program already asked the question. The answer was no or false or zero,
however you want to think about it, so no sound was played. So what might be the solution here be? I could move my cursor
quickly, but that feels like never going to work out right. Other solutions here? Yeah, in way back? Could you use the forever loop? The Forever loop. So I could indeed use this Forever
loop because if I want my program to just constantly listen to me, well
let's literally do something forever, or at least forever as
long as the program is running until I explicitly hit Stop. So let me grab that. Let me go to Control, let
me grab the Forever block, let me move the If inside of this
Forever block, reconnect this, go back up here, click the green
flag, and now nothing's happened yet, but let me try moving my cursor now. [MEOW] Oh. So now. [MEOW] That's kind of cute. So now the cat is actually
responding and it's going to keep doing
this again and again. So now we have this idea of taking these
different ideas, these different puzzle pieces, assembling them into
something more complicated. I could definitely put a name to this. I could create a custom
block, but for now let's just consider what kind
of more interactivity we can do. Let me go ahead and do this. By again grabbing a,
when green flag clicked, let me go ahead and
click the video sensing, and I'm going to rotate the
laptop because otherwise we're going to get a little inception thing
here where the camera is picking up the camera is up there. So I'm going to go reveal to
you what's inside the lectern here while we rotate this. Now that we have a non video
backdrop, I'm going to say this. Instead of the green flag
clicked, actually, I'm going to say when the video motion
is greater than some arbitrary measurement of motion, I'm going to go
ahead and play sound meow until done. And then I'm going to
get out of the way. So here's the cat. We'll put them on top of there. [MEOW] OK. All right, and here we go. [MEOW] So my hand is moving faster
than 50 something or other, whatever the unit of measure is. [MEOW] AUDIENCE: Aw. DAVID MALAN: (LAUGHING) Thank you. So now we have an even
more interactive version. [MEOW] But I think if I sort of slowly. [LAUGHING] (LAUGHING) Right? It's completely creepy, but I'm
not like exceeding the threshold-- [MEOW] Until finally my hand
moves as fast as that. And so here actually is
an opportunity to show you something a former student did. Let me go ahead here and-- [MEOW TWICE] OK, got to stop this. Let me go ahead and zoom out
of this in just a moment. [MEOW] If someone would be-- [LAUGHING] (LAUGHING) If someone
would be comfortable coming up not only masked but
also on camera on the internet I thought we'd play one of your former
classmate's projects here up on stage. Would anyone like to volunteer
here and be up on stage? Who's that? Yeah. Come on down. What's your name? AUDIENCE: Sahar. DAVID MALAN: Sahar. All right, come on down. Let me get it set up for you here. [MEOW] [APPLAUSE] [MEOW] All right, let me go ahead
and full screen this here. So this is whack-a-mole by one
of your firmer predecessors. It's going to use the camera focusing
on your head, which will have to position inside of this rectangle. Have you ever played the
whack-a-mole game at an arcade? AUDIENCE: Yeah. DAVID MALAN: OK. So for those who haven't,
these little moles pop up and with a very fuzzy
hammer you sort of hit down. You though, if you
don't mind, you're going to use your head to do this virtually. So let's line up your head with
this red rectangle, if you could, we'll do beginner. [MUSIC PLAYING] All right, here we go. Sahar. Give it a moment. OK, come a little closer. [DINGING] And now hit the moles with your head. [DING] There we go, one point. [DING] One point. [DINGING] Nice. 15 seconds to go. There we go. Oh yeah. One point. [LAUGHING] [DINGING] Six seconds. AUDIENCE: Oh no. DAVID MALAN: There we go. Quick! [DINGING] All right, a round of
applause for Sahar. Thank you. [APPLAUSE] So beyond having a
little bit of fun here, the goal was to
demonstrate that by using some fairly simple, primitive,
some basic building blocks but assembling them in a fun
way with some music, maybe some new costumes or artwork, you
can really bring programs to life. But at the end of the day, the
only puzzle pieces really involved were ones like the ones I just
dragged and dropped and a few more, because there were
clearly lots of moles. So the student probably created a few
different sprites, not a single cap, but at least four different moles. They had like some kind of graphic
on the screen that showed Sahar where to position her head. There were some kind of
timer, maybe a variable that every second was counting down. So you can imagine taking what looks
like a pretty impressive project at first glance, and
perhaps overwhelming to solve yourself, but just think about
what are the basic building blocks? And pluck off one piece of the
puzzle, so to speak, at a time. So indeed if we rewind a little bit. Let me go ahead here
and introduce a program that I myself made
back in graduate school when Scratch was first
being developed by MIT. Let me go ahead and open
here, give me just one second, something that I called back
in the day Oscar Time that looks a little something like this. If I fullscreen it and hit Play. [MUSIC - SESAME STREET, "I LOVE TRASH"] OSCAR THE GROUCH:
(SINGING) Oh, I love trash. DAVID MALAN: So you'll notice
a piece of trash is falling. I can click on it and drag and as I get
close and close to the trash can notice OSCAR THE GROUCH: (SINGING)
Anything ragged or-- DAVID MALAN: It wants
to go in, it seems. And if I let go-- OSCAR THE GROUCH: (SINGING) Yes, I-- DAVID MALAN: One point. Here comes another. OSCAR THE GROUCH: (SINGING) If you
really want to see something trashy-- DAVID MALAN: I'll do
the same, two points. OSCAR THE GROUCH: (SINGING) I have here
a sneaker that's tattered and worn-- DAVID MALAN: There's a
sneaker falling from the sky, so another sprite of some sort. OSCAR THE GROUCH: (SINGING)
The laces are torn. A gift from my mother-- DAVID MALAN: I can also
get just a little lazy and just let them fall into the
trash themself if I want to. So you can see it doesn't have
to do with my mouse cursor, it has to do apparently
with the distance here. Let's listen a little further. I think some additional trash
is about to make its appearance. Presumably there's some kind of variable
that's keeping track of this score. OSCAR THE GROUCH: (SINGING) I love-- DAVID MALAN: OK, let's see
what the last chorus here is. OSCAR THE GROUCH:
(SINGING) Rotten stuff. I have here some newspaper, crusty and DAVID MALAN: OK, and thus he continues. And the song actually
goes on and on and on and I do not have fond memories
of implementing this and hearing this song for like 10
straight hours, but it's a good example to just consider
how was this program composed? How did I go about implementing
it the first time around? And let me go ahead and
open up some programs now that I wrote in advance
just so that we could see how these things are assembled. Honestly, the first thing
I probably did was probably to do something a little like this. Here is just a version
of the program where I set out to solve
just one problem first of planting a lamp post in the program. Right? I kind of had a vision of what I wanted. You know, it evolved
over time, certainly, but I knew I wanted
trash to fall, I wanted a cute little Oscar
the Grouch to pop out of the trashcan, and some other
stuff, but wow that's a lot to just tackle all at once. I'm going to start easy, download
a picture of a lamp post, and then drag and drop it into the
stage as a costume and boom, that's version one. It doesn't functionally do anything. I mean, literally that's the
code that I wrote to do this. All I did was use like
the Backdrops feature and drag and drop and
move things around, but it got me to version
one of my program. Then what might version two be? Well I considered what
piece of functionality frankly might be the easiest to
pluck off next and the trash can. That seems like a pretty
core piece of functionality. It just needs to sit
there most of the time. So the next thing I
probably did was to open up, for instance, the trash can version
here that looks a little something now like this. So this time I'll show
you what's inside here. There is some code, but not much. Notice at bottom right I change the
default cat to a picture of a trashcan, instead, but it's the same
principle that I can control. And then over here I added this code. When the green flag is
clicked, switch the costume to something I arbitrarily
called Oscar 1. So I found a couple
of different pictures of a trash can, one that looks
closed, one that looks partly open, and eventually one that
has Oscar coming out, and I just gave them different names. So I said Switch to Oscar 1, which
is the closed one by default, then forever do the following:
if touching the mouse pointer, then switch the costume to
Oscar 2, else switch to Oscar 1. That is to say, I just wanted to
implement this idea of the can opening and closing, even if it's not
exactly what I wanted ultimately, I just wanted to make
some forward progress. So here, when I run this program by
clicking Play, notice what happens. Nothing yet, but if I get
closer to the trash can, it indeed pops open because
it's forever listening for whether the sprite,
the trash can in this case, is touching the mouse pointer. And that's it. That was version 2, if you will. If I went in now and added the lamp
post and compose the program together, now we're starting to make progress. Right? Now it would look a little
something more like the program I intended ultimately to create. What piece did I probably
bite off after that? Well, I think what I did
is I probably decided let me implement one of the pieces of
trash, not the shoe in the newspaper all at once. Let's just get one piece of
trash working correctly first. So let me go ahead and open this one. And again, all of these examples will
be available on the course's website so you can see all of
these examples, too. It's not terribly long, I
just implement it in advance so we could flip
through kind of quickly. Here's what I did here. On the right hand side, I turned
my sprite into a piece of trash this time instead of a cat,
instead of a trash can, and I also created, with Carter's help,
a second sprite, this one a floor. It's literally just a black line
because I just wanted initially to have some notion of a
floor so I could detect if the trash is touching the floor. Now without seeing the code yet,
just hearing that description, why might I have wanted the second
sprite and this black line for a floor with the trash intending
to fall from the sky? What might I have been thinking? Like what problem might
I be trying to solve? Yeah? AUDIENCE: You don't want the
first sprite to go through it. DAVID MALAN: Yeah, you don't want
the first sprite to start at the top, go through, and then boom,
you completely lose it. That would not be a very useful thing. Or it would seem to maybe eat up more
and more of the computer's memory if the trash is just endlessly
falling and I can't grab it. It might be a little traumatic
if you tried to get it and you can't pull it back out
and you can't fix the program. So I just wanted the thing to stop. So how might I have implemented this? Let's look at the code at left. Here I have a bit of randomness,
like I proposed earlier exists. There's this blue
function called Go To x, y that lets me move a
sprite to any position, up, down, left, right, I picked a random
x location, either here or over here, negative 240 to positive 240, and then
a y value of 180, which is the top. This just makes the
game more interesting. It's kind of lame pretty quickly if the
trash always falls from the same spot. Here's this a little bit of randomness,
like most any game would have, that spices things up. So now if I click the green flag,
you'll see that it just falls, nothing interesting is
going to happen, but it does stop when it touches the black
line because notice what we did here. I'm forever asking the question if
the distance of the sprite, the trash, is to the floor is greater
than zero, that's fine. Change the y location by negative 3. So move it down 3 pixels, down 3
pixels, until the distance to the floor is not greater than zero, it is zero
or even negative, at which point it should just stop moving altogether. There's other ways we could
have implemented this, but this felt like a nice,
clean way that logically, just made it make sense. OK, now I got some trash falling, I
got a trash can that opens and closes, I have a lamp post, now I'm a
good three steps into the program. We're making progress. If we consider one or two
final pieces, something like the dragging of the trash, let me
go ahead and open up this version 2. Dragging the trash requires
a different type of question. Let me zoom in here. Here's the piece of trash. I only need one sprite, no
floor here because I just want the human to move it up,
down, left, right and the human's not going to physically be able
to move it outside of the world. If we zoom in on this code, the way
we've solved this is as follows. We're using that And conjunction
that we glimpsed earlier because when the green flag is clicked, we're
forever asking this question or really these questions, plural,
if the mouse is down and the trash is touching the
mouse pointer, that's equivalent logically to clicking on the trash. Go ahead and move the
trash to the mouse pointer. So again it takes this
very familiar idea that you and I take for granted
every day on Macs and PCs of clicking and dragging and dropping. How is that implemented? Well Mac OS or Windows are
probably asking a question. For every icon, is the mouse down
and is the icon touching the mouse? If so, go to the location
of the mouse forever while the mouse button is clicked down. So how does this work in reality now? Let me go ahead and click on the Play. Nothing happens at first, but if
I click on it, I can move it up, down, left, right. It doesn't move thereafter. So I now need to kind of combine
this idea of dragging with falling, but I bet I could just start
to use just one single program. Right now I'm using separate
ones to show different ideas, but now that's another
bite out of the problem. If we do one last one,
something like the scorekeeping is interesting, because recall that
every time we dragged a piece of trash into the can, Oscar popped out
and told us the current score. So let me go ahead and find
this one, Oscar variables, and let me zoom in on this one. This one is longer because we
combined all of these elements. So this is the kind of thing that
if you looked at first glance, like, I have no idea how I would
have implemented this from nothing, from scratch literally. But again, if you take your
vision and componenitize it into these smaller,
bite-sized problems, you could take these baby
steps, so to speak, and then solve everything collectively. So what's new here is this bottom one. Forever do the following:
if the trash is touching Oscar, the other sprite that
we've now added to the program, change the score by 1. This is an orange and
indeed if we poke around we'll see that orange is a variable,
like an x or y but with a better name, changing it means to add 1 or
if it's negative subtract 1. Then go ahead and have the
trash go to pick random. What is this all about? Well, let me show you what it's doing
and then we can infer backwards. Let me go ahead and hit Play. All right, it's falling, I'm clicking
and dragging it, I'm moving it over, and I'm letting go. All right, let me do it once more. Letting go, let me stop. Why do I have this function at the
end called Go To x and y randomly? Like what problem is this solving here? Yeah, in way back. AUDIENCE: Just the same
track teleported to the top after you put it in the trash can. DAVID MALAN: Yeah, exactly. Even though the human perceives
this as like a lot of trash falling from the sky, it's
actually the same piece of trash, just kind of being
magically moved back to the top as though it's a new one. There, too, you have this
idea of reusable code. If you were constantly copying
and pasting your pieces of trash and creating 20 pieces of trash, 30
pieces of trash, just because you want the game to have that many
levels, probably doing something wrong. Reuse the code that you wrote,
reuse the sprites that you wrote, and that would give you not just
correctness, but also a better design. Well let's take a look at one
final set of building blocks that we can compose
ultimately into something particularly interactive as follows. Let me go ahead and
zoom out here and let me propose that we implement something
like some kind of maze-based game. Let me go ahead here. So I want to implement
some maze-based game that looks at first glance like this. Let me hit Play. It's not a very fun game yet,
but here's a little Harvard shield, a couple of black lines, this
time vertical instead of horizontal, but notice you can't
quite see my hand here, but I'm using my arrow keys to go down,
to go up, to go left, to go right, but if I keep going right, right,
right, right, right, right, right it's not going anywhere. And left, left, left, left, left, left,
left, left, left, left, left, left, left it eventually stops. So before we look at the code,
how might this be working? What kinds of scripts,
collections of puzzle pieces, might collectively
help us implement this? What do you think? AUDIENCE: [INAUDIBLE] DAVID MALAN: Perfect, yeah. There's probably some question being
asked, if touching the black line, and it happens to be a couple
of sprites, each of which is just literally a vertical black line
we're probably asking a question like, are you touching it? Is the distance to it
zero or close to zero? And if so, we just ignore the left
or the right arrow at that point. So that works. But otherwise, if we're
not touching a wall, what are we probably doing
instead forever here? How is the movement working presumably? Yeah and back. Oh are you scratching? OK, sure. Let's go on. AUDIENCE: [INAUDIBLE] DAVID MALAN: Sorry, say a little louder. AUDIENCE: Presumably it's continually
looking for you to hit the arrow keys and then moving when you do. DAVID MALAN: Exactly. It's continually, forever listening for
the arrow keys up, down, left, right, and if the up arrow is
pressed, we're probably changing the y by a positive value. If the down arrow is pressed,
we're going down by y, and left and right accordingly. So let's actually take a quick look. If I zoom out here and take a look
at the code that implements this, there's a lot going on at
first glance, but let's see. First of all, let me drag
some stuff out of the way because it's kind of
overwhelming at first glance, especially if you, for instance, were
poking around online as for problem set 0 just to get inspiration,
most projects out there are going to look
overwhelming at first glance until you start to wrap your
mind around what's going on. But in this case, we've
implemented some abstractions from the get go to explain to
ourselves and to anyone else looking at the code what's going on. This is that program with the two black
lines and the Harvard shield going up, down, left, and right. It initially puts the shield
in the middle, 0,0, then forever listens for keyboard,
as I think you were describing, and it feels for the walls, as
I think you were describing. Now how is that implemented? Don't know yet. These are custom blocks we created
as abstractions to kind of hide those implementation details
because honestly that's all I need to know right now. But, as aspiring programmers,
if we're curious now, let's scroll down to the
actual implementation of listening for keyboard. This is the one on the left
and it is a little long, but it's a lot of similar structure. We're doing the following, if the up
arrow is pressed, then change y by 1. Go up. If the down arrow is pressed,
then change y by negative 1. Go down. Right arrow, left arrow, and that's it. So it just assembles all
of those ideas, combines it into one new block just because
it's kind of overwhelming, let's just implement it
once and tuck it away. And if we scroll now over to
the Feel for Walls function, this now is asking the
question as hypothesized, if I'm touching the left wall, change my
x value by 1, sort of move away from it a little bit. If I'm touching the right
wall, then move x by negative 1 to move a little bit away from it. So it kind of bounces off the wall. Just in case it slightly went over, we
keep the crest within those two walls. All right, then a couple of
more pieces here to introduce. What if we want to actually add
some kind of adversary or opponent to this game? Well, let me go ahead to maybe this one
here where the adversary in this game might, for instance, be designed to
be bouncing to stand in your way. This is like a maze and you're trying to
get the Harvard shield from the bottom to the top or vice versa. Uh oh, Yale is in the way and it
seems to be automatically bouncing back and forth here. Well, let me ask someone else. Hypothesize. How is this working? This is an idea you have,
this as an idea you see. Let's reverse engineer in
your head how it works. How might this be working? Yeah, in back. AUDIENCE: If the Yale symbol is
touching a right wall or left wall, then have it bounce. DAVID MALAN: Yeah, so
if the Yale symbol is touching the left wall or the right
wall, we somehow have it bounce. And indeed we'll see there's a
puzzle piece that can do exactly that technically off
the edge, as we'll see, but there's another way we can do this. Let's look at the code. The way we ourselves
can implement exactly that idea bounce is just
with a little bit of logic. So here's what this version
of the program is doing. It's moving Yale by default to 0,0
just to arbitrarily put it somewhere, pointing it direction 90 degrees, which
means just horizontally, essentially, and then it's forever doing
this: if touching the left wall or touching the right wall,
here's our translation of bounce. We're just turning 180 degrees. And the nice thing
about that is we don't have to worry if we're going from
right to left or left to right. 180 degrees is going to
work on both of the walls. And that's it. After we do that, we just move
one step, one pixel, at a time but we're doing it forever so
something is happening continually and the Yale icon is
bouncing back and forth. Well one final piece
here, what if now we want another adversary, a more advanced
adversary down the road for instance, to go and follow us wherever
we are such that this time we want the other sprite to
not just bounce back and forth, but literally follow us
no matter where we go. How might this be
implemented on the screen? I bet it's another forever
block, but what's inside? AUDIENCE: So forever get the
location of the of the Harvard shield and move one step towards it. DAVID MALAN: Yeah, forever point at
the location of the Harvard shield and go one step toward it. This is just going to go on forever if I
just give up, at least in this version. Notice it's sort of twitching
back and forth because it goes one pixel then one pixel then one pixel. It's sort of in a frantic state here. We haven't finished the game yet, but if
we see inside, we'll see exactly that. It didn't take much to
implement this simple idea. Go to a random position just
to make it kind of fair, initially, then forever
point towards Harvard, which is what we called the Harvard
crest sprite, move one step. Suppose we now wanted to
make a more advanced level. What's a minor change I could
logically make to this code just to make MIT even better at this? AUDIENCE: Change the
number of steps to two. DAVID MALAN: All right, change
the number of steps to two. So let's try that. So now they got twice as fast. Let me go ahead and just
get this out of the way. Oops, let me make it a fair fight. Green flag. All right, I unfortunately am
still moving one pixel at a time, so this isn't going to end well. It caught up to me. And if we're really aggressive and
do something like 20 steps at a time, click the green flag. Jesus, OK, so that's how you might
then make your levels progressively harder and harder. So it's not an accident that we
chose these particular examples here involving these particular
schools because we have one more demonstration we thought
we'd introduce today if we could get one other
volunteer to come up and play what was called by one of your
predecessors Ivy's Hardest Game. Let's see, you in the middle. Do you want to come on up? What's your name? AUDIENCE: Celeste. DAVID MALAN: Say again? AUDIENCE: Celeste. DAVID MALAN: Come a
little closer, actually. Sorry, hard to hear here. All right, round of applause
here if we could, too. [APPLAUSE] OK, sorry, what was your name? AUDIENCE: Celeste. DAVID MALAN: Ceweste AUDIENCE: Celeste. DAVID MALAN: Celeste. AUDIENCE: Yes. DAVID MALAN: Come on over. Nice to meet you, too. So here we have on this other
screen Ivy's Hardest Game written by a former CS50 student. I think you'll see that it
combines these same principles. The maze is clearly a
little more advanced. The goal at hand is to initially move
the Harvard crest to the sprite all the way on the right so that you
catch up to him in this case, but you'll see that
there's different levels and different levels of sophistication. So if you're up for it, you can use just
these arrow keys up, down, left, right. You'll be controlling the
Harvard sprite and if we could raise the volume just a little
bit, we'll make this our final example. Here we go, clicking the green flag. [MUSIC PLAYING] Feeling ready? AUDIENCE: Yep. DAVID MALAN: Spacebar. [MUSIC - MC HAMMER, "U CAN'T TOUCH
THIS"] MC HAMMER: (SINGING) Can't touch this. You can't touch this. You can't touch this. Can't touch this. My, my, my, my music-- DAVID MALAN: Excellent. MC HAMMER: (SINGING) so hard. Makes me want to say, oh my Lord. Thank you for blessing me-- DAVID MALAN: Two Yales now. MC HAMMER: (SINGING) Feels
good when you know you're down. A super dope homeboy-- AUDIENCE: Oh! DAVID MALAN: Oh! Keep going. MC HAMMER: (SINGING)
You can't touch this. I told you, homeboy. Can't touch this. Yeah, that's how living-- DAVID MALAN: All right. MC HAMMER: (SINGING) Can't touch this. Look at my eyes, man. You can't touch this. You let me bust the funky lyrics. You can't touch this. Fresh new kicks and pants. You got it like that and
you know you want to dance. So move out of your seat and get
a fly girl and catch this beat. [LAUGHING] Hold on. Pump a little bit and let them know
what's going on like that, like that. Cold on a mission, so fall on back. Let them know that you're too-- DAVID MALAN: There you go. There you go. [APPLAUSE] MC HAMMER: (SINGING) Can't touch this. Why you standing there, man? You can't touch this. Yo, sound the bell. School's in, sucker. Can't touch this. Give me a song or rhythm, making
them sweat that's what give them. [CHEERING] They know. You talking the Hammer when
you're talking about a show. That's hyped and tight. Singers are sweating so them
a wipe or a tame to learn. DAVID MALAN: Second to last level. Oh! MC HAMMER: (SINGING) That chart's legit. Either work hard or
you might as well quit. That word because you know-- DAVID MALAN: Oh! Keep going, keep going! Yes! MC HAMMER: (SINGING)
You can't touch this. DAVID MALAN: You're almost there. MC HAMMER: (SINGING) Break it down. DAVID MALAN: There you go. Go, go, go! Oh. One more. Yes! [CHEERING] There you go. MC HAMMER: (SINGING) Stop, Hammer time. "Go with the flow," it is said. If you can't groove to this,
then you're probably dead. So wave your hands in the
air, bust a few moves, run your fingers through your hair. This is it. For a winner. Dance to this and you're
going to get thinner. Now move, slide your rump. Just for a minute let's all do the bump. [CHEERING] DAVID MALAN: Yes! [APPLAUSE] Congratulations. All right, that's it for CS50. Welcome to the class. We'll see you next time. [MUSIC PLAYING] DAVID J. MALAN: All right. So this is CS50. And this is week 1, the one in which
you learn a new language, which is something we technically said
last week, at least if you had never played with this graphical language
known as Scratch before, which itself was a programming language. But today, as promised,
we transition to something a little more traditional,
a little more text-based, not puzzle piece- or
block-based, known as C. This is an older language. It's been around for decades. But it's a language that underlies so
many of today's more modern languages, among them something called
Python that we'll also come to in a few weeks' time. Indeed, at the end of
the semester, the goal is for you to feel that
you've not learned Scratch, you've not learned C, or
even Python, for that matter, but fundamentally that you've
learned how to program. Unfortunately, when you
learn how to program with a more traditional language like
this, there's just so much distraction. Last week I described all of the
syntax, all of the weird punctuation that you see in this, like the
hash symbol, these angled brackets, parentheses, curly braces,
backslash n, and more. Well, today we're not going to reveal
what all of those little particulars mean. But by next week, will this no
longer look like the proverbial Greek to you, a language that, presumably,
you've never actually seen or typed before. But to do that, we'll explore some
of the very same topics as last week. So recall that, via Scratch-- and
presumably via problem set 1-- we took a look at things called
functions that are actions or verbs. And related to functions
were arguments like inputs. And related to some functions
were returned values like outputs. Then we talked a bit about conditionals,
forks in the road, so to speak, Boolean expressions, which are
just yes/no questions or true/false questions, loops, which let you do
things again and again, variables, like in math, that let you
store values temporarily, and then even other topics still. So if you were comfortable on the
heels of problem set 0 and last week, realize that all of these topics
are going to remain with us. So really, today is just about acquiring
all the more of a mental model for how you translate those ideas into,
presumably, a very cryptic new syntax-- a new syntax, frankly,
that's actually more simple in some ways than
your own human language, be it English or something else, because
there's far fewer vocabulary words. There's actually far less
syntax that you might have in, say, a typical human language. But you need to be with these computer
languages all the more precise so that you're most,
ultimately, correct, and ultimately will see to your code
is successful along a few other lines as well. So if you think about the last time
you kind of wandered around not really knowing what you were doing
or encountered something new-- might not have been that long ago,
entering Harvard Yard for the very first time, or Old Campus or the like,
be it in Cambridge or New Haven-- you didn't really need to know how
to do everything as a first year. You didn't need to
know who everyone was, where everything was, how Harvard or
Yale, or anything else for that matter, worked. You sort of got by day to day by just
focusing on those things that matter. And anything you didn't
really understand, you sort of turned a blind
eye to until it's important. And that's, indeed, what
we're going to do today. And really, for the next
several weeks, we'll focus on details that
are initially important and try to wave our hands, so to speak,
at details that, yeah, eventually we'll get to, might be interesting. But for now, they might be distractions. And by distractions, I really
mean some of that syntax to which I alluded earlier. So by the end of today-- and
really, by the end of problem set 1, your first foray, presumably,
into this language called C-- you'll have written some code. And you'll be asking yourself--
we'll be asking yourselves-- just how good is that code? Well, first and foremost, per
last week, be it in Scratch or phone book form, code ultimately
needs to be correct, to be well done. You want the problem
to be solved correctly. So that one sort of goes without saying. And along the way this term, we'll
provide you with tools and techniques so you don't have to just sit there
sort of endlessly trying an input, checking the output, trying
another input, checking the output. There's a lot of automation
tools in the real world-- and in this class and
others like it-- that will help facilitate you answering
that question for yourself, is my code correct, according to our
specifications or the like. But then something that's
going to take more time and you're probably not going to feel
100% comfortable with the first week, the first weeks, is just how
well designed your code is. It's one thing to speak
English or write English, but it's another thing-- or
any language, for that matter. But it's another thing to
speak it or write it well. And we spend all these years in middle
school, high school, presumably, writing papers and other documents,
getting grades and feedback on them as to how well formulated your
arguments were, how well structured your paper was, and the like. And there's that same
idea in programming. It doesn't matter necessarily that
you've just solved a problem correctly. If your code is a complete visual
mess, or if it's crazy long, it's going to be really
hard for someone else to wrap their mind around what your code
is doing and, indeed, to be confident if it is correct. And honestly, you-- the
next morning, the next year, the next time you look at
that code-- might have no idea what you yourself were even thinking. But you will if you focus,
too, on designing good code, getting your algorithms efficient,
getting your code nice and clean, and even making sure your
code looks pretty, which we'd describe as a matter of style. So in the written human world, having
punctuation in the right place, capitalization and the like-- the
sort of way you write an essay but not necessarily
send a text message-- relates to style, for instance. And so good style in
code is going to have a few of these characteristics that are
pretty easily taught and remembered. But you just have to start to get in the
habit of writing code in a certain way. So these three axes, so to speak,
correctness, design, and style, are really the overarching
goals when writing code that ultimately is going to look like this. So this program we
conjectured last week does what if you run it on a Mac or
PC or somewhere else, presumably? What does it do? Yeah? AUDIENCE: [INAUDIBLE]. DAVID J. MALAN: It just
prints, Hello, world. And honestly, that's kind
of atrocious that you need to hit your keyboard keys this
many times with this cryptic syntax just to get a program to say, Hello, world. So a spoiler-- in a
few weeks' time when we introduce other, more modern
languages, like Python, you can distill this same logic
into literally one line of code. And so we're getting there, ultimately. But it's helpful to understand
what it is that's going on here, because even though this
is a pretty cryptic syntax, there's nothing after this week and,
really, next week that you shouldn't be able to understand even about
something that right now looks a little something like this. So how do you write code? Well, I've given us sort
of the answer to a problem. How do you print, Hello,
world, on the screen? So what do I do with this code? Well, we're in the habit of typically
writing things with, like, Microsoft Word or Google documents. And yeah, I could open up Word or
Google Docs or Pages or the like and just literally transcribe
that character for character, save it, and boom, I've got a program. But the problem, per last week, is
that computers only understand or speak what other language, so to speak? AUDIENCE: [INAUDIBLE]. DAVID J. MALAN: Yeah, so
binary, zeros and ones. And so this, obviously,
is not zeros and ones. So it doesn't matter if I put it in
a Word doc, Google Doc, Pages file, or the like. The computer is not going to
understand it until I somehow translate it to zeros and ones. And honestly, none of those
tools that I rattled off are really appropriate for programming. Why? Well, they come with features
like bold facing and italics and sort of fluffy, aesthetic stuff
that has no functional impact on what you're trying to do with your code. And they don't have
the ability, it would seem, to convert that code
ultimately to zeros and ones. But tools that do have
this capability might be called Integrated Development
Environments, or IDEs, or, more simply, text editors. A text editor is a tool that a
programmer uses perhaps every day to write their code. And it's a simple program-- here,
for instance, a very popular one called Visual Studio Code, or VS Code. And at the top here, you
see that I've actually created in advance before class a very
simple empty file called "hello.c." Why? Well, .c indicates by convention that
this is going to be a file in which there is C code. It's not .docx, which would mean in
this file is a Microsoft Word document, or .pages is a Pages file. This is .c, which means in this file is
going to be text in the language called C. This number 1 here is just an
automatic line number that's going help me keep track of how long
or short this program is. And the cursor is just
blinking there, waiting for me to start typing some code. Well, let me go ahead and type
out exactly the same code. For me, it comes pretty
comfortably from memory. So I'm going to go ahead and include
something called standardio.h-- more on that later. I'm going to magically type int
main(void), whatever that means-- we'll come back to that later-- one of these curly braces and then a
sibling there that closes the same. Then I'm going to hit Tab
to indent a few spaces. And then I'm going to type not print,
but printf, then "Hello, world," /n, close quote, close
parenthesis, semicolon. And I dare say this was essentially
the very first program I wrote some 25 years ago. I wrote it to say, "Hi, CS50." Now it just says the more canonical,
conventional, "Hello, world." But that's it. That's my very first program. And all I need to now do is
maybe hit Command-S or Control-S to save the file. And voila, I am a programmer. The catch though, is,
OK, how do I run this? Like, on your Mac or PC,
how do you run a program? Well, usually double-click an icon. On your phone, you tap an icon. In this environment that we're using
and that many programmers-- dare say most programmers-- use, you don't
have immediately a nice, pretty icon to double-click on. That's very user friendly,
but it's not very necessary. Especially when you get more
comfortable with programming, you're going to want to type commands
because it's just faster than pointing and clicking a mouse. And you're going to
want to automate things, which is a lot easier if it's all
command or text-based, as opposed to mouse and muscular movements. And so here I have my program. It lives in this file called "hello.c." I need to now convert it,
though, to zeros and ones. Well, how do I go about doing this,
and how am I going to get from this so-called code-- or source code, as it's
conventionally called-- to this, these zeros and ones that
we'll now start calling machine code. The zeros and ones from last
week can be used not only to represent numbers and letters,
colors, audio, video, and more. It can also represent instructions to a
computer, like print, or play a sound, or delete a file, or save a file. All the sort of basics
of a computer somehow can be represented by other
patterns of zeros and ones. And just like last week,
it depends on the context in which these numbers are stored. Sometimes they're interpreted as
numbers, like in a spreadsheet. Sometimes they're interpreted as colors. Sometimes they're interpreted as
instructions, commands to your computer to do very low-level operations,
like print something on the screen. So fortunately, last week's definition
of computer science of problem solving is a nice mental model for
exactly the goal at hand. I have some input, AKA source code. I want to output ultimately
machine code, those zeros and ones. I certainly don't want to do
this kind of process by hand. So hopefully there's an algorithm
implemented by some special program that does exactly that. And those of you who do
have some prior experience, this program might be called a? A compiler. So a few of you have,
indeed, programmed before. Not all languages use compilers. C, in fact, is a language
that does use a compiler. And so I just need to find myself-- on my computer somewhere,
presumably-- a so-called compiler, a program whose purpose in life is
to convert one language to another. And source code written textually
in C, like we saw a moment ago, is source code. The machine code is the
corresponding zeros and ones. So let me go back to the same
programming environment called Visual Studio Code or VS Code. This is typically a program you
or any programmer on the internet can download onto their own Mac or
PC and be on their way with whatever computer you own writing some code. A downside, though, of that
approach is that all of us have slightly different
versions of Macs or PCs. We have slightly different
versions of operating systems. They may or may not be up to date. It's just a technical support nightmare
to create a uniform environment, especially for an introductory
class, where everyone should ideally be on the same page so we can
get you up and running quickly. And so I'm actually using a cloud-based
version of VS Code, something that you only need a browser to access. And then you can be on any
computer, today or tomorrow. By the end of the semester, we're
going to get you out of the cloud, so to speak, as best we can and
get you onto your own Mac or PC, so that after this class, especially if
it's the only CS class you ever take, you feel like you can continue
programming in any number of languages, even with CS50 behind you. But for now, wonderfully, the
browser version of VS Code should pretty much be identical
to what the eventual downloadable version of the same would be. And you'll see in problem
set 1 how to access this and how to get going yourself
with your first programs. But I haven't mentioned this
bottom part of the screen, this bottom part of the screen. And this is an area where we have
what's called a terminal window. So this is sort of old-school technology
that allows you, with a keyboard, to interact with a computer, wherever
it may be-- on your lap, in your pocket, or even, in this case, in the cloud. So on the top-hand
portion of this screen is my text editor, like tabbed
windows, like in many programs, where I can just create files and write code. The bottom of the screen here,
my so-called terminal window, gives me the ability to
run commands on a server that currently I have
exclusive access to. So because I logged into VS
Code with my account online, I have my own sort of virtual
server, if you will, in the cloud-- otherwise known as, in
this context, a container. This has its own operating system
for me, its own hard drive, if you will, where I can save
and create files of my own, separate from yours and vice versa. And it's at this very
simple prompt, which is conventionally-- but not always--
abbreviated by a dollar sign, has nothing to do with currency. It just means, type your commands here. This is where I'm going to
be able to type commands, like compile my source
code into machine code. So it's a Command Line Interface, or
CLI, on top of an operating system that you might not have ever used
or seen, but it's very popular, called Linux. Odds are almost all of us in this room
are using Mac OS or Windows right now, but we're all going to start using an
operating system called Linux, which is in a family of operating systems
that offer not only this command line interface, but are used not
just for programming, but for serving websites and developing
applications and the like. And it's, indeed, a familiar and very
powerful interface, as we'll see. So how do I go about making this
file, hello.c, into a program? There's no icon to double-click,
but there is a command. I can type, make hello, at this dollar
sign prompt, go ahead and hit Enter, and nothing appears to happen. But that's a good thing. And as we'll see in
programming, almost always, if you don't see anything go wrong,
that means everything went right. So this is going to
be a rarity at first, but this is a good thing that
it just seems to do nothing. But now there is in the
folder in my accounts in this on the cloud
a file called "hello." And it's a bit of a weird command,
but you'll get familiar with it before long. . just means go into my current folder. /hello means run the program called
"hello" in this current folder. So ./hello, and then Enter, and voila,
now I'm actually not just programming, but running my actual code. So what have I just done? Let me go ahead and do this. I'm going to go ahead and open
up the sidebar of this program, and you'll see in problem
set 1 how to do this. And this might look a little different
based on your own configuration. Even the color scheme I'm using might
ultimately look different from yours, because it supports a
nice colorful theme. So you can have different colors
and brightnesses depending on your mood or the time of day. What I've opened here, though, is
what is called in VS Code Explorer, and this is just all of the
files in my cloud account. And there's not many right now. There's only two. One is the file called
hello.c, and it's highlighted because I've got it open right there. And the other is a file called
"hello," which is brand new and was created when I ran that command. And what's now worth noting is that
now things are getting a little more like Mac OS and Windows. Like on the left-hand side, you have
a GUI, a Graphical User Interface. But on the bottom here, again, you
have a CLI, Command Line Interface. These are just different ways
to interact with computers, and you'll get comfortable with both. And honestly, you're certainly familiar
and comfortable with GUIs already, so it's the command line one
with which we'll spend some time. Now suppose that I just
wanted to do something more than compile this program. Suppose I wanted to go
ahead and remove it. Like, uh-uh, no, I made a mistake. I want to say, "Hello,
CS50," not "Hello, world." I could just hover up here, like in
any software, and I could right-click, and I could poke around, and
there, delete permanently. So most of us might have
that instinct on a Mac or PC. You right-click or Control-click,
and you poke around. But in a command line interface,
let me do this instead. The command for removing
or deleting a file in the world of Linux, this
other operating system, is just a type rm for remove,
and then "hello," Enter. It's a somewhat cryptic confirmation
message, but this just means, are you sure? I'm going to go ahead
and type Y for Yes. And now when I hit
Enter, watch what happens at top left in the Explorer, the
GUI, the graphical interface. Voila, it disappears. Not terribly exciting,
but this just means this is a graphical version
of what we're seeing here. And in fact, if you want to
never use the GUI again-- I'll go ahead and close it
with a keyboard shortcut here-- you can forever just type
ls for list and hit Enter. And you will see in the
command line interface all of the files in your current folder. So anything you can do
with a mouse, you can do with this command line interface. And indeed, we'll see many more
things that you can do as well. But the inventors of this, this
operating system and its predecessors, were very succinct. Like, the command is rm for remove. The command is ls for list. It's very terse. Why? Because it's just faster to type. So before we forge ahead with making
something more interesting than just "Hello, world,"
let me pause here to see if there's questions on
source code or machine code or compiler or this
command line interface. Yeah? AUDIENCE: [INAUDIBLE]. DAVID J. MALAN: Really good
question, and let me recap. If I were to make
changes to the program, run it, and then maybe make other
changes and try to rerun it, would those changes be reflected,
even though I've reworded slightly. Well, let's do this. I already removed the old version. So let me go ahead and point
out that if I do ./hello now, I'm going to see some kind of error
because I just deleted the file. No such file or directory, so
it's not terribly user friendly, but it's saying what the problem is. Let me go ahead and remake
it by typing make hello. Now if I type ls, I'll see not one
but two files again, and one of them is even green with a little asterisk
to indicate that it's executable. It's sort of the textual
version of something you could double-click
in our human world. So now, of course, if I run hello, we're
back where I started, "Hello, world." But now suppose I change it to
"Hello, CS50," like I did years ago. Let me go ahead and save the file with
Command-S or Control-S. Down here now, let me run ./hello again, and voila. Huh. So let me ask someone else
to answer that question. What's the missing step? Why did it not say, "Hello, CS50." Yeah? AUDIENCE: [INAUDIBLE]. DAVID J. MALAN: Yeah, so
I didn't compile it again. So sort of newbie mistake, you're going
to make this mistake and many others before long. But now let me go ahead
and remake hello, enter. It's going to seemingly
make the same program. But this time when I run
it, it's, "Hello, CS50." Any other questions on some
of these building blocks? And we'll come back to all the
crazy syntax I typed before long. But for now, we're focusing
on just the output. Yeah? AUDIENCE: [INAUDIBLE]. DAVID J. MALAN: When
I keep running make, it creates a new version
of the machine code. So it keeps changing the hello program
and the hello file, and that's it. There's no make file, per se. AUDIENCE: [INAUDIBLE]. DAVID J. MALAN: Good question, no. If I open up that directory, you'll
see that there's just the one. And it doesn't matter how
many times I run make hello-- three, four, five-- it just
keeps overwriting the original. So it's kind of like just saving in
the world of Google Docs or Microsoft Word or the like. But there's an additional step today. We have to then convert my words to
the computer's, the zeros and ones. Yeah, in front. AUDIENCE: [INAUDIBLE]. DAVID J. MALAN: Oh, what
happens if I run hello.c? So let me go ahead and do ./hello.c,
which is a mistake you'll invariably make early on. Permission denied. So what does that mean? This is where the error
messages mean something to the people who designed the operating
system, but it's a little cryptic. It's not that you don't
have access to the file. It means that it's not executable. This is not something you
have permission to run, but you do have permission to read
or write it-- that is, change it. AUDIENCE: [INAUDIBLE]. DAVID J. MALAN: Oh,
really good question. So if I have named my file, hello
dot C, or more generally something dot C, of the things that
Make does is it automatically picks the file name for me. And we'll discuss a bit-- we'll discuss this a bit more next week. Make itself-- is kind of the
first of white lies today-- itself is not a compiler. It's a program that knows how to find
and use the compiler on the system and automatically create the program. If I use, as we'll discuss next
week, the actual compiler myself, I have to type a much longer sequence
of commands to specify explicitly what do I want the name
of my program to be. Make is a nice program,
especially in week 1, because it just automates
all of that for us. And so here, we have now a
program that very simply prints something on the screen. So let's not put this
into the context of where we left off last time in the context
of Scratch and inputs and outputs. So we discuss the last time, of
course, functions and arguments. Functions, again, are those actions and
verbs like say, or ask, or the like. And the arguments were the
inputs to those functions, generally in those little white
ovals that, in Scratch, you could type words or numbers into. We'll see, in all of the languages
we're going to see this term, have that same capability. And let's just start to translate
one of these things to another. So for instance, let's put
this same program in C, in the context of Scratch. This is what Hello, World looked like
last week in the form of one function. This week, of course,
it looks like print. And then the parentheses,
notice, are kind of deliberately designed in the world of
Scratch to resemble that same shape. Even though this is a
white oval, you kind of get that it's kind of evoking that
same idea with the parentheses. Technically the function
in C, it's not called say. It's not even called print. It's called printf. The F stands for formatted, but we'll
see what that means in a moment. But printf is the closest
analogous function for say in the world of
C. Notice if, though, you want to print something like
Hello, World or Hello CS50 in C, you don't just write the
words as we did last week. You also had an add what,
if you notice already what's missing from this version. Yeah, so the double quotes
on the left and the right. So, that's necessary in C whenever
you have a string of words. And I'm using that word deliberately. Whenever you have multiple words
like this, this is known as a string as we'll see. And you have to put it in double
quotes, not single quotes. You have to put it in double quotes. There's one other stupid thing
that we need to have in my C code in order to get this function to do
something ultimately, which is what? Semicolon. So just like in our human
world, you eventually got into the habit of using, at
least in formal writing, periods. Semicolon is generally what
you use to finish your thought in the world of programming with C. All right, so we have
that function in place. Now, what does this really fit
into in terms of the mental model? Well, functions take arguments. And it turns out functions can
have different types of outputs. And we've actually seen
both already last week. One type of output from a function
can be something called a side effect. And it generally refers
to something visual, like something appearing on the screen
or a sound playing from your computer. It's sort of a side effect of
the function doing its thing. And indeed, last week we saw this in
the context of passing in something like Hello, World as
input to the say function. And we saw on the screen Hello,
World, but it was kind of a one off. It's one and done. You can't actually do anything
with that visual output other than consume it,
visually, with your human eyes. But sometimes, recall last week, we
had functions like the ask block that actually returned me some value. Remember the ask, what's your name. It handed me back whatever
answer the human typed in. It didn't just arbitrarily
display it on the screen. The cat didn't necessarily
say it on the screen. It was stored, instead, in that special
variable that was called answer. Because some functions have not
side effects but return values. They hand you back an output
that you can use and reuse, unlike the side effect, which,
again displays and that's it. You can't sort of catch
it and hold on to it. So, in the context of last
week, we had the ask block. And that had this special
answer return value. In C, we're going to
see in just a moment, we could translate this as follows. The closest match I can
propose for the ask block is a function that we're going
to start calling get string. String is, again, a word, a
set of words, like a phrase or a sentence in programming. It, too, is a function insofar as
it takes input and pretty much-- this isn't always true--
but very often when you have a word in C followed by an open
parenthesis and a closed parenthesis, it's most likely the name of a function. And we're going to see that
there's some exceptions to that. But for now this indeed
looks like a function because it matches that pattern. If I want to ask the question,
what's your name, question mark-- and I'm even going to deliberately
put a space there just to kind of move the cursor a little bit over so that
the human isn't typing literally after the question mark. So that's just the nitpicky aesthetic. This is perhaps the closest analog
to just asking that question. But because the ask
block returns a value, the analog here forget string is
that it, too, returns a value. It doesn't just print the human's input. It hands it back to you in the form
of a variable, a.k.a. return value, that I can then use and reuse. Now ideally it would be as
simple as this literally saying answer on the left equals. And this is where
things start to diverge from math and sort of our human world. This equal sign, henceforth,
is not the equal sign. It is the assignment operator. To assign a value means to
store a value in some variable. And you read these things,
weirdly, right to left. So here is a function called get string. I claim that it's going
to return to you whatever the human types in as their name. It's going to get stored
over here on the left because of this so-called assignment
operator, that yes is an equal sign. But it doesn't mean
equality in this context. It makes things equal. But it does so by copying the value on
the right into the thing on the left. Unfortunately, we're not
quite done yet with C. And this is where, again, it
gets a little annoying at first where Scratch just let us express
our ideas without so much syntax. In C when you have a
variable you don't just give it a name like you did in Scratch. You also have to tell the computer
in advance what type of value it is storing. String is one such type of value. Int, for integer, is
going to be another. And there's even more than that
we'll see today and beyond. And this is partly an answer to the
question that came up one or more times last week, which was how does a computer
distinguish this pattern of zeros and ones from this. Like is this a letter, a number,
a color, a piece of video. And I just claimed last week that
it totally depends on the program. It depends on the context. And that's true. But within those
programs, it often depends on what the human programmer
said the type of the value is. If this specifies that
the string, which means interpret the following
zeros and ones that are stored in my program as
words or letters, more generally. If it's an int for integer, it would
be implying, by the programmer, treat the following zeros and
ones in my program as a number, an integer, not a string. So here's where this
week, unlike with Scratch, which is kind of figures out what you
mean, with C in a lot of languages you have to be this pedantic
and tell it what you mean. There's still one stupid thing
missing from my code here. What's still missing here? Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: And we still
need the stupid semicolon. And I'm sort of impugning it here. Because honestly, these are
the kinds of stupid mistakes you're going to make today,
tomorrow, this weekend, next week, a few weeks from now, until you
start to notice this and recognize it as well as you do English or
whatever your spoken language is. Yeah, question. Good question. Suppose I mix apples and
oranges, so to speak, and I try to put a string in
an int or an int in a string, the compiler is going to complain. So when I run that make
command as I did earlier, it's not going to be nice and blissfully
quiet and just give me another prompt. It's going to yell at me
with honestly a very cryptic looking error message until we get
the muscle memory for reading it. Other questions. Ah, what happened to the backslash n. So, we'll come back to that
in just a moment, if we may. Because I have deliberately omitted
it here but we did have it earlier. And we'll see the different
behavior in a sec. Other questions. Yeah, not at all nitpicky. These are the kinds of
things that just matter. And it's going to take time to recognize
and develop this muscle memory. Everything I've typed here except,
for the W at the moment, is lowercase. And the W is capitalized
just because it's English. Everything else is lowercase. And this kind of varies by
language and also context. So, in many languages the convention
is to use all lowercase letters for your variable names. Other languages might use
some capitals, as well. But we'll talk about that before long. But this is the kind
of thing that matters and is hard to see at first, especially
when a little S doesn't look that different when it's on your tiny
laptop screen from a capital S. But you'll start to
develop these instincts. All right, so besides
this particular block, let's go ahead and consider how we can
go about implementing this now in code. So let me switch back to VS Code here. This was the program I had earlier. And let me go ahead and
undo my CS50 change. And this time just rerun it. Rerun Make on Hello with the original
version with the backslash n. Enter, nothing bad
seems to have happened. So dot slash Hello, enter Hello, World. Now, if you're curious,
this is a good instinct to start to acquire what
happens if I get rid of this. Well, I'm probably not going
to break things too badly. So let's try. Let me go ahead now and do Make Hello. Still compile. So it's not a really bad mistake. So let me go ahead and
run dot slash Hello. What's the difference here? Yeah, what do you see that's different? Yeah, the dollar sign, my so-called
prompt, stayed on the same line. Why? Well, we can presumably
infer now that the backslash n is some fancy notation for
saying create a new line, move the cursor, so to
speak, to the next line. Notice that the cursor will move to
the next line in my terminal window. If I keep hitting it,
it just automatically, by nature of hitting enter, does it. But it'd be kind of stupid if when
you run a program in this world, simple as it is, if
the next command is now weirdly spaced in the middle of
the terminal with the dollar sign, it just looks sloppy. It's really just an aesthetic argument. And notice that it's not acceptable or
correct to do this, to hit enter there. Let me go ahead and save that,
though, and see what happens. Let me go ahead now and
run Make Hello enter. Oh my god, like four errors. This is like, what, 10 lines of
errors for a one line program. And this is where, again, you'll start
to develop the instincts for just reading this stuff. These kinds of tools, like
the compiler tool we're using, were not designed necessarily
with user friendliness in mind. That's changed over the
decades, but certainly early on it's really just meant to be
correct and precise with its errors. So what did I do here? Missing terminating
close quote character, long story short, when
you have a string in C, your double quotes just have to
be on the same line just because. Now, there's the slight white lie. There's ways around this. But the best way around it is to
use this so-called escape sequence. To escape something means generally
to put a backslash, and then a special symbol like n for new line. And this is just the agreed upon way
that humans, decades ago, decided, OK you don't just hit your enter key. You instead put backslash n
and that tells the computer to move the cursor to the new line. So again, kind of cryptic. But once you know it, that's it. It's just another word
in our vocabulary. So now let me transition to making
my program a little more interactive. Instead of just saying
Hello, world, let me change it like last week
to say Hello, David, or whoever is interacting
with the program. So I'm going to do string
answer gets, get string, quote unquote, what's your name. I'm not going to bother
with a new line here. I could. This is now just a judgment call. I deliberately want the human to
type their name on the same line just because. And how do I now print this? Well last week recall we used say. And then we use the
other block called join. So the idea here is the same. But the syntax this week is
going to be a little different. It's going to be printf, which
prints something on the screen. I'm going to go ahead
and say Hello comma. And let me just go with this initially
with the backslash n, semicolon. Let me go ahead and recompile my code. Whoops, damn doesn't work still. And look at all these errors. There's more errors than code I wrote. But what's going on here? Well, this is actually
something, a mistake you'll see, somewhat often, at least initially. And let's start to glean
what's going on here. So here, if I look at the very first
line of output after the dollar sign-- so even though it jumped
down the screen pretty fast, I wrote Make Hello at
the dollar sign, prompt. And then here's the first error. On Hello dot C, line 5-- technically character 5, but generally
line is enough to get you going-- there's an error, use of
undeclared identifier string. Did you mean standard in? So, I didn't. And this is not an
obvious solution at first. But you'll start to recognize
these patterns in error messages. It turns out that if I want to use
string, I actually have to do this. I have to include another library
up here, another line of code, rather, called CS50
dot H. We'll come back to what this means in just a moment. But if I now retroactively say,
all right, what does standard I/O do for us up here. Before I added that new line,
what is standard I/O doing? Well, if you think
back to Scratch, there were a few examples with the camera and
with the speech to-- the text to voice. Remember I had to poke around
in the extensions button. And then I had to load it into Scratch. It didn't come natively with Scratch. C is quite like that. Some functions come with the language. But for the most part, if you want to
use a function, an action or a verb like printf, you have to load
that extension, so to speak, that more traditionally
is called a library. So there is a standard I/O
library, STD I/O, standard I/O, where I/O just means input and output. Which means, just like
in MIT's World, there was an extension for doing text
to voice or for using your camera. In C, there's an extension, a.k.a. a library, for doing
standard input and output. And so if you want to use any functions
related to standard input and output, like text from a keyboard, you
have to include standard I/O dot H. And then can you use printf. Same goes here. Get string, it turns out, is a
function that CS50 wrote some time ago. And as we'll see over
the coming weeks, it just makes it way easier to
get input from a user. C is very good with printf at
printing output on the screen. C makes it really annoying and
hard, as we'll see in a few weeks, to just get input from the user. So we wrote a function
called get_string, but the only way you can use
that is to load the extension, a.k.a. load the library called CS50. And we'll come back in time, like,
why is it .h, why is it a hash symbol. But for now, standard
I/O is a library that gives you access to printf and
input- and output-related stuff. CS50 is a second library
that provides you with access to functions
that don't come with C that include something like get_string. So with that said, we've
now kind of teased apart at a high level what lines
2 and now 1 are doing. Let me go ahead and rerun make hello. Now it worked. So all those crazy error messages
were resolved by just one fix, so key takeaway is not to get
overwhelmed by the sheer number of errors. Let me now do ./hello and if I type
in my name, what am I going to say? What do you think? Yeah, hello answer, because the
computer is going to take me literally. And it turns out that if
you just write "hello, answer" all in the double quotes,
you're really just passing English as the input
to the printf function, you're not actually
passing in the variable. And unfortunately in
C, it's not quite as easy to plug things in to
other things that you've typed. Remember in Scratch, there
was not just the Save block but the Join block,
which was kind of pretty, you can combine apples and oranges-- or was it apple and banana? Then we changed it to hello and then
the answer that the human typed in. In C, the syntax is going
to be a little different. You tell the computer inside of your
double quotes that you want to have a placeholder there, a so-called
format code. %s means, hey, computer, put a string here eventually. Then outside of your quotes, you
just add a comma and then you type in whatever variable you want the
computer to plug in at that %s location for you. So %s is a format code which
serves as a placeholder. And now the printf function
was designed by humans years ago to figure out how to
do the apple and banana thing of joining two words together. It's not nearly as user-friendly
as it is in Scratch, but it's a very common paradigm. So let me try and rerun
this now. make hello. No errors, that's good. ./hello. What's my name, David? If I type Enter now, now it's hello. David. And the printf, here's the F in printf. It formats its input for you by using
these placeholders for things like strings, represented again by %s. So a quick question then, if I focus
here on line 7 for just a moment and even zoom in here, how many
inputs is printf taking as a function? A moment ago, I'll admit that it was
taking one input, "hello, world," quote unquote. How many inputs might you
infer printf is taking now? 2. And it's implied by this comma here,
which is separating the first one, quote, unquote, "hello, %s"
from the second one, answer. And then just as a quick safety
check here, why is it not 3? Because there's obviously
two commas here. Why is it not actually
3 arguments or inputs? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Exactly. The comma to the left is actually
part of my English grammar, that's all, so same syntax. And, again, here's where
programming can just be confusing early on because we're using the
same special punctuation to mean different things, it just
depends on the context. And so now is actually
a good time to point out all of the somewhat pretty colors that
have been popping up on the screen here-- even though I wasn't going to a format
menu, I wasn't boldfacing things, I certainly wasn't changing
things to red or blue or whatnot-- that's because a text editor like
VS Code syntax highlights for you. This is a feature of so many different
programming environments nowadays, VS Code does it as well. If your text editor understands the
language that you're programming in-- C, in this case-- it highlights in different colors the
different types of ideas in your code. So, for instance, string and
answer here are in black, but get_string a function is in
this sort of nasty brown-yellow here right now, but that's just
how it displays on the screen. The string, though, here in red
is kind of jumping out at me, and that's marginally useful. The %s is in blue. That's kind of nice, because
it's jumping out at me. And so it's just using different colors
to make different things on the screen pop so you can focus on
how these ideas interrelate and, honestly, when you
might make a mistake. For instance, let me accidentally
leave off this quote here. And now all of a sudden,
notice if I delete the quote, the colors start to get a little awry. But if I go back there and put it
back, now everything's back in place. What's another feature
of this text editor? Notice when my cursor is next
to this parenthesis, which demarcates the end of the
inputs to the function, notice that highlighted in green
here is the opening parenthesis. Why? It's just a visually useful
thing, especially when you start writing more and
more code, just to make sure your parentheses are lining up. And that's true for these curly braces
over here on the left and the right. We'll come back to those in a moment. If I put my cursor there, you can
see that these things correspond to one another. So it's nothing in your code
fundamentally, it's just the editor trying to help you, the human, program. And you can even see it,
though it's a little subtle-- see these four dots here
and these four dots here? That's my indentation. I configured VS Code to
indent by four spaces, which is a very common convention. Any time I hit the Tab key, this
too can help you make sure-- once we have more interesting
and longer programs-- that everything lines
up nice and neatly. Phew. All right, any questions
then on printf or more? Yeah. AUDIENCE: [? Would ?]
the printf [INAUDIBLE]?? DAVID J. MALAN: Short
answer, yes. printf can handle more than one
type of variable or value. %s is one. We're going to see %i is another
for plugging in an integer. You can have multiple i's, multiple
s's, and even other symbols too. We'll come back to that
in just a little bit. printf can take many more
arguments than just these two. This is just meant to be representative. Yeah, over here. Can you declare variables
within the printf? No. The only variable I'm
using right now is answer, and it's got to be done outside
the context of printf in this case. Good question, we'll see
more of that before long. Yeah, in back. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: How do we
download the CS50 library? So we will show you in problems
set 1 exactly how to do that. It's automatically done for you in
our version of VS Code in the cloud. If, ultimately, you program on your own
Mac or PC, either initially or later on, it's also installable online. But if you want to ask that
via online or afterward, we can point you in the right direction. But PSet 1 will itself. Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: String is the type
of the variable or, more properly, the data type of the variable. int is another keyword I alluded
to earlier, I haven't used it yet. int, for integer, is going to be
another type, or data type, of variable. AUDIENCE: OK. [? Thank you. ?] DAVID J. MALAN: Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Oh, good question. Could I go ahead and just
plug in this function, kind of like we did in Scratch,
getting rid of the variable altogether and just do this, which
recall, is reminiscent of what I did in Scratch by plopping
block on top of block on block? Am I answering that right? Can I put string in front of get_string? No. You only put the word string
in front of a variable that you want to make string. And even though I'm apparently
answering the wrong question, let me go ahead and zoom out,
save this, do make hello again. Seems to compile OK. If I run ./hello, type in David, voila. That, too, works. And so, actually, let's go down
this rabbit hole for just a moment. Clearly, it's still correct-- at least, based on my limited testing. Is this better designed
or worse designed? Let's open that question
like we did last week. Yeah? Yeah, I kind of agree with that. Reasonable people could
disagree, but I do agree that this seems harder to
read because I start reading here, but wait a minute. get_string
is going to get used first, and then it's going to
give me back a value. So, yeah, it just feels like it
was nicer to read top to bottom, I would say. Your thoughts? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah. And so this is useful if I only want
to print out the person's name once. If I want to use it later in a
longer program, I'm out of luck, and so I haven't saved it in a variable. So I think, long story short, we
could debate this all day long. But in this case, eh, if you can
make a reasonable argument one way or the other, that's a
pretty solid ground to stand on. But, invariably,
reasonable people are going to disagree, whether first-time
programmers or many years after that. So let's frame this one last example
in the context of the same process of taking inputs and outputs. The functions we've
been talking about all take inputs, otherwise now known as
arguments, or parameters, pretty much synonymous. That's just the fancy word
for an input to a function. And some functions have either
side effects, like we saw-- printing something, saying
something on the screen, sort of visually or audibly-- or they return a value, which is a
reusable value, like name or answer, in this case. If we look then at what we did last
time in the world of Scratch last week, the input was what's your
name, the function was ask, and the return value was answer. And now let's take a look at this block,
which is honestly a more user-friendly version of what we just did with the %s. Last week we said save, then
join, then hello and answer. But the interesting takeaway there
was not how to say hello anything. It was the fact that in Scratch
2, the output of one function, like the green join, could become
the input to another function, the purple say. The syntax in C is
admittedly pretty different, but the idea is essentially the same. Here, though, we have
hello, a placeholder, but we have to, in this
world of C, tell printf what we want to plug in
for that placeholder. It's just different. But that's the way to do it. When we get to Python and other
languages later in the term, there's actually easier ways to do this. But this is a very common
paradigm, particularly when you want to format
your data in some way. All right, let's then take a
step back to where we began, which was with that whole
program, which had the include and it had int main(void) and
all of this other cryptic syntax. This Scratch piece last week
was kind of like the go-to whenever you want to have a
main part of your program. It's not the only way to
start a Scratch program. You could listen for clicks or other
things, not just the green flag. But this was probably the most popular
place to start a program in Scratch. In C, the closest analog is
to literally write this out. So just like last week, if you were
in the habit of dragging and dropping when green flag clicked,
as a C programmer, the first thing you would do is
after creating an empty file, like I did with hello.c,
you'd probably type int main(void) open curly
brace, closed curly brace, and then you can put all of your
code inside of those curly braces. So just like Scratch had
this sort of magnetic nature to it where the puzzle pieces would snap
together, C, as a text-based language, tends to use these curly braces, one
of them opened, the other one closed. And anything inside of
those braces, so to speak, is part of this puzzle piece, a.k.a. main. So what was atop them? We went down this rabbit hole moment ago
with these things called header files, even though I didn't
call them by this name. But, indeed, when we have a whole
program in Scratch, super easy. Just have the one green flag
clicked and then say hello, world. There's no special syntax. After all, it's meant to be very
user-friendly and graphical. In C, though, you technically can't just
put int main(void) printf hello, world. You also need this. Because, again, you need to tell
the compiler to load the library-- code that someone else wrote-- so that
the compiler knows what printf even is. You have to load the
CS50 library whenever you want to use get_string or
other functions, like get_int, as we'll soon see. Otherwise, the compiler won't
know what get_string is. You just have to do it this way. The specific file name
I'm mentioning here, stdio.h, cs50.h, is what C programmers
called a call a header file. We'll see eventually what's
inside of those files. But long story short, it's like a menu
of all of the available functions. So in cs50.h, there's a menu
mentioning get_string, get_int, and a bunch of other stuff. And in stdio.h, there's a menu of
functions, among which are printf. And that menu is what
prepares the compiler to know how to implement
those same functions. All right, let me pause here. Question. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Not quite. A library provides all of the
functionality we're talking about. A header file is the very specific
mechanism via which you include it. And we'll discuss this more next week. For now, they're essentially
the same, but we'll discuss nuances between the two next week. Yeah, the library would be standard
I/O. The library would CS50. The corresponding header
file is stdio.h, cs50.h. Indeed. Other questions. Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Indeed. That, too, is on the menu. We'll come back to that. But the word string-- incredibly common in the world of
programming, it's not a CS50 idea-- but in C, there's technically no
such data type as string by default. We have sort of conjured it up
to simplify the first few weeks. That's a training wheel that we'll
very deliberately, in a few weeks, take away, and we'll see why we've
been using get_string and string. Because C otherwise makes things
quite more challenging early on, which then gets besides
the point for us. Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yes. Early on, you will have to use whatever
is prescribed by the specification. That will include CS50's functions. Long story short, you referred, I
think, a moment ago to another function called scanf, we won't
talk about for a few weeks. Long story short, in C, it's pretty easy
and possible to get input from a user. The catch is that it's really
easy to do it dangerously. And C, because it's an older,
lower-level language, so to speak, that gives you pretty much ultimate
control over your computer's hardware. It's very easy to make mistakes. And, indeed, that's too
why we use the library, so your code won't crash unintendedly. All right, so with this in
mind, we have this now mapping between the Scratch
version and the other. Let me just give you a quick tour of
some of the other placeholders and data types that students will start seeing as
we assemble more interesting programs. In the world of Linux, here
is a non-exhaustive list of commands with which you'll get
familiar over the next few weeks by playing with problem sets. We've only seen two of these so
far, ls for list, rm for others. But I mention them now
just so that it doesn't feel too foreign when you see them
on screen or online in a problem set. cp is going to stand for copy. mkdir is going to stand
for make directory. mv is going to stand for move or rename. rmdir is going to be remove directory,
and cd is going to be for change / and let me show you this
last one here first, only because it's something
you'll use so commonly. If I go back to my code here on
the screen, I'm going to go ahead and re-open the little GUI on the
left-hand side, the so-called Explorer, revealing that I've got two
files, hello and hello.c so nothing has changed since there. Suppose now that it's
a few weeks into class and I want to start
organizing the code I'm writing so that I have a folder
for this week or next week, or maybe a folder for
problem set 1, problem set 2. I can do this in a few ways. In the GUI, I can go up
here and do what most of you would do instinctively on a Mac or PC. You look for like a
folder icon, you click it, and then you name a
folder like PSet1, Enter. Voila, you've got a folder called PSet1. I can confirm as much with my command
line interface by typing what command? How can I list what's in my folder? Yeah, so ls for list. And now I see hello-- and it's green with an
asterisk because that's my executable, my runnable program-- hello.c, which is my source
code, and now PSet1 with a slash at the end, which just implies
that it's indeed a folder. All right, I didn't really
want to do it that way. I'd like to do it more advanced. So let me go ahead and right-click
on PSet1, delete permanently. I get a scary irreversible
error message. But there's nothing
in it, so that's fine. Now I've deleted it using the GUI. But now let me go ahead and start doing
the same thing from the command line. And if you're wondering how
things keep disappearing, if you hit Control-L in your terminal
window or explicitly type clear, it will delete everything you previously
typed just to clean things up. In practice, you don't need
to be doing this often. I'm doing it just to keep our
focus on my latest commands. If I do-- what was the command
to make a new directory? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah, so
mkdir, make directory. Let me create PSet1, Enter. And notice at left, there's my PSet1. If I want to get a little
overzealous, plan for next week, here's my PSet2 directory. Suppose now I want to open those
folders on a Mac or PC or in this GUI, I could double-click on
it like this, and you'd see this little arrow is moving. It's not doing anything because there's
nothing in there, but that's fine. But suppose again I want to get more
comfortable with my command line. Notice if I type ls now, I
see all four same things. Let me change directories
with cd space PSet1 Enter. And now notice two things
will have happened. One, my prompt has changed
slightly to remind me where I am, just to keep me sane so that I don't
forget what folder I'm actually in. So here is just a visual reminder
of what folder I'm currently in. If I type ls now, what should
I see after hitting Enter? Nothing, because I've only
created empty folders so far. And, indeed, I see nothing. If I wanted to create a folder called
Mario for a program that might be called Mario this week, I can do that. Now if I type ls, there is Mario. Now if I do cd Mario,
notice my prompt's going to change to be a little more precise. Now I'm in PSet1/Mario. And notice what's happening at top left. Nothing now, because these
folders are collapsed. But if I click the little
triangle, there I see Mario. Nothing's going on in there
because there's no files yet. But suppose now I want to
create a file called mario.c. I could go up here, I could click the
little plus icon, and use the GUI. Or I can just type code mario.c. Voila. That creates a new tab for me. I'm not going to write any code in here
yet, but I am going to save the file. And now at top left, you'll
see that mario.c appears. So at some point, you can
eventually just close the Explorer. Because, again, it's not providing
you with any new information. It's maybe more
user-friendly, but there's nothing you can't do at the command
line that you could do with the GUI. All right, but now I'm kind of stuck. How do I get out of this folder? In my Mac or PC world,
I'd probably click the Back button or something like that
or just close it and start all over. In the terminal window,
I can do cd dot dot. Dot dot is a nickname, if you
will, for the parent directory. That is, the previous directory. So if I hit Enter now, notice I'm
going to close the Mario folder, a.k.a. directory, and
now I'm back in PSet1. Or, if I want to be fancy, let me
go back into Mario temporarily. If I type ls, there's
mario.c, just to orient us. If I want to do multiple things
at a time, I could do cd../.. which goes to my parent to my
grandparent all in one breath. And voila, now I'm back in my
default folder, if you will. And one last little trick of the
trade, if I'm in PSet1/Mario like I was a moment ago, and you're
just tired of all the navigation, if you just type cd and
hit Enter, it'll whisk you away back to your default
folder, and you don't have to worry about getting there manually. Recall a bit ago, though, that I
was running hello as this, ./hello. If dot refers to my parent,
perhaps infer here syntactically, what does a single dot mean instead? It means this directory,
your current directory. Why is that necessary? It just makes super
explicit to the computer that I want the program called
hello that's installed here, not in some random other folder
on my hard drive, so to speak. I want the one that's
right here instead. All right, so besides
these commands, there's going to be others that
we encounter over time. Those are kind of the basics. That allows you to wean yourself off
of a GUI, Graphical User Interface, and start using more comfortably,
with practice and time, a command line interface instead. Well, what about those other
types, now back in the world of C? Those commands were not C. Those are
just command-specific to a command line interface, like in Linux, which,
again, we're using in the cloud. It's an alternative
to Mac OS and Windows. Back in the world of C now, we've
seen strings, which are words. I mentioned int or integer,
but there's others as well. In the world of C, we've
seen string, we will see int. If you want a bigger integer, there's
something literally called a long. If you want a single character,
there's something called a char. If you want a Boolean value,
true or false, there is a bool. And if you want a floating-point value-- a fancy way of saying a real number,
something with a decimal point in it-- that is what C and other
languages call a float. And if you want even more numbers
after the decimal point that is more precision, you can
use something called a double. That is to say, here is, again,
an example in programming where it's up to you now to provide
the computer with hints, essentially, that it will rely on to know what
is this pattern of zeros and ones. Is it a number, a letter? Is it a sound, an image,
a color, or the like? These are the types of data types
that provide exactly those hints. What are the functions that come in
the menu that is the CS50 library? We talked about standard I/O, and
that's just one function so far, printf. In the CS50 library, you can
see that it follows a pattern. The C50 library exists largely
for the first few weeks of the class to make our lives easier
when you just want to get user input. So if you want to get a string,
like a word or words from the human, you use get_string. If you want to get an integer from
the user, you're going to use get_int. When you want to get any of those
other data types, for the most part, you use get_ something else. And they're indeed all
lowercase by convention. What about printf? If we have the ability now to
store different types of data and we have functions with which
to get different types of data, how might you go about printing
different types of data? Well, we've seen %s for string,
%i for integer, %c for char, %f for a float or a double, those
real numbers I described earlier, and then %li for a long integer. So here's the first
example of inconsistencies. In an ideal world, that would
just be %l and we'd move on. It's %li instead in this case. That's printf and some
of its format codes. What more might we do? Well, in C, as we'll see-- no pun intended-- there is
a whole bunch of operators. And, indeed, computers, one
of the first things they did was a lot of math and calculations, so
there's a lot of operators like these. Computers, and in turn, C, really
good at addition, subtraction, multiplication, division,
and even the percent sign, which is the remainder operator. There's a special symbol
in C and other languages just for getting the remainder, when
you divide one number by another. There are other features in the world
of C, like variables, as we've seen. And there's also what is of
playfully called syntactic sugar that makes it easier over time
to write fewer characters but express your thoughts the same. So just as a single example
of this, as a single example, consider this use of
a variable last week. Here in Scratch is how you might
set a variable called counter to 0. In C, it's going to be similar. If you want the variable
to be called counter, you literally write the word counter,
or whatever you want it to be called. You then use the assignment
operator, a.k.a. the equals sign, and you assign it whatever its initial
value should be here on the right. So, again, the 0 is going to get copied
from right to left into the variable because of that single equal sign. But this isn't sufficient
in C. What else is missing on the right-hand
side, instinctively now? Even if you've never
programmed in this before. Yeah, in front. AUDIENCE: Semicolon. DAVID J. MALAN: A semicolon at the end. And one other thing, I
think, is probably missing. Again. AUDIENCE: A data type. DAVID J. MALAN: A data type. So if we can keep going
back and forth here, what data type seems appropriate
intuitively for counter? int for integer. So, indeed, we need to
tell the computer when creating a variable what
type of data we want, and we need to finish our
thought with the semicolon. So there might be a counterpart there. What about in Scratch if we wanted
to increment that counter variable? We had this very user-friendly
puzzle piece last time that was change counter
by 1, or add 1 to counter. In C, here's where things get
a little more interesting. And pretty commonly done, you might
do this. counter = counter + 1; with a semicolon. And this is where, again, it's
important to note, the equal sign, it's not equality. Otherwise, this makes no sense. counter cannot equal
counter plus 1, right? That just doesn't work if we're
talking about integers here. That's because the equal
sign is assignment. So it can certainly be the
case that you calculate counter plus 1, whatever that is,
then you update the value of counter from right to left to be that new value. This, as we'll see,
is a very common thing to do in programming just to kind of
count upward, for whatever reason. You can write this more succinctly. This code here is what we'll
call syntactic sugar, sort of a fancy way of saying the same thing
with fewer words or fewer characters on the screen. This also adds 1, or whatever
number you type over here, to the variable on the left. And there's one other form of syntactic
sugar we're going to start seeing too, and it's even more terse than this. That too will increment counter by 1
by literally changing its value by 1. Or if you change it to minus
minus, subtracting 1 from it. You can't do that with
2 and 3 and 4, but you can do it by default with just plus plus
or minus minus adding or subtracting 1. Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Ah, so when you are
changing a variable that already has been created, as we did with
the code that looked like this, you no longer need to remind the
computer what the data type is. Thankfully, the computer is
at least as smart as that. It will remember the type of
the data that you intended. Other questions or comments on this? All right, that's quite a lot. Why don't we go ahead and
here take a 10-minute break? And we'll be back, we'll
start writing some code. All right, so we are back. We've just looked at some
of the basics of compiling, even if it doesn't
quite feel that basic. But now, let's actually
start focusing really on writing more and more code,
more and more interesting code, kind of like we dove
into Scratch last week. So here I have these code open. I've closed the GUI. I'm going to focus more on my
terminal window and my code editors. Many different ways I can create new
files, but I want to create something called a calculator. So, again, within this
environment of VS Code, I can literally write the code
command which is VS Code specific, and it just creates a new
file for me automatically. Or I could do that in the GUI. I'm going to go ahead and create
this file called calculator.c and I'm going to go ahead and
include some familiar things. So I'm just going to go ahead and
proactively include cs50.h, stdio.h. I'm going to go ahead from
memory and do the int void main-- more on that next week, why it's
int, why it's void, and so forth. And now let me just implement
a very simple calculator. We saw some mathematical
operators, like plus and the like. So let's actually use this. So let me go ahead and
first give myself a variable called x, sort of like grade
school math or algebra. Let me go ahead then and
get an int, which is new, but I mentioned this exists. And then let me just ask the user
for whatever their x value is. The thing in the quotes is
just the English, or the string that I'm printing on the screen.
so I could say anything I want. I'm just going to say x colon
to prompt the user accordingly. Now I'm going to go ahead and
get another variable called y. I'm going to get int again. And now, I'm going to
prompt the user for y. And I'm just very nitpickly
using a space just to move the cursor so it doesn't
look too messy on the screen. And then lastly, let me go ahead and
just print out the sum of x and y. In an ideal world, I would just
say something like printf x + y. But that is not valid in C. The
first argument, recall, in printf has to be a string in double quotes. So if I want to print out
the value of an integer, I need to put something in quotes
here, maybe followed by a newline, if I want to move the cursor as well. So, again, we only glimpsed
it briefly, but what do I replace these question marks with
if I want a placeholder for an integer? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah, so %i. Just like %s was string, %i is integer. So I change this %i. And now if I want to add x and y, for
instance, super-- simple calculator, doesn't do much of anything other
than addition of two integers-- I think this works. And, again, it looks definitely
cryptic at first glance. It would be if programming
weren't this cryptic. Other languages will
clean this up for us. But, again, if you focus on the
basics, printf takes one input first-- which is a format string with
English or whatever language, some placeholders, maybe-- then it takes potentially more
arguments after the comma, like the value of x plus y. All right, let me go ahead
now and make calculator, which, again, compiles
my source code in C, pictured above, and converts
it into corresponding machine code, or zeros and ones. No error messages. so that's already good. Now I do ./calculator. Let's do 1 plus 1 and Enter. Voila. Now I have the makings of a calculator. Now let's start to tinker
with this a little bit. What if I instead had done this? int z = x + y and then plug-in z here. If I rerun make calculator, Enter,
rerun ./calculator, type in 1 plus 1, still equals 2, and let me claim that
it will work for other values as well-- which of these versions
is better designed? If both seem to be correct at very
cursory glance, is this version better or is the previous one without the z? OK, so this one is arguably better
because I've now got a reusable variable called z that I
cannot only print but, heck, if my program is longer,
I can use it elsewhere. Counterthoughts? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah. Debatable, like before, because
it depends on my intent. And, honestly, I think
a pretty good argument can be made for the first version. Because if I have no
intention of-- as you note-- using that variable
again, you know what? Maybe I might as well do
this, just because it's one less thing to think about. It's one less distraction. It's one less line of code
to have to understand. It's just a little tighter. So here, again, it does
depend on your intention. But this field is pretty reasonable. And I think, as someone
noted earlier, when I did the same thing with get_string,
that, yeah, maybe kind of crossed s line because get_string and the
what's your name inside of it, it was just so much longer. But x + y, eh, it's not that hard
to wrap our mind around what's going on inside of the printf argument. So, again, these are the kinds
of thoughts that hopefully you'll acquire the instinct for
on not necessarily reaching the same answer as someone
else, but, again, the thought process is what matters here. All right, so how might I enhance
this program a little bit? Let's just talk about
style for just a moment. So x and y, at least in this case,
are pretty reasonable variable names. Why? Because that's the go-to
variable names in math when you're adding two things together. So x and y seem pretty reasonable. I could have done something like,
well, maybe my first variable should be called first
number and my next variable should be called second number. And then down here, I
would have to change this to first number plus second number. Like, eh, this isn't really
adding anything semantically to help my comprehension. But that would be one other
direction we could have taken things. So if you have very simple
ideas that are conventionally expressed with common variable names
like x and y, totally fine here. What if I want to annotate this program
and remind myself what it is it does? Well, I can add in C
what are called comments. With a slash slash, two forward slashes,
you can write a note to yourself, like prompt user for x. And then down here, I could
do something like prompt user for y, just to remind
myself what I'm doing there. And down here, perform addition. Now, in this case, I'm not
sure these commands are really adding all that much. Because in the time it took me to write
and eventually read these comments, I could have just read
the three lines of code. But as our programs
get more sophisticated and you start to learn more syntax-- that, honestly, you might forget
the next day, the next week, the next month-- might be useful
to have these notes to self that reminds you of what your
code is doing or maybe even how it is doing that thing. With these early programs,
not really necessary, doesn't really add all that
much to our comprehension, but it is a mechanism
you have in place that can help you actually remind
yourself or remind someone else what it is that's going on. Well, let me go ahead and rerun
this again in this current version, make calculator. And here, too, you might
think I'm typing crazy fast-- not really. I'm hitting Tab a lot. So it turns out that
Linux, the operating system we're using here in the cloud-- but, actually, Windows and Mac
OS nowadays support this too-- supports autocomplete. So if you only have one
program that starts with C-A-L, you don't have to finish writing
calculator, you can just hit Tab, and the computer will
finish your thought for you. The other thing you can do is
if you hit Up and keep going up, you'll scroll through your
entire history of commands. So there too, I've been
saving some keystrokes by hitting Up quickly rather than
retyping the same darn thing again and again. So, again, just another
little convenience to make programming and interacting with
the command line interface even faster. All right, let me go ahead and just make
sure it's compiled in the current form. The comments have no functional impact. These green things are
just notes to self. Let me run calculator with
maybe-- how about this? Instead of 1 plus 1,
how about 1 billion-- whoops, let's do that again. Wa, da, da. 1 million, 1 billion, and another 1
billion, and that answer is 2 billion. All right, so that seems correct. Let's run this program one more time. How about 2 billion
plus another 2 billion? Did you know that? So, apparently, it's not so correct. And, clearly, running 1 plus 1 was
not the most robust testing of my code here. What might have gone wrong? What might have gone wrong? Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah. The computer probably ran
out of space with bits. So it turns out with these data types--
we've been talking about string and int and also float and char and those
other things-- they all use a specific, and, most importantly, finite
number of bits to represent them. It can vary by computer. Newer computers use more bits, older
computers tended to use fewer bits. It's not necessarily standardized
for all of these data types. But in this case, in this environment,
it is using 32 bits for an integer. That's a lot. So with 32 bits, you
can count pretty high. This is 64 light bulbs on the
stage and could count even higher. An int is only using half of these, or
we have two integers here on the stage. Now, if you think back to last week,
we talked about 8 bits at one point. And if you have 8 bits, 8 zeros and
ones, you can count as high as 256-- just a good number to
generally remember as trivia. 8 bits gives you 256
permutations of zeros and ones. 32 gives you roughly how
many, if anyone knows? It's 2 to the 32 power. So it's roughly 4 billion, 2 to the 32. If you don't know that, it's fine. Most programmers, though, eventually
remember these kinds of heuristics. So it's roughly 4 billion. So that feels like enough. 2 billion plus 2 billion
is exactly 4 billion. And that actually should
fit in a 32-bit integer. The catch is that my Mac,
your PC, and the like also like to support negative numbers. And if you want to support both positive
and negative numbers, that technically means with 32-bit integers,
you can count as high as roughly 2 billion positive
or 2 billion negative in the other direction. That's still 4 billion, give or
take, but it's only half as many in one direction or the other. So how could I go about implementing
a correct calculator here? What might the solution be? Yeah, so not just li,
which was for long integer. I have to make one more change,
which is to the data type itself. So let me go back up here and change
x from an int to a long, a.k.a. long integer. And then let me change y as well. And then let me change the format code
per the little cheat sheet we had up a few minutes ago to li. Let me recompile the calculator-- seems to work OK. Let's rerun it. Now let's do 1 plus 1. That's should obviously be the same. Now let's do 2 billion
and another 2 billion and cross our fingers this time. Now we're counting as high as 4 billion. And we can go way higher than
4 billion, but we're only kicking the can down the street a bit. Even though we're now using-- with a long-- 64 bits, which is as long as this
stage now, that's still a finite value. It might be a really big
value, but it's still finite. And we'll come back at the
end of today to these kinds of fundamental limitations. Because arguably now, my calculator
is correct for like millions, billions of possible inputs but not all. And that's problematic
if you actually want to use my calculator for any
possible inputs, not just ones that are roughly less than,
say, 2 billion, as in this case. All right, any questions then on that? But it's really just a
precursor for all the problems that we're going to have to
eventually deal with later on. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: A good question. Yes. If we were still using z, we would
also have to change it to a long. Otherwise, we'd be ignoring
32 of the bits that had been added together via the longs. Good question. All right, so how about we spice things
up with maybe not just addition here, how about something
with some conditions? Let's start to ask
some actual questions. So a moment ago, recall that we had
just the declaration of variables. Now let's look back at
something in Scratch that looked a little something like
this, a bunch of puzzle pieces asking questions by way
of these conditionals and then these Boolean expressions
here in green, maybe saying something like x is less than y. In C, this actually maps pretty cleanly. It's much cleaner from left to right
than it was with printf and join. Here, we have just code
that looks like this. If, a space, two parentheses
and then x less than y, and then we have something like
printf there in the middle. So here, it's actually
kind of a nice mapping. Notice that, just as the
yellow puzzle piece in Scratch is kind of hugging the
purple puzzle piece, that's effectively the role that
these curly braces are playing. They're sort of encapsulating
all of the code on the inside. The parentheses represent
the Boolean expression that needs to be asked and answered to
decide whether or not to do this thing. And here's an exception to
what I alluded to earlier. Usually, when you see a word and
then a parenthesis, something, and then closed parenthesis, I
claimed that's usually a function. And I'm still feeling pretty
good about that claim. But there are exceptions. And the word if is not a function. It's just a programming construct. It's a feature of the C language
that similarly uses parentheses, just for different purposes
for a Boolean expression. How about something like this? Last week, if you wanted to
have a two-way fork in the road, go this way or that way,
you can have if and else. In C, that would look a
little something like this. And if we add in the printf's,
it now looks quite like the same, but it adds, of course, the word else
and then a couple of more curly braces. As an aside, in C, It's not strictly
necessary to have curly braces if you have only one line
of code indented underneath. For best practice, though, do so anyway,
because it makes super clear to you and ultimately anyone
else reading your code that you intend for just that one
or more line of code to execute. How about this from last week? Here was a three-way fork in the road. If x is less than y, else if x is
greater than y, else if x equals y. Now, here's where you have some
disparities between Scratch and C. Scratch uses an equals sign
for equality, to compare two values. C uses a single equals sign
for assignment from right to left, minor difference
between the two worlds. In C, we could implement the same
code like this, the addition being just this additional else if. And if we add in the printf's, it
looks a little something now like this. This is correct both in the
Scratch world and in the C world. But could someone make a claim that
this is not, again, well-designed? Exactly. We don't need the last if. We need the else, at least,
but we don't need the last if. Because, at least in the
world of comparing integers, it's either going to be less
than, greater than, or equal to. There is no other case. So you can save a few
seconds, if you will, of your program running-- a blink of
the eye-- by only asking two questions and then inferring what
the answer to the third must be just by nature of
your own human logic here. Now, why is that a good thing? If, for instance, x and y
happen to equal each other-- I type in 1 and 1 for both values,
either in Scratch or in the C world-- in the case of this version,
you're sort of stupidly asking three questions, all of
which are going to get asked even though the answer is no, no, yes. That is false, false, true. That seems to be unnecessary because
if we instead optimize this code, get rid of the unnecessary if and
just do as you proposed logically-- else print that x is equal to y-- now if x indeed equals y because
they're both 1 or some other value, now you're only going to ask two
questions, so 2/3 as many questions, and then you're going to get
your same correct result. So, again, a minor detail,
but, again, the kinds of things you should be thinking
about, not only as you write your code to
be correct but also write it to be well-designed as well. All right, so why don't we
go ahead and translate this into the context of an
actual program here? I'll create a blank window here. And let's do something with points,
like points on my own very first CS50 problem set. Let me go ahead and
run code of points.c. That's just going to
give me a new text file. And then up here, I'm going to
do my usual, include cs50.h. include stdio.h. int main void. So a lot of boilerplate, so to
speak, in these early programs. And now, let's see. Let's ask the user, how
many points did they lose on their most recent CS50 PSet? So sort of evoke my photograph of
my own very first PSet last week where I lost a couple of points myself. So int points = get_int. Then I'll ask a question in English
like, how many points did you lose, question mark, space? And then once I have this answer,
let's now ask some questions of it. So if points is less than 2-- borrowing the syntax that we
saw on the screen a moment ago-- let's go ahead and print
out something explanatory like you lost fewer points
than me, backslash n. else if points greater than 2-- which is, again how many I lost-- I'm going to go ahead and print out you
lost more points than me, backslash n. else if-- wait a minute, else seems
to be sufficient logically here. I'm just going to go ahead
and print out something like you lost the same number
of points as me, backslash n. So, really, just a straightforward
application of that simple idea but to a concrete scenario here. So let me go ahead and save this. Let me go ahead and
run make points, Enter. No errors, that's good. Run points. And then, how many points did you lose? How about, it's 1 point? All right, you lost
fewer points than me. How about 0 points? Even better. How about 3 points? And so forth. So, again, we have the ability to
express in C now pretty basic idea from last week in reality, which is
this notion of conditionals and asking questions. There's something subtle here, though,
that's maybe not super well-designed that someone might call a magic number. This is programming speak
for something I've done here. There's a bit of redundancy unrelated
to the if and the else and the else. But is there something I typed twice
just to ask, perhaps, for the obvious? Exactly, I've hard-coded, so to speak,
manually typed out the number 2-- in two locations, in this case-- that did not come from the user. So, apparently, once I
compile this, this is it. You're always comparing
yourself to me in like, 1996, which for better or for worse,
is all the program can do. But this is an example too of
a magic number in the sense like, wait, where did that 2 come
from, and why is it in two places? It feels like we are setting the
stage for just a higher probability of screwing up down the road. Because the longer this code gets,
suppose I'm comparing against 2 points elsewhere-- 2, 3, 4, 5 places-- am I going to keep typing the number 2? Like, yeah, that's fine. It's correct. It's going to work. But, honestly, eventually,
you're going to screw up, and you're going to miss one of the
2's, you're going to change it to a 3, because maybe I did worse the
next year, or 1, I did better. And you don't want these
numbers to get out of sync. So what would be a logical
improvement to this design, rather than hard-coding the
same number sort of magically in two or more places? Yeah, why don't I make a
variable that I can use in there? So, for instance, I
could create a variable like this, another integer called mine. And I'm just going to
initialize it to 2. And then I'm going to change
mentions of 2 to this. And mine is a pretty reasonable
name for a variable insofar as it refers to exactly
whose points are in question. There's a risk here,
though, minor though it is. I could accidentally
change mine at some point. Maybe I forget what mine represents,
and I do some addition or subtraction. So there's a way to tell the
computer "don't trust me, because I'm going to
screw up eventually" by making a variable constant too. So a constant in a
programming language-- this did not exist in Scratch-- is just an additional hint to the
computer that essentially enables you to program more defensively. If you don't trust
yourself necessarily to not screw up later, or
honestly, in practice, if you know that number should
never change, make it constant and never think about it again. This tells the compiler to make sure
that even you later in your code cannot change the number 2. And another convention in C and other
languages, when you have a constant, it's often common to just
capitalize the variable. Kind of like you're
yelling, but it really just visually makes it stand out. So it's kind of like
a nice rule of thumb that helps you realize, oh,
that must be a constant. Capitalization alone does
not make it constant. The word const does. But the capitalization
is just a visual reminder that this is somewhere,
somehow a constant. So just a minor refinement,
but, again, we're sort of getting better at
programming just by instilling these kinds of heuristics. Questions, then, on conditionals
in C or these constants? Yeah. AUDIENCE: Why do you not use
semicolons after line 9 and line 13? DAVID J. MALAN: Yeah, why do you
not use a semicolon in lines 9, 13? Just because. This is the way the
language was designed. And it's confusing early on. Generally speaking, when you're
using conditionals-- and eventually, we'll see loops-- there's no semicolons involved. For now, assume that semicolons usually
finish your thought after a function. That's not 100% reliable of a heuristic,
but it'll get you most of the way there. And just because. Left hand was not talking to the right
hand when some of these languages were designed. All right, so let's do something else. How about this? If I have the ability to ask
something conditionally-- is this thing true or
is this other thing-- could I write a very simple program
that does something basic like, tells me if a number the
human types is even or odd? Well, let me just get the
framework for that in place. Let me go ahead and
write code of a parity-- is a fancy way of saying even or odd. And let me go ahead and include cs50.h,
include stdio.h, int main void-- again, more on those down the road. But, for now, I'm going to go ahead
and get a number n from the user by calling get_int and asking
them for whatever n is. And then now I'm going to
introduce some pseudocode. So here's the first
example of a program, honestly, that I'm not
really sure how to proceed. So let me just resort to some
pseudocode using comments. Eventually, I'll get rid of
this and write actual code. But if n is even, then print-- actually, let me just print that. Let me just go ahead and say
printf, quote unquote, "even", because I know how to use printf. else-- all right, I
know how to printf odd, so let me just say printf,
quote unquote, "odd". So here, I've sort of taken a bite
out of the problem, if you will. And let me go ahead and put
in my little placeholders. I want to do some kind of conditions. So if, question marks now, let me go
ahead and fill in the blanks here. else I'll put this here. So I think I'm OK now. I'm getting closer to solving this. But I still have this
question mark here. How, using syntax we've seen, might
I determine if n is even or odd? What do you think? Nice. There's this little operator I
mentioned by name earlier, the remainder operator, that will let
you do exactly that. If you divide any number by
2, that mathematical heuristic is going to tell you if it's even
or odd based on whether there's a remainder of 0 or 1. And that's nice because the alternative
would seem to be doing something stupid like if n == 0 or if n
equals 2 or n equals 4-- your code would be infinitely long if
you had to ask all possible questions. But if I do n divided by 2
and look at the remainder-- it's a little cryptic, but
this will indeed do the trick. So the percent sign is
the remainder operator. It does numerator divided by denominator
and returns not the result of that but, rather, the remainder of that. So if you divide anything by 2,
it's going to be a 0 or 1 remainder. And if, indeed, 2 divides
into n evenly, giving you 0, then you're going to print even. Else, it's got to be odd. But there is something odd-- pun
intended-- in this highlighted line. What is another new piece of syntax,
apparently, besides the percent sign? What's a little off there? Yeah. Yeah, so that's not a typo. And I even caught myself
verbally saying it a moment ago, just because it's so ingrained. What must this mean here? Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah, if
something's equivalent to the other. So now this is the equality operator. It's not assignment from right to left. And this one too is an
example of, literally, humans not really planning
ahead, perhaps, left hand not talking to right hand
in that someone decided, let's use the equals
sign for assignment. And then some number of minutes or
days later, people are like, damn, how do we now compare for equality? Well, let's just use two. And if you think this is a little weird,
in some languages, like JavaScript, there's a third version where
you use three equal signs. So, again, it's humans that
design these languages. So if you're ever frustrated by them,
confused by them, eh, admittedly, it might just not have
been the best design. But we just kind of have
to live with it ever since. So let me go ahead and zoom out here. Let me go ahead and make parity here. So make parity-- and, again, parity
is just the name of my file, parity.c. ./parity, type in a number like 2. That's indeed even. 4, that's indeed even. 3, that's indeed odd, and so forth. If we continue testing, presumably,
we'll get the same kinds of answers. How about something else? Let me go ahead now and let me start
copying and pasting some of this code because, admittedly, it's getting
a little tedious to keep typing out all of that boilerplate at the top. Let me create a program
called agree.c that's reminiscent of any of
those forms you have to agree to online with a checkbox
or typing in yes or no or the like. So let me throw away all the
guts of this main program and now ask something like this. Let me go ahead and prompt
user to agree to something. I'm going to go ahead and say, how
about get_string do you agree-- whatever the question might be--
and I want the human to type y or n for yes or no, respectively. So if it's only a single
character, actually, I can actually get by with just get_char. Not used it before,
but it was on our menu of functions from the CS50 library. And if I want to get
the user's response, the return value should be
a char also on the left. So now we've seen strings,
ints, and now chars, if we only care about a single letter. And now let's go ahead,
check whether user agreed. So how about if c == "y", then let me
go ahead and, inside of my curly braces, print out agreed or some
such sentence like that. else if they did not type
c-- or you know what? Let's be explicit here,
just so they can't type z or b or some random letter. else if c=="n" n for no, then let me
go ahead and print out not agreed, or something like that. And I'm just going to ignore
the user if they don't cooperate and they type z or b or
something that's not y or n. All right, let me go ahead now and
compile this code, make agree, ./agree. All right, do I agree? Yes. Let's go with the default.
OK, so that seems to work. No, I don't agree this time. That seems to work. How about my caps lock key is on or
I'm just really yelling, capital Y? It ignores me. Capital N, it ignores me. So, obviously, a bug, at least if I want
to tolerate uppercase and lowercase, which is kind of reasonable. So what would be the possible
solutions here, do you think? How do I solve this and tolerate
both capital and lowercase? Maybe what's the simplest,
most naive implementation? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah, so why
don't I just ask two questions? Or you know what, even more simplistic
based only on what we've seen before-- if you will, let me just copy
and paste some of this code. Change this to an else-- whoops,
not in caps-- else if "Y". And then I bet I could
do the same thing with n. But here too, just like
with Scratch, as soon as you start to find
yourself copying and pasting, you're probably doing something wrong. And what you said verbally,
if I may, was actually better. Because you're implying that I could
just say something like OR c == "Y" or, down here, c == "N". The catch is, you can't use the word OR
in C. It's actually two vertical bars. So you can express one
question or another. You only need one of the
answers to be yes or true, and you use two vertical bars. By contrast, just so
you've seen it, if you wanted to check if something is equal
to something AND something else, you could use two ampersands. This logically would make
no sense here, though. Certainly, what the human typed can't
both be lowercase and uppercase. That just makes no sense. So in this case, we do want OR. But that allows me to
tighten my code up. I don't have to start copying
and pasting whole branches. I can now ask two questions at once. Questions, then, on this variation? Really good question. Can you convert the
input to all lowercase? Absolutely, you could. We don't have the capability yet. It turns out that's going to require-- to be easy, another library,
though we could do it ourselves knowing a little bit about
ASCII or Unicode from last week. But, yes, that would be an alternative,
but more on that a different time. Other questions? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Good question. Unfortunately, you have to be explicit
in C. You can't just say this, even though that's kind of
how you might think about it. You have to ask a complete question
using the equality sign twice in this case. Let me ask a question now too. It's not a typo. I deliberately used single quotes
around all of my single letters here. Why might that be? Previously, we used double quotes
for anything that looked like text. Yeah. Correct, string is double quotes for
multiple characters-- or even one, technically, but yes. And single quotes for single characters. Because my data type is different. I chose the simple route of
just using a single char. In fact, this program
won't work with Y-E-S or N-O. That's not supported at the
moment-- more on that another time. I had to use single quotes
because that's how C does it. If you're dealing with
single characters, a.k.a. chars, use single quotes. If it's a string-- even if it's one single
character in a string as though you're starting to write
out a longer word or sentence-- that would be double quotes. And we'll see why this
is before long too. But, again, just things to keep
in mind whenever writing code in this particular language. Yeah, down here. So, short answer, if I'm understanding
correctly, this would be incorrect. And this would be even more incorrect. But if you don't mind, let me
kick the can a couple of weeks on this as to why this doesn't work. The most pleasant way to do this would
indeed be to do something like this. But even this is a slippery
slope, because what if the user does something weird,
like they capitalize just the Y? You can imagine this
getting messy quickly. I like your idea earlier
about just forcing everything to lowercase just to standardize things. Unfortunately, you cannot compare
strings for equality like this for, again, reasons will
come to before long. So for today, we're keeping it
simple, even though, arguably, it's not nearly as user-friendly to
only tolerate individual letters. And there's a question over here. On the US English keyboard
it's shift and then the backslash key above Return,
but depending on your keyboard, it will vary. All right, so let's
actually now look back at something we did a
little bit of last week. Let me go ahead and open
a file called meow.c, because, recall, that's what
we had Scratch do initially. Let me include not the
C50 library this time, but just stdio.h because I
only want printf for this demo. Let me go ahead now and
just print out meow. And then if I want the cat to meow
three times, like it did last week, meow, meow, meow. Save it. make meow, ./meow. Voila. The program is written--
correct, I claim. It ran. It compiled OK. But, again, this was the
beginning of our conversation last week of not being
particularly well-designed. And if someone wants to maybe
point out the now obvious, why is this not
well-designed, necessarily? Yeah, it's just repetition, right? Again, I literally
resorted to copy-paste. That should be the signal
that you're probably doing something wrong or, at best,
just lazy of you, in this case. So the solution, as you
might glean from last week, is probably going to be one
of those things called loops. So let's just take a look at some
of the syntax for loops in C. But, again, no new ideas,
it's just some new syntax that'll take some getting used to. In Scratch, if you wanted to meow
forever with something like this, there's not a forever keyword in C, so
this one's a little weird to look at. But this is the best we can do. It turns out there is a
keyword called while in C. And that kind of has
the right semantics, because it's like while I do
something again and again, that's the best I can do. But just like an if condition
or an else if condition, those took a Boolean
expression in parentheses, a while loop also takes a Boolean
expression in parentheses. So I have to ask a question. Now, if I want to do something
forever, I could kind of stupidly just say while 2 is greater than
1, while 3 is greater than 2, or just something completely arbitrary. But that should rub you the wrong
way, because like, why 2 versus 1? Why 3-- if you want true, just say true. So it turns out in C, there are
special keywords, true and false, that are literally true
and false, respectively. I could also put the number 1 for
true and the number 0 for false, but most people would just
say true to be explicit. So it's a little hackish, if
you will, but very conventional. There's no forever keyword in C. If
I want to then print meow forever, I'm going to just use
something like printf here. So, again, not perfect
translation from one to the other, but absolutely
possible in C. What about this? This is a little more common
if you want to do something a finite number of times, like repeat 3. There's a few different ways we can
do this in C. Here's one approach. And here's where C-- like a
lot of text-based languages, you kind of have to whip out that
toolkit of all of the basic building blocks and think about,
all right, how can I build a little machine in software that
does something some number of times? Well, let me give myself a variable
called counter, set it equal to 0. Let me create a loop whose Boolean
expression is counter less than 3, the idea being here, why don't
I just kind of count 1, 2, 3? So how do I implement
this physicality in code? I give myself a variable,
set it to 0, 0 fingers up. Now, I ask the question,
is counter less than 3? If so, go ahead and print out meow. And just intuitively, even if
you've never seen C code or any code before Scratch, what
more do I need to do? I've left room here for
one more line of logic. Yeah. We have to increase counter. So I need code like I showed earlier,
like counter equals counter plus 1. And so here's where
programming sometimes becomes a bit more like plumbing. You can't just say what you
mean, like you couldn't Scratch. You have to build a little
sort of software machine that initializes a value, does
something, increments it, checks it. And so it's kind of like
this software-based machine, but together, that's just using
some familiar building blocks. But this is pretty common. Just like in Scratch, you
might have used loops a bunch of times, pretty common in C. So can we tighten this code up? This is correct, but here are
some conventions that are popular. If you're going to count, just say i. A convention in
programming-- with, at least, languages like C-- is just use i
as an integer if all its purpose is is to count from like, 0 on up. Counter is not wrong. It's not bad. It's just more verbose
than you need to be. Just call it i. You don't need more semantics than that. All right, what else can I do here? There's another opportunity
to tighten up this code. Do you recall? Yeah. Yeah, that syntactic sugar
that does nothing new, but it does it more succinctly. I can change this to either the
intermediate format or even tighter format of just i++. Now, this is pretty canonical. This is how most people
would implement something three times using a loop in C-- using a while loop, that is. Turns out that it's so common
in C and other languages to do something finitely many times,
there's a couple of ways to do it. In this model, to be
clear, the logic, though, is that we start by initializing the
variable, like I've highlighted here. We then ask the question,
is i less than 0? If so, everything that's
indented inside the curly braces gets executed-- namely,
meow then the update. Then the computer is going to
have to recheck the condition to make sure that i hasn't gotten
so big that it's greater than 3. But if not, it then does this
again and it does this again. And then it repeats, constantly
checking the condition and executing what's in the
block, checking the condition and executing what's in the block. After three times of that, the condition
is going to be false, or a no answer, and that's it for the code. It just proceeds to whatever's
down here, just like with Scratch. It jumps to the next blocks down below. All right, what's another
way, though, to do this? Well, I've deliberately
been counting from 0-- and that's a programming
convention, right? We started last week with all
the light bulbs off, which was 0. So it's pretty reasonable to
start counting at 0's, just like you would here. Like, no fingers are up, this is 0-- fingers on your hand. But if you prefer, you could
start counting at i equals 1. But then you don't want to
do it while i is less than 3, you want to do i is
less than or equal to 3. On most keyboards, there's no symbol for
less than or equal to or greater than or equal to, so in C, you
use two characters, less than and then an equals sign
with no spaces in between. That just means less than or equal to. We could change it to set i to 2
and make this condition be less than or equal to 4. We could make this be a 10
and less than or equal to 12. But, again, just stick with the basics. Start at 0 and count on up
would be the convention. Or if you prefer to count
down, that's fine too. Set i to 3 and then do this so
long as i is greater than 0, but you have to decrement
instead of increment. So, again, we could
do this all day long. There's literally an infinite number
of ways to implement this idea. And that's why I keep
emphasizing convention. Call the variable i for
something like this, initialize it to 0 for something like
this, and just generally count up, unless you really prefer to count down. Again, just certain human conventions. All right, how about
another way to do this? This is what's called a for
loop in C, also very common. It's not quite as straightforward
in that it doesn't really read top to bottom in exactly the same way. This kind of has a lot more
logic tucked into its first line. But it does exactly the same thing. What happens here is-- notice that inside the
parentheses, next to the word for, there's two semicolons-- which
is another weird use of syntax. They're not at the end
of the line, now they're in the middle of the parentheses. But that's what the
humans chose years ago. The first thing before the semicolons
initializes your variable, int i = 0. The next thing is the condition
that's going to constantly get checked every cycle through this loop. And the last thing is going to be
what you do after each loop, which in this case is going to be count up. So, again, if I rewind
we initialize i to 0. We then ask the question,
is i less than 3? If so, execute what's
inside of the loop. Then the computer does this, it does
the update, incrementing i by 1. And then it's not going
to blindly meow again. It's going to check again the
condition, is i less than 3? Then it's going to meow if so. Then it might go ahead and increment
i and check the condition again. So, again, this does not read quite
the same simple fashion top to bottom. You kind of read it left to
right and then jump around. But, again, the initialization,
the constant Boolean expression being checked, and the
update after each time does the exact same thing as what we saw
a moment ago in this while loop format. Which one is better? Eh, they're the same. I think most people would
probably eventually use a for loop once comfortable, but just
because is really the answer there. All right, any questions, then, on
loops as we've translated them to C? Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: A for
loop and while loop can both be used to do
exactly the same thing. There are subtle differences
with issues of scope, which we'll discuss before
long, where when you create a variable in a for loop-- notice that it was, again, inside
of those parentheses, which technically means it's only going to
exist in these four lines of code. By contrast, with the while loop,
I declared my variable outside of the loop. That variable is going to continue
to exist elsewhere in my program. So that's one of the
minor differences there. Good question. But you'll see some others over time. All right, so we claim then
that it's better in some form to do this with loops. So let's actually jump back to the code. Let me go ahead and now re-implement
meowing with a for loop, for instance. So how about for int i
= 0, i less than 3, i++. Then inside my curly braces, let me go
ahead and print out with printf, meow, with a newline and a semicolon. So I did it pretty quickly just because
I've long acquired the muscle memory. But if I now make meow, no errors there. Run ./meow. And I see meow, meow, meow. Well, let's do now what
we did last week, which was to begin to make our own
custom functions, if you will, by using our own in C. So here's
where the syntax gets a little funky, but we'll explain over time what
each of these keywords is doing. If I want to create a
function called meow-- because the authors of C did not create
a function called meow decades ago-- I need to give it a name, like meow. I need to specify if
it takes any inputs. For now, I'm going to say no. And I'm going to explicitly say no
by writing the special word void. It's also necessary when
implementing a function in C-- which was not necessary in Scratch-- to specify what its return type is. But for now, I'm just going to say
that meow is the name of the function, it takes no inputs-- and that's what the void
in parentheses means-- and it does not return
anything like ask did, or like get_string or get_int does. meow's purpose in life is just to
have side effects, visual side effects by printing something on the screen. So what is meow going to do? I'm going to have it
quite simply say printf, quote unquote, "meow", backslash n. And now, just like in
Scratch, I can now just call a brand new function called meow. And here's where too, if you
really don't like the curly braces, technically speaking, you can
get rid of them when there's only one line of code inside your loop. But, again, stylistically,
I would encourage you to preserve them to make
super clear to yourself and others what it is that's going on. Let me go ahead and save
this and do make meow. Whoops. Darn. All right, what did I do? Something stupid. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah, so
0 does not belong there. I meant to hit parenthesis. So let me rerun make meow. OK, fixed. My mistake. All right, it's still working OK. But recall what I did in Scratch,
kind of out of sight, out of mind. And just to make a point, let me
just highlight this and move it way down in the file. Because, again, now that meow
exists, it's an abstraction. I just know a meow function exists. I want to be able to use it. So let me scroll back up. My main function is the same. Let me go ahead and make meow again. And now, just by moving that function,
I've created all these lines of errors. And let's look at the first. Again, the rule of thumb
here-- it's a little small, but it says meow.c in bold-- which is
the name of the file where the bug is-- 5 is the line number,
and 20 is the character. So line number is enough alone. Let's see. Oh, this is what happens
when I scrolled up too far. Sorry. This is the error we're
now looking at, line 7. I was looking at the old error message
from earlier before I fixed the 0. meow.c line 7. All right, apparently, C does not
know what the meow function is. Implicit declaration of
function meow is invalid in C99. Well, what does that mean? Declaration of function means
your creation of a function. Like, I'm declaring that meow
exists, but I haven't apparently defined it yet. And then C99 is the version
of C from the year 1999, which we generally use here, it's
one of the more recent versions. So why is that the case? Can you infer from the mere fact
that I just moved meow to the bottom of the file-- which was fine
in Scratch but now is bad-- why is that? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah, C is
just kind of old school. It reads your code top to bottom. And if it does not know what meow
is when you first try to use it, it just freaks out and prints
out these error messages. So the solution is, quite simply, don't
do that, just leave it where it was. But you can imagine this getting a
little annoying over time, if only because main is, by name, the
main part of your program. And, honestly, it would just
be nice if main were always at the top of your code. Because if you want to
understand what a file is doing, it makes sense to just
read it top to bottom. Well, there is a solution to this. You can put functions in different
orders with main at the top so long as you-- and this is perhaps the
only time copy paste is appropriate-- so long as you leave a little
breadcrumb for the compiler at the very top of your
file that literally repeats the return value,
the name, and the arguments to that function, semicolon. This is, so to speak,
declaring your function-- and the real fancy way
is this is a prototype. It's like, what is this
thing going to look like? But the semicolon means I'm not
going to deal with this yet. I'm going to actually
define the function or implement it down below here. This is kind of a stupid detail. More recent languages
get rid of this need, you can put your functions in any order. But, again, if you just
think about the basics of programming languages
like this one here-- and as you noted-- it must just be reading
your code top to bottom. So annoying, yes, but
explained, yes too. So let me go ahead and make meow one
more time, ./meow, still working OK. And let me make one final enhancement
to this meow program here. Let me go ahead now and
say something like this. Let me go ahead and say,
all right, wouldn't it be nice if my meow function could do
something for me some number of times? So suppose I want to do this. This meow function at the moment
is going to meow three times. But suppose I want to meow
n times, where n is just some number provided by the user. Well, just like in Scratch,
custom functions can take inputs, I just presently am saying void. But if I change this to int n,
thereby telling the compiler, hey, meow still doesn't
return something, but it does take something as input. It takes an integer,
and I want to call it n. So this is another way
of declaring a variable but a way of declaring a
variable that gets handed into, as input, the function. So now if I tighten up main here, now
I can actually do something really cool just like in Scratch, which is this. If I now look at this
code-- let me Zoom in here-- now my main program is really
well-written in the sense that it just says what it
does, meow three times. This works, though, because I
defined meow as now taking an input, an integer called n, and then using
n in my now familiar for loop. There's one change. You might have caught my one mistake. I also have to remind myself up
here to make that change too. Again, this is one of the only
redundancies or copy-paste that's sort of reasonable. But there, I have now a better version. So let me go ahead and rerun
this, make meow, ./meow. Voila. So, again, no change
in correctness but now, again, we're sort of
modularizing our code. And, heck, what you could do now-- and
this is just a tease about a feature down the road-- those header files we talked
about early, those libraries, this is the kind of modularization
we're talking about. We, the staff, wrote a function called
get_string, get_int, and so forth, we put it in a file called CS50, and we
put little breadcrumbs-- specifically, these things called prototypes-- in cs50.h. So that when you all, as aspiring
programmers, include cs50.h, you are sort of secretly telling the
compiler at the very top of your code what the menu of available functions is. Why? Because in CS50 is lines like
these-- obviously, not for meow, but for get_string,
get_int, and so forth. And stdio.h is the same lines
of code for things like printf. So that's all that's going on there. It's just a way of telling the computer
in advance what functions to expect. All right, any questions,
then, on these here? Correct. So if you don't mind, I
want to continue to wave my hand at that detail for today. Indeed, int main void is a little weird,
because what would the input domain be? We have no mechanism
for providing input yet. And what does it mean for
main to return anything? Like, who is it returning to? For another day, if we may. They're going to come into
play but that, for now, today is just something you
should take at face value, as necessary copy-paste
to begin programs. So meow is a function that takes an
input, the number of times to meow, but it didn't actually have a
return value, hence the void. But what if we actually want
to create our own function that not only takes 0 or
more inputs as arguments but also returns some value, maybe an
int, maybe a float, maybe something else altogether? Well, it turns out, in C,
we can do that as well. Let me go ahead and create a
new file here called discount. And let's implement a
quick program via which we can discount some regular
price by some percentage, as though there's a sale
going on in a store. Let me go ahead and include our usual
cs50.h followed by stdio.h at the top. Let me give myself int
main void as before. And inside of main, let's go
ahead and do something simple. Let's give ourselves a
float called regular, representing the regular
price of something in a store. Let's go ahead and get a float
from the user asking them what that regular price is. Then, next, let's go ahead and declare
a second variable-- also a float-- called sale, ultimately
representing the sale price after some percentage discount off. And let's go ahead and simply
calculate whatever regular is. And, say, 15% off is a
pretty good discount. So let's go ahead and discount
regular, whatever it is, by 15%, which is equivalent, of course, to
multiplying it with the asterisk by 0.85. Of course, if we're taking off 15%,
we multiply the regular price by 0.85. Now, let's go ahead and
print out the results here. Let me go ahead and say
printf sale price, colon-- let me go ahead and %f,
but, more specifically, %.2f because, at least in US currency
we typically show cents to two decimal places-- followed by a newline. And then let me go ahead and
plug in the value of sale. All right, let's go down here
and do make discount, Enter. So far, so good-- ./discount. And the regular price is maybe $100. So the sale price should be $85. So our arithmetic seems
to be correct here. But let's fast-forward now in time. Suppose that we find
ourselves discounting a lot of prices in an
application, maybe a website like Amazon where they're offering
some kind of percentage discount. And it'd be nice to have
a reusable function that just does this arithmetic for
us, simple though it may be. So let's go ahead and
modify discount this time to give ourselves our own
function called discount, for instance, that takes an input-- like the regular price
that you want to discount-- and then it also returns a value. It doesn't just print it out. It returns a value, namely, a float
that represents what the sale price is. So let me go down
below main and go ahead and define a function that's
going to return a float, because we're dealing
with dollar amount still. The function is going
to be called discount. And it's going to take one input, like
the price that we want to discount. In here, I'm going to do
something very simple. I'm going to say float sale equals
whatever that price is times 0.85. And then I'm going to go
ahead and return sale. Now, for that matter, I can
actually tighten this up a bit. If I'm only declaring a variable
to store a value that I'm then returning with this keyword return, I
actually don't even need that variable. So I can delete the second line. And I can actually just go ahead
and get rid of that variable altogether and immediately
return whatever the arithmetic result is of taking the price input,
the argument that's being passed in, times 0.85. So very simple function that
simply does the discounting for me. As always, let me go
ahead and copy-paste-- the only time it's OK to copy-paste--
the prototype of that function, so the top of the file, so that
when compiling this code, main has already seen
the word discount before. And now let me go into the code here. And instead of doing
the math myself in main, let me presume that we
have some function already in our toolkit called discount that
lets me discount the regular price and return that value. And then down here, my code
doesn't need to change. I'm still going to print out
sale the variable in which I'm storing that result. But
notice what I've done here. I've sort of abstracted
the way the notion of taking a discount by creating my
own function that takes a float called price, or anything else as input. It does a little bit of math,
simple though it is here, and then it returns a value. But notice that discount
is not printing that value. It's literally using
this other keyword called return so that I can hand back that
value, just like get_string hands back a value, just like get_int
back an integer without printing it for you-- so that I up here on
line 9 can go ahead and store that value in a variable if I want
and then actually print it out. Let me go ahead now and recompile
this code with make discount. Let me go ahead and do ./discount. And let's, again, do $100. Sale price is going to be $85 as well. Now, it turns out that functions don't
have to take just 0 or 1 argument as input. They can actually take 2 or 3 or more. So, in fact, suppose we wanted to now
enhance this version of my program and take in as input to the discount
function, not just the price that I want to discount but
also the percentage off, thereby allowing us to support not just
15% off but any number of percentage points off. Well, let me go up here and declare
an int, say, and call it percent_off. And let me ask the user
for how many percentage points they want to take off. So I'm going to say percent_off
inside of the prompt here, get that int called percent_off. And now in addition to
passing in regular as an input to the discount function, I'm
also going to pass in percent_off. But I need to tell the computer
that it is taking now two arguments, and the way I do this
is just with a comma down here in the
function's own definition. Here is going to be a percentage
argument, a second argument, per the comma. And I'm now going to use that
percentage in a slightly familiar way. I don't want to just do percentage
like this, because, of course, that's going to increase
the size of the total price. I actually need to do a little bit
of real-world math where if this is a percentage off, like the number
15 for 15 percentage points, I need to do 100 minus that
many percentage points, thereby giving me 100 minus 15-- 85. And then I need to divide
that by 100 in order now to give myself 0.85 times
the price that was passed in. But if I go ahead now and save this,
run, make discount one last time, I notice that I've
actually got an error here. What have I done wrong? Well, I need to change
that prototype too. And, again, this is admittedly
an annoying aspect of C that you have to maintain
consistency here. But that's fine. I'm just going to go up
here, change this to int percentage-- spelling incorrectly. And now let me retry
compilation, make discount, crossing my fingers this time. Worked OK. ./discount, and voila, $100. And percent off, say, 15 points. And, voila, $85. Now, it's worth noting
that I've deliberately returned the results of my
math from this function. I haven't just done the math on the
original variable that's being passed. In fact, if we take a look
at this second version where discount is now taking a price
argument and a percentage argument, notice that I'm not doing
something like this. I'm not just saying price
equals price times 100 minus percentage divided
by 100 and leaving at that. The problem there is that
this variable price is going to be scoped to that discount function. And we'll encounter this again
before long, but this notion of scope just refers to where in which a
variable actually lives or exists or is accessible. So it turns out if I change price
in the context of this discount function, that's not going
to have a lasting effect. If I actually want to
get the result back to the function that used the
discount function, namely, main, I actually do need to take this
approach of actually returning the value explicitly so that ultimately
I'm handing back the discounted price. All right. Well, let's go ahead and
maybe how about let's just use these primitives in
just a few different ways. How about a little game of
yesteryear, Super Mario Brothers? And in the original Super Mario
Brothers and in bunches of variants, so you have these
side-scrolling worlds that look like this where there's some coins
in the sky hidden behind these question marks. So let's just use this as a
visual to consider how in C could I start to make
something semi-graphical. Like, not actual colors or fanciness,
that feels like too much too soon-- just something like printing
out some question marks. Well, if I go back over here,
let me create that actual file that I alluded to earlier. So let me code up mario.c. Let me go ahead and include
stdio.h, int main void, again, which we'll continue to
copy-paste for today. And then let me just go ahead and
do something simple like 1, 2, 3, 4, and a newline. All right, this is what we
might call ASCII art, which just means graphics but really just
implemented with your keyboard. And if I make mario and do ./mario,
it's not nearly as engaging visually as this, but it's the beginning
of this kind of map for a game. Well, if I wanted to now print
out of those things dynamically, let me go back to my code here. And instead of printing
out for all at once, I could do something like four int i
gets 0, i less than 4, i plus plus. And then inside here, I could just
print out one of them at a time. Let me save that, make mario. And, at the risk of
disappointing, so close but I made a mistake,
just a stupid aesthetic. The prompt is not on the new line. How could I move it? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah, I need an
escape character, the backslash n. But should I put it here? OK, no, because that's going
to put it after everyone, and it's going to make this thing
vertical instead of horizontal. So, logically, just like in Scratch, put
it at the end of the loop, so something out here. And just print out, for instance,
only, quote unquote, new line. And now if I do make
mario again, ./mario, OK. We're back in business. But a little better
designed in that now I'm not repeating myself multiple times,
I'm doing this again and again. But let's do one other
thing here with mario. Let me go ahead and ask the user how
many question marks or coins to print. The catch here is that there's another
type of loop that's helpful for this, and it's called a do
while loop, generally. A do while loop is
similar to a while loop, but it checks the condition
last instead of first. Recall earlier on the
slide, we had while, open parenthesis, closed parenthesis. And I kept claiming that we check
whether i is less than-- whatever it was, 3 in advance again and again. A do while loop just inverts the
logic so that you can actually do something like this. At the top of this program,
I'm going to go ahead now and give myself a variable
n like this of type integer. And then I'm going to do, literally,
the following with the keyword do. n equals get_int-- and I'm going
to ask the user for the width, like the number of
dollar signs to print. And I'm going to do this
while n is less than, say, 1. So this is a little cryptic,
but the salient differences are the Boolean expression is now
at the bottom of my block of code, not at the top. Now, why is this? Well, the difference
here if I make mario is-- whoops. I need to add cs50.h, because
I'm now using get_int. If I now compile this version
of Mario and do ./mario, a do while loop is helpful when you want
to do something no matter what first and then check some condition or some
Boolean expression to see if maybe, in this case, the user cooperated. It would make no sense if
the user typed in, say, 0, because there's no work to be done. It'd be really weird if
they said negative 100, because that makes no sense logically. So with this simple construct
here, I am doing the following while n is less than 1. The implication is that as soon
as n equals 1 or is bigger than 1, I'm going to break out of
this loop, and I've got myself a variable called n containing,
essentially, a positive value, 1 through 2 billion or so. And I can now use this, for
instance, here, change the 4 to an n so now my program is completely dynamic. Let me go ahead and do
make mario, ./mario again. And I'll do 4, still works. I'll do 40, still works. And the difference here with the
do while is if something like this involves getting user input,
well, there's no question to ask. The user hasn't given you anything yet. So you have to do something first,
then check, and break out of the loop if the human has, for instance,
cooperated, in this case. All right, well why don't
we escalate to something more like this in the same game,
where you're underground as Mario, and this is like a two-dimensional
wall that's popping up here? It looks like a 3 by 3, for
instance, for the sake of discussion. And it's like, made of bricks, so
I'll use maybe hash symbols this time. Well, it turns out that we can nest-- that is, combine-- some of
these same ideas as follows. Let me go ahead now and
change back to this code. And I'm going to keep the
do while loop from before. And I'm going to ask,
though, this question, what's the size of this square? I'm going to assume it's n by
n, so 3 by 3, 4 by 4, whatever. So I'm just going to ask for the
size of this square of bricks. And now, how do I do this? Well, I'm going to go ahead,
for instance, and print out-- how about for int i =
0, i less than n, i++. Let me just keep it simple
and print out something like this, just a single
hash symbol that is a brick, and a newline after it. All right, let's make mario. Run mario of 3. OK, that's close to being it. I've got a column. All right, but I need it to be wider. So the solution last time
was to get rid of the newline and then maybe put the
newline here, after the loop. All right, so let's do make mario,
./mario, and type in 3 and huh. All right, so I kind of need to
combine these two ideas somehow. So how might we solve this problem? I want to print rows and
columns, not row or column. How do I do this? Yeah. AUDIENCE: Add another
loop in the for loop. DAVID J. MALAN: Yeah. Add another loop in the for loop, right? If you use one loop conceptually
to kind of count the rows from top to bottom, and then
within each row, you then sort of typewriter style--
old school typewriter-- do like, character, character,
character, character horizontally, I think we could do exactly
what we want to achieve here. So how about this? Let me get rid of this line and
get rid of this line for now. And let me just give myself
another loop on the inside. And since I'm already using i,
another reasonable convention here would be to say something like j. So j also gets 0, j is less than n, j++. And now, what's going to happen? Let me go ahead and print out just
one of these things at a time. And let me save and let me run this. Let me see how close we are. Make mario 3. OK, three, that's clearly wrong, but
I see nine things there on the screen. So we're close. What's the one fix I need now to
move the old school typewriter head down to the next row when appropriate? What do you think? Yeah, I need one of these backslash n's. And let me add some comments now to
help everyone visualize what I've done. For each row, for each column,
how about print a brick-- just to kind of explain the logic? And so I add that because
now move to next row, I could do something like
this with a backslash n. So here is where the comments,
really, my pseudocode actually kind of illuminates
the situation a bit. Let me go ahead and recompile
mario, ./mario 3, now we're talking. It's not a perfect square,
just because these hash symbols are a little taller than they are wide,
but that's just a font detail here. Now I've done something that's quite
more akin to something like this. All right, so let me pause here
and see if there are any questions. Again, the code's getting
a little more complicated, but we're just building more
complicated programs like in Scratch, with familiar puzzle
pieces-- some variables, some loops, some conditionals. It's all the same as before. Yeah. Can you multiply strings in C? No. But ask that same question again in
a few weeks when we get to Python, and the answer will be yes. Other questions. Yeah. In C, you must specify
the return type, the name of the function, and the
inputs, or arguments, to the function in that order. And if none of them are applicable,
you write the word void. So same question as earlier, let
me kick that can a week or so, and we'll come back to
that and we'll see why. But for now, just take on faith
that you need to do that with main. Because main is a little
special, similar to the when green flag is clicked. It too was a little special as well. Yeah AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yes. If you want to get out of a
loop early, you could do this. So let me answer this question this way. An alternative to a do while loop
would be to do something like this. How about while true-- so do the following forever-- let me go ahead and get an inch from
the user for the size of this thing. If n is greater than 0-- that is, a positive integer-- then go ahead and use a
new keyword called break. This is identical to what we just did. It's just a little longer. It's like a couple extra
lines, a lot of them are blank. And so it's just an alternative. But a do while does the same
thing but a little tighter-- if that's in answer to your question. All right, so let's now introduce,
finally, a sequence of problems that I've kind of been
brushing under the rug, though we did see a little bit
of evidence of this earlier when we tried to add 2
billion and 2 billion, and it overflowed the number
of bits in an int, so to speak. Let me go ahead and code up a
program called calculator again. But I'm going to go ahead now
and change this to floats. So I'm going to change x to a float,
and I'm going to use get_float. And a float, again, is just
a floating point value, which is a fancy way of saying a real
number with a decimal point in it. And down here, I'm going to
go ahead and use %f for float. And I'm going to go ahead
now and do one more thing. Instead of addition, I want to do
something fancier, like division, so divide x by y. And I'm going to give myself
another third float called z, as we did at the beginning of today. And I'm going to print out z
instead of x and y explicitly. So I'm going to go ahead now and
do make calculator, ./calculator. And let's do something like, oh, 2/3. 2 divided by 3 is 0.66667. So that's what you would rather expect. Let me run it again, 1/10. All right, so 0.1, and a bunch of zeros. That too is what you
would rather expect. But now let me get a little curious. It turns out that in C, you can
modify the behavior of these format codes a little bit. By default, you get 6 or so digits. Suppose that you want
to get exactly 2 digits. You can more succinctly say 0.2
before the f and after the percent. This is the kind of thing that's
hard to remember, but you Google it, and you find that, OK,
format code for floats uses 0.2 to do two decimal points. So let me do make calculator
again, ./calculator. How about 2/3? 0.67. So it handles the display of
significant digits for us here. And now let me go ahead
and do 1/10 and 0.10. So it's adhering to that. Well, maybe I really want
a lot of precision, right? I've got a really powerful computer. Let me see 50 numbers
after the decimal point. That's a lot of significant digits. Let me remake the
calculator-- whoops, typo. Let me remake the calculator,
./mario calculator. And how about 2/3 again? Well, that's interesting. Pretty sure it's supposed to be
a 0.6 with a line over it, right? In grade school math. All right, well, maybe
that's just a bug. How about 1/10? OK, that's really getting funky. So what's going on? It seems that my program cannot
only not do addition very well-- we eventually hit
problems in the billions-- we can't even do very
precise numbers here. What's going on? Exactly. In a nutshell, the computer's
approximating the answer using that many numbers
after the decimal point. But the problem
fundamentally is actually very similar to that integer
overflow from before. And I'm using that now as a term of art. Integers can overflow if you're trying
to use more bits than you actually have available to you. You sort of change them all to ones, and
then you're out of bits, so to speak. Same thing here, but in the
different context of floats-- if you only have 32
bits-- or, heck, if we change to double and only have 64
bits, that's a lot of precision, but it's not infinite. And, yet, pretty sure there's an
infinite number of real numbers. In the world, which is to say a computer
with finite memory cannot possibly represent all possible
numbers in the world. Because, again, there's
not an infinite number of permutations of 32 or 64 bits. It might be a lot, in the billions
or more, but it's still finite. And so, indeed, this is the
computer's closest approximation to what's actually going on there. And so this is an example of what
we would actually generally call floating-point imprecision. Floating-point imprecision refers to the
inability for computers fundamentally to represent all possible
real numbers 100% precisely, at least by default
in languages like C. Thankfully, in the world of scientific
computing and so forth, there are solutions to this problem
that just give you more digits. But the problem fundamentally
is still going to be there. So there's a reason I
changed x and y to floats. Let's see what would
happen if we rewound a bit. And instead of using floats for x and y,
again, you say integer, so int x and y. And let's go far back
and use get_int as well, thereby giving us integers x and y. Let's still leave z as a float,
because at the end of the day, we want to be able to handle
fractions or floating-point values. But let's go ahead now and
print out this value of z having changed x and y now to ints. make calculator, ./calculator, and
let's do, say, 2 for the numerator, 3 for the denominator. And it's not 0.666, and it's
not even rounding oddly. It's just all zeros this time. So why is that? Well, it turns out that C, when
dividing an integer by an integer, is always going to give you
back an integer, an int. The problem is that floating-point
values don't fit in ints. Only the integral part to the
left of the decimal point does. Everything at and beyond the decimal
point itself get thrown away, known as a feature in
C called truncation. When dividing an integer by an
integer, you get back an integer. But if you're trying to then store
what's actually a floating point result in that integer, C is just
going to throw away everything at and beyond the decimal point,
leaving us with this case, in just the 0 from what should
have been 0.666666 and so forth. So let's see one more example, in fact. Let me go back to my terminal here. Let me do ./calculator again. And let's do 4/3. This time, It should be
1.33333 and so forth. But let's see, 4 divided by 3, both as
integers, this time gives us 1.0000, but there too the
answer should be 1.333. But the floating-point part is
getting truncated or thrown away, leaving us with just 1. So how do we solve this? Well, certainly, we could just use
floats from the get-go, as I did. But if, by nature of your program,
you only have access to integers-- or maybe even longs, for which
the same problem would occur-- what we can actually do
is called type conversion. And we can explicitly tell
the computer that we actually want to treat this int as though
it's a floating-point value. And we can do that for both x and y. So let me go back to my code here, and
I have a couple of options, in fact. I can convert y to a float by
doing this, I can cast y to a float by literally writing the type
float inside of parentheses right before the y. And if I really want to be explicit,
I can also do the same to x. But, strictly speaking, it suffices
to just change one or the other, not necessarily both. Let me go ahead now and do make
calculator again, ./calculator, and let's try 2 divided by 3. And now, we're back to an
answer that's closer to correct. But, indeed, we're still having
some rounding issues there. Let's run it one more
time for 4 divided by 3. There too we're closer to
the right answer, at least. But we still have that
floating-point imprecision, but that's going to be another
problem altogether to solve. And here in a little
more detail is that issue of integer overflow, which
is in the context of ints. Suppose that we think back to
last week when we had three bits, and we counted from 0 to
7, 0, 1, 2, 3, 4, 5, 6, 7. I think I asked the question,
how would we count to 8? Someone proposed, well,
we need a fourth bit. That's fine if you have a
fourth bit, if you have access to another light bulb or transistor. If you don't, though, the next number
after this is technically 1000. But if you don't have space for
or hardware for that fourth bit, you might as well just be
representing the number 0. So in the world of integers, if
you're only using three bits, those three bits eventually
overflow when you count past 7. Because what should be 8 can't fit, so
to speak, so it rolls back over to 0. And as arcane as this
problem might seem, we humans have done
this a couple of times. You might recall
knowing about or reading about the Y2K problem,
where a lot of people thought the world was going to end. Why? Because on January 1st of
2000, a lot of computers, presumably, were going to update their
clocks from 1999 to the year 2000. The problem is, though, for
decades, for efficiency, we humans were honestly in the habit of
not storing years as four digits. Why? Because that's just a lot of space
to waste, especially since centuries don't happen that often. So a lot of computer systems,
especially early on when hardware was very expensive
and memory was very tight, just stored the last
two digits of any year. The problem, of course, on January 1st
of 2000 is that 99 rolls over to 100. But if you don't have room for
another digit it's just 00. And if your code assumes a prefix of
19, well, we just went from the year 1999 back to the year 1900. Thankfully, long story short, a
lot of people wrote a lot of code in a lot of old languages and
mostly warded off this problem, so the world did not end. The next time the world might end
though, is on January 19, 2038. Now, that might feel
like a long time away, but so did the year 2000, at one point. Why might clocks again break in
today's modern computers in 2038, might you think? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Indeed. So this refers to some
number of seconds. So it turns out that the way
computers generally keep track of time is they count the total number
of seconds since the epoch, which is defined as January 1, 1970. Why? It was just a good year
to start counting at, when computers really
came onto the scene. Unfortunately, most computers used 32
bits to count the number of seconds since January 1, 1970, the
implication of which is we can only count up to
roughly 2 billion seconds. 2 billion seconds is going to
happen in 2038, at which 30 11's are going to roll over as follows. That number 2 billion,
which is the max-- because if you're representing
positive and negative numbers, recall that you can only count
as high as positive 2 billion or negative 2 billion-- looks like this. This is roughly the number
2 billion in binary. It's all ones with one
zero way over here. If I count one second past that
2 billion number, give or take-- that means, all right,
I add 1, I carry the 1-- it's just like 9's
becoming 0's in decimal. If I keep this sort of simple
animation and I keep carrying the 1, carrying the 1, carrying the 1, 1 second
after 2 billion seconds, give or take, I have this number in
the computer's memory. So there's still 1 bit that's
a 1 all the way to the left. Unfortunately, that bit
often represents negativity, whereby if that first bit is negative,
that represents that the rest of it somehow represents a negative number. It's not negative 0. There's a fancier representation. But a very big, positive
number very suddenly becomes a very big, negative number. And that number is roughly
negative 2 billion. That means computers
in 2038 on that date are going to accidentally
think that it's been negative 2 billion seconds since
January 1, 1970, which is going to make computers potentially think it's 1901. So what is the solution to
the 2038 problem, perhaps? Y2K was because we were
using two digits for years. What about 2038? More bits. And, thankfully, we're getting a
little better at lessons learned here, and computers now are
increasingly using 64 bits. And all of us will be long
gone by the time we run out of that number of seconds, so
it's someone else's problem many, many years from now. But that's really the
fundamental solution. If you're running up against
something finite, well, just kick the can further and
just give yourself more bits. And, frankly, because hardware
is so much cheaper these days, computers are so much faster,
it's not as big of a deal as it might have been decades ago. But that's indeed the solution. But this arises in very common contexts. In fact, let me go ahead and write a
real quick program here called pennies. You might think that just converting
dollars to pennies in US currency might be simple, but let
me go ahead and do this. In pennies.c, I'm going to
go ahead and include cs50.h. And I'm going to include stdio.h,
int main void as my starting point. And now down here, I'm going to do this. I'm going to get a float
called amount, and I'm going to ask the user for some amount
of dollars, so a dollar amount, and I'm going to store that
in a variable called amount. Then I'm going to simply convert that
amount to pennies by doing, say, how about amount times 100? And then I'm going to go ahead and print
out that the number of pennies is %i-- because that's just an
integer in pennies-- backslash n, quote
unquote, comma, pennies. All right, so if I didn't make any
mistakes here, let me make pennies, ./pennies. And suppose I have, say, $0.99, so 0.99. That's 99 pennies. Suppose I have $1.23. That's pretty good. Suppose I have $4.20. Huh. There's that imprecision issue. And this isn't even
that big of an amount. Now, not a big deal if the cashier gives
you one penny less than you're owed, but you can imagine this adding up. You can imagine this being worrisome
for financial implications, for financial transactions, for
scientific measurements and the like. My program can't even handle this. Well, there are some solutions here. And it looks like what's
really happening-- if I print it out using the %f with a
0.50 or whatever to see more decimal points-- presumably, the computer is struggling
to represent $4.20 precisely. It's probably storing 4 dollars
and 19.9999-something cents. So it's close, but it's not quite there. So I could at least solve this
by rounding up, for instance. And it turns out there is
a round function out there. And it turns out that it's in a
library called the math library. And you would know this by looking
at online documentation and the like, as we'll point you to. And if I now make pennies again and
do ./pennies, I can now do $4.20. And, voila. Now it's correct. So at least in this context, it
seems like a solvable problem. But it's certainly something I
need to be mindful of, nonetheless. Unfortunately, even professional,
full-time programmers over the years have not been particularly
attentive to these kinds of details. And in a class like this, the goal
is not just to teach you programming but to really teach you what's going
on underneath the hood, so to speak, so that you have a bottom-up
understanding of how data is represented, how computers
are manipulating it, so that you are not on the failing
end of some program having some bug. And so that we as a society are not
beholden to those kinds of mistakes too. And this happens,
unfortunately, all of the time. This is a Boeing airplane
that a few years ago needed to be rebooted after every 248 days. Why? Because this Boeing airplane software
was using a 32-bit integer counting up tenths of a second to keep
track of something or other related to its electrical power. And, unfortunately, after 248 days of
the airplane being continuously on-- which in the airline
industry is apparently not uncommon to make every dollar count,
keeping the planes up and running all the time-- the 32-bit number would
roll over and the power would shut off on the airplane
as a side effect because of sort of undefined behavior in that case. The temporary solution by Boeing at
the time was apparently, essentially, sort of operating system style,
well, have you rebooted your plane? And that was indeed the fix until they
rolled out an actual software patch. This stuff really matters. And the more hardware we carry
around and the more we as a society use these kinds of devices,
the more of these problems we're going to run into down the road. That's it for CS50. We'll see you next time. DAVID MALAN: This is
CS50 and this is week 2. Now that you have some programming
experience under your belts, in this more arcane language called c. Among our goals today is to help
you understand exactly what you have been doing these past several days. Wrestling with your first programs in
C, so that you have more of a bottom up understanding of what
some of these commands do. And, ultimately, what more
we can do with this language. So this recall was the very
first program you wrote, I wrote in this language
called C, much more textual, certainly, than the Scratch equivalent. But at the end of the day,
computers, your Mac, your PC, VS Code doesn't understand
this actual code. What's the format into which we need
to get any program that we write, just to recap? AUDIENCE: [INAUDIBLE] DAVID MALAN: So binary,
otherwise known as machine code. Right? The 0s and 1s that your computer
actually does understand. So somehow we need to
get to this format. And up until now, we've been
using this command called make, which is aptly named, because
it lets you make programs. And the invocation of that
has been pretty simple. Make hello looks in your current
directory or folder for a file called hello.c, implicitly, and then it
compiles that into a file called hello, which itself is executable,
which just means runnable, so that you can then do ./hello. But it turns out that make is
actually not a compiler itself. It does help you make programs. But make is this utility that comes on
a lot of systems that makes it easier to actually compile code by
using an actual compiler, the program that converts source code
to machine code, on your own Mac, or PC, or whatever cloud environment
you might be using. In fact, what make is
doing for us, is actually, running a command automatically
known as clang, for C language. And, so here, for instance, in VS
Code, is that very first program again, this time in the context
of a text editor, and I could compile
this with make hello. Let me go ahead and use the
compiler itself manually. And we'll see in a moment why we've
been automating the process with make. I'm going to run clang instead. And then I'm going to run hello.c. So it's a little different
how the compiler's used. It needs to know, explicitly,
what the file is called. I'll go ahead and run
clang, hello.c, Enter. Nothing seems to happen, which,
generally speaking, is a good thing. Because no errors have popped up. And if I do ls for list, you'll see
there is not a file called hello. But there is a curiously-named
file called a.out. This is a historical convention,
stands for assembler output. And this is, just, the default
file name for a program that you might compile yourself,
manually, using clang itself. Let me go ahead now and
point out that that's kind of a stupid name for a program. Even though it works,
./a.out would work. But if you actually want to
customize the name of your program, we could just resort to make,
or we could do explicitly what make is doing for us. It turns out, some
programs, among them make, support what are called
command line arguments, and more on those later today. But these are literally words or
numbers that you type at your prompt after the name of a program that just
influences its behavior in some way. It modifies its behavior. And it turns out, if you read
the documentation for clang, you can actually pass a -o, for
output, command line argument, that lets you specify,
explicitly what do you want your outputted program to be called? And then you go ahead and type the
name of the file that you actually want to compile, from
source code to machine code. Let me hit Enter now. Again, nothing seems to happen,
and I type ls and voila. Now we still have the old a.out,
because I didn't delete it yet. And I do have hello now. So ./hello, voila, runs
hello, world again. And let me go ahead
and remove this file. I could, of course, resort to using
the Explorer, on the left hand side. Which, I am in the habit of closing,
just to give us more room to see. But I could go ahead and right-click
or control-click on a.out if I want to get rid of it. Or again, let me focus on
the command line interface. And I can use-- anyone recall? We didn't really use it much,
but what command removes a file? AUDIENCE: rm. DAVID MALAN: So rm for
remove. rm, a.out, Enter. Remove regular file,
a.out, y for yes, enter. And now, if I do ls
again, voila, it's gone. All right, so, let's
now enhance this program to do the second version we ever did,
which was to also include cs50.h, so that we have access to functions
like, get string, and the like. Let me do string, name, gets,
get string, what's your name, question mark. And now, let me go ahead and say hello
to that name with our %s placeholder, comma, name. So this was version 2 of
our program last time, that very easily compiled with make
hello, but notice the difference now. If I want to compile this
thing myself with clang, using that same lesson learned,
all right, let's do it. clang-o, hello, just so I get a better
name for the program, hello.c, Enter. And a new error pops up that some of
you might have encountered on your own. So it's a bit arcane here, and there's
this mention of a cryptic-looking path with temp for temporary there. But somehow, my issue's in
main, as we can see here. It somehow relates to hello.c. Even though we might not have seen
this language last time in class, but there's an undefined
reference to get string. As though get string doesn't exist. Now, your first instinct might be, well
maybe I forgot cs50.h, but of course, I didn't. That's the very first
line of my program. But it turns out, make is doing
something else for us, all this time. Just putting cs50.h, or any header
file at the top of your code, for that matter, just teaches the
compiler that a function will exist. It, sort of, asks the compiler
to-- it asks the compiler to trust that I will, eventually,
get around to implementing functions, like get string, and cs50.h,
and stdio.h, printf, therein. But this error here, some kind of
linker command, relates to the fact that there's a separate
process for actually finding the 0s and 1s that
cs50 compiled long ago for you. That authors of this operating
system compiled for you, long ago, in the form of printf. We need to, somehow,
tell the compiler that we need to link in code
that someone else wrote, the actual machine code that someone
else wrote and then compiled. So to do that, you'd have to
type -lcs50, for instance, at the end of the command. So additionally, telling clang
that, not only do you want to output a file called hello, and you want
to compile a file called hello.c, you also want to quote-unquote
link in a bunch of 0s and 1s that collectively implement
get string and printf. So now, if I hit enter,
this time it compiled OK. And now if I run ./hello, it works
as it did last week, just like that. But honestly, this is just going to
get really tedious, really quickly. Notice, already, just
to compile my code, I have to run clang-o,
hello, hello.c, lcs50, and you're going to have
to type more things, too. If you wanted to use the math library,
like, to use that round function, you would also have
to do -lm, typically, to specify give me the math
bits that someone else compiled. And the commands just
get longer and longer. So moving forward, we won't have
to resort to running clang itself, but clang is, indeed, the compiler. That is the program that converts
from source code to machine code. But we'll continue to use make because
it just automates that process. And the commands are
only going to get more cryptic the more sophisticated and
more feature full year programs get. And make, again, is just a tool
that makes all that happen. Let me pause there to see if
there's any questions before then we take a look further under the hood. Yeah, in front. AUDIENCE: Can you explain again what
the -lcs50-- just why you put that? DAVID MALAN: Sure, let me
come back to that in a moment. What does the -lcs50 mean? We'll come back to that,
visually, in just a moment. But it means to link in the
0s and 1s that collectively implement get string and printf. But we'll see that, visually, in a sec. Yeah, behind you. AUDIENCE: [INAUDIBLE]. DAVID MALAN: Really good question. How come I didn't have
to link in standard I/O? Because I used printf in version 1. Standard I/O is just, literally,
so standard that it's built in, it just works for free. CS50, of course, is not. It did not come with the
language C or the compiler. We ourselves wrote it. And other libraries, even though
they might come with the language C, they might not be enabled by default,
generally for efficiency purposes. So you're not loading more 0s
and 1s into the computer's memory than you need to. So standard I/O is special, if you will. Other questions? Yeah? AUDIENCE: [INAUDIBLE] DAVID MALAN: Oh, what does the -o mean? So -o is shorthand for
the English word output, and so -o is telling clang to
please output a file called hello, because the next thing I
wrote after the command line recall was clang -o hello, then
the name of the file, then -lcs50. And this is where these commands
do get and stay fairly arcane. It's just through muscle
memory and practice that you'll start to remember, oh
what are the other commands that you-- what are the command line arguments
you can provide to programs? But we've seen this before. Technically, when you run make
hello, the program is called make, hello is the command line argument. It's an input to the
make function, albeit, typed at the prompt, that tells
make what you want to make. Even when I used rm a moment
ago, and did rm of a.out, the command line argument
there was called a.out and it's telling rm what to delete. It is entirely dependent on the programs
to decide what their conventions are, whether you use dash this
or dash that, but we'll see over time, which ones
actually matter in practice. So to come back to the first question
about what actually is happening there, let's consider the code more closely. So here is that first
version of the code again, with stdio.h and only
printf, so no cs50 stuff yet. Until we add it back in
and had the second version, where we actually get the human's name. When you run this command,
there's a few things that are happening
underneath the hood, and we won't dwell on these
kinds of details, indeed, we'll abstract it away by using make. But it's worth understanding
from the get-go, how much automation is going on, so
that when you run these commands, it's not magic. You have this bottom-up
understanding of what's going on. So when we say you've been
compiling your code with make, that's a bit of an oversimplification. Technically, every time
you compile your code, you're having the computer do
four distinct things for you. And this is not four distinct things
that you need to memorize and remember every time you run your
program, what's happening, but it helps to break it
down into building blocks, as to how we're getting from source
code, like C, into 0s and 1s. It turns out, that when you compile,
quote-unquote, "your code," technically speaking, you're doing four things
automatically, and all at once. Preprocessing it, compiling it,
assembling it, and linking it. Just humans decided, let's just
call the whole process compiling. But for a moment, let's
consider what these steps are. So preprocessing refers to this. If we look at our source code,
version 2 that uses the cs50 library and therefore get string, notice that
we have these include lines at top. And they're kind of special
versus all the other code we've written, because they start
with hash symbols, specifically. And that's sort of a
special syntax that means that these are, technically,
called preprocessor directives. Fancy way of saying they're handled
special versus the rest of your code. In fact, if we focus on
cs50.h, recall from last week that I provided a hint as to what's
actually in cs50.h, among other things. What was the one salient thing that
I said was in cs50.h and therefore, why we were including
it in the first place? AUDIENCE: Get string? DAVID MALAN: So get
string, specifically, the prototype for get string. We haven't made many of
our own functions yet, but recall that any time
we've made our own functions, and we've written them
below main in a file, we've also had to, somewhat
stupidly, copy paste the prototype of the function
at the top of the file, just to teach the compiler that
this function doesn't exist, yet, it does down there, but it will exist. Just trust me. So again, that's what these
prototypes are doing for us. So therefore, in my
code, If I want to use a function like get string,
or printf, for that matter, they're not implemented
clearly in the same file, they're implemented elsewhere. So I need to tell the compiler
to trust me that they're implemented somewhere else. And so technically,
inside of cs50.h, which is installed somewhere in the
cloud's hard drive, so to speak, that you all are accessing via VS Code,
there's a line that looks like this. A prototype for the get string function
that says the name of the functions get string, it takes one input,
or argument, called prompt, and that type of that
prompt is a string. Get string, not surprisingly, has a
return value and it returns a string. So literally, that line and a
bunch of others, are in cs50.h. So rather than you all having
to copy paste the prototype, you can just trust that
cs50 figured out what it is. You can include cs50.h
and the compiler is going to go find that prototype for you. Same thing in standard
I/O. Someone else-- what must clearly be in stdio.h,
among other stuff, that motivates our including stdio.h, too? Yeah? AUDIENCE: Printf. DAVID MALAN: Printf, the
prototype for printf, and I'll just change it here
in yellow, to be the same. And it turns out, the format-- the prototype for printf
is, actually, pretty fancy, because, as you might have noticed,
printf can take one argument, just something to print, 2, if you want
to plug a value into it, 3 or more. So the dot dot dot just
represents exactly that. It's not quite as simple a prototype
as get strain, but more on that another time. So what does it mean to
preprocess your code? The very first thing the
compiler, clang, in this case, is doing for you when it reads your
code top-to-bottom, left-to-right, is it notices, oh, here is hash include,
oh, here's another hash include. And it, essentially, finds those files
on the hard drive, cs50.h, stdio.h, and does the equivalent of copying
and pasting them automatically into your code at the very top. Thereby teaching the compiler
that gets string and printf will eventually exist somewhere. So that's the preprocessing
step, whereby, again, it's just doing a find-and-replace of
anything that starts with hash include. It's plugging in the files
there so that you, essentially, get all the prototypes
you need automatically. OK. What does it mean, then,
to compile the results? Because at this point
in the story, your code now looks like this in
the computer's memory. It doesn't change your
file, it's doing all of this in the computer's
memory, or RAM, for you. But it, essentially, looks like this. Well the next step is what's,
technically, really compiling. Even though again, we use
compile as an umbrella term. Compiling code in C
means to take code that now looks like this in
the computer's memory and turn it into something
that looks like this. Which is way more cryptic. But it was just a few
decades ago that, if you were taking a class like
CS50 in its earlier form, we wouldn't be using C it didn't exist
yet, we would actually be using this, something called assembly language. And there's different types of,
or flavors of, assembly language. But this is about as low level as
you can get to what a computer really understands, be it a
Mac, or PC, or a phone, before you start getting
into actual 0s and 1s. And most of this is cryptic. I couldn't tell you what this is doing
unless I thought it through carefully and rewound mentally, years
ago, from having studied it, but let's highlight a
few key words in yellow. Notice that this assembly language
that the computer is outputting for you automatically,
still has mention of main and it has mention of get string,
and it has mention of printf. So there's some relationship to
the C code we saw a moment ago. And then if I highlight
these other things, these are what are called
computer instructions. At the end of the day,
your Mac, your PC, your phone actually only
understands very basic instructions, like addition, subtraction, division,
multiplication, move into memory, load from memory, print something to
the screen, very basic operations. And that's what you're seeing here. These assembly instructions
are what the computer actually feeds into the brains of the computer,
the CPU, the central processing unit. And it's that Intel CPU,
or whatever you have, that understands this instruction, and
this one, and this one, and this one. And collectively, long
story short, all they do is print hello, world on
the screen, but in a way that the machine understands how to do. So let me pause here. Are there any questions on
what we mean by preprocessing? Which finds and replaces the hash
includes symbols, among others, and compiling, which technically
takes your source code, once preprocessed, and converts it to
that stuff called assembly language. AUDIENCE: [INAUDIBLE] each CPU has-- DAVID MALAN: Correct. Each type of CPU has
its own instruction set. Indeed. And as a teaser, this is why,
at least back in the day, when we used to install software from
CD-ROMs, or some other type of media, this is why you can't take a program
that was sold for a Windows computer and run it on a Mac, or vice-versa. Because the commands, the instructions
that those two products understand, are actually different. Now Microsoft, or any company, could
generally write code in one language, like C or another, and they can
compile it twice, saving a PC version and saving a Mac version. It's twice as much work and sometimes
you get into some incompatibilities, but that's why these steps
are somewhat distinct. You can now use the same code and
support even different platforms, or systems, if you'd want. All right. Assembly, assembling. Thankfully, this part is fairly
straightforward, at least, in concept. To assemble code, which is step
three of four, that is just happening for you every time
you run make or, in turn, clang, this assembly language, which the
computer generated automatically for you from your source code,
is turned into 0s and 1s. So that's the step that, last
week, I simplified and said, when you compile your code, you convert
it to source code-- from source code to machine code. Technically, that happens
when you assemble your code. But no one in normal
conversations says that, they just say compile for all of these terms. All right. So that's assembling. There's one final step. Even in this simple program
of getting the user's name and then plugging it into printf, I'm
using three different people's code, if you will. My own, which is in hello.c. Some of CS50s, which is
in hello.c, sorry-- which is in cs50.c, which is not
a file I've mentioned, yet, but it stands to reason, that if
there's a cs50.h that has prototypes, turns out, the actual
implementation of get string and other things are in cs50.c. And there's a third file
somewhere on the hard drive that's involved in compiling
even this simple program. hello.c, cs50.c, and by that
logic, what might the other be? Yeah? AUDIENCE: stdio? DAVID MALAN: Stdio.c. And that's a bit of a white lie,
because that's such a big, fancy library that there's actually multiple files
that compose it, but the same idea, and we'll take the simplification. So when I have this code,
and I compile my code, I get those 0s and 1s that end up taking
hello.c and turning it, effectively, into 0s and 1s that are combined with
cs50.c, followed by stdio.c as well. So let me rewind here. Here might be the 0s and 1s for my code,
the two lines of code that I wrote. Here might be the 0s and 1s for what
cs50 wrote some years ago in cs50.c. Here might be the 0s and 1s that someone
wrote for standard I/O decades ago. The last and final step
is that linking command that links all of these
0s and 1s together, essentially stitches them together
into one single file called hello, or called a.out, whatever you name it. That last step is what combines all of
these different programmers' 0s and 1s. And my God, now we're
really in the weeds. Who wants to even think about
running code at this level? You shouldn't need to. But it's not magic. When you're running make,
there's some very concrete steps that are happening that humans
have developed over the years, over the decades, that breakdown
this big problem of source code going to 0s and 1s, or machine code,
into these very specific steps. But henceforth, you can
call all of this compiling. Questions? Or confusion? Yeah? AUDIENCE: Can you explain
again what a.out signifies? DAVID MALAN: Sure. What does a.out signify? a.out is just the conventional,
default file name for any program that you compile directly
with a compiler, like clang. It's a meaningless name, though. It stands for assembler output, and
assembler might now sound familiar from this assembling process. It's a lame name for a
computer program, and we can override it by outputting
something like hello, instead. Yeah? AUDIENCE: [INAUDIBLE] DAVID MALAN: To recap, there are
other prototypes in those files, cs50.h, stdio.h, technically, they're
all included on top of your file, even though you, strictly
speaking, don't need most of them, but they are there, just in
case you might want them. And finally, any other questions? Yeah? AUDIENCE: [INAUDIBLE] DAVID MALAN: Does it matter what order
we're telling the computer to run? Sometimes with libraries,
yes, it matters what order they are linked in together. But for our purposes, it's
really not going to matter. It's going to-- make is going to take
care of automating that process for us. All right. So with that said, henceforth,
compiling, technically, is these four things. But we'll focus on it as a higher
level concept, an abstraction, known as compiling itself. So another process that we'll
now begin to focus on all the more this week because, invariably,
this past week you ran against-- ran up against some challenges. You probably created your very first
bugs, or mistakes, in a program and so let's focus for a moment on
actual techniques for debugging. As you spend more time
this semester, in the years to come If you continue to program,
you're never, frankly, probably, going to write bug
free code, ultimately. Though your programs are going to get
more featureful, more sophisticated, and we're all going to start to
make more sophisticated mistakes. And to this day, I write
buggy code all the time. And I'm always horrified
when I do it up here. But hopefully, that
won't happen too often. But when it does, it's a process,
now, of debugging, trying to find the mistakes in your program. You don't have to stare at your code,
or shake your fist at your code. There are actual tools
that real world programmers use to help debug their
code and find these faults. So what are some of the techniques
and tools that folks use? Well as an aside, if you've ever-- a bug in a program is a mistake,
that's been around for some time. If you've ever heard this tale,
some 50 plus years ago, in 1947. This is an entry in a log book written
by a famous computer scientist known as-- named Grace Hopper,
who happened to be the one to record the very first discovery of a
quote-unquote actual bug in a computer. This was like a moth
that had flown into, at the time, a very sophisticated system
known as the Harvard Mark II computer, very large, refrigerator-sized
type systems, in which an actual bug caused an issue. The etymology of bug though,
predates this particular instance, but here you have, as any computer
scientists might know, the example of a first physical bug in a computer. How, though, do you go
about removing such a thing? Well, let's consider a very
simple scenario from last time, for instance, when we were trying to
print out various aspects of Mario, like this column of 3 bricks. Let's consider how I might go about
implementing a program like this. Let me switch back over to VS
Code here, and I'm going to run-- write a program. And I'm not going to
trust myself, so I'm going to call it
buggy.c from the get-go, knowing that I'm going
to mess something up. But I'm going to go ahead
and include stdio.h. And I'm going to define main, as usual. So hopefully, no mistakes just yet. And now, I want to print those
3 bricks on the screen using just hashes for bricks. So how about 4 int i get 0, i less
than or equal to 3, i plus plus. Now, inside of my
curly braces, I'm going to go ahead and print out a hash
followed by a backslash n, semicolon. All right, saving the file, doing
make, buggy, Enter, it compiles. So there's no syntactical errors,
my code is syntactically correct. But some of you have probably
seen the logical error already, because when I run this
program I don't get this picture, which was 3 bricks
high, I seem to have 4 bricks instead. Now, this might be jumping out
at you, why it's happening, but I've kept the program
simple just so that we don't have to find an actual bug, we can
use a tool to find one that we already know about, in this case. What might be the first strategy
for finding a bug like this, rather than staring at your code,
asking a question, trying to think through the problem? Well, let's actually try to diagnose
the problem more proactively. And the simplest way to do
this now, and years from now, is, honestly, going to be to
use a function like printf. Printf is a wonderfully
useful function, not for formatting-- printing
formatted strings and all that, for just looking inside
the values of variables that you might be curious
about to see what's going on. So you know what? Let me do this. I see that there's 4 coming
out, but I intended 3. So clearly, something's
wrong with my i variables. So let me be a little more pedantic. Let me go inside of this
loop and, temporarily, say something explicit, like, i is-- &i /n, and then just
plug in the value of i. Right? This is not the program I want to
write, it's the program I'm temporarily writing, because now I'm going
to say make buggy, ./buggy. And if I look, now,
at the output, I have some helpful diagnostic information.
i is 0, and I get a hash, i is 1, and I get a hash, 2 and I
get a hash, 3 and I get hash. OK, wait a minute. I'm clearly going too many
steps because, maybe, I forgot that computers are,
essentially, counting from 0, and now, oh, it's less than or equal to. Now you see it, right? Again, trivial example,
but just by using printf, you can see inside of
the computer's memory by just printing stuff out like this. And now, once you've figured it out, oh,
so this should probably be less than 3, or I should start
counting from 1, there's any number of ways I could fix this. But the most conventional is
probably just to say less than 3. Now, I can delete my temporary print
statement, rerun make buggy, ./buggy. And, voila, problem solved. All right, and to this day, I do this. Whether it's making a command line
application, or a web application, or mobile application,
It's very common to use printf, or some equivalent
in any language, just to poke around and see what's
inside the computer's memory. Thankfully, there's more
sophisticated tools than this. Let me go ahead and
reintroduce the bug here. And let me reopen my
sidebar at left here. Let me now recompile the code
to make sure it's current. And I'm going to run a
command called debug50. Which is a command that's
representative of a type of program known as a debugger. And this debugger is
actually built into VS Code. And all debug50 is doing for us is
automating the process of starting VS Code's built-in debugger. So this isn't even a
CS50-specific tool, we've just given you a debug50
command to make it easier to start it up from the get-go. And the way you run this debugger
is you say debug50, space, and then the name of the program
that you want to debug. So, in this case, . /buggy. So you don't mention your c-file. You mention your already-compiled code. And what this debugger is going
to let me do is, most powerfully, walk through my code step-by-step. Because every program we've written
thus far, runs from start to finish, even if I'm not done thinking
through each step at a time. With a debugger, I can
actually click on a line number and say pause execution
here, and the debugger will let me walk through my code one
step at a time, one second at a time, one minute at a time,
at my own human pace. Which is super compelling when
the programs get more complicated and they might, otherwise,
fly by on the screen. So I'm going to click
to the left of line 5. And notice that these
little red dots appear. And if I click on one it
stays, and gets even redder. And I'm going to run debug50 on ./buggy. And in just a moment, you'll see that a
new panel opens on the left hand side. It's doing some
configuration of the screen. Let me zoom out a little bit here so
we can see more on the screen at once. And sometimes, you'll see in VS
Code that debug console opens up, which looks very cryptic, just go back
to terminal window if that happens. Because at the terminal window is where
you can still interact with your code. And let's now take a
look at what's going on. If I zoom in on my
buggy.c code here, you'll notice that we have the same program
as before, but highlighted in yellow is line 5. Not a coincidence, that's the line
I set a so-called breakpoint at. The little red dot means break
here, pause execution here. And the yellow line has
not yet been executed. But if I, now, at the top of my
screen, notice these little arrows. There's one for Play. There's one for this,
which, if I hover over it, says Step Over, there's another
that's going to say Step Into, there's a third that says Step Out. I'm just going to use the
first of these, Step Over. And I'm going to do this, and
you'll see that the yellow highlight moved from line 5 to line
7 because now it's ready, but hasn't yet printed out that hash. But the most powerful thing here,
notice, is that top left here. It's a little cryptic, because
there's a bunch of things going on that will make more
sense over time, but at the top there's a section called variables. Below that, something
called locals, which means local to my current function, main. And notice, there's my variable
called i, and its current value is 0. So now, once I click Step Over
again, watch what happens. We go from line 7 back to line 5. But look in the terminal window,
one of the hashes has printed. But now, it's printed at my own pace. I can think through this step-by-step. Notice that i has not changed, yet. It's still 0 because the yellow
highlighted line hasn't yet executed. But the moment I click Step Over,
it's going to execute line 5. Now, notice at top left, i has become
1, and nothing has printed, yet, because now, highlighted is line 7. So if I click Step Over
again, we'll see the hash. If I repeat this process at my
own human, comfortable pace, I can see my variables changing, I
can see output changing on the screen, and I can just think about
should that have just happened. I can pause and give
thought to what's actually going on without trying to race the
computer and figure it all out at once. I'm going to go ahead and
stop here because we already know what this particular problem
is, and that brings me back to my default terminal window. But this debugger, let me
disable the breakpoint now so it doesn't keep
breaking, this debugger will be your friend
moving forward in order to step through your code step-by-step,
at your own pace to figure out where something has gone wrong. Printf is great, but it gets annoying if
you have to constantly add print this, print this, print this, print this,
recompile, rerun it, oh wait a minute, print this, print this. The debugger lets you do the
equivalent, but automatically. Questions on this debugger, which you'll
see all the more hands-on over time? Questions on debugger? Yeah? AUDIENCE: You were using
a Step Over feature. What do the other
features in the debugger-- DAVID MALAN: Really good question. We'll see this before long, but those
other buttons that I glossed over, step into and step out of, actually
let you step into specific functions if I had any more than main. So if main called a
function called something, and something called a function
called something else, instead of just stepping over the entire execution of
that function, I could step into it and walk through its
lines of code one by one. So any time you have
a problem set you're working on that has multiple functions,
you can set a breakpoint in main, if you want, or you can set it inside
of one of your additional functions to focus your attention only on that. And we'll see examples
of that over time. All right, so what else? And what's the sort of, elephant
in the room, so to speak, is actually a duck in this case. Why is there this duck and
all of these ducks here? Well, it turns out, a third, genuinely
recommended, debugging technique is talking through problems, talking
through code with someone else. Now, in the absence of having
a family member, or a friend, or a roommate who actually wants to
hear you talk about code, of all things, generally, programmers turn to a
rubber duck, or other inanimate objects if something animate is not available. The idea behind rubber duck
debugging, so to speak, is that simply by looking at your code
and talking it through, OK, on line 3, I'm starting a 4 loop and
I'm initializing i to 0. OK, then, I'm printing out a hash. Just by talking through your
code, step-by-step, invariably, finds you having the proverbial
light bulb go off over your head, because you realize, wait a minute
I just said something stupid, or I just said something wrong. And this is really just a proxy for any
other human, teaching fellow, teacher or friend, colleague. But in the absence of any
of those people in the room, you're welcome to take,
on your way out today. One of these little, rubber ducks and
consider using it, for real, any time you want to talk through one
of your problems in CS50, or maybe life more generally. But having it there on
your desk is just a way to help you hear illogic
in what you think might, otherwise, be logical code. So printf, debugging, rubber-duck
debugging are just three of the ways, you'll see over time, to
get to the source of code that you will write that has mistakes. Which is going to happen,
but it will empower you all the more to solve those mistakes. All right, any questions on debugging,
in general, or these three techniques? Yeah? AUDIENCE: [INAUDIBLE] DAVID MALAN: What's the difference
between Step Over and Step Into? At the moment, the only one that's
applicable to the code I just wrote is Step Over, because it means
step over each line of code. If, though, I had other functions
that I had written in this program, maybe lower down in the file, I
could step into those function calls and walk through them one at a time. So we'll come back to this
with an actual example, but step into will allow
me to do exactly that. In fact, this is a perfect segue to
doing a little something like this. Let me go ahead and open
up another file here. And, actually, we'll
use the same, buggy. And we're going to write one
other thing that's buggy, as well. Let me go up here and
include, as before, cs50.h. Let me include stdio.h. Let me do int main(void). So all of this, I think,
is correct, so far. And let's do this, let's
give myself an int called i, and let's ask the user
for a negative integer. This is not a function that
exists, technically, yet. But I'm going to assume, for the
sake of discussion, that it does. Then, I'm just going to print
out, with %i and a new line, whatever the human typed in. So at this point in the story,
my program, I think, is correct. Except for the fact that
get negative int is not a function in the CS50
library or anywhere else. I'm going to need to invent it myself. So suppose, in this case, that I declare
a function called get negative int. It's return type, so to speak, should
be int, because, as its name suggests, I want to hand the user back
in integer, and it's going to take no input to keep it simple. So I'm just going to say void there. No inputs, no special
prompts, nothing like that. Let me, now, give myself
some curly braces. And let me do something familiar,
perhaps, from problem set 1. Let me give myself a variable,
like n, and let me do the following within this block of code. Assign n the value of get int, asking
the user for a negative integer using get int's own prompt. And I want to do this while
n is less than 0, because I want to get a negative from the user. And recall, from having
used this block in the past, I can now return n as the
very last step to hand back whatever the user has typed in, so
long as they cooperated and gave me an actual negative integer. Now, I've deliberately
made a mistake here, and it's a subtle,
silly, mathematical one, but let me compile this program after
copying the prototype up to the top, so I don't make that mistake again. Let me do make buggy, Enter. And now, let me do ./buggy. I'll give it a negative
integer, like negative 50. Uh-huh. That did not take. How about negative 5? No. How about 0? All right. So it's, clearly, working backwards,
or incorrectly here, logically. So how could I go about debugging this? Well, I could do what I've done before? I could use my printf technique and
say something explicit like n is %i, new line, comma n, just to print
it out, let me recompile buggy, let me rerun buggy, let
me type in negative 50. OK, n is negative 50. So that didn't really
help me at this point, because that's the same as before. So let me do this, debug50, ./buggy. Oh, but I've made a mistake. So I didn't set my breakpoint, yet. So let me do this, and I'll
set a breakpoint this time. I could set it here, on line 8. Let's do it in main, as before. Let me rerun debug50, now. On ./buggy. That fancy user interface
is going to pop up. It's going to highlight the line
that I set the breakpoint on. Notice that, on the left
hand side of the screen, i is defaulting, at the moment to 0,
because I haven't typed anything in, yet. But let me, now, Step Over this
line that's highlighted in yellow, and you'll see that I'm being prompted. So let's type in my negative 50, Enter. Notice now that I'm
stuck in that function. All right. So clearly, the issue seems to be
in my get negative int function. So, OK, let me stop this execution. My problem doesn't seem to be in
main, per se, maybe it's down here. So that's fine. Let me set my same breakpoint at line 8. Let me rerun debug50 one more time. But this time, instead of just stepping
over that line, let's step into it. So notice line 8 is, again,
highlighted in yellow. In the past I've been
clicking Step Over. Let's click Step into, now. When I click Step Into,
boom, now, the debugger jumps into that specific function. Now, I can step through these
lines of code, again and again. I can see what the value of
n is as I'm typing it in. I can think through my logic, and voila. Hopefully, once I've solved the issue,
I can exit the debugger, fix my code, and move on. So Step Over just goes over
the line, but executes it, Step Into lets you go into
other functions you've written. So let's go ahead and do this. We've got a bunch of
possible approaches that we can take to solving some
problems let's go ahead and pace ourselves today, though. Let's take a five-minute break, here. And when we come back, we'll take
a look at that computer's memory we've been talking about. See you in five. All right. So let's dive back in. Up until now, both, by way of week 1
and problems set 1, for the most part, we've just translated from Scratch into
C all of these basic building blocks, like loops and conditionals,
Boolean expressions, variables. So sort of, more of the same. But there are features in C that
we've already stumbled across already, like data types, the types of variables
that doesn't exist in Scratch, but that, in fact, does
exist in other languages. In fact, a few that
we'll see before long. So to summarize the types we saw last
week, recall this little list here. We had ints, and floats, and
longs, and doubles, and chars, there's also Booles and also string,
which we've seen a few times. But today, let's actually start to
formalize what these things are, and actually what your Mac and PC
are doing when you manipulate bits as an int versus a char, versus
a string, versus something else. And see if we can't put more tools
into your toolkit, so to speak, so we can start quickly writing
more featureful, more sophisticated programs in C. So it turns out, that on
most systems nowadays, though this can vary by
actual computer, this is how large each of the
data types, typically, is in C. When you store a Boolean value,
a 0 or 1, a true, a false, or true, it actually uses 1 byte. That's a little excessive,
because, strictly speaking, you only need 1 bit,
which is 1/8 of this size. But for simplicity,
computers use a whole byte to represent a Boole, true or false. A char, we saw last week,
is only 1 byte, or 8 bits. And this is why ASCII, which uses 1
byte, or technically, only 7 bits early on, was confined to only 256
maximally possible characters. Notice that an int is
4 bytes, or 32 bits. A float is also 4 bytes or 32 bits. But the things that we call long,
it's, literally, twice as long, 8 bytes or 64 bits. So is a double. A double is 64 bits of precision
for floating point values. And a string, for today, we're
going to leave as a question mark. We'll come back to that,
later today and next week, as to how much space a string
takes up, but, suffice it to say, it's going to take up a
variable amount of space, depending on whether the
string is short or long. But we'll see exactly what
that means, before long. So here's a photograph of
a typical piece of memory inside of your Mac, or PC, or phone. Odds are, it might be a little
smaller in some devices. This is known as RAM,
or random access memory. Each of these little black
chips on this circuit board, the green thing,
these little black chips are where 0s and 1s are actually stored. Each of those stores
some number of bytes. Maybe megabytes, maybe
even gigabytes, nowadays. So let's focus on one of those chips,
to give us a zoomed in version, thereof. Let's consider the fact that, even
though we don't have to care, exactly , how this kind of thing is made, if
this is, like, 1 gigabyte of memory, for the sake of discussion,
it stands to reason that, if this thing is storing 1
billion bytes, 1 gigabyte, then we can number them, arbitrarily. Maybe this will be byte
0, 1, 2, 3, 4, 5, 6, 7, 8. Then, maybe, way down here in the bottom
right corner is byte number 1 billion. We can just number these things,
as might be our convention. Let's draw that graphically. Not with a billion squares,
but fewer than those. And let's zoom in further,
and consider that. At this point in the
story, let's abstract away all the hardware,
and all the little wires, and just think of memory as taking
up-- or, rather, just think of data as taking up some number of bytes. So, for instance, if you were to store
a char in a computer's memory, which was 1 byte, it might be stored
at this top left-hand location of this black chip of memory. If you were to store something like
an integer that uses 4 bytes, well, it might use four of those bytes,
but they're going to be contiguous back-to-back-to-back, in this case. If you were to store a long or a double,
you might, actually, need 8 bytes. So I'm filling in these
squares to represent how much memory and given variable
of some data type would take up. 1, or 4, or 8, in this case, here. Well, from here, let's abstract
away from all of the hardware and really focus on
memory as being a grid. Or, really, like a canvas that
we can paint any types of data onto that we want. At the end of the day, all of this
data is just going to be 0s and 1s. But it's up to you and I to build
abstractions on top of that. Things like actual numbers,
colors, images, movies, and beyond. But we'll start
lower-level, here, first. Suppose I had a program
that needs three integers. A simple program whose purpose
in life is to average your three scores on an exam, or some such thing. Suppose that your three scores were
these, 72, 73, not too bad, and 33, which is particularly low. Let's write a program that does
this kind of averaging for us. Let me go back to VS Code, here. Let me open up a file called scores.c. Let me implement this as follows. Let me include stdio.h at the
top, int main(void) as before. Then, inside of main, let me
declare score 1, which is 72. Give me another score, 73. Then, a third score, called
score 3, which is going to be 33. Now, I'm going to use printf to print
out the average of those things, and I can do this in
a few different ways. But I'm going to print out %f, and
I'm going to do score 1, plus score 2, plus score 3, divided by 3,
close parentheses semicolon. Some relatively simple arithmetic to
compute the average of three scores, if I'm curious what my average grade
is in the class with these three assessments. Let me, now, do make scores. All right, so I've somehow
made an error already. But this one is, actually, germane
to a problem we, hopefully, won't encounter too frequently. What's going on here? So underlined to score 1, plus
score 2, plus score 3, divided by 3. Format specifies type double, but
the argument has type int, well, what's going on here? Because the arithmetic
seems to check out. Yeah? AUDIENCE: So the computer is doing the
math, but they basically [INAUDIBLE] just gives out a value at the
end because, well [INAUDIBLE] DAVID MALAN: Correct. And we'll come back to
this in more detail, but, indeed, what's happening here
is I'm adding three ints together, obviously, because I
define them right up here. And I'm dividing by another
int, 3, but the catch is, recall that C when it performs math,
treats all of these things as integers. But integers are not
floating point value. So if you actually want to get a
precise, average for your score without throwing away the remainder,
everything after the decimal point, it turns out, we're going to have to-- we're going to-- aww-- we're going to have to-- [LAUGHTER] we're going to have to
convert this whole expression, somehow, to a float. And there's a few ways to
do this but the easiest way, for now, I'm going to go
ahead and do this up here, I'm going to change the
divide by 3 to divide by 3.0. Because it turns out, long story short,
in C, so long as one of the values participating in an
arithmetic expression like this is something
like a float, the rest will be treated as promoted to
a floating point value as well. So let me, now, recompile this
code with make scores, Enter. This time it worked OK, because
I'm treating a float as a float. Let me do . /scores, Enter. All right, my average is
59.33333 and so forth. All right. So the math, presumably, checks out. Floating point imprecision
per last week aside. But let's consider the
design of this program. What is, kind of, bad about it, or if
we maintain this program longer term, are we going to regret the
design of this program? What might not be ideal here? Yeah? AUDIENCE: [INAUDIBLE] DAVID MALAN: Yeah, so in this case,
I have hard coded my three scores. So, if I'm hearing you
correctly, this program is only ever going to tell
me this specific average. I'm not even using
something like, get int or get float to get three different
scores, so that's not good. And suppose that we wait
later in the semester, I think other problems could arise. Yeah? AUDIENCE: Just thinking
also somewhat of an issue that you can't reuse that number. DAVID MALAN: I can't
reuse the number because I haven't stored the average in some
variable, which in this program, not a big deal, but certainly, if
I wanted to reuse it elsewhere, that's a problem. Let's fast-forward again, a
little later in the semester, I don't just have three
test scores or exam scores, maybe I have 4, or 5, or 6. Where might this take us? AUDIENCE: Yeah, if you
ever want to have to take the average of any number of
scores other than 3, [INAUDIBLE] DAVID MALAN: Yeah, I've sort
of, capped this program at 3. And honestly, this is, kind
of, bordering on copy paste. Even though the variables, yes, have
different names; score 1, score 2, score 3. Imagine doing this for a
whole grade book for a class. Having to score 4, 5, 6, 11 10, 12,
20, 30, that's a lot of variables. You can imagine just
how ugly the code starts to get if you're just defining variable
after variable, after variable. So it turns out, there are
better ways, in languages like C, if you want to have multiple
values stored in memory that happened to be of the same data type. Let's take a look back
at this memory, here, to see what these things
might look like in memory. Here's that grid of memory. Each of these recall represents a byte. To be clear, if I store
score 1 in memory first, how many bytes will it take up? AUDIENCE: [INAUDIBLE] DAVID MALAN: So 4, a.k.a. 32 bits. So I might draw a score 1 as
filling up this part of the memory. It's up to the computer as to whether it
goes here, or down there, or wherever. I'm just keeping the pictures clean
for today, from the top-left on down. If I, then, declare another
variable, called score 2, it might end up over there,
also taking up 4 bytes. And then score 3 might end up here. So that's just representing what's going
on inside of the computer's memory. But technically speaking, to
be clear, per week 0, what's really being stored in the computer's
memory, are patterns of 0s and 1s. 32 total, in this case,
because 32 bits is 4 bytes. But again, it gets boring
quickly to think in and look at binary all the time. So we'll, generally, abstract
this away as just using decimal numbers, in this case, instead. But there might be a better way to
store, not just three of these things, but maybe four, maybe,
five, maybe 10, maybe, more, by declaring one variable to store
all of them, instead of 3, or 4, or 5, or more individual variables. The way to do this is by way
of something known as an array. An array is another type of data that
allows you to store multiple values of the same type back-to-back-to-back. That is, to say, contiguously. So an array can let you create
memory for one int, or two, or three, or even more than
that, but describe them all using the same variable
name, the same one name. So for instance, if, for one
program, I only need three integers, but I don't want to messily declare
them as score 1, score 2, score 3, I can do this, instead. This is today's first
new piece of syntax, the square brackets
that we're now seeing. This line of code, here, is
similar to int score 1 semicolon, or int score 1 equals 72 semicolon. This line of code is declaring for
me, so to speak, an array of size 3. And that array is going
to store three integers. Why? Because the type of that
array is an int, here. The square brackets tell the
computer how many ints you want. In this case, 3. And the name is, of course, scores. Which, in English, I've
deliberately pluralized so that I can describe this array
as storing multiple scores, indeed. So if I want to now assign values
to this variable, called scores, I can do code like this. I can say, scores bracket 0 equals
72, scores bracket 1 equals 73, and scores bracket 2 equals 33. The only thing weird
there is, admittedly, the square brackets which are still new. But we're also, notice,
0 indexing things. To zero index means to
start counting at 0. When we've talked about
that before, our four loops have, generally, been zero indexed. Arrays in C are zero indexed. And you do not have choice over that. You can't start counting at 1
in arrays because you prefer to, you'd be sacrificing
one of the elements. You have to start in
arrays counting from 0. So out of context, this
doesn't solve a problem, but it, definitely, is
going to once we have more than, even, three scores here. In fact, let me change
this program a little bit. Let me go back to VS Code. And delete these three lines, here. And replace it with a
scores variable that's ready to store three total integers. And then, initialize them as
follows, scores bracket 0 is 72, as before, scores bracket 1 is
going to be 73, scores bracket 2 is going to be 33. Notice, I do not need to say
int before any of these lines, because that's been
taken care of, already, for me on line 5, where I already
specified that everything in this array is going to be an int. Now, down here, this code needs
to change because I no longer have three variables, score 1, 2, and 3. I have 1 variable, but
that I can index into. I'm going to, here, then, do scores
bracket 0, plus scores bracket 1, plus scores bracket 2, which is
equivalent to what I did earlier, giving me back those three integers. But notice, I'm using the same
variable name, every time. And again, I'm using this new square
bracket notation to, quote-unquote, index into the array to get at the first
int, the second int, and the third, and then, to do it again down here. Now, this program, still not really
solving all the problems we describe, I still can only store three
scores, but we'll come back to something like that before long. But for now, we're just introducing
a new syntax and a new feature, whereby, I can now store multiple
values in the same variable. Well, let's enhance this a bit more. Instead of hard coding these scores,
as was identified as a problem, let's use get int to ask
the user for a score. Let's, then, use get int to
ask the user for another score. Let's use get int to ask
the user for a third score, storing them in those
respective locations. And, now, if I go ahead and save
this program, recompile scores, huh. I've messed up, here. Now these errors should be
getting a little familiar. What mistake did I make? Let me give folks a moment. AUDIENCE: cs50.h DAVID MALAN: cs50.h. That was not intentional, so still
making mistakes all these years later. I need to include cs50.h. Now, I'm going to go back to the bottom
in the terminal window, make scores. OK. We're back in business, ./scores. Now, the program is getting
a little more interesting. So maybe, this year was better and I got
a 100, and a 99, and a 98, and there, my average is 99.0000. So now, it's a little more dynamic. It's a little more interesting. But it's still capping the number
of scores at three, admittedly. But now, I've introduced another,
sort of, symptom of bad programming. There's this expression in programming,
too, called code smell, where like-- [SNIFFS AIR] something
smells a little off. And there's something off here in
that I could do better with this code. Does anyone see an opportunity to
improve the design of this code, here, if my goal, still, is to get three
scores from the user but [SNIFF SNIFF] without it smelling [SNIFF] kind of bad? Yeah? AUDIENCE: [INAUDIBLE] use a 4 loop? That way you don't have to copy
and paste all of those scores. DAVID MALAN: Yeah, exactly. Those lines of code
are almost identical. And honestly, the only thing
that's changing is the number, and it's just incrementing by 1. We have all of the building
blocks to do this better. So let me go ahead and improve this. Let me delete that code. Let me, now, have a 4 loop. So for int i get 0, i
less than 3, i plus plus. Then, inside of this 4 loop,
I can distill all three of those lines into
something more generic, like scores bracket i equals get
int, and now, ask the user, just once, via get int, for a score. So this is where arrays
start to get pretty powerful. You don't have to hard
code, that is, literally, type in all of these magic
numbers like 0, 1, and 2. You can start to do
it, programmatically, as you propose with a loop. So now, I've tightened things up. I'm now, dynamically, getting
three different scores, but putting them in three
different locations. And so this program, ultimately, is
going to work, pretty much, the same. Make scores, ./scores, and 100, 99,
98, and we're back to the same answer. But it's a little better designed, too. If I really want to
nitpick, there's something that still smells, a little bit, here. The fact that I have indeed, this
magic number three, that really has to be the same as this number here. Otherwise, who knows
what's going to go wrong. So what might be a
solution, per last week, to cleaning that code up further, too? AUDIENCE: [INAUDIBLE]
the user's discretion how many input scores [INAUDIBLE]. DAVID MALAN: OK, so we could leave
it up to the user's discretion. And so we could, actually,
do something like this. Let me take this a few steps ahead. Let me say something like, int n gets
get int, how many scores question mark, then I could actually change this
to an n, and then this to an n, and, indeed, make the
whole program dynamic? Ask the human how many tests
have there been this semester? Then, you can type in
each of those scores because the loop is going
to iterate that many times. And then you'll get the average
of one test, two test, three-- well, lost another-- or however
many scores that were actually specified by the user Yeah, question? AUDIENCE: How many bits or
bytes get used in an array? DAVID MALAN: How many
bytes are used in an array? AUDIENCE: [INAUDIBLE] point of
doing this is to save [INAUDIBLE] DAVID MALAN: So the purpose of
an array is not to save space. It's to eliminate having
multiple variable names because that gets very messy quickly. If you have score 1, score 2,
score 3, dot, dot, dot, score 99, that's, like, 99 different
variables, potentially, that you could collapse into one
variable that has 99 locations. At different indices, or indexes. As someone would say,
the index for an array is whatever is in the square brackets. AUDIENCE: [INAUDIBLE] DAVID MALAN: So it's a good question. So if you-- I'm using
ints for everything-- and honestly, we don't
really need ints for scores because I'm not likely to get a
2 billion on a test anytime soon. And so you could use
different data types. And that list we had on the screen,
earlier, is not all of them. There's a data type called short,
which is shorter than an int, you could, technically, use char, in
some form or other data types as well. Generally speaking, in
the year 2021, these tend to be over optima--
overly optimized decisions. Everyone just uses
ints, even though no one is going to get a test score that's 2
billion, or more, because int is just, kind of, the go-to. Years ago, memory was expensive. And every one of your
instincts would have been spot on because memory is so tight. But, nowadays, we don't
worry as much about it. Yeah? AUDIENCE: I have a question
about the error [INAUDIBLE].. Could it-- when you're doing a
hash problem on the problem set-- DAVID MALAN: So what is the
difference between dividing two ints and not getting an error, as
you might have encountered in a program like cash,
versus dividing two ints and getting an error
like I did a moment ago? The problem with the scenario I created
a moment ago was printf was involved. And I was telling printf to use a %f,
but I was giving printf the result of dividing integers by another integer. So it was printf that was yelling at me. I'm guessing in the scenario you're
describing, for something like cash, printf was not involved in
that particular line of code. So that's the difference, there. All right. So we, now, have this
ability to create an array. And an array can store multiple values. What, then, might we do that's more
interesting than just storing numbers in memory? Well, let's take this one step further. As opposed to just storing 72, 73, 33 or
100, 99, 98, at these given locations, because again, an array gives you one
variable name, but multiple locations, or indices therein,
bracket 0, bracket 1, bracket 2 on up, if it
were even bigger than that. Let's, now, start to consider something
more modest, like simple chars. Chars, being 1 byte each,
so they're even smaller, they take up much less space. And, indeed, if I wanted
to say a message like, hi I could use three variables. If I wanted a program to print,
hi, H-I exclamation point, I could, of course, store those in
three variables, like c1, c2, c3. And let's, for the sake of discussion,
let's whip this up real quickly. Let me create a new
program, now, in VS Code. This time, I'm going to call it hi.c. And I'm not going to bother
with the CS50 library. I just need the standard
I/O one, for now. int main(void). And then, inside of main, I'm going
to, simply, create three variables. And this is already, hopefully,
striking you as a bad idea. But we'll go down this
road, temporarily, with c1, and c2, and, finally, c3. Storing each character in
the phrase I want to print, and I'm going to print this
in a different way than usual. Now I'm dealing with chars. And we've, generally, dealt with
strings, which was easier last week. But %c, %c, %c, will let me print out
three chars, and like c1, c2, and c3. So, kind of, a stupid way
of printing out a string. So we already have a solution
to this problem last week. But let's poke around at what's
going on underneath the hood, here. So let's make hi, ./hi. And, voila no surprise. But we, again, could
have done this last week with a string and just one
variable, or even, 0, at that. But let's start converting
these characters to their apparent numeric equivalents
like we talked about in week 0 too. Let me modify these %c's,
just to be fun, to be %i's. And let me add some spaces so there
are gaps between each of them. Let me, now, recompile
hi, and let me rerun it. Just to guess, what should
I see on the screen now? Any guesses? Yeah? AUDIENCE: The ASCII values? DAVID MALAN: The ASCII values. And it's intentional that
I keep using the same word, hi, because it should be, hopefully,
the old friends, 72, 73, and 33. Which, is to say, that c knows about
ASCII, or equivalently, Unicode, and can do this conversion
for us automatically. And it seems to be doing it
implicitly for us, so to speak. Notice that c1, c2 and
c3 are, obviously, chars, but printf is able to tolerate
printing them as integers. If I really want it to be pedantic,
I could use this technique, again, known as typecasting,
where I can actually convert one data type to another,
if it makes logical sense to do so. And we saw in week 0,
chars, or characters, are just numbers, like 72, 73, and 33. So I can use this parenthetical
expression to convert, incorrectly, [LAUGHTER] three chars to
three integers, instead. So that's what I meant
to type the first time. There we go. Strike two, today. So parenthesis, int,
close parenthesis says take whatever variable comes after this,
c1, c2, or c3 and convert it to an int. The effect is going to be no different,
make hi, and then rerunning whoops-- then running ./hi still works the same,
but now I'm explicitly converting chars to ints. And we can do this all day long,
chars to ints, floats to ints, ints to floats. Sometimes, it's equivalent. Other times, you're going
to lose information. Taking a float to an
int, just intuitively, is going to throw away everything
after the decimal point, because an int has no decimal point. But, for now, I'm going to
rewind to the version of this that just did implicit-type
conversion, or implicit casting, just to demonstrate that we can, indeed,
see the values underneath the hood. All right. Let me go ahead and do
this, now, the week 1 way. This was kind of stupid. Let's just do printf, quote-unquote-- Actually, let's do this, string
s equals quote-unquote hi, and then let's do a simple printf
with %s, printing out s's there. So now I've rewound to last
week, where we began this story, but you'll notice that, if we
keep playing around with this-- whoops, what did I do here? Oh, and let me introduce the C50 library
here, more on that next before long. Let me go ahead and
recompile, rerun this, we seem to be coding in circles, here. Like, I've just done the same
thing multiple, different ways. But there's clearly
an equivalence, then, between sequences of chars and strings. And if you do it the
real pedantic way, you have three different variables, c1, c2,
c3, representing H-I exclamation point, or you can just treat them all together
like this h, i, exclamation point. But it turns out that
strings are actually implemented by the computer
in a pretty now familiar way. What might a string actually be
as of this point in the story? Where are we going with this? Let me try to look further back. Yeah, in way back? Yeah? AUDIENCE: Can a string like
this be an array of chars? DAVID MALAN: Yeah, a string
might be, and indeed is, just an array of characters. So last week we took for
granted that strings exist. Technically, strings exist,
but they're implemented as arrays of characters,
which actually opens up some interesting possibilities for us. Because, let me see, let
me see if I can do this. Let me try to print out,
now, three integers again. But if string s is but an array, as you
propose, maybe I can do s bracket 0, s bracket 1, and s bracket 2. So maybe I can start poking
around inside of strings, even though we didn't
do this last week, so I can get at those individual values. So make hi, ./hi and,
voila, there we go again. It's the same 72, 73, 33, but
now, I'm sort of, hopefully, like, wrapping my mind around
the fact that, all right, a string is just an array of
characters, and arrays, you can index into them using this
new square bracket notation. So I can get at any one of
these individual characters, and, heck, convert it to an
integer like we did in week 0. Let me get a little curious now. What else might be in
the computer's memory? Well, let's-- I'll go back to the
depiction of these same things. Here might be how we
originally implemented hi with three variables, c1, c2, c3. Of course, that map to these
decimal digits or equivalent, these binary values. But what was this
looking like in memory? Literally, when you create a
string in memory, like this, string s equals quote-unquote hi,
let's consider what's going on underneath the hood, so to speak. Well, as an abstraction, a string,
it's H-I exclamation point taking up, it would seem, 3 bytes, right? I've gotten rid of the
bars, there, because if you think of a string as a type, I'm just
going to use one big box of size 3. But technically, a string, we've
just revealed, is an array, and the array is of size 3. So technically, if the
string is called s, s bracket 0 will give
you the first character, s bracket 1, the second,
and s bracket 3, the third. But let me ask this question now,
if this, at the end of the day, is the only thing in
your computer memory and the ability, like a canvas to draw
0s and 1s, or numbers, or characters, or whatever on it, but
that's it, like this is what your Mac, and PC, and
phone ultimately reduced to. Suppose that I'm running a piece
of software, like a text messenger, and now I write down
bye exclamation point. Well, where might that go in memory? Well, it might go here. B-Y-E. And then the next thing I type
might go here, here, here and so forth. My memory just might get
filled up, over time, with things that you or
someone else are typing. But then how does the computer know if,
potentially, B-Y-E exclamation point is right after H-I exclamation point
where one string ends and the next one begins? Right? All we have are bytes, or 0s and 1s. So if you were designing
this, how would you implement some kind of
delimiter between the two? Or figure out what the
length of a string is? What do you think? AUDIENCE: A nul character. DAVID MALAN: OK, so the right
answer is use a nul character, and for those who don't
know, what does that mean? AUDIENCE: It's special. DAVID MALAN: Yeah, so
it's a special character. Let me describe it as
a sentinel character. Humans decided some
time ago that you know what, if we want to delineate
where one string ends and where the next one begins,
we just need some special symbol. And the symbol they'll use is
generally written as backslash 0. This is just shorthand notation
for literally eight 0 bits. 0, 0, 0, 0, 0, 0, 0, 0. And the nickname for eight
0 bits, in this context, is nul, N-U-L, so to speak. And we can actually see this as follows. If you look at the
corresponding decimal digits, like you could do by doing out
the math or doing the conversion, like we've done in code, you would
see for storing hi, 72, 73, 33, but then 1 extra byte that's sort of
invisibly there, but that is all 0s. And now I've just written
it as the decimal number 0. The implication of this is
that the computer is apparently using, not 3 bytes to store
a word like hi, but 4 bytes. Whatever the length of the string is,
plus 1 for this special sentinel value that demarcates the end of the string. So we might draw it like this instead. And this character is, again,
pronounced nul, or written N-U-L. So that's all, right? If humans, at the end of the day,
just have this canvas of memory, they just needed to
decide, all right, well, how do we distinguish
one string from another? It's a lot easier with
chars, individually, it's a lot easier with ints, it's
even easier With floats, why? Because, per that chart earlier,
every character is always 1 byte. Every int is always 4 bytes. Every long is always 8 bytes. How long is a string? Well, hi is 1, 2, 3 with
an exclamation point. Bye is 1, 2, 3, 4 with
an exclamation point. David is D-A-V-I-D, five
without an exclamation point. And so a string can be
any number of bytes long, so you somehow need to
draw a line in the sand to separate in memory
one string from another. So what's the implication of this? Well, let me go back to code, here. Let's actually poke around. This is a bit dangerous, but I'm going
to start looking at memory locations past my string here. So let me go ahead and
recompile, make hi. Whoops, what did I do here? I forgot a format code. Let me add one more %i. Now let me go ahead and
rerun make hi, ./hi, Enter. There it is. So you can actually see in the
computer, unbeknownst to you previously, that there's indeed
something else going on there. And if I were to make one
other variant of this program-- let's get rid of just this
one word and let's have two. So let me give myself
another string called t, for instance, just this common
convention with bye exclamation point. Let me, then print out with %s. And let me also print out with %s,
whoops, printf, print out t, as well. Let me recompile this program,
and obviously the out-- ugh-- this is what happens
when I go too fast. All right, third mistake
today, close quote. As I was missing. Make hi. Fourth mistake today. Make hi. Dot slash hi. OK, voila. Now we have a program that's
printing both hi and bye, only so that we can consider what's
going on in the computer's memory. If s is storing hi and
apparently one bonus byte that demarcates the end of that
string, bye is apparently going to fit into the
location directly after. And it's wrapping around, but that's
just an artist's rendition, here. But bye, B-Y-E exclamation
point is taking up 1, 2, 3, 4, plus a fifth byte, as well. All right, any questions on this
underlying representation of strings? And we'll contextualize
this, before long, so that this isn't just
like, OK, who really cares? This is going to be the source
of actually implementing things. In fact for problem set 2, like
cryptography, and encryption, and scrambling actual human messages. But some questions first. AUDIENCE: So normally if
you were to not use string, you would just make a character
range that would declare, how many characters there are so
you know how many characters are going to be there. DAVID MALAN: A good
question, too and let me summarize as, if we were
instead to use chars all the time, we would indeed have to know in advance
how many chars you want for a given string that you're storing, how, then,
does something like get string work, because when you CS50 wrote
the get string function, we obviously don't know
how long the words are going to be that you all are typing in. It turns out, two weeks from
now we'll see that get string uses a technique known as
dynamic memory allocation. And it's going to grow or shrink
the array automatically for you. But more on that soon. Other questions? AUDIENCE: Why are we using a nul value? Isn't that wasting a byte? DAVID MALAN: Good question. Why are we using a nul value,
isn't it wasting a byte? Yes. But I claim there's really no other way
to distinguish the end of one string from the start of another, unless we
make some sort of notation in memory. All we have, at the end of the day,
inside of a computer, are bits. Therefore, all we can do is spin
those bits in some creative way to solve this problem. So we're minimally going to spend
1 byte to solve this problem. Yeah? AUDIENCE: How does our memory device
know to enter a line when you type the /n if we don't have
it stored as a char? DAVID MALAN: If you don't-- how does the computer know to move
to a next line when you have a /n? So /n, even though it
looks like two characters, it's actually stored as just 1
byte in the computer's memory. There's a mapping between
it and an actual number. And you can see that, for instance,
on the ASCII chart from the other day. AUDIENCE: So with that being
stored would be the [INAUDIBLE].. DAVID MALAN: It would be. If I had put a /n in my code here,
right after the exclamation point here and here, that would actually shift
everything in memory because we would need to make room for a /n
here and another one over here. So it would take two
more bytes, exactly. Other questions? AUDIENCE: So if hi exclamation
point is written in binary and ASCII too as 72, 73, 33, if we are to
write those numbers in the string, and convert them into binary how
would the computer know what's 72 and what's 8? DAVID MALAN: And what's
the last thing you said? AUDIENCE: 8, for example. DAVID MALAN: It's context sensitive. So if, at the end of the day, all
we're storing is these numbers, like 72, 73, 33, recall
that it's up to the program to decide, based on context,
how to interpret them. And I simplified this story in week 0
saying that Photoshop interprets them as RGB colors, and iMessage
or a text messaging program interprets them as letters, and
Excel interprets them as numbers. How those programs do it is by way
of variables like string, and int, and float. And in fact, later this
semester, we'll see a data type via which you can represent
a color as a triple of numbers, and red value, a green
value, and a blue value. So we'll see other data types as well. Yeah? AUDIENCE: It seems easy enough to just
add a nul thing at the end of the word, so why do we have integers
and long integers? Why can't we make everything
variable in its data size? DAVID MALAN: Really
interesting question. Why could we not just make all
data types variable in size? And some languages, some
libraries do exactly this. C is an older language, and
because memory was expensive memory was limited. The reality was you
gain benefits from just standardizing the size of these things. You also get performance
increases in the sense that if you know every int is
4 bytes, you can very quickly, and we'll see this next week,
jump from integer to another, to another in memory just by adding
4 inside of those square brackets. You can very quickly poke around. Whereas, if you had variable
length numbers, you would have to, kind of, follow, follow, follow,
looking for the end of it. Follow, follow-- you would have to
look at more locations in memory. So that's a topic we'll come back to. But it was generally for efficiency. And other question, yeah? AUDIENCE: Why not store the
nul character [INAUDIBLE] DAVID MALAN: Good question
why not store the-- why not store the nul
character at the beginning? You could-- let's see, why
not store it at the beginning? You could do that. You could absolutely--
well, could you do this? If you were to do that
at the beginning-- short answer, no. OK, now I retract that. No, because I finally thought
of a problem with this. If you store it at
the beginning instead, we'll see in just a moment how
you can actually write code to figure out where
the end of a string is, and the problem there
is wouldn't necessarily know if you eventually hit a
0 at the end of the string, because it's the number 0 in the
context of Excel using some memory, or if it's the context of some
other data type, altogether. So the fact that we've standardized-- the fact that we've standardized
strings as ending with nul means that we can reliably distinguish
one variable from another in memory. And that's actually a
perfect segue way, now, to actually using this
primitive to building up our own code that manipulates
these things that are lower level. So let me do this. Let me create a new file called length. And let's use this basic idea to
figure out what the length of a string is after it's been stored in a variable. So let's do this. Let me include both the CS50
header and the standard I/O header, give myself int main(void) again
here, and inside of main, do this. Let me prompt the user for
a string s and I'll ask them for a string like their name, here. And then let me name it more
verbosely name this time. Now let me go ahead and do this. Let me iterate over every
character in this string in order to figure out
what its length is. So initially, I'm going
to go ahead and say this, int length equals 0, because
I don't know what it is yet. So we're going to start at 0. And then while the following is true-- while-- let me-- do I want to do this? Let me change this to i,
just for clarity, let me do this, while name bracket i does not
equal that special nul character. So I typed it on the slide is N-U-L,
but you don't write N-U-L in code, you actually use its numeric equivalent,
which is /0 in single quotes. While name bracket i does not equal the
nul character, I'm going to go ahead and increment i to i plus plus. And then down here I'm going
to print out the value of i to see what we actually get,
printing out the value of i. All right, so what's
going to happen here? Let me run make length. Fortunately no errors. ./length and let me type in something
like H-I, exclamation point, Enter. And I get 3. Let me try bye,
exclamation point, Enter. And I get 4. Let me try my own name, David, Enter. 5, and so forth. So what's actually going on here? Well, it seems that
by way of this 4 loop, we are specifying a
local variable called i initialized to 0, because we're
figuring out the length of the string as we go. I'm then asking the
question, does location 0, that is i in the name string,
which we now know is an array, does it not equal /0? Because if it doesn't, that means it's
an actual character like H, or B, or D. So let's increment i. Then, let's come back around to line
9 and let's ask the question again. Now i equals 1. So does name bracket 1 not equal /0? Well, if it doesn't, and it won't
if it's an i, or a y, or an a, based on what I typed in, we're
going to increment i once more. Fast-forward to the end of the story,
once I get to the end of the string, technically, one space
past the end of the string, name bracket i will equal /0. So I don't increment i anymore, I
end up just printing the result. So what we seem to have here with some
low level C code, just this while loop, is a program that figures out the length
of a given string that's been typed in. Let's practice our abstraction
and decompose this into, maybe, a helper function here. Let me grab all of this
code here, and assume, for the sake of discussion for a moment,
that I can call a function now called string length. And the length of the string
is name that I want to get, and then I'll go ahead and print
out, just as before with %i, the length of that string. So now I'm abstracting away
this notion of figuring out the length of the string. That's an opportunity for to
me to create my own function. If I want to create a
function called string length, I'll claim that I want to
take a string as input, and what should I have this
function return as its return type? What should get string
presumably return? Yeah? AUDIENCE: Int. DAVID MALAN: An int, right? An int makes sense. Float really wouldn't
make sense because we're measuring things that are integers. In this case, the length of something. So indeed, let's have it return an int. I can use the same
code as before, so I'm going to paste what I
cut earlier in the file. The only thing I have to change
is the name of the variable. Because now this function,
I decided arbitrarily that I'm going to call it
s, just to be more generic. So I'm going to look at s
bracket i at each location. And I don't want to print it at the
end, this would be a side effect. What's the line of code I should
include here if I actually want to hand back the total length? Yeah? AUDIENCE: Return i. DAVID MALAN: Say again? AUDIENCE: Return i. DAVID MALAN: Return i, in this case. So I'm going return i, not print it. Because now, my main function can
use the return value stored in length and print it on the next line itself. I just need a prototype, so that's
my one forgivable copy paste here. I'm going to rerun make length. Hopefully I didn't screw up. I didn't. ./length,
I'll type in hi-- oops-- I'll type in hi, again. That works. I'll type in bye again, and so forth. So now we have a function that
determines the length of a string. Well, it turns out we didn't
actually need this all along. It turns out that we can get rid of my
own custom string length function here. I can definitely delete the
whole implementation down here. Because it turns out, in
a file called string.h, which is a new header file today, we
actually have access to a function called, more succinctly,
strlen, S-T-R-L-E-N. Which, literally does that. This is a function that comes with C,
albeit in the string.h header file, and it does what we just
implemented manually. So here's an example of, admittedly, a
wheel we just reinvented, but no more. We don't have to do that. And how do what kinds
of functions exist? Well, let me pop out of my
browser here to a website that is a CS50's incarnation of
what are called manual pages. It turns out that in a lot
of systems, Macs, and Unix, and Linux systems, including
the Visual Studio Code instance that we have
in the cloud, there are publicly accessible
manual pages for functions. They tend to be written very
expertly, in a way that's not very beginner-friendly. So we have here at
manual.cs50.io is CS50's version of manual pages that have this
less-comfortable mode that give you a, sort of, cheat
sheet of very frequently used, helpful functions in C. And
we've translated the expert notation to things that a
beginner can understand. So, for instance, let me go ahead and
search for a string up at the top here. You'll see that there's documentation
for our own get string function, but more interestingly
down here, there's a whole bunch of
string-related functions that we haven't even seen most of, yet. But there's indeed one
here called strlen, calculate the length of a string. And so if I go to strlen here, I'll
see some less-comfortable documentation for this function. And the way a manual
page typically works, whether in CS50's format
or any other, system is you see, typically, a
synopsis of what header files you need to use the function. So you would copy paste
these couple of lines here. You see what the prototype
is of the function so that you know what its inputs are,
if any, and its outputs are, if any. Then down below you might see a
description, which in this case, is pretty straightforward. This function calculates
the length of s. Then you see what the
return value is, if any, and you might even see an example, like
this one that we've whipped up here. So these manual pages
which are again, accessible here, and we'll link to these in
the problem sets moving forward, are pretty much the place to
start when you want to figure out has a wheel been invented already? Is there a function that might help
me solve some problems set problems so that I don't have to really
get into the weeds of doing all of those lower-level steps as I've had. Sometimes the answer is going to be
yes, sometimes it's going to be no. But again the point of our
having just done this together is to reveal that even the
functions you start taking for granted, they all reduce to some
of these basic building blocks. At the end of the day, this is
all that's inside of your computer is 0s and 1s. We're just learning,
now, how to harness those and how to manipulate them ourselves. Any questions here on this? Any questions at all? Yeah. AUDIENCE: We did just see
[INAUDIBLE] Is that so common that we would have to
specify it, or is it not? DAVID MALAN: Good question. Is it so common that you would
have to specify it or not? You do need to include its
header files because that's where all of those prototypes are. You don't need to worry about
linking it in with -l anything. And in fact, moving
forward, you do not ever need to worry about linking in
libraries when compiling your code. We, the staff, have configured make to
do all of that for you automatically. We want you to understand
that it is doing it, but we'll take care of
all of the -l's for you. But the onus is on you for the
prototypes and the header files. Other questions on these
representations or techniques? Yeah? AUDIENCE: [INAUDIBLE] exclamation mark. How does it actually define
the spaces [INAUDIBLE]?? DAVID MALAN: A good question. If you were to have a string with actual
spaces in it that is multiple words, what would the computer actually do? Well for this. let me
go to asciichart.com. Which is just a random website that's
my go-to for the first 127 characters of ASCII. This is, in fact, what we had
a screenshot of the other day. And if you look here, it's a little
non-obvious, but S-P is space. If a computer were to store a space, it
would actually store the decimal number 32, or technically, the pattern of 0s
and 1s that represent the number 32. All of the US English keys that
you might type on a keyboard can be represented with a
number, and using Unicode can you express even things like
emojis and other languages. Yeah? AUDIENCE: Are only strings
followed by nul number, or let's say we had a series of
numbers, would each one of them be accompanied by nuls? DAVID MALAN: Good question. Only strings are accompanied
by nuls at the end because every other data type
we've talked about thus far is of well defined finite length. 1 byte for char, 4 bytes
for ints and so forth. If we think back to last week, we did
end the week with a couple of problems. Integer overflow, because 4 bytes, heck,
even 8 bytes is sometimes not enough. We also talked about
floating point imprecision. Thankfully in the world of scientific
computing and financial computing, there are libraries you can
use that draw inspiration from this idea of a
string, and they might use 9 bytes for an integer
value or maybe 20 bytes that you can count really high. But they will then start to
manage that memory for you and what they're really probably doing
is just grabbing a whole bunch of bytes and somehow remembering how
long the sequence of bytes is. That's how these higher-level
libraries work, too. All right, this has been a lot. Let's take one more break here. We'll do a seven-minute break here. And when we come back, we'll
flesh out a few more details. All right. So we just saw strlen as an
example of a function that comes in the string library. Let's start to take more of these
library functions out for a spin. So we're not relying only on the
built ins that we saw last week. Let me switch over to VS Code. And create a file called, say string.h. to apply this lesson
learned, as follows. Let me include cs50.h,
stdio.h, and this new thing, string.h as well, at the top. I'm going to do the usual
int main(void) here. And then in this program suppose,
for the sake of discussion, that I didn't know about
%s for printf or, heck, maybe early on there
was no %s format code. And so there was no easy
way to print strings. Well, at least if we know that
strings are just arrays of characters, we could use %c as a
workaround, a solution to that, sort of, contrived problem. So let me ask myself for a
string s by using get string here and I'll ask the user for some input. And then, let me print out say, output
, and all I want to do is print back out what the user typed. Now, the simplest way to do this, of
course, is going to be like last week, printf %s, and plug in
the s, and we're done. But again, for the sake of
discussion, I forgot about, or someone didn't implement %s,
so how else could we do this? Well, in pseudo code, or in English
what's the gist of how we could solve this problem, printing out the string
s on the screen without using %s? How might we go about solving this? Just in English, high-level? What would your pseudo code look like? Yeah? AUDIENCE: You could
just print each letter. DAVID MALAN: OK, so
just print each letter. And maybe, more precisely,
some kind of loop. Like, let's iterate over
all of the characters in s and print one at a time. So how can I do that? Well, for int i, get 0 is kind of the
go-to starting point for most loops, i is less than-- OK, how long do I want to iterate? Well, it's going to
depend on what I type in, but that's why we have strlen now. So iterate up to the length of
s, and then increment i with plus plus on each iteration. And then let's just print
out %c with no new line, because I want everything
on the same line, whatever the character
is at s bracket i. And then at the very
end, I'll give myself that new line, just to move the
cursor down to the next line so the dollar sign is
not in a weird place. All right, so let's see if I
didn't screw up any of the code, make string, Enter, so far so good,
string and let me type in something like, hi, Enter. And I see output of hi, too. Let me do it once more with
bye, Enter, and that works, too. Notice I very deliberately
and quickly gave myself two spaces here and one space
here just because I, literally, wanted these things to line up properly,
and input is shorter than output. But that was just a
deliberate formatting detail. So this code is correct. Which is a claim I've made before,
but it's not well-designed. It is well-designed in that I'm using
someone else's library function, like, I've not reinvented
a wheel, there's no line 15 or below, I didn't implement
string length myself. So I'm at least practicing
what I've preached. But there's still an
imperfection, a suboptimality. This one's really subtle though. And you have to think
about how loops work. What am I doing that's
not super efficient? Yeah, in back? AUDIENCE: [INAUDIBLE]
over and over again. DAVID MALAN: Yeah, this
is a little subtle. But if you think back to the
basic definition of a 4 loop and recall when I highlighted
things last week, what happens? Well, the first thing
is that i gets set to 0. Then we check the condition. How do we check the condition? We call strlen on s,
we get back an answer like 3 if it's a H-I exclamation point
and 0 is less than 3, so that's fine, and then we print out the character. Then we increment i from 0 to 1. We recheck the condition. How do I recheck the condition? I call strlen of s. Get back the same answer, 3. Compare 3 against 1. We're still good. So we print out another character. i
gets incremented again, i is now 2. We check the condition. What's the condition? Well, what's the string like the best? It's still 3. 2 is still less than 3. So I keep asking the same
question sort of stupidly because the string is, presumably,
never changing in length. And indeed, every time
I check that condition, that function is going to get called. And every time, the answer
for hi is going to be 3. 3. 3. So it's a marginal suboptimality,
but I could do better, right? Don't ask multiple times questions
that you can remember the answer to. So how could I remember the answer to
this question and ask it just once? How could I remember the
answer to this question? Let me see. Yeah, back there? AUDIENCE: Store it in a variable. DAVID MALAN: So store
it in a variable, right? That's been our answer most any time
we want to keep something around. So how could I do this? Well, I could do something like this,
int, maybe, length equals strlen of s. Then I can just change
this function call. Let me fix my spelling here. Let me fix this to be comparing
against length, and this is now OK. Because now strlen is only
called once on line 9. And I'm reusing the value
of that variable, a.k.a. length, again, and again, and again. So that's more efficient. Turns out that 4 loops let you
declare multiple variables at once, so we can do this a little
more elegantly all in one line. And this is just some
syntactic improvement. I could actually do something
like this, n equals strlen of s, and then I could just say n
here or I could call it length. But heck, while I'm being succinct
I'm just going to use n for number. So now it's just a marginal
change but I've now declared two variables
inside of my loop, i and n. i is set to 0. n extends
to the string length of s. But now, hereafter, all of my condition
checks are just, i less than n, i less than n, and n is never changing. All right, so a marginal
improvement there. Now that I've used this
new function, let's use some other functions
that might be of interest. Let me write a quick program here
that capitalizes the beginning of-- changes to uppercase some
string that the user types in. So let me code a file
called uppercase.c. Up here I'll use my new friends,
cs50.h, and standard I/O, and string.h. So standard I/O, and string.h So
just as before int main(void). And then inside of main, what
I'm going to do this time, is let's ask the user for a string
s using get string asking them for the before value. And then let me print
out something like after. So that it-- just so I can see what
the uppercase version thereof is. And then after this, let me
do the following, for int, i equals 0, oh, let's
practice that same lesson, so n equals the string length of
s, i is less than n, i plus plus. So really, nothing
new, fundamentally yet. How do I now convert characters from
lowercase, if they are, to uppercase? In other words, if I type
in hi, H-I in lowercase, I want my program, now, to uppercase
everything to capital H, capital I. Well how can I go about doing this? Well you might recall
that there is this-- you might recall that
there is this ASCII chart. So let's just consult this
real quick on asciichart.com. We've looked at this last week
notice that a-- capital A is 65, capital B is 66, capital
C is 67, and heck, here's lowercase a, lowercase b,
lowercase c, and that's 97, 98, 99. And if I actually do some
math, there's a distance of 32. Right? So if I want to go from
uppercase to lowercase, I can do 65 plus 32 will give me
97 and that actually works out across the board for everything else. 66 plus 32 gets me to 98 or lowercase b. Or conversely, if you have a
lowercase a, and its value is 97, subtract 32 and boom, you have capital
A. So there's some arithmetic involved. But now that we know that
strings are just arrays, and we know that characters,
which are in those arrays, are just binary
representations of numbers, I think we can manipulate a
few of these things as follows. Let me go back to my
program here, and first ask the question, if the current
character in the array during this loop is lowercase, let's
force it to uppercase. So how am I going to do that? If the character at s bracket i,
the current location in the array, is greater than or equal to
lowercase a, and s bracket i is less than or equal to
lowercase z, kind of a weird Boolean expression but it's completely
legitimate, because in this array s is a whole bunch of characters
that the humans typed in, because that's what a string is,
greater than or equal to a might be a little nonsensical
because when have you ever compared numbers to letters? But we know from week 0 lowercase a
is 97, lowercase z is, what is it, 1? I don't even remember. AUDIENCE: 132. DAVID MALAN: What's that? AUDIENCE: 132? DAVID MALAN: 132, We know. And so that would allow us to answer
the question is the current letter lowercase? All right, so let me
answer that question. If it is, what do I want to print out? I don't want to print
out the letter itself, I want to print out the
letter minus 32, right? Because if it happens to be a
lowercase a, 97, 97 minus 32 gives me 65, which is
uppercase A, and I know that just from having stared
at that chart in the past. Else if the character is not
between little a and big A, I'm just going to
print out the character itself by printing s bracket i. And at the very end of this, I'm
going to print out a new line just to move the cursor to the next line. So again, it's a little wordy. But this loop here, which I
borrowed from our code previously, just iterates over the string, a.k.a. array, character-by-character,
through its length. This line 11 here is
just asking the question if that current character,
the i-th character of s, is greater than or equal
to little a and less than or equal to little z, that
is between 97 and 132, then we're going to go ahead and
force it to uppercase instead. All right, and let me zoom
out here for just a second. And sorry, I misspoke 122, which
is what you might have said. There's only 26 letters. So 122 is little z. Let me go ahead now and
compile and run this program. So make uppercase, ./uppercase, and
let me type in hi in lowercase, Enter. And there's the capitalized
version, thereof. Let me do it again, with
my own name in lowercase, and now it's capitalized as well. Well, what could we do to improve this? Well. You know what? Let's stop reinventing wheels. Let's go to the manual pages. So let me go here and search for
something like, I don't know, lowercase. And there I go. I did some auto complete
here, our little search box is saying that, OK there's
an is-lower function, check whether a character is lowercase. Well how do I use this? Well let me check, is lower, now I see
the actual man page for this function. Now we see, include ctype.h. So that's the protot-- that's the header file
I need to include. This is the prototype for is-lower,
it apparently takes a char as input and returns an int. Which is a little weird. I feel like is-lower should
return true or false. So let's scroll down to the
description and return value. It returns, oh this is interesting. And this is a convention in C. This
function returns a non-zero int if C is a lowercase letter and 0
if C is not a lowercase letter. So it returns non-zero. So like 1, negative 1, something that's
not 0 if C is a lowercase letter, and 0 if it is not a lowercase letter. So how can we use this building block? Let me go back to my code here. Let me add this file, include ctype.h. And down here, let me get rid of
this cryptic expression, which was kind of painful to come up with,
and just ask this, is-lower s bracket i? That should actually work but why? Well is-lower, again, returns a non-zero
value if the letter is lowercase. Well, what does that mean? That means it could return 1. It could return negative 1. It could return 50 or negative 50. It's actually not
precisely defined, why? Just, because. This was a common convention to
use 0 to represent false and use any other value to represent true. And so it turns out, that
inside of Boolean expressions, if you put a value like a function
call like this, that returns 0, that's going to be equivalent to false. It's like the answer
being no, it is not lower. But you can also, in
parentheses, put the name of the function and its arguments,
and not compare it against anything. Because we could do something like
this, well if it's not equal to 0, then it must be lowercase. Because that's the definition,
if it returns a non-zero value, it's lowercase. But a more succinct way to do that
is just a bit more like English. If it's is lower, then print
out the character minus 32. So this would be the common
way of using one of these is- functions to check if
the answer is true or false. AUDIENCE: [INAUDIBLE] DAVID MALAN: OK, well we might be done. OK. AUDIENCE: [INAUDIBLE] DAVID MALAN: No. So it's not necessarily 1. It would be incorrect to check for
1, or negative 1, or anything else. You want to check for the opposite of 0. So not equal 0. Or more succinctly, like I did by
just putting it into parentheses. Let me see what happens here. So this is great, but some of you
might have spotted a better solution to this problem. A moment ago when we were on
the manual pages searching for things related to lowercase,
what might be another building block we can employ here? Based on what's on the screen here? Yeah? AUDIENCE: To-upper. DAVID MALAN: So to-upper. There's a function that would literally
do the uppercasing thing for me so I don't have to get into the
weeds of negative 32, plus 32. I don't have to consult that chart. Someone has solved this
problem for me in the past. And let's see if I can
actually get back to it. There we go. Let me go ahead, now, and use this. So instead of doing
s bracket i minus 32, let's use a function that someone else
wrote, and just say to-upper, s bracket i. And now it's going to
do the solution for me. So if I rerun make uppercase, and then
do, slowly, .uppercase, type in hi, now it's working as expected. And honestly, if I read the
documentation for to-upper by going back to its man page,
or manual page, what you'll see is that it says if it's lowercase,
it will return the uppercase version thereof. If it's not lowercase, it's already
uppercase, it's punctuation, it will just return
the original character. Which means, thanks to this
function, I can actually tighten this up significantly,
get rid of all of my conditional there, and just print out
the to-upper return value, and leave it to whoever wrote
that function to figure out if something's uppercase or lowercase. All right, questions on
these kinds of tricks? Again, it all reduces to
week 0 basics, but we're just building these abstractions on top. Yeah? AUDIENCE: I'm wondering
if there's any way just to import all packages under
a certain subdomain instead of having to do multiple
[INAUDIBLE] statements, kind of like a star [INAUDIBLE] DAVID MALAN: Yes. Unfortunately, no. There is no easy way in C
to say, give me everything. That was for, historically,
performance reasons. They want you to be explicit
as to what you want to include. In other languages like
Python, Java, one of which we'll see later this term, you
can say, give me everything. But that, actually, tends to be best
practice because it can slow down execution or compilation of your code. Yeah? AUDIENCE: Does to-upper
accommodate for special characters? DAVID MALAN: Ah. Does to-upper accommodate special
characters like punctuation? Yes. If I read the documentation
more pedantically, we would see exactly that. It will properly hand me
back an exclamation point, even if I passed it in. So if I do make uppercase here,
and let me do ./upper, sorry-- ./uppercase, hi with an exclamation
point, it's going to handle that, too, pass it through unchanged Yeah? AUDIENCE: Do we access to a
function that would do all of that but just to the screen
rather than to [INAUDIBLE] DAVID MALAN: Really good question, too. No, we do not have access to a function
that at least comes with C or comes with CS50's library that will just
force the whole thing to uppercase. In C, that's actually
easier said than done. In Python, it's trivial. So stay tuned for another language
that will let us do exactly that. All right, so what does
this leave us with? There's just a-- let's
come full circle now, to where we began today where we
were talking about those command line arguments. Recall that we talked about rm
taking command line argument. The file you want to delete,
we talked about clang taking command line
arguments, that again, modify the behavior of the program. How is it that maybe you and I
can start to write programs that actually take command line arguments? Well here is where I
can finally explain why we've been typing int
main(void) for the past week and just asking that you take on faith
that it's just the way you do things. Well, by default in C, at least
the most recent versions thereof, there's only two official
ways to write main functions. You might see other formats
online, but they're generally not consistent with the
current specification. This, again, was sort of a
boilerplate for the simplest function we might write last
week, and recall that we've been doing this the whole time. (Void) What that (void) means, for all
of the programs I have written thus far and you have written thus far,
is that none of our programs that we've written take
command line arguments. That's what the void there means. It turns out that main is the way you
can specify that your program does, in fact, take command
line arguments, that is words after the command
in your terminal window. If you want to actually not
use get int or get string, you want the human to be able to
say something, like hello, David and hit Enter. And just run-- print
hello, David on the screen. You can use command line arguments,
words after the program name on your command line. So we're going to change this in a
moment to be something more verbose, but something that's now a bit
more familiar syntactically. If you change that (void) in main
to be this incantation instead, int, argc, comma, string, argv,
open bracket, close bracket, you are now giving yourself
access to writing programs that take command line arguments. Argc, which stands for
argument count is going to be an integer that stores how many
words the human typed at the prompt. The C automatically gives that to you. String argv stands for
argument vector, that's going to be an array of all of the words
that the human typed at the prompt. So with today's building
block of an array, we have the ability now to let
the humans type as many words, or as few words, as
they want at the prompt. C is going to automatically put
them in an array called argv, and it's going to tell us how many
words there are in an int called argc. The int, as the return type here,
we'll come back to in just a moment. Let's use this definition
to make, maybe, just a couple of simple programs. But in problem set 2
will we actually use this to control the
behavior of your own code. Let me code up a file called
argv.0 just to keep it aptly named. Let me include cs50.h. Let me go ahead and include-- oops. That is not the right name of a
program, let's start that over. Let's go ahead and code up argv.c. And here we have-- include cs50.h, include
stdio.h, int, main, not void, let's actually say int, argc, string,
argv, open bracket, close bracket. No numbers in between because
you don't know, in advance, how many words the human's
going to type at their prompt. Now let's go ahead and do this. Let's write a very simple program that
just says, hello, David, hello, Carter, whoever the name is that gets typed. But not using get string, let's
instead have the human just type their name at the prompt, just like
rm, just like clang, just like make, so it's just one and
done when you hit Enter. No additional prompts. Let me go ahead then and do this,
printf, quote-unquote, hello, comma, and instead of world
today, I want to print out whatever the human typed in. So let's go ahead and do
this, argv, bracket 0 for now. But I don't think this is quite
what I want because, of course, that's going to literally print
out argv, bracket, 0, bracket. I need a placeholder, so let me
put %s here and then put that here. So if argv is an array, but
it's an array of strings, then argv bracket 0 is
itself a single string. And so it can be plugged
into that %s placeholder. Let me go ahead and save my program. And compile argv, so far, so good. Let me now type in my name
after the name of the program. So no get string. I'm literally typing an extra word,
my own name at the prompt, Enter. OK, it's apparently a little
buggy in a couple of ways. I forgot my /n but
that's not a huge deal. But apparently, inside of
argv is literally everything that humans typed in including
the name of the program. So logically, how do I print out hello,
David, or hello so-and-so and not the actual name of the program? What needs to change here? Yeah? AUDIENCE: Change the index to 1. DAVID MALAN: Yeah. So presumably index to 1, if that's
the second thing I, or whichever human, has typed at the prompt. So let's do make argv
again, ./argv, Enter. Huh. Hello, nul. So this is another form of nul. But this is user error, now, on my part. I didn't do exactly what I said I would. Yeah? AUDIENCE: You forgot the parameter. DAVID MALAN: Yeah, I
forgot the parameter. So that's actually, hm. I should probably deal
with that, somehow, so that people aren't
breaking my program and printing out random
things, like nul. But if I do say argv, David,
now you see hello, David. I can get a little curious,
like what's at location 2? Well we can see, make argv,
bracket, ./argv, David, Enter. All right, so just nothing is there. But it turns out, in a couple of weeks,
we'll start really poking around memory and see if we can't crash
programs deliberately because nothing is
stopping me from saying, oh what's at location 2
million, for instance? We could really start to get curious. But for now, we'll do the right thing. But let's now make sure the human has
typed in the right number of words. So let's say this, if argc equals
2, that is the name of the program and one more word after that, go
ahead and trust that in argv 1, as you proposed, is the person's name. Else, let's go ahead and default
here to something simple and basic, like, well, if we don't get a name
from the user, just say hello, world, like always. So now we're programming defensively. This time the human, even if they
screw up, they don't give us a name or they give us too many names,
we're just going to say hello, world, because I now have some
error handling here. Because, again, argc is argument
count, the number of words, total, typed at the command line. So make, argv, ./argv. Let me make the same mistake as before. OK. I don't get this weird nul behavior. I get something well-defined. I could now do David. I could do David Malan, but
that's not currently supported. I would need to alter my logic to
support more than just two words after the prompt. So what's the point of this? At the moment, it's
just a simple exercise to actually give myself a way of taking
user input when they run the program. Because, consider, it's
just more convenient in this new, command-line-interface world. If you had to use get string
every time you compile your code, it'd be kind of annoying, right? You type make, then you might get a
prompt, what would you like to make? Then you type in hello, or cash, or
something else, then you hit Enter, it just really slows the process. But in this
command-line-interface world, if you support command line arguments,
then you can use these little tricks. Like, scrolling up and down in
your history with your arrow keys. You can just type commands more quickly
because you can do it all at once. And you don't have to keep
prompting the user, more pedantically, for more and more info. So any questions then on
command line arguments? Which, finally, reveals why
we had (void) initially, but what more we can now put in main. That's how you take
command line arguments. Yeah? AUDIENCE: If you were to put-- if you were to use argv, and you
were to put integers inside of it, would it still give you, like, a string? Would that still be considered string? Or would you consider [INAUDIBLE]? DAVID MALAN: Yes. If you were to type at
the command line something like, not a word, but
something like the number 42, that would actually be
treated as a string. Why? Because again, context matters. So if your program is
currently manipulating memory as though its characters or strings,
whatever those patterns of 0s and 1s are, they will be interpreted
as ASCII text, or Unicode text. If we therefore go to the chart here,
that might make you wonder, well, then how do you distinguish numbers
from letters in the context of something like chars and strings? Well, notice 65 is a, 97 is a,
but also 49 is 1, and 50 is 2. So the designers of ASCII,
and then later Unicode, realized well wait a minute,
if we want to support programs that let you type things
that look like numbers, even though they're not
technically ints or floats, we need a way in ASCII and
Unicode to represent even numbers. So here are your numbers. And it's a little silly that we have
numbers representing other numbers. But again, if you're in the
world of letters and characters, you've got to come up with
a mapping for everything. And notice here, here's the dot. Even if you were to represent 1.23
as a string, or as characters, even the dot now is going to be
represented as an ASCII character. So again, context here matters. All right, one final example
to tease apart what this int is and what it's been
doing here for so long. So I'm going to add one
bit of logic to a new file that I'm going to call exit.c. So an exit.c. We're going to introduce something that
are generally known as exit status. It turns out this is not
a feature we've used yet, but it's just useful to know about. Especially when automating
tests of your own code. When it comes to figuring out if
a program succeeded or failed. It turns out that main has one
more feature we haven't leveraged. An ability to signal to the user
whether something was successful or not. And that's by way of
main's return value. So I'm going modify this
program as follows, like this. Suppose I want to write
a similar program that requires that the user
type a word at the prompt. So that argc has to be 2
for whatever design purpose. If argc does not equal 2, I want to
quit out of my program prematurely. I want to insist that the user
operate the program correctly. So I might give them an error message
like, missing command line argument /n. But now I want to quit
out of the program. Now how can I do that? The right way, quote-unquote, to do
that is to return a value from main. Now it's a little weird
because no one called main yet, right, main just gets
called automatically, but the convention is
anytime something goes wrong in a program you should
return a non-zero value from main. 1 is fine as a go-to. We don't need to get into the weeds of
having many different exit statuses, so to speak. But if you return 1, that is a clue to
the system, the Mac, the PC, the cloud device that's something went wrong. Why? Because 1 is not 0. If everything works fine, like, let's go
ahead and print out hello comma %s like before, quote-unquote argv bracket 1. So this is just a version of
the program without an else. So this is the same
as doing, essentially, an else here like I did earlier. I want to signal to the
computer that all is well. And so I return 0. But strictly speaking, if
I'm already returning here, I don't technically need, if
I really want to be nit picky, I don't technically need the
else because the only way I'm going to get to line 11
is if I didn't already return. So what's going on here? The only new thing here logically,
is that for the first time ever, I'm returning a value from main. That's something I
could always have done because main has always been defined by
us as taking an int as a return value. By default, main automatically,
sort of secretly, returns 0 for you. If you've never once use the
return keyword, which you probably haven't in main, it just
automatically returns 0 and the system assumes
that all went well. But now that we're starting
to get a little more sophisticated with our
code, and you know, the programmer, something went
wrong, you can abort programs early. You can exit out of them by returning
some other value, besides 0, from main. And this is fortuitous
that it's an int, right? 0 means everything worked. Unfortunately, in programming, there are
seemingly, an infinite number of things that can go wrong. And int gives you 4
billion possible codes that you can use, a.k.a. exit
statuses, to signify errors. So if you've ever on your Mac
or PC gotten some weird pop up that an error happened, sometimes,
there's a cryptic number in it. Maybe it's positive,
maybe it's negative. It might say error code 123, or
negative 49, or something like that. What you're generally seeing, are
these exit statuses, these return values from main in a program
that someone at Microsoft, or Apple, or somewhere else
wrote, something went wrong, they are unnecessarily showing you,
the user what the error code is. If only, so that when you call
customer support or submit a ticket, you can tell them what exit
status you encountered, what error code you encounter. All right, any questions
on exit statuses, which is the last of our new
building blocks, for now? Any questions at all? Yeah? AUDIENCE: [INAUDIBLE] You know how
if you have get string or get int, if you want to make [INAUDIBLE] DAVID MALAN: No. The question is can you
do things again and again at the command line like you
could with get string and get int. Which, by default,
recall are automatically designed to keep prompting
the user in their own loop until they give you an int, or a
float, or the like with command line arguments, no. You're going to get an
error message but then you're going to be
returned to your prompt. And it's up to you to type
it correctly the next time. Good question. Yeah? AUDIENCE: [INAUDIBLE]
automatically for you. DAVID MALAN: If you
do not return a value explicitly main will
automatically return 0 for you, that is the way C simply works
so it's not strictly necessary. But now that we're starting
to return values explicitly, if something goes wrong,
it would be good practice to also start returning a value
for main when something goes right and there are no errors. So let's now get out of
the weeds and contextualize this for some actual problems that
we'll be solving in the coming days by way of problems set 2 and beyond. So here for instance-- So here for instance, is a
problem that you might think back to when you were a kid the
readability of some text or some book, the grade level in which
some book is written. If you're a young student, you
might read at first-grade level or third-grade level in the US. Or, if you're in college
presumably, you're reading at a university-level of text. But what does it mean
for text, like in a book, or in an essay, or something
like that to correspond to some kind of grade level? Well, here's a quote-- a
title of a childhood book. One Fish, Two Fish, Red Fish, Blue Fish. What might the grade level be for
a book that has words like this? Maybe, when you were a kid or if
you have a siblings still reading these things, what might the
grade level of this thing be? Any guesses? Yeah? AUDIENCE: Before grade 1. DAVID MALAN: Sorry, again? AUDIENCE: Before grade 1. DAVID MALAN: Before grade
1 is, in fact, correct. So that's for really young kids? Why is that? Well, let's consider. These are pretty simple phrases, right? One fish, two fish, red-- I mean there's not even
verbs in these sentences, they're just nouns and adjectives,
and very short sentences. And so that might be a
heuristic we could use. When analyzing text, well if
the words are kind of short, the sentences are kind of
short, everything's very simple, that's probably a very
young, or early, grade level. And so by one formulation, it might
indeed be even before grade 1, for someone quite young. How about this? Mr and Mrs. Dursley, of
number 4, Privet Drive, were proud to say that they were
perfectly normal, thank you very much. They were the last
people you would expect to be involved in anything
strange or mysterious because they just didn't
hold with such nonsense. And, onward. All right, what grade
level is this book? AUDIENCE: Third. DAVID MALAN: OK, I heard third. AUDIENCE: What? DAVID MALAN: Seventh, fifth. OK, all over the place. But grade 7, according to
one particular measure. And whether or not we can debate exactly
what age you were when you read this, and maybe you're feeling ahead
of your time, or behind now. But here, we have a snippet of text. What makes this text assume an older
audience, a more mature audience, a higher grade level, would you think? Yeah? AUDIENCE: [INAUDIBLE] DAVID MALAN: Yeah, it's longer,
different types of words, there's commas now in
phrases, and so forth. So there's just some kind
of sophistication to this. So it turns out for the
upcoming problem set, among the things you'll do is
take, as input, texts like this and analyze them. Considering , well, how
many words are in the text? How many sentences are in the text? How many letters are in the text? And use those according to a
well-defined formula to prescribe what, exactly, the grade level of some
actual text-- there's the third-- might actually be. Well what else are we going
to do in the coming days? Well I've alluded to this notion
of cryptography in the past. This notion of scrambling
information in such a way that you can hide the
contents of a message from someone who might
otherwise intercept it, right? The earliest form of this might
also be when you're younger, and you're in class, and you're passing
a note from one person to another, from yourself to someone else. You don't want to necessarily
write a note in English, or some other written,
language you might want to scramble it somehow, or encrypt it. Maybe you change the As
to a B, and the Bs to a C. So that if the teacher snaps
it up and intercepts it, they can't actually
understand what it is you've written because it's encrypted. So long as your friend,
the recipient of this note, knows how you manipulated it. How you added or subtracted
letters to each other, they can decrypt it, which
is to reverse that process. So formally, in the world of
cryptography and computer science, this is another problem to solve. Your input, though, when you have a
message you want to send securely, is what's generally known as plain text. There's some algorithm that's
going to then encipher, or encrypt that information, into what's
called ciphertext, which is the scrambled version that
theoretically can get safely intercepted and your message
has not been spoiled, unless that intercept
actually knows what algorithm you used inside of this process. So that would be generally
known as a cipher. The ciphers typically take,
though, not one input, but two. If, for instance, your cipher
is as simple as A becomes B, B becomes C, C becomes D,
dot dot dot, Z becomes A, you're essentially adding one to
every letter and encrypting it. Now that would be,
what we call, the key. You and the recipient both have to
agree, presumably, before class, in advance, what number you're
going to use that day to rotate, or change all of these letters by. Because when you add 1, they
upon receiving your ciphertext have to subtract 1 to
get back the answer. For instance, if the input,
plaintext, is hi, as before, and the key is 1, the ciphertext using
this simple rotational algorithm, otherwise known as the Caesar cipher,
might be ij exclamation point. So it's similar, but it's at
least scrambled at first glance. And unless the teacher
really cares to figure out what algorithm are they using today,
or what key are they using today, it's probably sufficiently
secure for your purposes. How do you reverse the process? Well, your friend gets this
and reverses it by negative 1. So I becomes H, J becomes I,
and things like punctuation remain untouched at
least in this scheme. So let's consider one
final example here. If the input to the algorithm
is Uijtxbtdt50, and the key this time is negative 1. Such that now B should become A, and C
should become B, and A should become A. So we're going in the other direction. How might we analyze this? Well if we spread all the letters
out, and we start from left to right, and we start subtracting one letter,
U becomes T, I becomes H, J becomes I, T becomes S, X becomes W, A, was, D, T-- this was CS50. We'll see you next time. [APPLAUSE] [MUSIC PLAYING] DAVID J. MALAN: This is CS50, and
this is already week three. And even as we've gotten much more
into the minutia of programming and some of the C stuff
that we've been doing is all the more cryptic looking,
recall that at the end of the day, like, everything we've been doing
ultimately fits into to this model. So keep that in mind,
particularly as things seem like they're getting more
complicated and more sophisticated. It's just a process of learning
a new language that ultimately lets us express this process. And of course, last week we really
went into the weeds of like how inputs and outputs are represented. And this thing here, a photograph
thereof, is called what? This is what? AUDIENCE: RAM. DAVID J. MALAN: RAM, I heard-- Random Access Memory or just
generally known as memory. And recall that we looked at
one of these little black chips that contains all of the bytes-- all of the bits, ultimately. It's just kind of a grid,
sort of an artist grid, that allows us to think about every
one of these memory locations as just having a number or
an address, so to speak. Like, this might be byte
number 0 and then 1 and then 2 and then, maybe way down here
again, something like 2 billion if you have 2 gigabytes of memory. And so as we did that, we started to
explore how we could use this canvas to create kind of our own information,
our own inputs and outputs, not just the basics like ints
and floats and so forth. But we also talked about strings. And what is a string as you now know it? How would you describe in
layperson's terms a string? Yeah, over there. AUDIENCE: I was gonna say-- [AUDIO OUT] DAVID J. MALAN: An array of characters. And an array, meanwhile--
let's go there. How might someone else define an
array in more familiar now terms? What would be an array? Yeah. AUDIENCE: Kind of like
an indexed set of things. DAVID J. MALAN: An indexed
set of things-- not bad. And I think a key characteristic to
keep in mind with an array is that it does actually pertain to memory. And it's contiguous memory. Byte after byte after byte
is what constitutes an array. And we'll see in a couple of
weeks time that there's actually more interesting ways to use this same
primitive Canvas to stitch together things that are sort of two directional
even that have some kind of shape to them. But for now, all we've talked about
is arrays and just using these things from left to right, top to bottom,
contiguously to represent information. So today, we'll consider still an array. But we won't focus so
much on representation of strings or other data types. We'll actually now focus on
the other part of that process, of inputs becoming outputs,
namely the thing in the middle-- algorithms. But we have to keep in mind, even though
every time we've looked at an array thus far, certainly on the board
like this, you as a human certainly have the luxury of
just kind of eyeballing the whole thing with a bird's eye view
and seeing where all of those numbers are. If I asked you where a
particular number is, like zero, odds are your eyes
would go right to where it is, and boom, problem solved
in sort of one step. But the catch is, with a computer
that has this memory, even though you, the human, can [INAUDIBLE] see
everything at once, a computer cannot. It's better to think of your
computer's memory, your phone's memory, or more specifically an
array of memory like this as really being a set of closed
doors, not unlike lockers in a school. And only by opening
each of those doors can the computer actually
see what's in there, which is to say that the
computer, unlike you, doesn't have this bird's eye view of all
of the data in all these locations. It has to much more
methodically look here, maybe look here, maybe look here, and
so forth in order to find something. Now fortunately, we already have some
building blocks-- loops, conditions, Boolean expressions, and the like-- where you could imagine
writing some code that very methodically goes from left
to right or right to left or something more sophisticated that actually
finds something you're looking for. And just remember that the
conventions we've had since last week now is that these arrays are
zero indexed, so to speak. To be zero indexed just means that the
data type starts counting from zero. So this is location 0, 1, 2, 3, 4, 5, 6. And notice even though there
are seven total doors here, the right-most one,
of course, is called 6 just because we've
started counting at 0. So in the general case, if you
had n doors or n bytes of memory, 0 would always be at the left, and n
minus 1 would always be at the right. That's sort of a generalization of just
thinking about this kind of convention. All right, so let's revisit the problem
that we started the whole term off with in week zero, which was
this notion of searching. And what does it mean
to search for something? Well, to find information-- and
this, of course, is omnipresent. Anytime you take out your phone, you're
searching for a friend's contact. Any time you pull up a browser,
you're googling for this or that. So search is kind of one of the
most omnipresent topics and features of any device these days. So let's consider how the Googles, the
Apples, the Microsofts of the world are implementing something as
seemingly familiar as this. So here might be the problem statement. We want some input to
become some output. What's that input going to be? Maybe it's a bunch of closed doors
like this out of which we want to get back an answer, true or false. Is something we're
looking for there or not? You can imagine taking this one
step further and trying to find where is the thing you're looking for. But for now, let's just take
one bite out of the problem. Can we tell ourselves, true or
false, is some number behind one of these doors or lockers in memory? But before we go there and start
talking about ways to do that-- that is, algorithms. Let's consider how we might
lay the foundation of, like, comparing whether one algorithm
is better than another. We talked about
correctness, and it sort of goes without saying that any code you
write, any algorithm you implement, had better be correct. Otherwise, what's the point if it
doesn't give you the right answers? But we also talked about design. And in your own words, what do we
mean when we say a program is better designed at this stage than another? How do you think about
this notion of design now? Yeah, in the middle? AUDIENCE: Easier to understand
or easier to institute. DAVID J. MALAN: OK, so easier to understand. I like that. Other thoughts? Yeah. AUDIENCE: Efficiency. DAVID J. MALAN: Efficiency, and what do
you mean by efficiency precisely? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Nice. It doesn't use up too much
memory, and it isn't redundant. So you can think about
design along a few of these axes-- sort of
the quality of the code but also the quality of the performance. And as our programs get bigger and
more sophisticated and just longer, those kinds of things are
really going to matter. And in the real world,
if you start writing code not just by yourself but with
someone else, getting the design right is just going to make it easier
to collaborate and ultimately produce, write code, with just
higher probability. So let's consider how we might focus
on exactly the second characteristic, the efficiency, of an algorithm. And the way we might talk about the
efficiency of algorithms, just how fast or how slow they are, is in
terms of their running time. That is to say, when they're
running, how much time do they take? And we might measure this in
seconds or milliseconds or minutes or just some number of
steps in the general case because presumably fewer steps, to
your point, is better than more steps. So how might we think
about running times? Well, there's one general
notation we should define today. So computer scientists tend to describe
the running time of an algorithm or a piece of code, for that matter, in
terms of what's called big O notation. This is literally a
capitalized O, a big O. And this generally means that the
running time of some algorithm is on the order of such and such,
where such and such, we'll see, is just going to be a very
simple mathematical formula. It's kind of a way of waving
your hands mathematically to convey the idea of just how fast
or how slow some algorithm or code is without getting into
the weeds of like, it took this many milliseconds or
this many specific number of steps. So you might recall then from week
zero, I even introduced this picture but without much context. At the time, we just use this to
compare those phone book algorithms. Recall that this red straight
line was the first algorithm, one page at a time. The yellow line that's still
straight differed how if you recall? That line represented what
alternative algorithm? Looking out and back. What is that second algorithm? Yeah, over there. AUDIENCE: Like, two pages at a time. DAVID J. MALAN: Two pages at a time, which
was almost correct so long as we potentially double back a page if maybe
we go a little too far in the phone book. So it had a potential bug
but arguably solvable. This last algorithm, though, was
the so-called divide and conquer strategy where I sort of unnecessarily
tore the phone book in half and then in half and
then in half, which, as dramatic as that was unnecessarily,
it actually took significantly bigger bites out of the
problem-- like 500 pages the first time, another 250, another
125 versus just 1 or 2 bytes at a time. And so we described its
running time as this picture there, though I didn't use that
expression at the time, running times. But indeed, time to solve
might be measured just abstractly in some unit of measure-- seconds, milliseconds, minutes, pages-- via this y-axis here. So let's now slap some numbers on this. If we had n pages in that
phone book, n just representing a generic number, the
first algorithm here we might describe as taking n steps. Second algorithm we might describe
as taking n divided by 2 steps, maybe give or take one if we
have to double back but generally n divided by 2. And then this thing, if you
remember your logarithms, was sort of a fundamentally
different formula-- log base 2 of n or just
log of n for short. So this is of a fundamentally
different formula. But what's noteworthy is that
these first two algorithms, even though, yes, the second
algorithm was hands down faster-- I mean, literally twice as fast-- when you start to zoom out and if
I increase my y-axis and x-axis, these first two start to look
awfully similar to one another. And if we keep zooming out
and zooming out and zooming out as n gets really large-- that is, the x-axis gets really long-- these first two algorithms start
to become essentially the same. And so this is where computer
scientists use big O notation. Instead of saying specifically,
this algorithm takes any steps. And this one n divided by 2, a
computer scientist would say, eh, each of those algorithms
takes on the order of n steps or on the order of n over 2. But you know what? On the order of n over 2
is pretty much the same when n gets really large as being
equivalent to big O of n itself. So yes, in practice, it's obviously
fewer steps to move twice as fast. But in the big picture, when n
becomes a million, a billion, the numbers are already
so darn big at that point that these are as, the
shapes of these curves imply, pretty much functionally equivalent. But this one still
looks better and better as n gets large because it's
rising so much less quickly. And so here, a computer
scientist would say that that third algorithm was on the
order of-- that is, big O of-- log n. And they don't have to
bother with the base because it's a smaller mathematical
detail that is also just in some sense a constant, multiplicative factor. So in short, what are
the takeaways here? This is just a new
vocabulary that we'll start to use when we just want to describe
the running time of an algorithm. To make this more real, if
any of you have implemented a for loop at this point in any of your
code and that for loop iterated n times where maybe in was the height
of your pyramid or maybe n was something else that you wanted
to do n times, you wrote code or you implemented an algorithm
that operated in big O of n time, if you will. So this is just a way now
to retroactively start describing with somewhat
mathematical notation what we've been doing in practice for a while now. So here's a list of commonly seen
running times in the real world. This is not a thorough list
because you could come up with an infinite number of
mathematical formulas, certainly. But the common ones we'll discuss
and you will see in your own code probably reduce to this list here. And if you were to study
more computer science theory, this list would get longer and longer. But for now, these are sort of the
most familiar ones that we'll soon see. All right, two other pieces
of vocabulary, if you will, before we start to use this stuff-- so this, a big omega,
capital omega symbol, is used now to describe a lower bound
on the running time of an algorithm. So to be clear, big O is
on the order of-- that is, an upper bound-- on how
many steps an algorithm might take, on the order of so many steps. If you want to talk, though,
from the other perspective, well, how few steps my algorithm take? Maybe in the so-called
best case, it'd be nice if we had a notation to
just describe what a lower bound is because some
algorithms might be super fast in these so-called best cases. So the symbology is almost the
same, but we replace the big O with the big omega. So to be clear, big O describes
an upper bound and omega describes a lower bound. And we'll see examples
of this before long. And then lastly, last one here, big
theta, is used by a computer scientist when you have a case where both
the upper bound on an algorithm's running time is the
same as the lower bound. You can then describe it in one breath
as being in theta of such and such instead of saying it's in big O
and in omega of something else. All right, so out of context, sort
of just seemingly cryptic symbols, but all they refer to is upper
bounds, lower bounds, or when they happen to be one in the same. And we'll now introduce over time
examples of how we might actually apply these to concrete problems. But first, let me pause to
see if there's any questions. Any questions here? Any questions? I see pointing somewhere. Where are you pointing to? Over here-- there we go. OK, sorry-- very bright. AUDIENCE: So, um, smaller-- DAVID J. MALAN: Smaller n
functions move faster. So yes, if you have something
like n, that takes only steps. If you have a formula like n
squared, just by nature of the math, that take more steps
and therefore be slower. So the larger the
mathematical expression, the slower your algorithm is because the
more time or more steps that it takes. AUDIENCE: So you want your
n function to be small? DAVID J. MALAN: You want your n function,
so to speak, to be small, yes. And in fact, the Holy
Grail, so to speak, would be this last one here either
in big O notation or even theta, when an algorithm is on
the order of a single step. That means it literally takes constant
time, one step, or maybe 10 steps, 100 steps, but a fixed,
constant number of steps. That's the best because even
as the phone book gets bigger, even as the data set you're
searching gets larger and larger, if something only takes a finite
number of steps constantly, then it doesn't matter how big
the data set actually gets. Questions as well on these notations--
yep, thank you for the pointing. This is actually very helpful. I'm seeing pointing this way? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: What is the input
to each of these functions? It is an expression of how
many steps an algorithm takes. So in fact, let me go
ahead and make this more concrete with an actual
example here if we could. So on stage here, we have
seven lockers which represent, if you will, an array of memory. And this array of
memory is maybe storing seven integers, seven integers that
we might actually want to search for. And if we want to search
for these values, how might we go about doing this? Well, for this, why don't
we make things interesting? Would a volunteer like to come on up? Have to be masked and on the
internet if you are comfortable. Both of-- oh, there's someone putting
their friend's hand up and back? Yes, OK. Come on down. And in just a moment,
our brave volunteer is going to help me find a
specific number in the data set that we have here on the screen. So come on down, and I'll get things
ready for you in advance here. Come on down nice to meet. And what is your name? AUDIENCE: [? Nomira. ?] DAVID J. MALAN: Minera? AUDIENCE: [? Nomira. ?] DAVID J. MALAN: [? Nomira. ?] Nice to meet. Come on over. So here we have for Nomira seven
lockers or an array of memory. And behind each of
these doors is a number. And the goal, quite simply,
is, given this array of memory as input, to return, true or false, is
the number I care about actually there? So suppose I care about the number 0. What would be the simplest,
most correct algorithm you could apply in order to find us the number 0? OK, try opening the first one. All right, and maybe just step
aside so the audience can see. I think you have not found 0 yet. OK, so keep the door open. Let's move on to your next choice. Second door, sure. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Oh, go ahead, second door. Let's keep it simple. Let's just move from left to
right, sort of searching our way. And what do you see there? Oh, 6, not 0. How about the next door? All right, also not working
out so well yet, but that's OK. If you want to go on to the
next, we're still looking for 0. All right, I see a 2. All right, it's not so good yet. Let's keep going. Next door. 2, 7-- no. OK, next door. No, that's a-- all
right, very well done. Oh. All right, so I kind of set you
up for a fairly slow algorithm, but let me just ask you
to describe what is it you did by following
the steps I gave you. AUDIENCE: I just went one
by one to each character. DAVID J. MALAN: You went one
by one to each character if you want to talk into here. So you went one by
one by each character. And would you say that algorithm
left or right is correct? AUDIENCE: No. DAVID J. MALAN: No? AUDIENCE: Or, yes, in the scenario. DAVID J. MALAN: OK, yes in this scenario. Why are you hesitating? What's going through your mind? AUDIENCE: Because it's not the
most efficient way to do it. DAVID J. MALAN: OK, good. So we see a contrast here
between correctness and design. I mean, I do think it was correct
because even though it was slow, you eventually found zero. But it took some number of steps. So in fact, this would be an algorithm. It has a name, called linear search. And, [? Nomira, ?] as you
did, you kind of walked along a line going from left to right. Now let me ask. If you had gone from right
to left, would the algorithm have been fundamentally better? AUDIENCE: Yes. DAVID J. MALAN: OK, and why? AUDIENCE: Because the zero is
here in the first scenario. But if it was like, the zero is in
the middle, it wouldn't have been. DAVID J. MALAN: Yeah, and so here is
where the right way to do things becomes a little less obvious. You would absolutely have
given yourself a better result if you would just happened
to start from the right or if I had pointed you
to start over there. But the catch is if I asked her to
find another number, like the number 8, well, that would have backfired. And this time, it
would have taken longer to find that number because
it's way over here instead. And so in the general case, going
left to right or, heck, right to left is probably as correct as you can
get because if you know nothing about the order of these numbers-- and
indeed, they seem to be fairly random. Some of them are smaller,
some of them are bigger. There doesn't seem to
be rhyme or reason. Linear search is about as good as you
can do when you don't know anything a priori about the numbers. So I have a little thank you gift
here, a little CS stress ball. Round of applause for
our first volunteer. Thank you so much. Let's try to formalize what I
just described as linear search because indeed, no matter which
end [? Nomira ?] had started on, I could have kind of
changed up the problem to make sure that it
appears to be running slow. But it is correct. If zero were among those doors,
she absolutely would have found it and indeed did. So let's now try to translate what
we did into what we might call again pseudo code as from week zero. So with pseudo code, we
just need a terse English like, or any language, syntax
to describe what we did. So here might be one formulation
of what [? Nomira ?] did. For each door, from left to right,
if the number is behind the door, return true. Else, at the very end of the program,
you would return false by default. And now you got lucky. And by the seventh door,
[? Nomira ?] had indeed returned true by saying,
well, there is the zero. But let's consider if this
pseudo code is now correct, an accurate translation. First of all, normally, when we've
seen ifs, we might see an if else. And yet down here, return
false is aligned with the for. Why did I not indent the return
false, or put another way, why did I not do if number is behind
door, return true, else return false? Why would that version of this
code have been problematic? Way in back. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: OK, I'm not sure
it's because of redundancy. Let me go ahead and
just make this explicit. If I had instead done
else return false, I don't think it's so much redundancy
that I'd be worried about. Let me bounce somewhere else. Yeah, in front? AUDIENCE: Um, maybe
[INAUDIBLE] for the entire list after just checking one number. DAVID J. MALAN: Yeah, it would
be returning falls for-- even though I'd only
looked at-- [? Nomira ?] had only looked at one element. And it would have been as though if
all of these doors were still closed, she opens this up and says, nope,
this is not zero, return false. That would give me an incorrect
result because obviously, at that stage in the algorithm,
she wouldn't have even looked through any of the other doors. So just the original indentation
of this, if you will, without the [? else, ?]
is correct because only if I get to the bottom of this
algorithm or the pseudo code does it make sense to conclude
at that point, once she's gone through all of the doors,
that nope, there's in fact-- the number I'm looking for is,
in fact, not actually there. So how might we consider now the
running time of this algorithm? We have a few different
types of vocabulary now. And if we consider now how
we might think about this, let's start to translate it from
sort of higher level pseudo code to something a little lower level. We've been writing code using
n and loops and the like. So let's take this higher level
pseudo code and now just kind of get a middle ground
between English and C. Let me propose that we think about
this version of the same algorithm as being a little more pedantic. For i from 0 to n minus 1, if number
behind doors bracket i return true. Otherwise, at the end of
the program, return false. Now I'm kind of mixing
English and C here, but that's reasonable if the
reader is familiar with C or some similar language. And notice this pattern here. This is a way of just saying in pseudo
code, give myself a variable called i. Start at 0 and then just
count up to n minus 1. And recall n minus 1 is not one
shy of the end of the array. N minus 1 is the end of
the array because again, we started counting at 0. So this is a very common way
of expressing this kind of loop from the left all the way
to the right of an array. Doors I'm kind of implicitly
treating as the name of this array, like it's a variable from last
week that I defined as being an array of integers in this case. So doors bracket i means that
when i is 0, it's this location. When i is 1, it's this. When i is 7 or, more generally n minus-- sorry, 6 or, more generally, n
minus 1, that's this location here. So same idea but a translation of it. So now let's consider what the
running time of this algorithm is. If we have this menu of possible
answers to this question, how efficient or inefficient
is this algorithm, let's take a look in the
context of this pseudo code. We don't even have to bother
going all the way to C. How do we go about analyzing
each of these steps? Well, let's consider this. This outermost loop here for i from
0 to n minus 1, that line of code is going to execute how many times? How many times will that loop execute? Let me give folks this
moment to think on it. How many times is that
going to loop here? Yeah, over there. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: n times, right? Because it's from 0 to n minus 1. And if it's a little weird to
think in from 0 to n minus 1, this is essentially the same
mathematically as from 1 to n. And that's perhaps a
little more obviously more intuitively n total steps. So I might just make a note to myself
this loop is going to operate n times. What about these inner steps? Well, how many steps or seconds
does it take to ask a question? If the number behind-- if the number you're looking
for is behind doors bracket i, well, as [? Nomira ?] did,
that's kind of like one step. So you open the door and boom. All right, maybe it's two steps,
but it's a constant number of steps. So this is some constant
number of steps. Let's just call it one for simplicity. How many steps or seconds
does it take to return true? I don't know exactly in
the computer's memory but that feels like a single step. Just return true. So if this takes one
step, this takes one step but only if the condition
is true, it looks like you're doing a constant
number of things n times. Or maybe you're doing
one additional step. So in short, the only thing
that really matters here in terms of the efficiency or
inefficiency of the algorithm is what are you doing again and again
and again because that's obviously the thing that's going to add up. Doing one thing or two things
a constant number of times? Not a big deal. But looping, that's going to add up over
time because the more doors there are, the bigger n is going to be and the
more steps that's going to take, which is all to say if you
were to describe roughly how many steps does this
algorithm take in big O notation, what might your instincts say? How many steps is this algorithm on the
order of given n doors or n integers? Yeah? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Say again? AUDIENCE: O n. DAVID J. MALAN: Big O of n. And indeed, that's going
to be the case here. Why? Because you're essentially,
at the end of the day, doing n things as an upper
bound on running time. And that's, in fact, what
exactly what happened with [? Nomira. ?] She had
to look at all n lockers before finally getting
to the right answer. But what if she got
lucky and the number we were looking for was not
at the end of the array but was at the beginning of the array? How might we think about that? Well, have a nomenclature for this
too, of course-- omega notation. Remember, omega notation
is a lower bound. So given this menu of possible running
times for lower bounds on an algorithm, what might the omega notation be
for [? Nomira's ?] linear search? AUDIENCE: Omega 1. DAVID J. MALAN: Omega of 1, and why that? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Right, because if
just by chance she gets lucky and the number she's looking
for is right there where she begins the algorithm, that's it. It's one step. Maybe it's two steps if you have
to unlock the door and open it, but it's a constant number of steps. And the way we describe
constant number of steps is just with a single number like 1. So the omega notation for linear
search might be omega of 1 because in the best case, she might just
get the number right from the get go. But in the worst case, we need to
talk about the upper bound, which might indeed be big O of n. So again there's this way
now of talking symbolically about best cases and worst cases
or lower bounds and upper bounds. Theta notation, just
as a little trivia now, is it applicable based on the
definition I gave earlier? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: OK, no, because you
only take out the theta notation when those two bounds,
upper and lower, happen to be the same for shorthand
notation, if you will. So it suffices here to talk about
just big O and omega notation. Well, what if we are a
little smarter about this? Let me go ahead and sort
of semi-secretly here rearrange these numbers. But first, how about
one other volunteer? One other volunteer-- you have
to be comfortable with your mask and your being on the internet. How about over here? Yes, you want to come on down? All right, come on down. And don't look at what I'm
doing because I'm going to-- take your time and
don't look up this way because I need a moment to
rearrange all of the numbers. And actually, if you could stay
right there before coming up, just an awkward few seconds
while I finish hiding the numbers behind these doors for you. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: I will be right with you. Actually, if-- do you want to
warm up the crowd for a moment and I'll be right back? So you want to introduce yourself? AUDIENCE: Yeah, hi, guys. I'm Rave. Yeah! DAVID J. MALAN: All right,
I think I am ready. Thank you for stalling there. AUDIENCE: Of course. DAVID J. MALAN: And I didn't catch your name. What was your name? AUDIENCE: I'm Rave. DAVID J. MALAN: I'm sorry? AUDIENCE: Rave, like a party. DAVID J. MALAN: Rave, OK. Nice to meet. Come on over. So Rave has kindly volunteered now. And I'm going to give you an
additional advantage this time. AUDIENCE: OK. DAVID J. MALAN: Unbeknownst to you, I
now took numbers behind the doors, but I sorted them for you. So they're not in the same
random order like they were for [? Nomira. ?]
You now have the advantage to know that the numbers are
sorted from small to big. AUDIENCE: OK. DAVID J. MALAN: Given that, and given perhaps
what we talked about in week zero with the phone book, where might you
propose we begin the story this time? With which locker? AUDIENCE: To find zero? DAVID J. MALAN: Let's find
number six this time. Let's make things interesting. AUDIENCE: OK. I'll start in the middle. DAVID J. MALAN: OK, so the middle. There's seven total. So-- AUDIENCE: OK. DAVID J. MALAN: --that would be right here. Go ahead. Open that up. And you find, sadly, the number five. So what do you know now? AUDIENCE: I know to go up. DAVID J. MALAN: OK. AUDIENCE: OK. DAVID J. MALAN: All right, and
just to keep it uniform, just like I did, I opened to the
right half of the phone book. AUDIENCE: Yes. DAVID J. MALAN: Let's keep it similar. Yeah. AUDIENCE: All right. DAVID J. MALAN: All right,
and, uh, a little too far even though I know you
wanted to go one over. AUDIENCE: All good, all good. DAVID J. MALAN: And now we're
going to go which direction? AUDIENCE: Over here in the middle. DAVID J. MALAN: Right, and
voila, the number six. All right, so very nicely done. A little stressful for you as well. Thank you again. So here we see by nature
of the locker door still being open sort of an
artifact of the greater efficiency, it would seem, of this
algorithm because now that Rave was given the assumption that
these numbers are sorted from small on the left to large on the right,
she was able to apply that same divide and conquer algorithm from week zero
which we're now going to give a name-- binary search. And simply by starting in
the middle and realizing, OK, too small, then by going to
the right half and realizing, oh, went a little too far, then by
going to the left half, which, Rave able to find in just
three steps instead of seven the number six in this case that
we were actually searching for. So you can see that this would
seem to be more efficient. Let's consider for just
a moment is it correct. If I had used different numbers but
still sorted them from left to right, would it still have
worked this algorithm? You're nodding your head. Can I call on you? Like, why would it still
have worked, do you think? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah, so
so long as the numbers are always in the same
order from left to right or, heck, they could even be in reverse
order, so long as it's consistent, the decisions that Rave was making--
if greater than, else, if less than-- would guide us to the
solution no matter what. And it would seem to take fewer steps. So if we consider now the
pseudo code for this algorithm, let's take a look how we
might describe binary search. So binary search we might
describe with something like this. If the number is behind the middle
door, which is where Rave began, then we can just return true. Else if the number is
less than the middle door, so if six is less than whatever
is behind the middle door, then Rave would have
searched the left half. Else if the number is
greater than the middle door, Rave would have searched the right half. Else, if there are no doors-- and
we'll see in a moment why I put this up top just to keep things clean. If there's no doors, what should Rave
have presumably returned immediately if I gave her no lockers to work with? Just returned false. But this is an important
case to consider because if in the process of
searching by locker by locker, we might have whittled down the
problem from seven doors to three doors to one door to zero
doors-- and at that point, we might have had no
doors left to search. So we have to naturally have a
scenario for just considering if there were no doors. So it's not to say that maybe I don't
give Rave any doors to begin with. But as she divides and
divides and divides, if she runs out of lockers to ask those
questions of-- or a few weeks ago, if I ran out of phone book
pages to tear in half, I too might have had to
return false as in this case. So how can we now describe
this a little more like C just to give ourselves a variable
to start thinking and talking about? Well, I might talk about
doors as being an array. And so if I want to express the middle
door, I could just, in pseudo code, say doors bracket middle. I'm assuming that
someone has done the math to figure out what the middle door
is, but that's easy enough to do. And then doors, if the
number we're looking for is less than doors bracket
middle, then search door zero through doors middle minus 1. So again, this is a more pedantic way of
taking what's a pretty intuitive idea-- search the left half,
search the right half-- but start to now describe it in
terms of actual indices or indexes like we did with our array notation. The last scenario, of
course, is if the number is greater than the
door's bracket middle, then Rave would have wanted to
search the middle door plus 1-- so 1 over-- through doors n minus 1-- through n minus 1. So again, just a way of sort of
describing a little more syntactically what it is that's going on. So how might we translate
this now into big O notation? Well, in the worst case, how many
steps total might Rave's binary search algorithm have taken? Given seven doors or given
more generically n doors, how many times could she go left or go
right before finding herself with one or no doors left? What's the way to think about that? Yeah, in the middle? AUDIENCE: Log n. DAVID J. MALAN: Log n. So there's log n again. And even if you're not feeling wholly
comfortable with your logarithm still, pretty much in programming and
in computer science more generally, any time we talk about some algorithm
that's dividing and conquering in half, in half, in half,
or any other multiple, it's probably involving
logarithms in some sense. And log base n essentially
refers to the number of times you can divide n by 2 until
you bottom out at just a single door or equivalently zero doors left. So log n. So we might say that indeed,
binary search is in big O of log n because the door that Rave
opened last, this one, happened to be three doors away. And actually, if you do
the math here, that roughly works out to be exactly that case. If we add one, that's sort of out
of seven doors or roughly eight, we were able to search it
in just three total steps. What about omega notation, though? Like, in the best case, Rave
might have gotten lucky. She opened the door, and there it is. So how might we describe a lower bound
on the running time of binary search. Yeah. AUDIENCE: 1. DAVID J. MALAN: Say again? AUDIENCE: 1. DAVID J. MALAN: Omega of 1. So here too, we see that in some cases
binary search and linear search, eh, like, they're pretty equivalent. And so this is why sometimes
compelling to consider both the best case in the worst case
because honestly, in general, who really cares if you just
get lucky once in a while and your algorithm is super fast? What you probably care about
is what's the worst case. How long are my users-- how long am I going to be sitting
there watching some spinning hourglass or beach ball trying to give myself
an answer to a pretty big problem? Well, odds are, you're going to
generally care about big O notation. So indeed, moving
forward, will generally talk about the running time of
algorithms often in terms of big O, a little less so in terms of omega. But understanding the
range can be important depending on the nature of the data that
you're going to actually be given here. All right let me pause and
see if there is any questions. Any questions here? Yes, thank you. AUDIENCE: So this method
is clearly more efficient, but it requires that the information
is all compiled in a certain order. How do you ensure that you
can compile information in a particular order at scale? DAVID J. MALAN: Yeah, it's
a really good question. And if I can generalize it, how do
you guarantee that you can do this at scale, which algorithm is better? I've sort of led us down
this road of implying that Rave's second
algorithm, binary search, is better because it's so much faster. It's log of n in the worst
case instead of big O of n. But Rave was given an advantage when
she came up here in that the doors were already sorted. And so that sort of
invites the question, well, given a whole
bunch of random data, either a small data set or, heck,
something Google sized with millions, billions of pieces of data,
should you sort it first from smallest to
largest and then search? Or should you just dive right
in and search it linearly? Like, how might you think about that? If you are Google, for
instance, and you've got millions, billions of web pages,
should they just go with linear search because it's always going to work
even though it might be slow? Or should they invest the time
in sorting all of that data-- we'll see how in a bit-- and then search it more efficiently? Like, how do you decide
between those options? AUDIENCE: If you're sorting
the data, then wouldn't you have to go through all of the data? DAVID J. MALAN: Yeah, if you had
to sort the data first-- and we don't yet formally
know how to do this. But obviously, as humans, we
could probably figure it out. You do have to look at
all of the data anyway. And so you're sort of wasting your
time if you're sorting it only then to go in search it. But maybe it depends a bit more. Like, that's absolutely
right, and if you're just searching for one thing in life,
then that's probably a waste of time to sort it and then search it because
you're just adding to the process. But what's another scenario
in which you might not worry about that whereby it might
make sense to sort it and then search? Yeah. AUDIENCE: [INAUDIBLE] you can go
and use the other values as a way to find out what's happening. DAVID J. MALAN: Yeah, exactly. So if your problem is a
Google-like problem where you have more than just one user
who's searching for more than just one website page, probably you
should incur the cost up front and sort the whole thing because
every subsequent request thereafter is going to be faster,
faster, faster because it's going to [INAUDIBLE] algorithm of binary
search, binary search, binary search that's going to add up
to be way fewer steps than doing linear search multiple times. So again, kind of
depends on the use case and kind of depends on
how important it is. And this happens even
in real world contexts. I think back always to graduate
school, when I was writing some code to analyze some large data set. And honestly, it was actually
easier at the time for me to write pretty inefficient
but hopefully correct code because you know what? I could just go to sleep for eight hours
and let it analyze this really big data set. I didn't have to bother writing
more complex code to sort it just to run it more efficiently. Why? Because I was the only user, and I
only needed to run these queries once. And so this was kind of
a reasonable approach, reasonable until I woke up eight
hours later and my code was incorrect. And now I had to spend another eight
hours rerunning it after fixing it. But even there, you
see an example where, what is your most precious resource? Is it time to run the code? Is it time to write the code? Is it the amount of memory
the computer is using? These are all resources we'll start
to talk about because it really depends on what your goals are. Any questions, then, on
upper bounds, lower bounds, or each of these two
searches, linear or binary? Yeah. AUDIENCE: So just, when you're
calculating running time, does the sorting step
count for that time? DAVID J. MALAN: When analyzing running
time, does the sorting step count? If you want it to if you actually do it. At the moment, it did not apply. I just gave Rave the luxury of
knowing that the data was sorted. But if I really wanted to charge
her for the amount of time it took to find that number six,
I should have added the time to sort plus the time to search. And in fact, that's
a road we'll go down. Why don't we go ahead and
pace ourselves as before? Let's take a 10 minute break here. And when we come back, we'll
write some actual code. So we've seen a couple of searches--
linear search and binary search, which, to be fair, we saw back in week zero. But let's actually translate at
least one of those now to some code using this building block from
last week where we can actually define an array if we want, like an
array of integers called numbers. So let me switch over to VS Code here. Let me go ahead and start
a program called numbers.c. And in numbers.c, let me go ahead here. And how about let's include
our familiar header files? So css50.h. I'll include standardio.h that we can
get input and print input if we want. And now I'm going to go ahead
and give myself int main void. No command line arguments today. So I'll leave that as void. And I'm going to go
ahead and give myself an array of how about seven numbers? So I'll call it int number 7. And then I can fill
this array with numbers. Like, numbers brackets 0 can be
the number 4, and numbers bracket 1 could be the number 6, and numbers
bracket 2 can be the number 8. And this is the same list that
we saw with [? Nomira ?] a bit ago where it was 4, then 6, then 8. But you know what? There's actually another
syntax I can show you here. If you know in advance
in a C program that you want an array of certain values and you
know therefore how many of those values you want, you can actually do this
little trick using curly braces. You can say, don't worry
about how big this is. It's going to be implicit by
way of these curly braces. Here, I can do 4, 6, 8, 2,
7, 5, 0, close curly brace. So it's a somewhat new
use of curly braces. But this has the effect of giving
me an array called numbers inside of which are a whole bunch of integers. How many? The compiler can infer it from what's
ever inside these curly braces. And it seems to be of
size 1, 2, 3, 4, 5, 6, 7. And all seven elements will be
initialized with 4, 6, 8, 2, 7, 5, 0 respectively. So just a minor optimization
code wise to tighten up what would have otherwise been
like eight separate lines of code. Now let's go ahead and implement
linear search, as we called it. And you can do this in a bunch of
ways, but I'm going to do it like this. For int i get 0, i is
less than 7 i plus plus. Then inside of my loop, I'm
going to ask the question, well, if the numbers at location i
equals equals, as we asked of [? Nomira, ?] the number 0, then I'm
going to go ahead and do something like printf found backslash n. And then I'm going to return 0. Just because of last week's
discussion of returning a value for main when all is well,
I'm going to return 0 by convention just to signal that indeed,
I found what I'm looking for. Otherwise, on what line do I want to
go and add a printf, like, not found and return something other than 0? Right, I don't think I want an else
here per our pseudo code earlier. So on what line would you prefer I
sort of insert a default scenario of not found and I'll return an error? Yeah, over here? [INTERPOSING VOICES] DAVID J. MALAN: Nice. So at the end of the
for loop because you want to give the
program or our volunteer earlier a chance to go through all
of the doors, all of the numbers. But if you go through the whole
thing, through the whole loop, at the very end, you probably just
want to conclude not found backslash n and then return
something like positive 1 just to signify that an error happened. And again, this was a
minor detail last week. Any time main is successful, the
programming convention is to return 0. That means all as well. And if something goes wrong, like you
didn't find what you're looking for, you might return something other than
0, like positive 1, maybe positive 2, or even negative numbers if you want. All right, well, let me
go ahead and save this. Let me do make numbers. Hopefully no syntax errors. All good so far. dot slash numbers, enter. All right, and it's found,
as I would hope it would be. And just as a little check, let's
search for something that's definitely not there, like the number negative 1. Let me go ahead and recompile
the code with make numbers. Let me rerun the code
with dot slash numbers and hopefully-- whew, OK, not found. So proof by example seems
to be working correctly. But let's make things a
little more interesting now. Right now, I'm using just
an array of integers. Let me go ahead and introduce
maybe an array of strings instead. And maybe this time, I'll store a
bunch of names and not just integers but actual strings of names. So how might I do this? Well, let me go back to my code here. I'm going to switch us over to
maybe a file called names.c. And in here, I'll go
ahead and include cs50.h. I'll include standardio.h. And I'm going to go ahead and
for now include a new friend from last week, string.h, which gives
me some string-related functionality. Int main void because I'm not going
to bother with any command line arguments for now. And now if I want an array of strings,
I could do something like this-- string names bracket 7. And then I could start
doing like before. Names bracket 0 could be someone
like Bill, and names bracket 1 could be someone like
Charlie and so forth. But there's this new
improvement I can make. Let me just let the compiler figure
out how many names there are. And using curly braces, I'll do Bill
and then Charlie and then Fred and then George and then Ginny and then Percy and
then Ron if there's the pattern there. All right, so now I have
these seven names as strings. Let's do something similar. So for int, i get 0. i is less than 7 as before,
i plus plus as before. And inside of the, loop lets this
time check for the string in question, and suppose we're searching
for Ron arbitrarily. He is there, so we should
eventually find him. Let me go ahead and say if names bracket
i equals quote unquote Ron, then inside of my if condition, I'm going to
say printf found just like before. And I'm going to return 0
just because all is well. And I'm going to take your
advice from the get go this time and, at the end of the loop, print out
not found because if I get this far, I have not printed found, and
I have not returned already. So I'm just going to go ahead and
return 1 after printing not found. All right, let me go ahead and
cross my fingers as always. Make names this time. And it doesn't seem
to like my code here. This is perhaps a new
error that you might not have seen yet in names.c line 11. So that's this line
here, my if condition. Result of comparison against a
string literal is unspecified. Use an explicit string
comparison function instead. I mean, that's kind of a mouthful,
and the first time you see it, you're probably not going to
know how to make sense of that. But it does kind of draw our
attention to something being awry with the equality checking
here, with equal equals and Ron. And here's where again
we've been telling sort of a white lie for
the past couple of weeks. Strings are a thing in C. Strings
are a thing in programming. But recall from last
week, I did disclaim there's no such thing
as a string data type technically because it's not
a primitive in the way an int and a float and a bool are that are
sort of built into the language. You can't just use equation
equals to compare two strings. You actually have to use
a special function that's in this header file we talked
briefly about last week. In that header file was
string length or strlen. But there's other
functions instead as well. Let me, in fact, go ahead
and open up the manual pages. And if we go to string.h-- let me scroll down a bit. In string.h you can perhaps infer
what function will probably take the place of equals equals for today. What do we want to use? Yeah. AUDIENCE: Strcmp? DAVID J. MALAN: So strcmp, S-T-R-C-M-P,
which apparently compares two strings. And if I click on that,
we'll see more information. And indeed, if I click on strcmp,
we'll see under the synopsis that, OK, I need to use the CS50 header
file and string.h, as I already have. Here is its prototype,
which is telling me that strcmp takes two
strings, S1 and S2, that are presumably going to be compared. And it returns an integer,
which is interesting. So let's read on. The description of this function is
that it compares two strings case sensitively. So uppercase or lowercase
matters, just FYI. And then let's look it
the return value here. The return value of this function
returns an int less than 0 if S1 comes before S2, 0 if S1 is the
same as S2, or an int greater than 0 if S1 comes after S2. So the reason that this function
returns an integer and not just a bool, true or false, is
that it actually will allow us to sort these things
eventually because if you can tell me if two strings come in this order or
in this order or they're the same, you need three possible return values. And a bool, of course,
only gives you two, but an int gives you like 4 billion
even though we just need the 3. So 0 or a positive number or a negative
number is what this function returns. And the documentation goes on to explain
what we mean by ASCIIbetical order. Recall that capital A
is 65, capital B is 66, and it's those underlying
ASCII or Unicode numbers that a computer uses to figure
out whether something comes before it or after it like in the dictionary. But for our purposes now,
we only care about equality. So I'm going to go ahead and do this. If I want to compare names
bracket i against Ron, I use stir compare or strcmp, names
bracket i comma, quote unquote, Ron. So it's a little more
involved than actually using equals equals, which
does work for integers, longs, and certain other values. But for strings, it turns out we
need to use a more powerful function. Why? Well, last week, recall
what a string really is. It's an array of characters. And so whereas you can use equals
equals for single characters, strcmp, as we'll
eventually see, is going to compare multiple characters for us. There's more logic there. There's a loop needed, and that's
why it comes with the string library. But it doesn't just work out of
the box with equals equals alone. That would literally be comparing
two things, not two arrays of things. And we'll come back to
this next week as to what's really going on under the hood. So let me go ahead and fix one
bug that I just realized I made. I want to check if the return
value of str compare is equal to 0 because per the documentation,
that meant they're the same. All right, let me go ahead
and make names this time. Now it compiles. Dot slash names, Enter, found. And just as a sanity check, let's
check someone outside the family. Searching now for Hermione
after recompiling the code, after rerunning the code. And she's not, in fact, found. So here's just a similar
implementation of linear search not for integers this time
but instead for strings, the subtlety really being we need
a helper function, str compare, to actually do the legwork for us of
comparing two arrays of characters. All right, questions on either of these
implementations-- yeah, in the middle? AUDIENCE: So, if I do [INAUDIBLE] DAVID J. MALAN: Ah, good question. If I had not fixed what I claimed was
a mistake earlier and I did this-- and we saw an example of
this last week, actually. If a function returns an integer,
be it negative or positive or 0, when you get back 0, the
expression, the Boolean expression, will be considered false. So 0 equals false always. If a function returns any positive
number, or any negative number, that's going to be
interpreted as true even if it's positive or negative, whether
it's 1, negative 1, 2, negative 2. And so if I did this, this
would be saying the opposite. So if I were to say this, if str compare
of names bracket i and Hermione, that's implicitly like saying this does not
equal 0, or it means sort of is true, but you don't want to check
for true because, again, we're comparing integers here. So the reason I did 0
here in this case is that it explicitly checks for the return
value that means they're the same. And yeah. Follow up? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yes, you might
not have seen this yet, but you can express the
equivalent because if you want to check if this is
false, you can actually use an exclamation point,
known as a bang in programming, that inverts the meaning. So false becomes true,
true becomes false. So this would be another
way of expressing it. This is arguably a worse design, though,
because the documentation explicitly says you should be checking
for 0 or a positive value or a negative value, and this
little trick, while correct, and I think you can make a reasonable
case for it, sort of hides that detail. And I would argue instead
for the first way, checking for equals equals 0 instead. And if that's a little
subtle, not to worry. We'll come back to little syntactic
tricks like that before long. Other questions on linear
search in these two forms. Is there another hand or hands? Two hands? No? OK, just holler if I missed. So let's now actually take
this one step further. Suppose that we want to write a
program that maybe implements something a little more like a phone book that
has both names and numbers and not just integers but actual phone numbers. Well, we could escalate
things like this. We could now have two arrays-- one
called names, one called numbers. And I'm going to use
strings for the numbers now, the phone numbers, because
in most communities, phone numbers might have dashes,
pluses, parentheses, so something that really looks more like a string
even though we call it a phone number. Probably don't want to use an int lest
we throw away those kinds of details. So let me switch back to VS Code here,
and let's do one more program, this one in a file called phonebook.c. And now let me go ahead and do the same. Let me include cs50.h. Let me include standardio.h,
and let me include string.h. I'm going to again do int main void. And then inside of my program, I'm
going to give myself two arrays-- the efficient way this time. String names will be
just two of us this time. How about Carter and me? And then I'll give myself--
oops, typo already. If I want this to be an array, I
don't have to specify the number. The compiler can count for me. But I do need the square brackets. Then for numbers, I'm again going to
use a string array specifying with the curly braces that how about
Carter can be at 1-617-495-1000. And how about my own number here-- 1-949-468-- oh pattern appearing-- 2750 will be mine. Why mine? Well, I'm just kind of lined things up. So Carter's number is
apparently first in this array, and I'm claiming that he'll be
first in this array, respectively. I, David, will be the first--
the second in the names array and second in the numbers array. If you want to have a little fun
with programming, feel free to text or call me some time at that number. So now let's actually use
this data in some way. Let's go ahead and actually search
for my own name and number here. So let me do. For int i, get 0. There's two of us this time-- so i less
than 2 and then i plus plus as before. And now I'm going to practice
what I preached earlier, and I'm going to use str compare
to find my name in this case. And I'm going to say if strcmp of names
bracket i equals quote unquote David and that equals 0,
meaning they're the same, then just as before, I'm going to
go ahead and print something out. But this time, I'm going to
make the program more useful and not just say found or not found. Now I'm implementing a phone book, like
the contacts app on iOS or Android. So I'm going to say something
like, quote unquote, found percent s backslash n and then actually
plug in numbers bracket i to correspond to the
current name bracket i. And then I'll return 0 as before. And then down here if we get
all the way through the loop and David's not there for some reason,
I'm going to print as before not found and then return 1. So let me go ahead and compile this
with make phone dot slash phonebook, and it seems to have found the number. So this code I'm going
to claim is correct. It's kind of stupid because I've
just made a phone book or a contacts app that only supports two people. They're only going to be me and Carter. This would be like downloading
the contacts app on a phone and you can only call
two people in the world. There's no ability to
add names or edit things. That, of course, could come later
using get string or something else. But for now for the
sake of discussion, I've just hardcoded two
names and two numbers. But for what it does, I
claim this is correct. It's going to find me
and print out my number. But is it well-designed? Let's start to now consider if
we're not just using arrays, but are we using them, well? We started to use them last week,
but are we using them well this week? And what might I even mean by
using an array well or designing this program well? Any critiques or concerns
with why this might not be the best road for us
to be going down when I want to implement something like a
phone book with pieces of information? It seems all too vulnerable
to just mistakes. For instance, if I screw up the actual
number of names in the names array such that it's now more or less than
is in the numbers array or vise versa, it feels like there's not a tight
relationship between those pieces of data, and it's just sort of
is trusting on the honor system that any time I use names bracket i
that it lines up with numbers bracket i. And that's fine. If you're the one writing
the code, you're probably not going to really screw this up. But if you start collaborating
with someone else or the program is getting much, much longer, the
odds that you or your colleagues remember that you're sort of just
trusting that names and numbers line up like this is going to fail eventually. Someone's not going to realize that,
and just, the code is going to break. And you're going to start out putting
the wrong numbers for names, which is to say it'd be much nicer if
we could somehow couple these two pieces of data, names and numbers,
a little more tightly together so that you're not just trusting that
these two independent variables, names and numbers, have this kind of
relationship with themselves. So let's consider how
we might solve this. A new feature today that we'll introduce
is generally known as a data structure. In C, we have the ability to
invent our own data types, if you will-- data types
that the authors of C decades ago just didn't envision
or just didn't think were necessary because we can implement
them ourselves-- similar to Scratch just as you could create
custom puzzle pieces, or in C, you can create
custom functions. So in C, can you create
your own types of data that go beyond the built in ints
and floats and even strings? You can make, for instance, a
person data type or a candidate data type in the context of
elections or a person data type more generically that might
have a name and a number. So how might we do this? Well, let me go here and propose
that if we want to define a person, wouldn't it be nice if we
could have a person data type, and then we could have
an array called people? And maybe that array is our
only array with two things in it, two persons in it. But somehow, those data
types, these persons, would have both a name and a
number associated with them. So we don't need two separate arrays. We need one array of persons,
a brand new data type. So how might we do this? Well, if we want every
person in the world or in this program to
have a name and a number, we literally right out
first those two data types. Give me a string called name. Give me a string called
number semicolon, after each. And then we wrap that,
those two lines of code, with this syntax, which at first
glance is a little cryptic. It's a lot of words all of a sudden. But typedef is a new keyword today
that defines a new data type. This is the C key word that
lets you create your own data type for the very first time. Struct is another related key word that
tells the compiler that this isn't just a simple data type, like an int or a
float renamed or something like that. It actually is a structure. It's got some dimensions to it, like
two things in it or three things in it or even 50 things inside of it. The last word down here is the name
that you want to give your data type, and it weirdly goes
after the curly braces. But this is how you invent
a data type called person. And what this code is
implying is that henceforth, the compiler clang will know that a
person is composed of a name that's a string and a number that's a string. And you don't have to worry
about having multiple arrays now. You can just have an array
of people moving forward. So how can we go about using this? Well, let me go back to
my code from before where I was implementing a phone book. And why don't we enhance the
phone book code a little bit by borrowing some of that new syntax? Let me go to the top of
my program above main and define a type that's
a structure or a data structure that has a name inside of
it and that has a number inside of it. And the name of this new structure
again is going to be called person. Inside of my code now, let me go ahead
and delete this old stuff temporarily. Let me give myself an array
called people of size 2. And I'm going to use the
non-terse way to do this. I'm not going to use the curly braces. I'm going to more pedantic spell out
what I want in this array of size 2 at location 0, which is the
first person in an array because you always start counting at 0. I'm going to give that person
a name of quote unquote Carter. And the dot is admittedly one
new piece of syntax today too. The dot means go inside
of that structure and access the variable called
name and give it this value Carter. Similarly, if I'm going
to give Carter a number, I can go into people bracket 0 dot
number and give that the same thing as before plus 1-617-495-1000. And then I can do the
same for myself here-- people bracket-- where should I go? OK, one because again, two elements. But we started counting at zero. Bracket name equals quote unquote David. And then lastly, people bracket 1
dot number equals quote unquote plus 1-949-468-2750. So now if I scroll
down here to my logic, I don't think this part
needs to change too much. I'm still, for the sake of discussion,
going to iterate 2 times from i is 0 on up to but not through 2. But I think this line
of code needs to change. How should I now refer to the
i-th person's name as I iterate? What should I compare quote
unquote David to this time? Let me see. On the end here? AUDIENCE: People bracket i dot name. DAVID J. MALAN: Yeah, people
bracket i dot name. Why? Because people is the name of the array. Bracket i is the i-th person that we're
iterating over in the current loop-- first zero, then one, maybe
higher if it had more people. Then dot is our new syntax for
going inside of a data structure and accessing a variable therein
which in this case is name. And so I can compare
David just as before. So it's a little more verbose, but
now arguably this is a better program because now these people are full
fledged data types unto themselves. There's no more honor
system inside of my loop that this is going to line
up because in just a moment, I'm going to fix this one last
remnant of the previous version. And if I can call back on
you again, what should I change numbers bracket i to this time? AUDIENCE: [INAUDIBLE] dot number. DAVID J. MALAN: Dot number, exactly. So gone is the honor
system that just assumes that bracket i in this array lines up
with bracket i in this other array. Now why? There's only one array. It's an array called people. The things it stores are persons. A person has a name and a number. And so even though it's kind
of marginal admittedly given that this is a short program and
given that this kind of made things look more complicated
at first glance, we're now laying the foundation for just
a better design because you really can't screw up now the
association of names with numbers because every person's
name and number is, so to speak, encapsulated inside
of the same data type. And that's a term of art in CS. Encapsulation means to
encapsulate-- that is, contain-- related pieces of information. And thus, we have a person that
encapsulates two other data types, name and number. And this just sets
the foundation for all of the cool stuff we've talked
about and you use every day. What is an image? Well, recall that an image is a bunch
of pixels or dots on the screen. Every one of those dots
has RGB values associated with it-- red, green, and blue. You could imagine now creating
a structure in C probably where maybe you have three values,
three variables-- one called red, one called green, one called blue. And then you could name the
thing not person but pixel. And now you could store in C three
different colors-- some amount of red, some green, some blue-- and collectively
treat it as the color of a pixel. And you could imagine doing something
similar perhaps for video or music. Music, you might have three
variables-- one for the musical note, the duration, the loudness of it. And you can imagine coming up with
your own data type for music as well. So this is a little low level. We're just using like a
familiar contacts application. But we now have the way in code
to express most any type of data that we might want to implement
or discuss ultimately. So any questions now on struct
or defining our own types, the purposes for which are to use
arrays but use them more responsibly now in a better design but
also to lay the foundation for implementing cooler and cooler
stuff per our week zero discussion? Yeah. AUDIENCE: What's the [INAUDIBLE] DAVID J. MALAN: What's the difference
between this and an object in an object oriented language? So slight side note, C
is not object-oriented. Languages like Java and C++ and
others which you might have heard of, programmed yourself, had friends
program in, are object oriented languages in those languages they have
things called classes or objects which are interrelated. And objects can store not
just data, like variables. Objects can also store functions, and
you can kind of sort of do this in C. But it's not sort of conventional. In C, you have data
structures that store data. In languages like Java and C+, you have
objects that store data and functions together. Python is an object-oriented
language as well. So we'll see this issue in a few weeks,
but let me wave my hands at it for now. Yeah. AUDIENCE: Could you
use this [INAUDIBLE]?? DAVID J. MALAN: Yes. Could you use this struct to
redefine how an int is defined? Short answer, yes. We talked a couple of times
now about integer overflow. And most recently, you might have seen
me mention the bug in iOS and Mac OS that was literally related
to an int overflow. That's the result of ints only
storing 4 bytes or 32 bits or even as long as 64 bits or 8 bytes. But it's finite. But if you want to implement
some financial software or some scientific or
mathematical software that allows you to count way bigger
than a typical int or a long, you could imagine John coming
up with your own structure. And in fact, in some
languages there is a structure called big int, which allows you
to express even bigger numbers. How? Well, maybe you store inside of
a big ant an array of values. And you somehow allow yourself
to store more and more bits based on how high you
want to be able to count. So in short, yes. We now have the ability now to do
most anything we want in the language even if it's not built in for us. Other questions. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Could you define a name
and a number in the same line? Sort of. It starts to get
syntactically a little messy, so I did it a little more
pedantic line by line. Good question. Over here. AUDIENCE: [INAUDIBLE] function
you use for the function at the bottom of the [INAUDIBLE]. Could you do something
like that [INAUDIBLE]?? DAVID J. MALAN: Prototypes--
you have to do A and C. You have to define anything you're going
to use or declare anything you're going to use before you actually use it. So it is deliberate that I put it
at the top of my code in this file. Otherwise, the compiler would not know
what I mean by person when I first use it here on what's line 14. So it has to come first, or it has to
be put into something like a header file so that you include it at
the very top of your code. Other questions over here. Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah, good
question, and we'll come back to this later in the term when
we talk about SQL, a database language, and storing things in actual databases. Generally speaking, even though we
humans call things phone numbers, or in the US, we have social security
numbers, those types of numbers often have other punctuation in it,
like dashes, parentheses, pluses, and so forth. You could not store any of that syntax
or that punctuation inside of an int. You could only store numbers. So one motivation for
using a string is just I can store whatever the human wanted
me to store, including parentheses and so forth. Another reason for
storing things as strings, even if they look like
numbers, is in the context of zip codes in the United States. Again, we'll come back to this. But long story short--
years ago, actually-- I was using Microsoft
Outlook for my email client. And eventually I switched to Gmail. And this is like 10 plus years ago now. And Outlook at the time lets you export
all of your contacts as a CSV file-- Comma Separated Values. More on that in the weeks to come too. And that just means I
could download a text file with all of my friends and
family and their numbers inside of it. Unfortunately, I open that same CSV
file with Excel, I think, at the time just to kind of spot
check it and see if what's in there was what it was expected. And I must have instinctively hit,
like, Command or Control-S to save it. And Excel at least has this habit
of sort of reformatting your data. If things look like numbers,
it treats them as numbers. And Apple Numbers does this too. Google Spreadsheets
does this to nowadays. But long story short, I then imported
my mildly saved CSV file into Gmail. And now 10 plus years later, I'm still
occasionally finding friends and family members whose zip codes are in
Cambridge, Massachusetts 2138, which is missing the 0 because
we here in Cambridge are 02138. And that's because I
treated or I let Excel treat what looks like a number
as an actual number or int, and now leading zeros become a
problem because mathematically, they mean nothing, but in the
mail system, they do-- sending envelopes and such. All right, other final questions here. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah, so could I have
used a 2D or two dimensional array to solve the problem
earlier of having just one array? Yes, but one, I would argue
it's less readable, especially as I get lots of names and numbers. And two, that too is also kind
of relying on the honor system. It would be all too easy to omit some
of the square brackets in the two dimensional array. So I would argue it too is not
as good as introducing a struct. More on that down the road. Two dimensional arrays just means
arrays of arrays, as you might infer. All right, so now that
we have this ability to store different types of data
like contacts in a phone book, having names and
addresses, let's actually take a step back and
consider how we might now solve one of the original problems by
actually sorting the information we're given in advance and considering,
per our discussion earlier, just how costly, how time consuming is
that because that might tip the scales in favor of sorting,
then searching, or maybe just not sorting and only searching. It'll give us a sense of just
how expensive, so to speak, sorting something actually is. Well, what's the
formulation of this problem? It's the same thing as week zero. We've got input to sort. We want it to be output as sorted. So for instance, if we're
taking unsorted input as input, we want the sorted output as
the result. More concretely, if we've got numbers like these-- 63852741, which are just
randomly arranged numbers-- we want to get back out 12345678. So we just want those
things to be sorted. So again, inside of
the black box here is going to be one or more algorithms
that actually gets this job done. So how might we go about doing this? Well, just to vary things a bit
more, I think we have a chance here for a bit more audience participation. But this time, we need
eight people if we may. All of you have to be comfortable
appearing on the internet. OK, so this is actually quite
convenient that you're all quite close. How about 1, 2, 3, 4, 5, 6, 7-- oh, OK, and someone volunteering
their friend-- number eight. Come on down. Come on down. And if you could, I'm
going to set things up. If you all could join Valerie,
my colleague over there, to give you a prop to use here,
we'll go ahead in just a moment and try to find some numbers at hand. In just a moment, each of our volunteers
is going to be representing an integer. And that integer is initially
going to be in unsorted order. And I claim that using an algorithm,
step by step instructions, we can probably sort these folks in
at least a couple of different ways. So they're in wardrobe right now just
getting their very own Harvard T-shirts with a Jersey number on it, which will
then represent an element of our array. Give us just a moment to finish
getting the attire ready. They're being handed
a shirt and a number. And let me ask the
audience for just a moment. As we have these numbers up here on the
screen, these numbers too are unsorted. They're just in random order. And let me ask the audience. How would you go about sorting
these eight numbers on the screen? How would you go about sorting these? Yeah, what are your thoughts? AUDIENCE: [INAUDIBLE] the number
at the end, the following number. DAVID J. MALAN: OK. AUDIENCE: The following number is
bigger, then I keep it as it is. DAVID J. MALAN: OK. AUDIENCE: If not, then [INAUDIBLE]. DAVID J. MALAN: OK, so just
to recap, you would start with one of the numbers on the end. You would look to the number to
the right or to the left of it, depending on which end you start at. And if it's out of order, you
would just start to swap things. And that seems reasonable. There's a whole bunch
of mistakes to fix here because things are pretty out of order. But probably, if you start to
solve small problems at a time, you can achieve the end result of
getting the whole thing sorted. Other instincts, if you were
just handed these numbers, how you might go about sorting them? How might you? Yeah, in the back. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: OK, I like that. So to recap there, find the smallest
one first and put it at the beginning, if I heard you correctly. And then presumably, you could do
that again and again and again. And that would seem to give you
a couple of different algorithms. And if you all are attired here-- do you want to come
on up if you're ready? We had some [? felt ?] volunteers too. Come on over. So if you all would like
to line yourselves up facing the audience in
exactly this order-- so whoever is number zero
should be way over here, and whoever is number five
should be way over there. Feel free to distance as much as you'd
like and scooch a little with this way if you could. OK, all right. And make a little more room. So seven-- let's see. 5, 2, 7, 4-- AUDIENCE: [INAUDIBLE] DAVID J. MALAN: 4, hopefully 1. Yeah, keep them to the side. OK, 1, 6, and there we go-- 3. Come on over, three. I was looking for you. All right, so here, we have
an array of eight numbers-- eight integers if you will. And do you want to each say
a quick hello to the group? AUDIENCE: Hello, I'm Quinn. Go [INAUDIBLE]. AUDIENCE: Hi, everyone. I'm [INAUDIBLE]. AUDIENCE: Hey, I'm Mitchell. AUDIENCE: Hi, I'm Brett. And also, go [INAUDIBLE]. AUDIENCE: I'm Hannah. Go [INAUDIBLE]. AUDIENCE: Hi, I'm Matthew. Go [INAUDIBLE] AUDIENCE: Hi, I'm Miriam. Go Winthrop. AUDIENCE: Hi, I'm
Celeste, and go Strauss. DAVID J. MALAN: Wonderful. Well, welcome all to the stage,
and let's just visualize, perhaps organically, how you
eight would solve this problem. So we currently have the numbers
0 through 7 quite out of order. Could you go ahead and just
yourselves from 0 through 7? AUDIENCE: Thank you. DAVID J. MALAN: OK, so what did they just do? OK, yes. First of all, yes, very well done. How would you describe
what they just did? Well, let's do this. Could you go back into
that order on the screen-- 52741630? And could you do exactly
what you just did again? Sort yourselves. All right, what did-- OK, yes. Well done again. All right, so admittedly, there's kind
of a lot going on because each of you, except number four, are doing something
in parallel all at the same time. And that's not really how
a computer typically works. Just like a computer can only look at
one memory location, at one locker, at a time, so can a computer only move
one number at a time-- sort of opening a locker, checking what's
there, moving it as needed. So let's try this more methodically
based on the two audience suggestions. If you all could randomize
yourself again to 52741630, let's take the second of
those approaches first. I'm going to look at these numbers. And even though I as the human
can obviously see all the numbers and I just kind of have the
intuition for how to fix this, we got to be more methodical
because eventually, we've got to translate this to
pseudo code and then code. So let me see. I'm going to search for, as you
proposed, the smallest number. And I'm going to start
from left to right. I could do it right to left, but left
to right just tends to be convention. All right, 5 at this moment is
the smallest number I've seen. So I'm going to remember that
in a variable, if you will. Now I'm going to take one more step-- 2. OK, 2 I'm going to compare to the
variable in mind, obviously smaller. I'm going to forget about 5 and only
now remember 2 as the now smallest elements. 7, nope-- I'm going to ignore that
because it's not smaller than the 2 I have in mind. 4, 1-- OK, I'm going to
update the variable in mind because that's indeed smaller. Now obviously, we the humans
know that's getting pretty small. Maybe it's the end. I have to check all values to see
if there's something even smaller because 6 is not, 3 is not, but 0 is. And what's your name again? AUDIENCE: Celeste. DAVID J. MALAN: Celeste. Where should Celeste or number 0 go
according to this proposed algorithm? All right, I'm seeing a lot of this. So at the beginning of the array,
so before doing this for real, let's have you pop out in front. And could you all shift
and make room for Celeste? Is this a good idea to have all
of them move or equivalently move everything in the array
to make room for Celeste and number 0 over there? No, probably not. That felt like a lot of work. And even though it happened pretty
quickly, that's like seven steps to happen just to move her in place. So what would be marginally
smarter perhaps-- a little more efficient, perhaps? What's that? AUDIENCE: Swapping. DAVID J. MALAN: Swapping. What do you mean by swap? AUDIENCE: Replacing swaps. DAVID J. MALAN: OK, replace two values. So if you want to go back to where
you were, one step Over, number 5, he's not in the right place. He's got to move eventually. So you know what? If that's where Celeste belongs,
why don't we just swap 5 and 0? So if you want to go ahead and
exchange places with each other. Notice what's just happened. The problem I'm trying to
solve has gotten smaller. Instead of being size
8, now it's size 7. Now granted, I moved 5 to
another wrong location. But if these numbers
started off randomly, it doesn't really matter where 5 goes
until we get him into the right place. So I think we've improved. And now if I go back, my loop
is sort of coming back around. I can ignore Celeste and make this
a seven step problem and not eight because I know she's in the right place. 2 seems to be the smallest. I'll remember that. Not 7, not 4-- 1 seems to be the smallest. Now I know as a human this
should be my next smallest. But why, intuitively, should
I keep going, do you think? I can't sort of optimize as a
human and just say, number 1, let's get you into the right place. I still want to check the whole array. Why? Yeah. AUDIENCE: Perhaps there's another 1. DAVID J. MALAN: Maybe there's
another 1, and that could be another problem altogether. Other thoughts? Yeah. AUDIENCE: Could be another 0 DAVID J. MALAN: There could
be another 0 indeed, but I did go through
the list once, right? And I kind of know there isn't. Your thoughts? AUDIENCE: You don't know that
every value is represented. So maybe there's a [INAUDIBLE] You
just don't know what kind of data you're working with. DAVID J. MALAN: Yeah, I don't
necessarily know what is there. And honestly, I only stipulated earlier
that I'm using one variable in my mind. I could use two and remember the
two smallest elements I've seen. I could use three variables, four. But then I'm going to start to use
a lot of space in addition to time. So if I've stipulated that I only have
one variable to solve this problem, I don't know anything
more about these elements because the only thing I'm
remembering at this moment is number 1 is the
smallest element I've seen. So I'm going to keep going. 6? Nope. 3? Nope. 5? Nope. OK, I know that number
1, and your name was-- AUDIENCE: Hannah. DAVID J. MALAN: --Hannah is
the next smallest element. I could have everyone move
over to make room, but nope. 2? You know, even though you're
so close to where I want you, I'm just going to keep it
simple and swap you two. So granted, I've made the
problem a little worse. But on average, I could get
lucky too and just pop number 2 into the right place. Now let me just accelerate this. I can now ignore Hannah and Celeste,
making the problem size 6 instead of 8. So it's getting smaller. 7 is the smallest. Nope, now 4 is-- 2 is the smallest. Still 2, still 2, still 2. So let's go ahead and swap 2 and 7. And now I'll just kind of
orchestrate it verbally. 4, you're about to have to do something. So we now have 4, 7, 6 3, 5. OK, 3-- could you swap with 4? All right, now we have 7, 6, 4, 5. OK, 4, could you swap with 7? Now we have 6, 7, 5. 5, could you swap with 6? And now we have 7, 6. 6, would you swap at 7? And now perhaps round of applause. They've sorted themselves. OK, hang on there one minute. So we'll do this one other approach. And my God, that felt so much
slower than the first approach, but that's, one, because I was
kind of providing a long voiceover. But two, we were doing one thing at a
time whereas the first time, you guys had the luxury of moving
like eight different CPUs-- brains, if you will-- were all
operating at the same time. And computers like that exist. If you have a computer with
multiple cores, so to speak, that's like having a
computer that technically can do multiple things at once. But software typically, at least
as we've written it thus far, can only do one thing at a time. So in a bit, we'll add
up all of these steps. But for now, let's take
one other approach. If you all could reorder
yourselves like that-- 52741630-- let's take
the other approach that was recommended by just fixing small
problems and see where this gets us. So we're back in the original order. 5 and 2 are clearly out of order. So you know what? Let's just bite this problem off now. 5 and 2, could you swap? Now let me take a next step. 5 and 7, I think you're OK. There's a gap, yes, but that
might not be a big deal. 7 and 4-- problem. Let's have you swap. OK, 7 and 1, let's have you swap. 7 and 6, let's have you swap. 7 and 3, you swap. 7 and 0, you swap. Now let me pause for just a moment. Still not sorted. So I'm clearly not done. But have I improved the problem? Right, I can't see-- like
before, I can't optimize like before because 0 is obviously not here. So unless they're still way back there,
so it's not like I've gone from 8 steps to 7 to 6 just yet. But have I made any improvements? AUDIENCE: Yes. DAVID J. MALAN: Yes. In what sense is this improved? What's a concrete thing you
could point to is better? Yeah. AUDIENCE: Sorted the highest number. DAVID J. MALAN: I've sorted the
highest number, which is indeed 7. And conversely, if you prefer, Celeste
is one step closer to the beginning. Now worst case, Celeste is going to
have to move one step on each iteration. So I might need to do this
thing like n total times to move her all the way over. But that might work out OK. Let me see. 2 and 5, you're good. 5 and 4, swap you. 5 and 1, let's swap you. 5 and 6, you're good. 6 and 3, let's swap you. 6 and 0, let's swap you. 6 and 7, you're good. And I think now-- notice that the high
values, as you noted, are sort of bubbling up, if you
will, to the end of the list. 2 and 4, you're good. 4 and 1, let's swap. 4 and 5, good. 5 and 3, swap. 5 and 0, swap. 5, 6, 7, of course, are good. So now you can sort of see
the problem resolving itself. And let's just do this part now faster. 2 and 1, 2 and 4. OK, 4 and 3, 4 and 0. All right, now 1 and 2,
2, and 3, and 0, and good. So we do have some optimization there. We don't need to keep going
because those all are sorted. 1 and 2, you're good. 2 and 0, all right, done. 1 and 0-- and big round
of applause in closing. OK, so thank you all. We need the puppets back,
but you can keep the shirts. Thank you for volunteering here. Feel free to make your
way exits left or right. And let's see if,
thanks to our volunteers here, we can't now formalize a little
bit what we did on both passes here. I claim that the first algorithm
our volunteers kindly acted out is what's called selection sort. And as the name implied, we selected
the smallest elements again and again and again, working our
way from left to right, putting Celeste into the right place,
and then continuing with everyone else. So selection sort, as
it's formally called, can be described, for instance,
with this pseudo code here-- 4i from 0 to n minus 1. And again, why this? This is just how talk about arrays. The left end is 0, the right end
is n minus 1 where in this case, n happened to be eight people. So that's 0 through 7. So for i from 0 to n
minus 1, what did I do? I found the smallest number between
numbers bracket i and numbers bracket n minus 1. It's a little cryptic at
first glance, but this is just a very pseudo
code-like way of saying find the smallest element
among all eight volunteers because if i starts at 0 and n minus
1 never changes because there's always 8, 8 people, so 8 minus
1 is 7, this first says find the smallest number
between numbers bracket 0 and numbers bracket 7, if you will. Then what do I do? Swap the smallest number
with numbers bracket i. So that's how we got Celeste from
over here all the way over there. We just swapped those two values. What then happens next
in this pseudo code? i, of course, goes from 0 to 1. And that's the technical
way of saying now find the smallest element among
the 7 remaining volunteers, ignoring Celeste this time because she
was already in the correct location. So the problem went
from size 8 to size 7. And if we repeat, size 6,
5, 4, 3, 2, 1, until boom, it's all done at the very end. So this is just one way of
expressing in pseudo code what we did a little more organically
and a formalization of what someone volunteered out in the audience. So if we consider, then, the
efficiency of this algorithm, maybe abstracting it away
now as a bunch of doors where the left most again is always
0, the right most is always n minus 1, or equivalently, the second to last
is n minus 2, the third to last is n minus 3 where n might
be 8 or anything else, how do we think about or quantify
the running time of selection sort? Big O of what? I mean, that was a lot
of steps to be adding up. It's probably more than n, right,
because I went through the list again and again. It was like n plus n
minus 1 plus n minus 2. Any instincts here? We got like the whole
team in the orchestra now. Let me propose we think about it this
way with just a bit of formula, say. So the first time, I had to
look at n different volunteers. n was 8 in this case, but generically,
I looked at all eight numbers in order to decide who was the smallest. And sure enough, Celeste
was at the very end. She happened to be all
the way to the right. But I only knew that once I looked
at all 8 or all n volunteers. So that took me n steps first. But once the list was swapped
into the right place, then my problem with size n minus 1,
and I had n minus 1 other people to look through. So that's n minus 1 steps. Then after that, it's n minus 2 plus
n minus 3 plus n minus 4 plus dot dot dot until I had one final step. And it's obvious that I only
have one human left to consider. So we might wave our hands at
this with a little ellipsis and just say dot dot dot
plus 1 for the final step. Now what does this actually equal? Well, this is where you
might think back on, like, your high school math
or physics textbook that has a little cheat sheet at the end
that shows these kinds of recurrences. That happens to work
out mathematically to be n times n plus 1 all divided by 2. That's just what that recurrence,
that series, actually adds up to. So if you take on faith that
that math is correct, let's just now multiply this
out mathematically. That's n squared plus n divided by 2 or
n squared divided by 2 plus n over 2. And here's where we're starting
to get annoyingly into the weeds. Like, honestly, as n gets really
large, like a million doors or integers or a billion web pages in Google search
engine, honestly, which of these terms is going to matter the
most mathematically if n is a really big number? Is n squared divided by
2 the dominant factor, or is n divided by 2
the dominant factor? AUDIENCE: n squared. DAVID J. MALAN: Yeah, n squared. I mean, no matter what n
is-- and the bigger it is, the bigger raising it to
the power 2 is going to be. So you know what? Let's just wave our hands at this
because at the end of the day, as n gets really large, the dominant
factor is indeed that first one. And you know what? Even the divided 2, as I claimed earlier
with our two phone book examples, where the two straight lines if you
keep zooming out essentially looked the same when n is large enough,
let's just call this on the order of n squared. So that is to say a computer scientist
would describe bubble sort as taking on the order of n squared steps. That's an oversimplification. If we really added it up, it's
actually this many steps-- n squared divided by 2 plus n over 2. But again, if we want to just be able
to generally compare two algorithms' performance, I think it's going to
suffice if we look at that highest order term to get a sense of what the
algorithm feels like, if you will, or what it even looks like graphically. All right, so with that said,
we might describe bubble sort as being in big O-- sorry, selection sort as
being in big O of n squared. But what if we consider now the
best case scenario-- an opportunity to talk about a lower bound? In the best case, how many
steps does selection sort take? Well, here, we need some context. Like, what does it mean to be
the best case or the worst case when it comes to sorting? Like, what could you imagine meaning
the best possible scenario when you're trying to sort a bunch of numbers? I got the whole crew here again. Yeah. AUDIENCE: They would already be sorted. DAVID J. MALAN: All right, they're
already sorted, right? I can't really imagine a better scenario
than I have to sort some numbers, but they're already sorted for me. But does this algorithm
leverage that fact in practice? Even if all of our humans
had lined up from 0 to 7, I'm pretty sure I would have
pretty naively started here. And yes, Celeste happens to be here. But I only know she needs to be here
once I've looked at all eight people. And then I would have realized,
well, that was a waste of time. I can leave Celeste be. But then what would I have done? I would have ignored her position
because we've solved one problem. I would have done the same thing now
for seven people, then six people. So every time I walk through,
I'm not doing much useful work. But I am doing those
comparisons because I don't know until I do the work that
the people were in the right order. So this would seem to imply that
the omega notation, the best case scenario, even, a lower bound on the
running time would be what, then? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: A little louder? AUDIENCE: N squared. DAVID J. MALAN: It's still
going to be n squared, in fact, because the code I'm giving
myself doesn't leverage or benefit from any of that scenario because
it just mindlessly continues to do this again and again. So in this case, yes, I would claim that
the omega notation for selection sort is also big O of n squared. So those are the kinds
of numbers to beat. It seems like the upper bound
and lower bound of selection sort are indeed n squared. And so we can also describe
selection sort, therefore, as being in theta of n squared. That's the first algorithm we've
had the chance to describe that in, which is to say that it's kind of slow. I mean, maybe other
algorithms are slower, but this isn't the best starting point. Can we do better? Well, there's a reason that I guided us
to doing the second algorithm second. Even though you verbally proposed
them in a different order, this second algorithm we did is
generally known as bubble sort. And I deliberately used
that word a bit ago, saying the big values are
bubbling their way up to the right to kind of capture the fact that,
indeed, this algorithm works differently. But let's consider if
it's better or worse. So here, we have pseudo
code for bubble sort. You could write this
too in different ways. But let's consider what
we did on the stage. We repeated the following
n minus 1 times. We initialized at least, even though
I didn't verbalize it this way, a variable like i from 0
to n minus 2, n minus 2. And then I asked this question. If numbers bracket i and numbers
bracket i plus 1 are out of order, then swap them. So again, I just did it more
intuitively by pointing, but this would be a way,
with a bit of pseudo code, to describe what's going on. But notice that I'm doing something
a little differently here. I'm iterating from if
equals 0 to n minus 2. Why? Well, if I'm comparing two
things, left hand and right hand, I'd still want to start at 0. But I don't want to go
all the way to n minus 1 because then, I'd be going past
the boundary of my array, which would be bad. I want to make sure that my
left hand-- i, if you will-- stops at n minus 2 so that when
I plus 1 in my pseudo code, I'm looking at the last two
elements, not the last element and then pass the boundary. That's actually a common
programming mistake that we'll undoubtedly
soon make by going beyond the boundaries of your array. So this pseudo code, then, allows me to
say compare every one again and again and swap them if they're out of order. Why do I repeat the whole
thing n minus 1 times? Like, why does it not suffice
just to do this loop here? Think what happened with Celeste. Why do I repeat this whole
thing n minus 1 times? Yeah, in the back? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Indeed, and I think
if I can recap accurately, think back to Celeste again. And I'm sorry to keep calling
on you as our number 0. Each time through bubble
sort, she only moved one step. And so in total, if there's n
locations, at the end of the day, she needs to move n minus 1 steps to get
0 all the way to where it needs to be. And so this inner loop, if you
will, where we're iterating using i, that just fixes some of the problems. But it doesn't fix all of the problems
until we do that same logic again and again and again. And so how might we quantify the
running time of this algorithm? Well, one way to see it is to just
literally look at the pseudo code. The outer loop repeats n
minus 1 times by definition. It literally says that. The inner loop, the for loop,
also iterates n minus 1 times. Why? Because it's going from 0 to n minus 2. And if that's hard to think about,
that's the same thing is 1 to n minus 1 if you just add 1 to
both ends of the formula. So that means you're doing n
minus 1 things n minus 1 times. So I literally multiply how
many times the outer loop is running by how many times the
inner loop is running, which gives me sort of FOIL method n minus 1 squared. And I could multiply
that whole thing out. Well, let's consider this just
a little more methodically here. If I have n minus 1 on the
outer, n minus 1 on the inner-- let's go ahead and FOIL this. So n squared minus n
minus n plus 1, combine like terms-- n squared minus 2n plus 1. And now which of these terms is clearly
going to be dominant, so to speak? The-- AUDIENCE: N squared. DAVID J. MALAN: --the n squared. So yes, even though
minus 2n is a good thing because it's subtracting off
some of the time required, plus 1 is not that big a thing,
there's such drops in the bucket when n gets really large, like
in the millions or billions, certainly, that bubble sort 2
is on the order of n squared. It's not the same exactly
as selection sort. But as n gets big,
honestly, we're barely going to be able to notice
the difference most likely. And so it too might be said to
be on the order of n squared. And if we consider now the lower
bound on bubble sort's running time, here's where things get
potentially interesting. What might you claim is the running
time of bubble sort in the best case? And the best case, I claim, is when
the numbers are already sorted. Is our pseudo code going
to take that into account? AUDIENCE: N DAVID J. MALAN: OK, n. Why do you propose n? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yes, and that's the key word. To summarize, in bubble sort, I do have
to minimally make one pass because if I don't look at all n elements,
that I'm theoretically just guessing if it's sorted or not. Like, I obviously
intuitively have to look at every element to decide yay
or nay, it's in the right order. And my original pseudo code,
though, is pretty naive. It's just going to blindly go back and
forth n minus 1 times again and again, and that's going to add up. But what if I add a
bit of an optimization that you might have glimpsed
on the slide a moment ago where if I compare two people and I
don't swap them, compare two people, don't swap them, and I go all the
way through the list comparing every pair of adjacent
people, and I make no swaps, it would be kind of not
just naive but stupid to do that same process again
because if the humans have not moved, I'm not going to make
any different decisions. I'm going to do nothing
again, nothing again. So at that point, it would be stupid,
very inefficient, to go back and forth and back and forth. So if I modify our pseudo code with
just an additional if condition, I bet we can speed this up. Inside of that same pseudo code, what
if I say, hey, if no swaps, quit? Like quit, prematurely before
the loops are finished running. One of the loops has gone
through per the indentation here. But if I do a loop from
left to right and I have made no swaps, which you
can think of as just being one other variable that's plus plusing
as I go keeping, track of how many swaps-- if I've made no swaps
from left to right, I'm not going to make any swaps
the next time around either. So let's just quit at that point. And that is to say in the
best case, if you will, when the list is already sorted,
the omega notation for bubble sort might indeed be omega of n
if you add that optimization so as to short circuit
all of that inefficient looping to do it only as
many times as is necessary. Let me pause to see if
there's any questions here. Yeah. AUDIENCE: [INAUDIBLE] to optimize the
running time for all cases possible? DAVID J. MALAN: Good question. If the running time of selection sort
and bubble sort are both in big O of n squared but selection sort is in
omega of n squared while bubble sort is in omega of n, which sounds better-- I think if I may, should we
just always use bubble sort? Yes if we think that we
might benefit over time from a lot of good case
scenarios or best case scenarios. However, the goal at
hand in just a bit is going to be to do even
better than both of these. So hold that question
further for a moment. Yeah. AUDIENCE: [INAUDIBLE] n minus 1? DAVID J. MALAN: No. So yes, good question. So I say omega of n, but is it
technically omega of n minus 1? Maybe, but again, we're
throwing away lower order terms. And that's an advantage because we're
not comparing things ever so precisely. Just like I plotted with the
green and yellow and red chart, I just want to get a sense of
the shape of these algorithms so that when n gets really
large, which of these choices is going to matter the most? At the end of the day, it's
actually perfectly reasonable to use selection sort
or bubble sort if you don't have that much data because
they're going to be pretty fast. My God, our computers
nowadays are 1 gigahertz, 2 gigahertz, 1 billion things per
second, 2 billion things per second. But if we have large data sets,
as we will later in the term and as you might in the real world,
that the Googles of the world, then you're going to want
to be more thoughtful. And that's where we're going today. All right, so let's actually see
this visualized a little bit. In a moment, I'm going
to change screens here to open up what is a little
visualization tool that will give us a sense of how these things actually
work and look at a faster rate than our humans are able
to do here on stage. So here is another visualization of a
bunch of numbers, an array of numbers. Short bars mean small numbers,
tall bars mean big numbers. So instead of having the
numbers on their torsos here, we just have bars that are small or tall
based on the magnitude of the number. Let me go ahead, and I
preconfigured this in advance to operate somewhat quickly. Let's go ahead and do selections
sort by clicking this button. And you'll see some pink bars flying by. And that's like me walking
left and right, left and right, to select the next smallest number. And so what you'll see happening on
the left of this array of numbers is Celeste, if you will, and
all of the other smaller numbers are appearing on the left while
we continue to solve the remaining problems to the right. So again, we no longer have to
touch the smaller numbers here. So that's why the problem is getting
smaller and smaller and smaller over time. But you can notice now
visually, look at how many times we're retracing our steps. This is why things
that are n squared tend to be frowned upon if avoidable because
I'm touching the same elements again and again. When I was walking through, I
kept pointing at the same humans again and again. And that adds up. So let's see if bubble sort looks
or feels a little different. Let me re-randomize the thing, and let
me now click Bubble Sort at the top. And as you might infer, there's
other sorting algorithms out there, not all of which we'll look at. But here's bubble sort. Same pink coloration, but it's
doing something different. It's two pink bars going through
again and again comparing the adjacent numbers. And you'll see that the largest
numbers are indeed bubbling the way up to the right, but the smaller
numbers, like our number 0 was, is only slowly making its way over. Here's a comparable. Here's the number one. And it's going to take a while
to get all the way to the left. And here too, notice how
many times the same bars are becoming pink, how many times the
algorithm is retracing and retracing its steps. Why? Because it's only solving one
problem at a time on each pass. And each time we do that, we're stepping
through practically the whole array. And now granted, I could speed this
up even further if I really wanted to, but my God, this is only, what, like
50 or 60 elements, something like that? This is slow. Like, this is what n squared
looks like and feels like. And now I'm just trying to
come up with words to say until we get to the finish line here. Like, this would be annoying if
this is the speed of sorting, and this is why I sort of secretly
sorted the numbers for Rave in advance because it would have taken
us an annoying number of steps to get that in place for her. So those two algorithms are n squared. Can we do, in fact, better? Well, to save the best algorithm for
last, let's take a shorter five minute break here. And when we come back, we'll
do even better than n squared. All right. So the challenge at hand is to
do better than selection sort and better than bubble sort
and ideally not just marginally better but fundamentally better. Just like in week zero, that third
and final divide and conquer algorithm was sort of fundamentally
faster than the other two. So can we do better than something
on the order of n squared? Well, I bet we can if
we start to approach the problem a little differently. The sorts we've done
thus far, generally known as comparison sorts--
and that kind of captures the reality that we were doing a huge
number of comparisons again and again. And you kind of saw that in the vertical
bars that were going pink as everything was being compared again and again. But there's this programming
technique, and it's actually a mathematical technique
known as recursion that we've actually seen before. And this is a building
block or a mental model we can bring to bear on the problem
to solve the sorting problem sort of fundamentally differently. But first, let's look at it
in a more familiar context. A little bit ago, I proposed this pseudo
code for the binary search algorithm. And notice that what was
interesting about this code, even though I didn't call it out at the
time, it's kind of cyclically defined. Like, I claim this is
an algorithm for search, and yet it seems a little unfair
that I'm using the verb search inside of the algorithm for search. It's like an English sort of
defining a word by using the word. Normally, you shouldn't
really get away with that. But there's something
interesting about this technique here because even though this
whole thing is a search algorithm and I'm using my own algorithm to
search the left half or the right half, the key feature here
that doesn't normally happen in English when you
define a word in terms of a word is that when I search the left
half or search the right half, yes, I'm doing the same thing. I'm using the same algorithm. But the problem is, by
definition, half as large. So this isn't going to be a
cyclical argument in the same way. This approach, by using
search within search is going to whittle the problem down
and down and down until hopefully, one door or no doors remains. And so recursion is a
programming technique whereby a function calls itself. And we haven't seen this yet in C, and
we haven't seen this really in Scratch. But in C, you can have
a function call itself. And the form that
takes is like literally using the function's name inside of
the function's implementation itself. We've actually seen an opportunity
for this once before too. Think back to week zero. Here's that same pseudo code
for searching for someone in an actual, physical phone book. And notice these yellow lines here. We described those in week zero
as inducing a loop, a cycle. And this is a very procedural approach,
if you will, because lines 8 and 11 are very mechanically,
if you will, telling me to go back to line three to
do this kind of looping thing. But really, what that's doing in the
binary search algorithm for the phone book is it's just telling me to search
the left half or search the right half. I'm doing it more mechanically
again by sort of telling myself what line number to go back to. But that's equivalent to just telling
myself go search the left half, search the right half, the
key thing being the left have and the right half are
smaller than the original problem. It would be a bug if I just said search
the phone book, search the phone book, because obviously, you
never get anywhere. But if you search the
half, the half, the half, problem gets smaller and smaller. So let's reformulate week zero's phone
book code to be not procedural as here but recursive whereby in
this search algorithm, AKA binary search, formerly
called divide and conquer, I'm going to literally use also
the keyword search here. Notice among the benefits
of doing this is it kind of tightens the code up,
makes it a little more succinct, even though that's kind
of a fringe benefit here. But it's an elegant
way too of describing a problem by just having
a function use itself to solve a smaller puzzle at hand. So let's now consider a
familiar problem, a smaller version than the one you've dabbled
with-- this sort of pyramid, this half pyramid from Mario. And let's throw away the parts
that aren't that interesting and just consider how we might, up
until now, implement this in C code, this left aligned pyramid, if you will. Let me go over here, and let me create
a file called-- how about iteration.c? And in this file, I'm going to
go ahead and include cs50.h. And I'm going to include stdio.h. And the goal at hand is to implement in
C a little program that just prints out this and exactly this pyramid. So no get string or any of that--
we're just going to keep it simple and print exactly this
pyramid of height 4 here. So how might I do this? Well, let me go ahead, and in main,
let me first ask the user for-- well, we'll go ahead and generalize it. Let's go ahead and ask
the user for heights. We're using getint as before. And I'll store that in a
variable called height. And then let me go ahead
and simply call the function draw passing in that height. So for the moment, let me
assume that someone somewhere has implemented a draw function. And this, then, is the
entirety of my program. All right, unfortunately, C does
not come with a draw function. So let me go ahead and invent one. It doesn't need to return a value. It just needs to print
something-- so-called side effect. So I'm going to define a function
called draw that takes as input an int. I'll call it n for number, but
I could call it anything I want. And inside of this. I'm going to go ahead and print out a
left aligned pyramid like this from top to bottom. The salient features here are that this
is a pyramid, at least in this example, of height four. And now in height four, the
first row has one brick. The second row has two. The third has three. The fourth has four. That's a nice pattern that I
can probably represent in code. So how might I do this? Well, how about 4 int i gets-- let me do it the old school way-- 1. And then i is less than or equal to n. And then i plus plus-- so I'm going from 1 to 4 just
to keep myself sane here. And then inside of this
loop, what do I want to do? Well, let me keep it
conventional, in fact. Let me just change this to
be the more conventional 0 to n even though it might not be
as intuitive because now on row 0, I want one brick. On row 1, I want two
bricks, dot dot dot. On row 3, I want four. So it's kind of offset now. But I'm being more conventional. So on each row, how many
bricks do I want to print? Well, I think I want to do this. For int j, for instance, common to
use j after if you have a nested loop, let's start j at 0 and do this
so long as is less than i plus 1 and then do j plus plus. So why i plus 1? Well, again, when I equals 0, that's
the first row, and I want one brick. When i equals 1, that's the second row. I want two bricks. And dot dot dot, when i
is 3, I want four bricks. So again, I have to add 1 to i
to get the total number of bricks that I want to print to the screen. So inside of this nested for loop,
I'm going to do printf of a hash with no new line. I'm going to save the new
line for about here instead. All right, the last
thing I'm going to do is copy and paste the prototype
at the top of the file. So that I can call this. And again, this is of
now week one, week two. Wouldn't necessarily come to
your mind as quickly as it might to mine after all this practice,
but this is something reminiscent of what you yourself did
already for Mario-- printing out a pyramid that hopefully in a
moment is going to look like this. So let me go back to my code. Let me run make iteration, and
let me do dot slash iteration. I'll type in 4, and voila. Seems to be correct, and let's assume
it's going to work for other inputs as well. Oh, thank you. So this is indeed an example
of iteration-- doing something again and again. And it's very procedural. Like, I literally have a function
called draw that does this thing. But I can think about implementing
draw in a somewhat different way that's kind of clever. And it's not strictly
necessary for this problem because this problem honestly
is not that complicated to solve once you have
practice under your belt. Certainly the first time around,
probably significantly challenging. But now that you kind of
associate, OK, row one with one brick, row two with two
bricks, it kind of comes together with these for loops. But how else could we
think about this problem? Well, this physical structure,
these bricks, in some sense is a recursive structure, a structure
that's defined in terms of itself. Now what do I mean by that? Well, if I were to ask you the question,
what does a pyramid of height 4 look like, you would point,
of course, to this picture. But you could also kind of
cleverly say to me, well, it's actually a pyramid of
height 3 plus 1 additional row. And here's that cyclical
argument, right? Kind of obnoxious to do typically
in English or in a spoken language because you're defining one
thing in terms of itself. What's a pyramid of height 4? Well, it's a pyramid of
height 3 plus 1 more row. But we can kind of leverage
this logic in code. Well, what's a pyramid of height 3? Well, it's a pyramid of
height 2 plus 1 more row. Fine, what's a pyramid of height 2? Well, it's a pyramid of
height 1 plus 1 more row. And then hopefully, this process
ends, and it does because notice, the pyramid is getting
smaller and smaller. So you're not going to have this
sort of silly back and forth with me infinitely many times because when
we finally get to the base case, the end of the pyramid, fine. What is a pyramid of height 1? Well, it's a pyramid of no
height plus one more row. And at that point, things
just get negative-- no pun intended. Things just would otherwise go negative. And so you can just kind of stop. The base case is when
there is no more pyramid. So there's a way to draw a line in the
sand and say, stop, no more arguments. But this idea of defining a physical
structure in terms of itself or code in terms of itself actually lets
us do some interesting new algorithms. Let me go back to my code here. Let me go ahead and create one
final file here called recursion.c that leverages this idea of this
built-in self-referential nature. Let me include cs50.h. Let me go ahead and include
standardio.h, int main void. And then inside of main, I'm going
to do the exact same thing-- int height equals get int,
asking the user for height. And then I'm going to go ahead
and call draw passing in height. So that's going to stay the same. I even am going to make my prototype
the same-- void draw int n semicolon. And now I'm going to
implement void down here with that same prototype, of course. But the code now is going
to be a little different. What am I going to do here? Well, first of all, if you ask
me to draw a pyramid of height n, I'm going to be kind of a wise
ass here and say, well, just draw a pyramid of n minus 1-- done. All right, but there's still
a little more work to be done. What happens after I print or
draw a pyramid of height n minus 1 according to our structural
definition a moment ago? What remains after drawing a pyramid
of height n minus 1 or 3, specifically? AUDIENCE: [INAUDIBLE] We need one more row of hashes. OK, so I can do that, right? I'm OK with the single loops. There's no nesting necessary here. I'm just going to do this-- for
int i get 0, i is less than n, which is the height that's
passed in, i plus plus. And then inside of this
loop, I'm very simply going to print out a single hash. And then down here, I'm going to
print out a new line at the very end. So that's good, right? I might not be as comfortable
with nested loops. This is nice and simple. What does this loop do
here on line 17 through 20? It literally prints n hashes by
counting from i equals 0 on up to but not through n. So that's sort of week one style syntax. But this is kind of trippy
now because I've somehow boiled down the implementation of
draw into printing a row after just drawing the thing above it. But this is problematic as
is because in this case, my drawer function, notice, is always
going to call the draw function forever in some sense. But ideally, when do I want
this cyclical process to stop? When do I want to not call draw anymore? Yeah, when n is 1, right? When I get to the top of
the pyramid, when n is 1, or heck, when the pyramids
all gone and n equals 0. I can pick any line in
the sand, so long as it's sort of at the end of the process. Then I don't want to call draw anymore. So maybe what I should do is this. If n equals equals 0, there's
really nothing to draw. So I'm just going to go
ahead and return like this. Otherwise, I'm going
to go ahead and draw n minus 1 rows and then one more row. And I could express this differently. I could do something like this,
which would be equivalent. I could say something like if n
is greater than or equal to 0, then go ahead and draw the row. But I like it this way first. For now, I'm going to
go with the original way just to ask a simple question and
then just bail out of the function if n equals 0. And heck, just to be
super safe, just in case the user types in a
negative number, let me also just check if n is a negative number,
also, just return immediately. Don't do anything. I'm not returning a value because
again, the function is void. It doesn't need or have a return value. So just saying return suffices. But if n equals 1 or 2
or 3 or anything higher, it is reasonable to draw a pyramid of
slightly shorter height like, instead of 4, 3, and then go ahead
and print one more row. So this is an example now of code
that calls itself within itself. Draw is calling draw. But this so-called base case
ensures, this conditional ensures, that we're not going to do this forever. Otherwise, we literally would
do this infinitely many times, and something bad is
probably going to happen. All right, let me go ahead and
compile this code-- make recursion. OK, no syntax errors-- dot slash
recursion, Enter, height of 4, and voila. If only because some of you have run
into this issue accidentally already, let me get rid of the base case
here, and let me recompile the code. Make recursion. Oh, and actually, now
it's actually catching it. So the compiler is smart
enough here to realize that all paths through this
function will call itself. AKA, It's going to loop forever. So let me do the first thing. Suppose I only check for n equaling 0. Let me go ahead and recompile
this code with make recursion. And now let me just be
kind of uncooperative. When I run this program, still
works for 4, still works for 0. What if I do like negative 100? Have any of you experienced a
segmentation fault or core dump? OK, so no shame in this. Like, this means I have somehow
touched memory that I shouldn't have. And in short, I actually called
this function thousands of times accidentally, it would seem
now, until the program just bailed on me because I eventually
touched memory in the computer that I shouldn't have. That'll make even more sense next week. But for now, it's simply a bug. And I can avoid that
bug in this context, probably not your own pset context,
by just making sure we don't even allow for negative numbers at all. So with this building
block in place, what can we now do in terms of
those same numbers to sort? Well, it turns out there's a
sorting algorithm called merge sort. And there's bunches of others too. But merge sort is a nice one to discuss
because it fundamentally, we hope, is going to do better than
selection sort and bubble sort that is better than n squared. But the catch is it's a
little harder to think about. In fact, I'll act it out myself with
just these numbers on the shelf here rather than humans because recursion
in general takes a little bit of effort to wrap your mind around,
typically a bit of practice. But I'll see if we can't
walk through it methodically enough such that this comes to light. So here's the pseudo code I propose
for this algorithm called merge sort. In the spirit of recursion,
this sorting algorithm literally calls itself by using
the verb sort in its pseudo code. So how does merge sort work? It sort of obnoxiously says, well, if
you want to sort all of these things, go sort the left half, then
go sort the right half, and then merge the two together. Now obnoxious in what sense? Well, if I just asked you to sort
something and you just tell me, well, go sort that
thing and then go sort that thing, what was the point
of asking you in the first place? But the key is that
each of these lines is sorting a smaller piece of the problem. So eventually, we'll be
able to pare this down into something that doesn't go on
forever because in fact, in merge sort, there's a base case too. There's a scenario where we
just check, wait a minute, if there's only one
number to sort, that's it. Quit then because you're all done. So there has to be this base
case in any use of recursion to make sure that you don't
mindlessly call yourself forever. You've got to stop at some point. So let's focus on the
third of these steps. What does it mean to merge two
lists, two halves of a list, just because this is
apparently going to be a key ingredient-- so
here, for instance, are two halves of a list of size 8. We have the numbers 2-- and I'll call
it out if you're at a bad angle-- 2457 and 0136. Notice that the left half at the
moment, 2457, is already sorted, and the right half, 0136,
is also sorted as well. So that's a good thing because
it means that theoretically, I've sorted the left half already. I've sorted the right half
already before we began. I just need to merge these two halves. What does it mean to sort two halves? Well, for the sake of
discussion, I'm just going to turn over most of the numbers
except for the first numbers in each of these halves. There's two halves here, left and right. At the moment, I'm
only going to consider the leftmost element of each half--
that is, the one on the left here and the one on the left here. How do I merge these two lists together? Well, if I look at 2 and I look at 0,
which one should presumably come first? The smaller one. So I'm going to grab
the 0, and I'm going to put it into its own place
on this new shelf here. And now I'm going to consider,
as part of my iteration, the beginning of this list and
the new beginning of this list. So I'm now comparing 2 and 1. Which one's smaller? I'm going to go ahead and grab the 1. Now I'm going to compare the
beginning of the left list and the new beginning of
the right list, 2 and 3. Of course, it's 2. Now I'm going to compare the
beginning of the left list and the beginning of
the right list, 4 and 3. It's of course 3. Now I'm going to compare the 4
against the beginning and end, it turns out, of the second list-- 4, of course. Now I'm going to compare the
beginning of the left list and the beginning of the right list-- 5, of course. I'm realizing this is not going
to end well because I left too much distance between the numbers. But that has nothing to
do with the algorithm. 7 is the beginning of the left list. 6 is the beginning of the right list. It's, of course, 6. And at the risk of
knocking all of these over, if I now make room for this
element, we have hopefully sorted the whole thing by having merged
together the two halves of the list. So in short-- thank you. I'm a little worried that's
just getting sarcastic now, but we now have merged two half lists. We haven't done the guts of the
algorithm yet-- sort the left half and sort the right half. But I claim that that
is how mechanically you merge two sorted halves. You keep looking at the
beginning of each list, and you just kind of
weave them together based on which one belongs
first based on its size. So if you agree that
that was a reasonable way to merge two lists together,
let's go ahead and focus lastly on what it means to
actually sort the left half and sort the right half of
a whole bunch of numbers. And for this, I'm going
to go ahead and order them in this seemingly random order. And I just have a little cheat
sheet above so that I don't mess up. And I'm going to start at
the very top this time. And hopefully, these will
not fall down at any point. But I'm just deliberately putting
them in this random order, 5274. And then we have 1630-- 1630. Hopefully this won't fall over. Here is now an array of
size 8 with eight integers. And I want to sort this. I could use selection sort and just
go back and forth and back and forth. I could use bubble sort and just
compare pairs, pairs, pairs. But those are going to be on
the order of big O of n squared. My hope is to do
fundamentally better here. So let's see if we can do better. All right, so let me
look now at my code. I'll keep it on the screen. How do I implement merge sort? Well, if there's only
one number, I quit. There's obviously not. There's eight numbers,
so that's not applicable. I'm going to go ahead and
sort the left half of numbers. All right, here's the left half-- 5274. Do I sort an array of size 4? Well, here's where the
recursion kicks in. How do you sort a list of size 4? Well, there's the pseudo
code on the board. I sort the left half
of the list of size 4. So here we go. I have a list of size 4. How do I sort it? I sort the left half. All right, now I have a list of size 2. How do I sort this? Well, sort the left half. So here we go. Here's a list of size 1. How do I sort this? I think it's done, right? That's quit, right? If only one number, I'm done. The 5 is sorted. All right, what was the next step? You have to now rewind in time. I just sorted the left half of
the left half of the left half. What do I now sort? The right half, which is 2. This is one element. So I'm done. So now at this point in the story,
I have sorted, sort of idiotically-- the 5 assorted, and the 2 is sorted. But what's the third and final step
of this phase of the algorithm? Merge the two together. So here's the left,
here's the right list. How do I merge these together? I compare the lists,
and I put the two there. I only have the [? 5 ?]
left, and I do that. So now we see some visible progress. But again, let's rewind. How did we get here? We started to sort the left half of
the left half of the left half, then the right half. And now where are we? We've just sorted the left
half of the left half. So what comes after sorting
the left half of anything? Right half. All right, here's the sort
of same nonsensical thing. Here's a list of size 2. Let's sort the left half. Done. Let's sort the right half. Done. What's the third step? Merge them together. So that's the 4, and that's the 7. What have I now done? In total, I've now sorted the
left half of the original thing. So what happens next? Wait a minute, wait a minute. I have not done that. What have I done? I have sorted the left
half of the left half, and I've sorted the right
half of the left half. What do I now need to do lastly? Merge those two lists together. So again, I put my
finger on the beginning of this list, the
beginning of this list. And if you want, I'll do the same
thing when I merged last time to be clear what I'm comparing. 2 and 4-- the 2 obviously comes first. What comes next? Well, the 4 comes next. What comes next? The 5 comes next and then
lastly, of course, the 7. Notice that the 2457 are now sorted. So the original left half is sorted. And I'll do the rest a little
faster because, my God, this feels like it takes forever. But I bet we're on to something here. What step remains next? I've just sorted the left
half of the original. Sort the right half of the original. How do I sort this? I sort the left half of the right half. How do I sort this? I sort the left half of the left half. Done. I sort the right half of the left half. Done. Now I merge the two together. The 1 comes first, the 6 comes next. Now I sort the right
half of the right half. What do I do? Sort the left half. Done. Sort the right half. Done. What do I do? Merge them together. So that's the third step of that phase. Now where are we in the stor-- oh
my God, where are we in the story? We have sorted the left
half of the right half and the right half of the right half. What comes next? Merge. So I'm going to compare,
and I'm going to move those down just to make clear
what I'm comparing, the beginning of both sublists. What comes first? Of course, the 0. What comes next? What comes next? The 1. What comes next? The 3. And then lastly comes the 6. All right, where are we in the story? We've now sorted the
left half of the original and the right half of the original. What step remains? Merge. All right, so I'm going
to make the same point. And this is actually
literally what we did earlier because I deliberately demoed those
original numbers in this order, 2 and a 0. This comes out first. What comes next? 2 and 1. The 1 comes out next. What comes next? The 2 comes next. What comes next? The 3 comes next. What comes next? The 4. What comes after that? The 5. What comes after that? The 6. And lastly-- this is when
we run out of memory-- the 7 over there is actually in place. OK. OK, so admittedly, a
little harder to explain, and honestly, it gets a
little trippy because it's so easy to forget about
where you are in the story because we're constantly
diving into the algorithm and then backing back out of it. But in code, we could
express this pretty correctly and, it turns out, pretty
efficiently because what I was doing, even though it's
longer when I do it verbally, I was touching these elements a
minimal amount of times, right? I wasn't going back and forth, back
and forth in front of the shelf again and again. I was deliberately only ever merging
the smallest elements in each list. So every time we merge, even
though I was doing it quickly, my fingers were only touching
each of the elements once. And how many times did we divide,
divide, divide in half the list? Well, we started with
all of the elements here, and there were eight of them. And then we moved them
1, 2, 3 positions. So the height of this visualization,
if you will, is actually log n, right? If I started with 8, turns
out if you do the arithmetic, this is log n height
because 2 to the 3 is 8. But for now, just trust
that this is a log n height. And how wide is the shelf? Well, it's of width n because
there's n elements any time they were on the shelf. So technically, I was kind of
cheating this algorithm because this is the first time I've needed shelves. With the human examples, we just had the
humans, and that's it, and only eight of them. Here, I was sort of using
more and more memory. In fact, I was using like
four times as much memory even though that was just
for visualization's sake. Merge sort actually requires that you
have some spare space, an empty array to move the elements into when
you're merging them together. But if I really wanted and if I
didn't have this shelf or this shelf, honestly, I could have just gone back
and forth between the two shelves. That would have been sufficient. So merge sort uses more memory
for this merging process, but the advantage of
using more memory is that the total running time, if you can
perhaps infer from that math, is what? The big O notation for
merge sort, it turns out, is actually going to be n times log n. And even if you're a little
rusty still on your logarithms, we saw in week zero and again
today that log n is smaller than n. That's a good thing. Binary search was log n. That's faster than linear
search, which was n. So n times log n is, of course,
smaller than n times n or n squared. So it's sort of lower on this little
cheat sheet that I've been drawing, which is to suggest that it's running
time is indeed better or faster. And in fact, if we consider
the best case running time, turns out it's not quite as good
as bubble sort with omega of n, where you can just sort of abort
if you realize, wait a minute, I've done no work. Merge sort, you actually have to do that
work to get to the finish line anyway. So it's actually in omega and
ultimately theta of n log n as well. So again, a trade off
there because if you happen to have a data set
that is very often sorted, honestly, you might want
to stick with bubble sort. But in the general case,
where the data is unsorted, n log n as sounding
better than n squared. Well, what does it
actually look or feel like? Give me a moment to just change
over to our visualization here. And we'll see with this example
what merge sort looks like depicted with now these vertical bars. So same algorithm, but instead
of my numbers on shelves, here is a random array
of numbers being sorted. And you can see it being
done half at a time. And you see sort of remnants
of the previous bars. Actually, that was unfair. Let me zoom out here. Let me zoom out so you can
actually see the height here. Let me go ahead and randomize
this again and run merge sort. There we go. Now you can see the second array and
where the values are going temporarily. And even though this one looks way
more cryptic visualization-wise, it does seem to be moving faster. And it seems to be merging halves
together, and boom, it's done. So let's actually see, in conclusion,
what these algorithms compare to and consider that moving forward
as we write more and more code, the goal is, again, not just to be
correct but to be well-designed. And one measure of design is
going to indeed be efficiency. So here we have, in final, a
visualization of three algorithms-- selection sort, bubble
sort, and merge sort-- from top to bottom. And let's see what these algorithms
might look or sound like here. Oh, if we can dim the
lights for dramatic effect-- selection's on top, bubble on
bottom, merge in the middle. [MUSIC PLAYING] [MUSIC PLAYING] [MUSIC PLAYING] DAVID J. MALAN: Well, this is CS50,
and already this is week four, and recall that last
week, week three, we began to explore the inside of
a computer's memory a bit more. We talked about arrays, which
were just chunks of memory back to back to back that really
lay things out left to right, top to bottom, and this is actually a
pretty common paradigm, even if you're new to programming,
and certainly new to C. You've seen this approach of just using
memory in some way to lay things out, like images, for instance. So for instance, here is a photo taken
of last week's front row, for instance, and this is an opportunity to
explore exactly what happens if we start to zoom in and zoom in and
zoom in, because it seems like most any TV show like CSI, or
whatever, or any movie that explores forensic information might
have the investigators zoom in on an image like this to see
what the glint in someone's eye is because that reveals the license
plate number of someone that just drove past. Something that's a little
over the top there, but there's an opportunity here to
speak to why that is so unrealistic. For instance, let's zoom on
this puppet here's eye and let's zoom in a little more to
see what might be reflected. Let's zoom in a little
more, and that's it. There's only finite
amount of information if you have an image
represented in this way. We're using pixels-- these dots on
the screen as rows and columns-- because if you're only using
a finite amount of memory then at the end of the day, you can only
store a finite amount of information. At least I don't really see in this
grid here any glint of a license plate or something like that that you
might otherwise see in Hollywood. So today we'll explore these
kinds of representations of how you might use memory
in new and interesting ways to represent now, very
familiar things, but also start to explore what some of the
limitations are of this representation. But consider after all that this doesn't
need to be even as high resolution, as many pixels as something
like this other image, you can imagine just doing something
silly with Post-It notes, like this. And if you think of an image as
just having rows and columns, these rows otherwise known
as scan lines-- something we'll explore in the coming week--
you could make this fun smiley face by just using two different
values, maybe a zero and a one. Or yellow and purple, or vice versa,
just to make something come to life. Now in practice, recall we talked
about storing not just a zero or one, but maybe an R, a G, and a B value--
like 24 bits, or three bytes in total-- but we'll come back to that. That would just be a
more involved image. But for fun, if today you want to tackle
something passively in the background, if you go to this URL here,
we've put together an opportunity to do a bit of pixel art. If you go to this URL here, that'll
redirect you to a Google Spreadsheet. If you have a laptop
with you today that'll look a little something like this, which
we've organized in rows and columns. So if you'd like to go ahead and use
Google Spreadsheet's colorization feature to color in those
individual squares if you'd like, see if you can't make something a little
creative and then email it to Carter and we'll exhibit some of the best or
favorites on the website thereafter. So let's transition then to something
a little more familiar-- images. And not all of you have
used, presumably, Photoshop, but you're probably generally familiar
with Photoshop as a program for editing and creating images
or photos or the like. And here is a screenshot
of p's color picker, via which you can
change what color you're going to draw with the paint
brush, or what color you're going to fill in with the paint bucket. It's representative of any
kind of graphical tool. And there's a lot of
information in here, but there's perhaps some
familiar terms now-- R, G, and B. In fact, right
now this is Photoshop's way of saying you're about to fill
in your background or foreground with the color black,
and that appears to be represented with an R, a G, and
a B value of zero, zero, zero. Or alternatively, using a
hash symbol and then 000000. And if some of you have
already made web pages before and you know a little
bit of HTML and CSS, you probably are familiar
with this kind of syntax-- a hash symbol and then six, or
sometimes three digits thereafter. And if we look at a few different
colors here, for instance, here might be the
representation of white. Now the R, the G, and the B values
went way up from 0 to 255, 255, 255. Or alternatively, it looks like
Photoshop, and in turn web browsers, could represent that same
color white with FFFFFF. And let's just do a few others. Here is red, and it turns out that
red is a whole lot of red, 255, but no green, no blue. Or, a.k.a. FF0000. So there's perhaps a
pattern here emerging. Here is green, zero, 255, zero, a.k.a. 00FF00, or lastly, here
blue, which is no red, no green but apparently a lot
of blue, 255 again, a.k.a. 0000FF. Now some of you, again, might
have seen this notation before, these zeros and these F's and all of
the numbers and letters in between, but this is another form of notation. And in fact, we'll explore
this today-- really is just a precondition for
talking about some other concepts. But the ideas, ultimately,
are really no different. What we're about to see is
a different base system-- not just binary, not just
decimal, but something we're about to call hexadecimal. But first, recall that with RGB
we previously did the following. Any RGB value-- red,
green, blue-- just combine some amount of red or green or blue. So here we have 72, 73, 33, which in the
context of an email or text, of course, said what-- a couple of weeks back? Just hi with an exclamation
point, but in the context of a Photoshop-like program, this
might instead be representing, collectively, this shade
of yellow, for instance, when you combine that much red
that much green that much blue. So here is the same idea. If you've got a lot of
red, no green, no blue, together that's going to give us red. If you've got no red, a
lot of green, no blue, that's going to give
us, of course, green. If you've got no red, no green,
a lot of blue, that of course, is going to give us blue. So there's a pattern emerging here
where apparently 00 is none, as always, and FF is apparently a lot. And it's maybe somehow equated with 255,
at least per that Photoshop screenshot. Meanwhile, if we combine one last
one, a lot of red, a lot of green, a lot of blue-- that's actually going to give us
a single white pixel like this. All right, so think back. Here was binary-- in the world of binary
you had just two digits, zero and one. Could have been anything else-- A or B, X or Y, but the world
standardized on these numerals zero and one. In our world's decimal system, of
course, you have zero through nine. As of today though, we're going to
start using hexadecimal sometimes in the context of images and also
files just because it's a convention and there's some conveniences to it. Where now, you're going
to be able to count up to F in a notation called hexadecimal. From zero through nine, then you keep
going to A to B to C to D to E to F, the idea being each of these,
even though it's weirdly a letter of the English alphabet,
it's still just a single symbol. It's not one zero for 10, or 1 1
for eleven-- all 16 of these values, these digits, so to speak, are
indeed still just single symbols, and that's a characteristic of just
using this other notational system. So how do we get from 00 and FF to
something like 0 and 255, respectively? Well, this hexadecimal system, a.k.a. Base 16, just does the math
from week zero and really, grade school, a little bit differently. For instance, if you have a
number that's got two digits, or hexadecimal digits as of today, the
columns are just a little different. Instead of powers of two or powers of
10, which we saw for binary and decimal respectively, it's powers of 16. So if we just do the math
out, that's the ones column, this is the 16s column, and so forth. Things get actually pretty big
pretty quickly in this system. But now let's just consider how we
would represent familiar numbers. If you've got two hexadecimal
digits for which these hashes are just placeholders, zero, zero
is going to mathematically equal the decimal number you
and I know, of course, as zero. Why? Same thing as week zero-- 16 times zero plus one times zero is
the number you and I know as zero. And we can count up from here. This, in hexadecimal,
would be how a computer represents the number we know as one. It would be zero one in this case. This would be two, three, four,
five, six, seven, eight, nine-- in decimal, we're about to go to 10. But in hexadecimal, to be
clear, what comes next? So, apparently A, so 0A, 0B, which
is now 10, or 11, or 12, 13, 14, 15. So using hexadecimal is
just an interesting way of using single symbols
now, zero through F, to count from zero through 15. And we'll see why it's 15 in a
moment, but as soon as we get to F, anyone want to conjecture how
in hexadecimal, a.k.a. hex, do we now count up one position higher? What comes after 0F in hexadecimal? So, one zero-- it's the
same kind of thing-- once you're at the highest
digit possible, F-- or in our decimal world
that would have been nine-- you add one more, nine wraps
around to zero, or in this case, F wraps around to zero. You carry the one and voila--
now we're representing the number you and I know as 16. And we could keep going
forever, literally. This could be 17, 18,
19, 20, and decimal-- but let's just wave our
hands at it and count as high as we can-- dot,
dot, dot-- the highest we could count in hexadecimal
with two digits, just logically, would be what, in hexadecimal? Something, something. FF, I heard. So yes, that's the biggest digit
possible, so FF is what we have. So how high can you count in hexadecimal
if you've got just two of these digits? Well, it's the same math as always. 16 times F, a.k.a. 15, so that's 16 times 15 plus
one times F, or one times 15-- that gives us 240 plus 15 in decimal,
the result of which, of course, now is 255. So this hexadecimal system-- you may
have seen in the world of web pages, and if you haven't we'll get to
that in this class in a few weeks, or we just saw in the
context of Photoshop-- just has this shorthand notation of counting
as high as 255 but just calling it FF. Now it's marginal, but that's like
50% savings of how many digits you need in order to count as high
as 255 because in decimal, of course, 255 is three digits. In hexadecimal you can count
as high using just two, and that difference is going to get
magnified the bigger our numbers get. Let me stipulate for now, you're
going to get more and more savings in terms of just how many symbols
you need on the screen to represent bigger and bigger numbers than that. All right, let me pause here just to
see if there's any questions thus far on what we've called hexadecimal, which
again, just gives us zero through nine as well as A through F.
Any questions or confusion? And if it feels like we're
lingering a bit much on arithmetic, we're not really going to see other
notations besides this moving forward. These are the go-to three in a
programmer's world, typically. But there are some others. Yeah. AUDIENCE: Does the hexadecimal
symbol take more storage than the decimal system? DAVID J. MALAN: Good question. Does hexadecimal require more storage
or less storage than the decimal system? Theoretically no, because this is
just a way of representing information and we'll see in a concrete
example in a moment. But inside of the computer, at the end
of the day, you're still storing bits. And using hexadecimal is not
using more or fewer bits, think of this as how
you might write it down on a piece of paper, just how
many digits you're going to write or on a computer screen, how many
digits you're going to see at once, but it doesn't change how the
computer is representing information because all they're representing at
the end of the day is zeros and ones. So in fact, let's go there. If this-- a moment ago
FF I claimed was 255-- let's just rewind to week
zero and if we wanted to count to 255 in binary, that's
as high as you can count, recall, with eight bits. And there's only a few
of these numbers that are useful to memorize, like 255 is as
high as you can count with eight bits if you start at zero, because two to the
eighth is 256, but if you start at zero it's zero through 255. So in binary, recall if you have
eight bits, all of which were ones, and I won't do out the
math pedantically here, but if I do do this plus
this plus this, dot, dot, dot-- that's also going to give me 255. So this is what's interesting
here about hexadecimal. It turns out that an upside of
storing values in hexadecimal is that we're going to
see the first F represents the left half of all these bits,
and the second F in this case represents the rightmost
four of these bits. So it turns out hexadecimal
is very useful when you want to treat data in units of four. It's not quite eight, but units
of four, and that's not bad. Which is why-- if you use two
digits like I have thus far, 00 or FF or anything in between-- that's actually a convenient way of
representing eight bits in total. One hex digit for the first four
bits, one hex digit for the second. And again, there's nothing new
intellectually here per se, it's just a different way of
representing the same story as before-- zeros and ones. So in what context do we see this? Well, we talked about
memory last week, and we're going to talk more about it this week. If this is my computer's
RAM-- random access memory-- you can again think of each byte as
having a number associated with it-- its address or location. This might be zero, this might
be 2 billion, and so in the past I've described these as just
this, using decimal numbers. Here's byte zero, one, two, three,
four, five, six, seven, 15, 16 would be here, and so forth. But it turns out in the world of memory,
and thus today, programming, people tend to count memory
bytes using hexadecimal. Partly just by convention,
but also partly because it's a little more
succinct and again, each digit represents four bits, typically. So what comes after F here? Well, if I think about
the computer's memory, I normally might do
after F, which is 15, 16. But instead, one zero, one
one, one two, one three-- this is not 10, 11, 12, 13, because I claim
I'm in the context of hexadecimal now. As per the previous
slide, we already started going into A's through
F's, so you immediately see here a possible problem. Why is this now worrisome,
if all of a sudden you're seeing seemingly familiar
numbers like 10, 11, 12, 13? We didn't really stumble
across this problem when it was all zeros and ones before. Yeah. AUDIENCE: Try to do math [INAUDIBLE]. DAVID J. MALAN: Yeah, so if you're
writing some code in C that's doing some math, you
might accidentally-- or the computer might accidentally
confuse hexadecimal with decimal if they look in some context the same. Any number on the board
that doesn't have a letter is ambiguously hexadecimal
or decimal at this point, and so how might we resolve this? Well, it turns out that what
computers typically do is this. By convention, any time you
see 0x and then a number, that's a human convention of saying-- signaling to the reader that this
is in fact a hexadecimal number. So if it's 0x10, that
is not the number 10, that is the hexadecimal number one
zero, which recall we said earlier, is how you count up to 16. And again, these are not the
kinds of things to memorize, it's really just the system for
how you think about these things. So henceforth today, we're going
to start seeing hexadecimal in a bunch of contexts. When you write code, you might even
write code using some hexadecimal but again, it's just a different
way of representing numbers and humans have different
conventions for different contexts. All right, so with that said, any
questions now on this building block? But here on out, we'll start
using it in some actual code. Any questions? Nothing so far? All right. So, let's go ahead and consider
maybe a familiar example. Something where involving code,
where I initialize a variable like n to a value like 50, in this case. And then let's start to tinker
around with what's going on inside of the computer's memory. In a moment I'm going to load
up VS Code on my computer and I'm going to go ahead and whip
up a program that very simply assigns a value like the number
50 to a variable called n, but today, keep in mind that
that variable n and that value 50 is going to be stored somewhere
in my computer's memory, and it turns out today we'll introduce
a bit more syntax so you can actually see where things are being stored. So let me click over to VS Code here. I'm going to create a
program called address.c just to explore computer's
addresses today, and I'm going to do an include stdio.h,
int main(void), as usual. No command line arguments for now. I'm going to declare that
variable n equals 50, and then I'm just going to
go ahead and print it out. So nothing very interesting but I'll
use %i backslash n and then comma n to print out that value. Nothing here should be very
interesting to compile or run, but I'll do it just to make
sure I didn't make any mistakes. Looks like as expected, it simply
prints out the number 50, like this. But let's consider then, what this
code is doing underneath the hood when it's actually run on your machine. So here we have that grid of memory. That variable n is an int,
and if you think back, how many bytes typically
do we use for an int? Yeah. Four, so four bytes, or 32 bits. So if each of these squares represents
one byte, then my computer, somewhere in my memory, or RAM, is
using four of these squares. Maybe it ends up over here just
because there's other stuff being used elsewhere, for instance. Though I don't really
know, and frankly, I don't really care where it ends
up, just that it ends up somewhere. So the variable-- the value 50 is
stored here in a variable called n. Even though I've written it as
decimal, just like in my code-- let me again remind that this is 32
zeros and ones representing that 50-- it's just going to be very tedious if
we start writing everything in binary, so I'll use the more comfortable
human decimal system. So that's what's going on
inside of the computer's memory. So what if I actually wanted to
start tinkering with its location, or maybe just knowing its location? Well, this variable n
indeed has a name, n-- that's a label of sorts for it--
but at the end of the day that 50 is technically at a specific address,
and I'm going to make one up-- 0x123, and it's 123
because I really don't care what it is, I just want an
address for the sake of discussion. So way over here off screen might be
byte zero, way down here is byte 0x123. It's in hexadecimal
notation just by convention. So how can I actually see where
my variables are ending up in memory if I'm curious to do so? Well, let me go back to my
code here and let me actually change this just a little bit. Let me go ahead and introduce,
for instance, another symbol here and another topic
altogether, namely pointers. So a pointer is a variable that
stores the address of some value-- the location of some value
or more specifically, the specific byte in which
that value is stored. So again, if you think of your memory
as being a whole bunch of bytes-- zero at top left, 2 billion
or whatever at bottom right, depending on how much RAM you have-- each of those things has
a location, or an address. A pointer is just a variable
storing one such address. So it turns out that in the world of
C, there's a couple of new symbols we can use if we want to see what
it is we're talking about here, and those two operators,
as of today, are these. You can use the ampersand
operator in C in a couple of ways. We already saw it very briefly
to do ampersand ampersand-- it's kind of and two
Boolean expressions together in the context of a conditional. This is different. A single ampersand is
the address of operator. So literally, in your code, if you've
got a variable like n or anything else and you write &n, C is going to figure
out for you what is the address of that variable n in the computer's memory. And it's going to give you a number,
otherwise known as the address of that. If you want to store that
address in a variable even though yes, it's a number like
0x123, you have to tell C in advance that you want to store not an int
per se, but the address of an int. And the syntax for doing that--
somewhat nonobviously-- is to use an asterisk here,
a star operator, and you say this when creating the variable. If you want p to be a pointer, that
is the address of some other variable, you do int star p. And the star just tells the computer,
this is not an integer per se, this is the address of
something that yes, is an int, but we're just being more precise. So on the right hand side you
have the address of operator. As always with the equal sign,
you copy from right to left. Because &n is by definition the address
of something you have to store it in a pointer, and the way to declare a
pointer is to specify the type of value whose address you're storing, and then
use the star to indicate that this is indeed a pointer and not
just a regular old int. So let's see this in practice. Let me go back to my own
source code here and let me make just a couple of tweaks. I'm going to leave n
alone here but I'm going to go ahead and initially just do this. Let me say int star
p equals ampersand n, and then down here, I'm going to
print out not n this time, but p-- the variable p. And then even though yes, it's just
a number and therefore I could use %i for integers, there's actually a special
format code in printf for printing pointers or addresses, and that's %p. So now let's go ahead and
recompile this, make address-- so far so good-- ./address,
Enter, and a little weirdly, but perhaps understandably now,
the address in my computer's memory at which the variable n happened to
be stored was not quite as simple as 0x123. This computer has a lot
more memory so technically, it was stored at 0x7FFCB4578E5C. Now that has no special
significance to me. It could have ended up
somewhere else altogether, but this is just where, in my
computer-- or technically the cloud server to which I'm connected
using VS Code here-- that just happens to
be where n ended up. And strictly speaking, I don't even
need to introduce this variable. I could get rid of p
and I could just say print not just n, but the address
of n and achieve the same thing. You don't need to temporarily
store it in a variable. Let me just do make
address again, ./address, and now I see this address here. And notice if I keep running the
program, it's actually moving around. There's other stuff presumably
going on inside of the computer. Maybe it's actually randomizing it so
it's not always at the same location. That can actually be a security
feature underneath the hood, but this happens to be at that moment
in time where that value is in memory, quite like our picture a moment ago. All right, so let me pause
here to see if there's now any questions on what we just did. Yeah? AUDIENCE: Is there any
way to control where you are storing something in memory? Does it even matter if
it works, or does it just matter that you could go in
and locate where something is? DAVID J. MALAN: Really good question. Is there any way to control
where something is in memory? Short answer is yes, and this is
both the power in the danger of C, and we're going to do this today
and make a few deliberate mistakes, because with this power of going to or
getting the address of any variable, I could just arbitrarily
right now write code that stores a value at byte 2 billion,
or zero, or anything in between. But that also means potentially,
I could start creepily looking around at all of the computer's memory,
even at things that I didn't put there. Maybe other programs, maybe
other parts of programs and indeed, this is a
potential security threat, if suddenly you're able
to just look anywhere you want in the computer's memory. Now, I'm overselling it a little bit
because nowadays, in this decade, there are some defenses
in place in compilers and in our operating systems that
do hedge against this a little bit. But this is still a very
frequent source of problems, and later today we'll
talk briefly about things called stack overflow,
which is not just a website, it is a problem that you can encounter. Heap overflow, and more
generally buffer overflows-- there's just so many things that can
go wrong using this language called C, and if any of you have encountered
a segmentation fault yet? I think we saw a few
hands for that already. You touched memory
that you shouldn't have and odds are you did it most recently
by going too far in an array. Going to the left, or negative in an
array, or somehow looking at memory you shouldn't have. And we'll explain today why it
is you were able to do that. Other questions on
these primitives so far? Yeah, from Carter? AUDIENCE: [INAUDIBLE] pointer star p,
but then we used p later in the code. Is it called star p or p? DAVID J. MALAN: Good question. Earlier, we used star p. Let me rewind in time to the
previous version of this code, where I actually had
a variable called p. Just like with variable
declarations in the past, once you've declared a variable to
be an int, a char, a bool, or an int star, a.k.a. a pointer,
you don't thereafter keep using the word
int or now, the star. Once you've declared it, that's it. You only refer to it by name. And so it's very
deliberate what I did here, saying that the type here is int star-- that is a pointer to an int-- but here I just said the name
of the variable, as always. I didn't repeat int, and
I also didn't repeat star. But at the risk of bending
one's minds a little bit there is unfortunately one other use for the
star operator, and that's as follows. If you want to print out not
the address of something, but what is at a specific
address, you can actually do this. If I want to print out the integer
via %i, that is at that address, I can actually use the star here, which
technically contradicts what I just said but it has a different
function here-- a different purpose. So let me go ahead and do
this in two different ways. I'm going to leave this
line of code as is, but I'm going to add
another line of code now that prints out what apparently
will be an integer, in a moment. So %i backslash n, and I could
see-- and let me just do n for now. So there's really nothing
special happening now, I'm just adding a sort of
mindless printing of n. So make address, ./address-- there's the current address of
n and there's the value of n. But what's kind of
cool about C here, too, is if you know that a value is
at a specific address like p, there's one other use for this
star operator, the asterisk. You can use it as the
so-called dereference operator, which means go to that address. And so here what we actually have
is an example of a pointer p, which is an address like
0x123 or 0x7FF and so forth. But if you say star p now, you're
not redeclaring the variable because I didn't mention int-- you're going to that address in p. So let me recompile this now. Make address, ./address,
and just to be clear-- what should I see? I'm first going to see the
pointer itself, 0x something. What's the second line of output
I should presumably see now? Shout a little louder. So I'm hearing 50, and that's true
because if you figure out the address of n and print it in line seven, but
then go to the address of n, a.k.a. p, that's indeed going to just
show you the number n-- the value of n again. All right, any questions now on
this syntax-- and I will concede, I think this is confusing--
the fact that we use the star for
multiplication, the fact that we use the star
to declare a pointer, but then we use a star in a third
way to dereference the pointer and go to the pointer. It's just too confusing, honestly,
but with practice comes comfort. Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Good question. Do you-- when you are using
the ampersand operator to get the address of
something, the onus is on you at the moment to know
what you are getting the address of. Is it a string? Is it a char? Is it a bool? Is it an int? I wrote this code so I
know in line six that I'm trying to get the address
of what is an integer. AUDIENCE: What about line eight? DAVID J. MALAN: In line
eight you don't have to worry about that-- good question. Notice in line eight, I didn't tell
the computer, other than the %i, what kind of address I'm going
to, but I did already in line six. I told the compiler
that p, now and forever, is going to be the address of an int. That's enough information in advance so
that printf, or really the language C, still knows on line eight
that p is a pointer to an int, and that way it will print out
all four bytes at that address, not just part of it, and not
more than those four bytes. Good question. Yeah, next to you. AUDIENCE: Do pointers have pointers? DAVID J. MALAN: Do
pointers have pointers? Yes. We won't do this today by
having pointers to pointers, but yes, you can use star
star, and then things get-- I'm sorry. We won't do that today and
we won't do that often. In fact Python, another language,
is just a couple of weeks away, so hang in there. Almost there. A question back here? Was there? That was-- more verbal
feedback like that is helpful as we forge into
the more complicated stuff. Other questions? Yeah. AUDIENCE: What's the
point of [INAUDIBLE]?? DAVID J. MALAN: What's the
point of printing the address? AUDIENCE: Like, using the
address to [INAUDIBLE].. DAVID J. MALAN: Sure. What's the point of doing this? If you don't mind, let me--
let's get there in a moment. This is not the common use case,
just printing out the address-- who really cares? At the moment we care only
for the sake of discussion. We're soon going to start
using these addresses. So hang in there just a
little bit for that one, too, but it will solve some
problems for us before long. So let's actually just now depict what
was going on inside of the computer's memory just a moment ago. So if I toggle back here, let
me redraw my computer's memory, now let me plop into the memory n,
which is storing in this program the number 50. Where is p in my computer's memory? Specifically, I don't know and
apparently it moves around each time I run the program so for
the sake of discussion, let's just propose that if 50 ended
up at address 0x123, I don't know-- p ends up over here, at address-- whoops-- at whatever
address this is here. But notice a couple of curiosities now. If p is a pointer, it's
the address of something. So the value in p should be an address,
and I've indeed written it as such-- 0x123, and technically there's not
an x there, there's not a zero there, there's not even a 123
there per se-- there's a pattern of bits that
represents the address 0x123. But again, that's weak zero--
don't care about binary day-to-day. So if this is p, and this I claimed
was n, why is p so much bigger? Can someone conjecture here? Because it turns out whether n
is an int or a char or a bool, which are different
types-- heck, even a long-- it turns out that p is always going
to take up eight squares on the board, but why might that be? What might explain that? Yeah, thoughts? AUDIENCE: Perhaps it
allocates eight bytes, but it doesn't know the type
of the data [INAUDIBLE].. DAVID J. MALAN: OK, fair. Maybe it's allocating eight bytes
because it doesn't know the type. Turns out that's OK because
an address is an address. It's really up to the programmer to
use it as a string or a char or a bool. Other thoughts? AUDIENCE: Maybe the first four for
the actual number and the last four is some null that [INAUDIBLE]
where the pointer ends. DAVID J. MALAN: OK, possibly. It could be that pointers have
some complexity like a backslash n or something curious like that,
like we talked about for strings. Turns out that's not the case. It turns out that pointers
nowadays typically are, but not always are eight bytes, a.k.a. 64 bits, because you and
I-- our Macs, our PCs, heck-- even our phones have a lot
more memory than they did years ago. Back in the day, a
pointer might have only been 32 bits, or even only
eight bits way back in the day. It's considered 32 bits, because
that was the norm for some time. How high can you count,
roughly, if you've got 32 bits? What's the number we keep rattling off? 32 bits is roughly 2 to
the 32, so it's 4 billion, and I keep saying it's 2 billion if you
do negative, but in the world of memory there's a reason I keep saying
2 billion bytes, two gigabytes, because for a very long time that
was the maximum amount of memory a computer could have. Why? Because the pointers that
the computers were using were only, for instance, 32 bits. And with 32 bits, depending on whether
you allow for negatives or not, you can count as high as 2 billion,
roughly, or maybe 4 billion but you know what-- your
Mac, your PC, your phone could not have had five gigabytes of
memory, or 5 billion bytes of memory. You certainly couldn't have had
what computers nowadays come with, which might be 8 gigabytes of memory-- 16 gigabytes of memory. Why? Because with 4 bytes, or 32
bits, you literally, physically, can't count that high, which means if I
drew a picture of all of the memory we would run out of numbers to describe
them, which means most of my memory would just be unusable. So pointers nowadays are
64 bits, or eight bytes. That's really big. I can't even pronounce
how big that number is, but it's plenty for the
next many years, and so we've drawn it that
way on the board here. Now let's just abstract this away. Let's get rid of all
the other bytes that are storing something or
nothing else, and let's now start to abstract away this
complexity because the reality is, to your question earlier-- what is this useful for, or
what do we-- do we actually care about these addresses? Generally, no. We're doing this so that
you see there's no magic. We're just moving things around
and poking around in memory. But what a person would typically
do when talking about pointers would literally be to
just point at something. I really don't care
what address n is at, so it suffices when general, when
drawing pictures on a whiteboard, having a discussion
with another programmer, you just draw an arrow from the
pointer to the value in question, because neither you nor I probably care
about the specifics of 0x whatever. There's your pointer-- it's literally
an arrow, and we can see this. So it turns out that these
pointers, these addresses, are not that dissimilar to what
we've done for hundreds of years in the form of a postal system. For instance, here is a post office-- here, no-- here is a
mailbox, and suppose that this is a mailbox labeled p. It's a pointer, and suppose
there's another mailbox way over there, which is just
another bite of my computer's memory. What are we really talking about? Well, you store in a computer's
memory values like the number 50, or the word "hi" inside of your
computer's memory at some location. But today we can also use
those same memory locations to store the address of things. For instance, if I
open this up here and I see OK, the value inside of this
mailbox is not a number like 50, it's actually an address-- 0x123-- that's like a
pointer, a breadcrumb leading from one location in memory to another. And in fact, would someone who's
seated roughly over there-- do you mind getting the mail over there? Any volunteers over in this section? Just need you to get to
the mailbox before I do. Who's being volunteered? Oh yes, please. Whoever is gesturing most
wildly, come on down. Sure. What's your name? AUDIENCE: Anfoo. DAVID J. MALAN: Say again? AUDIENCE: Anfoo. DAVID J. MALAN: Anfoo? OK, come on up to the edge of the
stage there and just to be clear-- if this is p, that is
apparently n, but to make clear what we're talking about when
we're storing 0x whatever values-- like 0x123, that's
essentially equivalent to my maybe pulling out something
like this and just abstractly pointing
to your mailbox there, or if you prefer,
pointing to the mailbox-- OK, all right. Thank you. All right. This is akin to me
pointing at your mailbox, and if you want to go
ahead and open your mailbox and reveal to the crowd what's
inside your mailbox labeled n. All right. Thank you. We have a little CS50 stress
ball for your trouble. Thank you for coming up. So that's just to put a visual on
what it is we're talking about, because it can get very abstract,
very cryptic quickly when we're talking about addresses and memory and
drawing it like these little squares. But if you think about just walking
into a post office or an apartment complex that's got a lot of
mailboxes, those mailboxes essentially are a big
chunk of memory and each of those mailboxes has an address-- this is apartment one, two,
three-- apartment 2 billion. And inside of those
mailboxes can go anything that can be represented as information. It could be a number
like n, or 50, or if you prefer it could be a
number that represents the address of another mailbox. And this is akin, really, if
you've ever had an apartment or you and your parents have moved,
to having a forwarding address. It's like having the
Post Office in the US put some kind of piece of paper
in your old mailbox saying, actually forward it
to that other mailbox. That really is all a pointer is doing. At the end of the day,
it's just a number but it's a number being
used in a different way and it's the syntax
that we've introduced, not just int but int star,
that tells the computer how to treat that number in
this slightly different way. Are there any questions then, on this? Yeah, in back. AUDIENCE: If you had a variable,
like int c, [INAUDIBLE].. DAVID J. MALAN: If I did int c and-- say the code again? Once more? Equal to n, so let me
actually type it out. If I give myself another
line of code, tell me one last time what to type.
int is equal to n, like this? So this is OK, and I can't draw it
quite quickly enough on the board here, but this would be like creating another
four bytes somewhere in memory, maybe down here, that stores
an identical copy of 50 because the assignment operator
from right to left copies one value to another. So that would just add one
more rectangle of size four to this particular picture. If I'm answering your
question as intended. OK, so that is week one style use of
assignment operators before pointers. I could, though, start copying
pointers but again, we'll come back to some of that complexity. Any other questions here? AUDIENCE: That was a great question. Does the pointer point-- does the same pointer point
to the new replica as well? DAVID J. MALAN: Ah, good question. Short answer, no. And to repeat for the camera, if I
create a second variable like this, int c equals n, and I claim without
actually drawing it on the board that this gives me another rectangle,
the value of which is also 50, p does not get touched. And this is what's important
and really characteristic of C. Nothing happens
automatically for you. p is not going to be updated
unless you update p in some way, so creating a third
variable called c-- even if you're copying its
value from right to left, that has no effect on
anything else in the program. A good question. So what have we seen that's perhaps
now a little more explainable? Well, recall that we talked quite a
bit last week about strings, and just to recap in layperson's terms, what is
this string as you now understand it? So say-- well, let me
take a specific hand here. What's a string? How about over here. AUDIENCE: An array of characters. DAVID J. MALAN: OK, sure. Both of you are right. An array of characters. An array of characters, and we-- I claimed-- or revealed last week
that string is not technically a feature built into C. It's
not an official data type but every programmer
in most any language refers to sequences of
characters-- words, letters, paragraphs-- as strings. So the vernacular exists but
the data type doesn't typically exist per se in C. So what
we're about to do, if you will, for dramatic effect, is take
off some training wheels today. The CS50 library implemented in the
form of the header file cs50.h-- we claim has had a
bunch of things in it. Prototypes for GetString,
prototypes for GetInt, and all of those other
functions, but it turns out it also is what defines the
word "string" in such a way that you all can use it
these past several weeks. So let's take a look at an
example of a string in use. Here, for instance,
is a tiny bit of code that uses the word "string,"
creating a variable called s and then storing quote
unquote, hi, exclamation point. Let's consider what this looks
like now in the computer's memory. I don't care about all the other
bytes, let's just focus on these, and this per last week is
how "hi" might be stored. h-i exclamation point and then
one more, as someone already observed, that sentinel value--
that null character which just means eight zero bits to
demarcate the end of that string just in case there's
something to the right of it, the computer can now distinguish
one string from another. So last week we introduced
this new syntax. Well, if strings are
just arrays of characters you can then very cleverly use
that square bracket notation and go to location zero or one
or two, which are like addresses, but they're relative to the string. This could be at 0x123 or 0x456,
but with this bracket notation zero is always the beginning
of the string, one is the next, two is the next, and so forth. So that was our array syntax
for indexing into an array. But technically speaking, we
can go a little deeper today-- technically speaking, if hi is
starting at the address 0x123 then it stands to reason that i is at
0x124, exclamation point's at 0x125, and the null is that 0x126. Now, I don't care about 123 per se,
but even though this is hexadecimal, this is correct math. Even in hex, if you just add
one when you start at 0x123, the next number is four,
five, six at the end. I don't have to worry
about A's, B's, and C's because I'm not counting
that high in this example. So if that's the case, and
my computer is actually laying out the word hi in memory
like that, well, what exactly is s? What exactly is s if,
at the end of the day, H-I exclamation point null is storing--
or is or stored at these addresses? Where is s? Now that I've taken off
those training wheels and showed you where H-I
exclamation point null actually are, what happened to s? Well s, as always, is
actually a variable. Even in the code I
proposed a moment ago, s is apparently a data type
that yes, doesn't come with C, but CS50's library makes it exist. s is a variable of type string,
so where is s in this picture? Well, it turns out that
s might be up here. Again, I'm just drawing it anywhere
for the sake of discussion, but s is a variable
per that line of code. What s is storing,
apparently, I claim, is 0x123. I actually don't really care about these
addresses, so let's abstract that away. s is apparently, as of now, today,
one week later, just a pointer to a character. Specifically, the first character in s. And this is the last
piece of the puzzle. Last week we had this clever way
of demarcating the end of a string. Well, it turns out that strings are
represented in the computer's memory as a variable that is a
pointer, inside of which is the address of the first
character in the string. So if s points at the
first character and you can trust that backslash zero
is at the end of the string, that's literally all you need to figure
out where a string begins and ends. So what do I mean by this? Well, let's be a little more concrete. In terms of this picture, if I've
started with this line of code here, it turns out all this time since
week 1, that the word string has just semi-secretly been an
alias for char star. I know, so char star. So why does this make sense? It's a little weird still,
but if in our previous example we were able to store the address of
an integer by declaring a variable called p, as int star p-- well, if as of now strings
are just the address of the first character in a string, then
probably a string is just a char star because that means s is the
address of a character, the very first character in the string. Now, the string might have three letters
like it did, or four, or even a hundred if it's a long paragraph,
but that's fine because you can trust
that there's going to be that null character at the very end. So this is a general purpose
way of representing strings using this new mechanism in C. So in fact, let me go ahead
here and introduce maybe a couple of manipulations of this. Let me go back to my code here, and
let's get rid of this integer stuff, and let's instead now
do, for instance, this. Let me add in the CS50 library,
so we'll include CS50.H for now. I'm going to go ahead
and inside of main, give myself a string s
equals hi exclamation point. I don't type the backslash zero. C does that for me automatically by
using my double quotes like this. Now let me just go ahead and print it. So this again is week 1 style stuff
where I'm just printing a string. No pointers yet. So let me do make address, Enter,
./address, and hopefully I see hi, so nothing new there. But let's start to peel back
some of these layers here. Let me first of all, get rid of
the CS50 library for a moment and let me change string to char star. And it's a little bit weird
but yes, the convention is to say char, a space, then the
star, and then immediately thereafter the name of the variable. Strictly speaking though, you might
see textbooks or websites that do it like this or like
this, but the canonical way is typically to do it like that. So now no more CS50 library, no
more training wheels, if you will. I'm just treating strings
for what they really are. Let me go ahead and do
make address, Enter-- so far so good-- ./address-- and that, too, still works. So %s is a thing that comes with printf
because the word string is programmer terminology but strictly speaking
C doesn't have a string data type. It's always been char star,
so what this means now is I can start to have some fun
with these basic ideas, even though this is not purposeful
other than for the sake of discussion. But if s is this-- let me go back
and give myself the CS50 library. Let's put those training wheels
back on for just a moment so that I can do one
manipulation at a time. Here's my string s, as before. Well, let me go ahead and
declare a char called c, and let me store the first character
in the string there, which is s bracket zero, and
that should give me h. And then just for kicks, let
me go ahead and do char star-- whoops-- let me go ahead and do
char star p equals ampersand c, and see what this
actually prints for me. Let me go ahead and
print out what p is here. So we're just playing around. So make address-- so
far so good-- ./address. All right, so what have I just done? I've just created a char c and
stored in it the letter H, which is the same thing as s bracket I, then
I'm saying, what's the address of c, and that's apparently 0x7FF whatever. So that's the address. But I technically
didn't have to do that. Let me go ahead and do two things now. Instead of just printing p, let me go
ahead and print out maybe s itself. Let me go ahead and do
make address, Enter-- so far so good-- ./address and-- damn it, what did I do wrong. Oh shoot, I didn't want to do that. Oh, I really made a mess of this. What did I want to do here? That was supposed to be impressive
but it was the opposite. So let me turn it around. So if I intended to do this,
why are lines nine and 10 printing different values? Didn't really intend to go here,
but let me try to save this. Why are we seeing different addresses,
namely this address 402004 for s, and then 0x7FF for p? Any thoughts? Yeah, over here. AUDIENCE: [INAUDIBLE]
is the character c is its own sort of location
of the [INAUDIBLE],, and it's taking off just
the values [INAUDIBLE].. DAVID J. MALAN: Correct. So if I really wanted to
weasel my way out of this, this is a great answer to the
previous question which was about, what if I introduce another variable,
c, that's a copy of the value, and not in this case an
int, but an actual char. Here, I've made c be a copy of the
character that's at the beginning of s, but that's indeed a copy. So if I were to draw
it on the screen that would give me a different
rectangle in which this copy of h would actually be stored. So I didn't intend to
do this, but what you're seeing is yes, the address of s-- and apparently that's at a
pretty low address by default here-- then you're
seeing the address of c. But even though each
of them is h, I claim one is at a different address in memory. And this has always been happening. Any time you created one variable
or another it was ending up here, or here, or here, or
somewhere else in memory. Now for the first time all we're
doing is actually just poking around the computer's memory to
see what is actually there. So let me actually back
this up a little bit and do what I intended to do here,
which was something like this. So if string s equals quote
unquote, hi, let's go ahead and give myself a pointer, called
p, to the first character in s. All right, so now let me go ahead and
print out the value of this pointer, %p, printing out p. So we're just going to
do one thing at a time. So make address, Enter, ./address. There, at the moment, is the
address of the first character in s. What I meant to do now, was this. If I want to print out
two things this time, let me print out not only what p is,
but also what s itself originally is. Because if I claim that everyone from
last week should be comfortable with s bracket zero just representing
the first character in s by definition of strings
being arrays of characters. Then s, as of today, is itself
the address of a character, the first one in s. So if I now do make
address, and do ./address, this time I see the same exact things. Thank you. This is really the lamest sort
of thing to be applauding over, but what we're demonstrating here is
that s is by definition the address of the first character in c. So if we borrow some of our
mental model from last week-- well, if s bracket zero is the first
character in c, doing the ampersand on that expression should be the same as s. Now this isn't to say that we
would jump through these hoops all the time with this much syntax,
but this is just to do proof by example that s is in fact, as I claimed a moment
ago, just the address of a character. Not even multiple characters, it's
the address of a single character, but the key thing is it's the address
of the first character in the string, and per last week we
trust that C is going to look for that null
character at the very end just to make sure it knows where
the string actually ends. All right, a question came up over here. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Correct. To summarize, on line
eight, when I am using %p-- that just means print a pointer
value, so 0x something-- I'm passing it s. Previously, when we used %s, printf knew
to print not just the first character of s, but h, i, exclamation point, and
then stop when it hits the backslash zero. p is different. %p tells the
computer to go to that address-- sorry, tells the computer to
print that address on the screen. So this is where %s all
this time has been powerful. The reason printf worked
in week 1 and 2 and 3 was because printf was designed
by some human years ago to go to the address that's
being passed in-- for instance, s-- and print out
character after character after character until it sees the
null character backslash zero, and then stop printing it. So that's-- you're getting a lot
of functionality for free from %s. Today we're using
something much simpler, %p, which just literally prints what s is. And the reason we
don't do this in week 1 is just because this
is like way too much to be interesting when
all you want to print out is hi or hello, world, or the like. But now what we're
really doing is revealing what's been going on this whole time. And let me make one other example here. Let me go ahead and get
rid of this variable here and let me just print out a
few things to make the same point. I'm going to print out not just s
like I did here, but let's go ahead and print out every-- the address of every character in s. So let's get the first letter
in s and get its address, and I'm going to do copy
paste for time's sake, but not something I would do frequently. So let me print out the address of the
first character, the second character, the third, and actually
even the fourth, which is the backslash zero, by doing this. So when I compiled this program--
make address, ./address-- I should see two
identical values and then additional values that
are one byte away. In my diagram a moment ago, my addresses
were arbitrarily 0x123, 124, 125, 126. Now it starts at, by chance,
0x402004, which is s. 0x402004 is the same thing
as s because I'm just saying go to the first character
and then get its address. Those are one in the same now. And then after that
is 0x402005, 006, 007, because that is just like the diagram. Go to the i, to the exclamation
point, and to the null character. So all I'm doing now is using my
newfound understanding of what ampersand does and what the star
does, is I'm just playing around. I'm poking around in
the computer's memory. Just to demonstrate there's no magic. It's all there very deliberately
because I or printf or someone else put it there. Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Really good observation. So it's indeed the case
that hi, unlike 50, is ending up at a very low address,
not the 0x7FF wherever it was. That's actually because,
long story short, strings are often stored in a different
part of the computer's memory-- more on that later
today-- for efficiency. There's actually only going to be one
copy of the word "hi" and exclamation point, and the computer is going to
tuck it at the beginning of my memory, but other values like
ints and floats and the like-- they end up lower
in memory by convention. But a good observation, because
that is consistent here. All right, so a couple final details
then, on what's been going on here. Let me go ahead and claim that
we implemented char star-- or rather, string as a
char star as follows. As of last week we
were writing this code. As of this week, we can now start
writing this code because char star specifically, we invented
in the CS50 library. But it turns out you've seen a way
of inventing your own data types. Recall this thing here. We played around last time with data
structures, or the struct keyword in C, and briefly the typedef keyword,
which defines a type for you. And if I highlight
what's interesting here, the way we invented a
person data type last time was to define a person as having
two variables inside of it-- a structure that encapsulates a
name and encapsulates a number. Now even though the syntax is a little
different today because of the star thing, notice that this could be a
similar application of that idea. If I want to create a type called
string, highlighted in yellow here, then I use typedef to make
it defined to be char star. So this is literally all
that has ever been in CS50.h, in addition to those prototypes
of functions we've talked about. typedef char star string
is a one-line code that brings the word string
as a data type into existence, and that's all that's ever been there. But the star, the char star,
is just too much in week 1. We wait until this point
to peel back that layer. are any questions, then,
on what a string is? What star or the ampersand are doing? Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Oh my God. Massive spoiler, but yes. If that is-- is that why when you
compare two strings as I briefly did, or almost did, problems arise. And in fact yes, last
week we use str compare-- STRCMP-- for a very deliberate
reason because yes, the spoiler is I accidentally would have compared two
addresses in memory, not the strings at those addresses. Other questions here. All right, well, before we give
ourselves maybe a 10 minute break here, we have lots of pieces of paper. If anyone wants to come on up and
play with this big stack of Post-Its, if you want to make your own
eight by eight grid of something to share with the class if you're
artistically inclined, come on up. Otherwise, let's take 10 minutes
and will return after 10. All right, so let's come
back to this question of how we can start to use these pointers
and these addresses, ultimately in an interesting way. The goal ultimately
next week is going to be to use these addresses to really
stitch together more complicated data structures than just persons,
like last week, or candidates in the context of an
electoral algorithm, if you will, and actually really use
our memory in the most versatile way to represent not just
images but maybe videos and other two-dimensional
structures as well. But for now, let's come back
to this address example, whittle it down to just a hi initially,
and see what's going on again, here underneath the hood. So let me re-add the
CS50 library just so we use our synonym for a moment,
that is the word string, and I'll redefine s as a string. And what I didn't mention before
is that these double quotes that you've been using for some
time are actually a little special. The double quotes are
a clue to the compiler that what is between them is in
fact a string as we now know it, which means the compiler will
do all the work of figuring out where to put the h, the
i, the exclamation point, and even adding for you
automatically a backslash zero. And what the compiler
will do for you, too, is figure out what address
all four of those chars ended up at and store it
for you in the variable s. So that's why it just happens with
strings without using ampersands or even stars explicitly, but the star
at least has been there because again, string is just synonymous
now with char star. It's not really as readable,
but it is now the same idea. So I'll leave string in place
just to do something week 1 style here for a moment, and let's go
ahead and print out a few characters. So I'm going to use %c this time, and
I'm going to print out s bracket zero and then I'm going to print out
s bracket one and s bracket two, literally doing week three
style from last week-- a printing of every character
in s as though it were an array. So ./address should give
me h-i exclamation point. And if I really want to get
curious, technically speaking, I could print out one more location,
and let me go ahead and recompile, make address ./address and there is,
it would seem, the backslash zero. I'm not seeing zero because I didn't
type literally the zero char in ASCII, it's literally eight zero bits
which are technically unprintable, if you will, in printf speak. And so what I'm seeing here
is like a blank symbol. That just means there is
something else there-- it's apparently all eight
zero bits, but they are there even though we're not seeing
them literally right now. Well, let's go ahead and
peel back one of these layers and let me go ahead and get rid of
the CS50 library and get rid of, therefore, the word string because
again, henceforth it's just char star. Nothing else is different. I'm going to now do
make address, ./address, and it's the same exact thing. And now, let's just focus on the hi
rather than even worry about that. So I'm going to recompile one last time
and now I have h-i exclamation point. Well, it turns out that the
array notation we used last week was technically some of
this syntactic sugar. Sort of a neat way to use
syntax in a useful way, but we can see more explicitly today
what the square brackets for a string is actually doing. Let me go ahead and do this. Let me adventurously say I
want to print out not s bracket zero, but I want to print out
whatever the first character of s is. So to be clear, what is s now? It's the address of a string. OK, but what is s, really? s is the address of the
first char in a string and again, that's sufficient for
defining a string because eventually the computer will see that there's
a backslash n at the end of it. So s is specifically the address
of the first character in a string. So that means, using my
new syntax, if I want to print out that first
character I can print out star s, because recall that star is the
dereference operator when you don't repeat the word char, you
don't repeat the word int-- you just use the star here. That means go to that address. Similarly, if I, in my newfound
knowledge of how strings work, know that the h comes first,
then the i right after it, then the exclamation point, then
the backslash zero, contiguously one byte apart, I could
start to do some arithmetic. I could go to s plus 1 byte and
print out the second character, and I could print out
whatever is at s plus 2-- in fact, doing what's generally
known as pointer arithmetic. Literally treating pointers
as the numbers they are-- hexadecimal or decimal, doesn't really
matter-- it's still just numbers. And go ahead and add
one byte or two bytes to them to start at the
beginning of a string and just poke around from left to right. So this now is equivalent to what we
did last week using square bracket notation, but now I'm re implementing
that same idea with this lower level plumbing, understanding ampersand
and stars now a little bit more, so if I remake this
program and do ./address, I should still see
h-i exclamation point. But what I'm really doing is
just kind of demonstrating, hopefully, my understanding
of what really is going on in the computer's memory. Now, programmers who are
maybe trying to show off might actually write this syntax. I think the more common syntax
would be what we did last week-- s bracket zero, s bracket one. Why? It's just a little more
readable and we don't need to brag about or care about
this underlying representation. The square brackets last week
we're an abstraction, if you will, on top of what is lower level math. But that's all that's going
on underneath the hood. We're poking around from
byte to byte to byte. All right, let me pause here, see if
there's any questions on that one. Any questions on this? Let's do one more then, just
to demonstrate that this is not even specific to strings. Let me go ahead and
get rid of all of this and let me give myself an array
of numbers like I did last week. So if I'm going to
declare all the numbers at once using this funky
curly brace notation, I can do like 4, 6, 8, 2, 7, 5, 0. So seven different numbers inside
of an array that's automatically initialized like this. I don't, strictly speaking,
need to say seven. The compiler is smart
enough to figure out how many numbers I put
with commas between them, and that just gives me an array
containing 4, 6, 8, 2, 7, 5, 0. So it turns out I can print each of
these numbers in the familiar way. I can do a printf of %i backslash n,
and I can print numbers bracket zero, and let me just do some quick copy/paste
just to print the first three of these. Theoretically, that should
print out 4, 6, 8, and so forth. But I can do the same sort
of manipulation understanding what pointers now are,
using pointer arithmetic. So let me actually unwind this
and just go back to one printf, and instead of printing numbers bracket
zero like I might have last week, let me just go and print out
whatever is at that address-- so asterisk numbers. Let me then print out
the second digit, which is going to be whatever is at numbers
plus 1, and then let me do this further and do whatever is at numbers plus 2,
and if I really want to repeat this, let me do it four more
times and do what's at location three, four, five, and six. And that's seven total numbers
because I started counting at zero. So let me just quickly run this. Make address, ./address. There are those seven
digits being printed. But there's something
subtle but also useful here. Each of these digits-- 4, 6, 8, 2,7,5, 0-- is an int. Why? Because I made an array of integers. But think back-- how big is a
typical integer, have we claimed? Four bytes, or 32 bits, so it's
worth noting that I don't really need to worry about that detail. Notice that I did not do plus 4,
plus 8, plus 12, plus 16, plus 20. I, the programmer,
strictly speaking, don't need to worry about how
big the data type is. This is the power of pointer arithmetic. The compiler is smart enough to know
that if you add 1 to this pointer, that is the same as saying
go one more piece of data-- not just one byte-- so if it's an int, move four. If it's a second int, move eight. If it's a third int, move 12. Pointer arithmetic handles that
annoying arithmetic for you so you can just think of this
as a number after a number after a number that are back to
back to back but not one byte apart, but four bytes apart. Which is only to say plus 1, plus 2,
plus 3 works no matter the data type. Why? Because the compiler knows what
type of data you're talking about. Now, there's one other
detail I should reveal here that I've taken for granted. In the past I was using double
quotes to represent strings, and I claim that the compiler's
smart enough to realize that oh, if I have double quote hi, that means
it's an array of h-i exclamation point, and then the backslash zero. Notice this usefulness. It turns out that you can actually treat
arrays as though the name of the array is itself a pointer,
and this is actually going to be something
useful in upcoming problems when we want to pass arrays
around in the computer's memory. Notice that strictly speaking on line
five, there's no pointers going on. There's no star, there's
no ampersand-- there's nothing new there, and yet
instantly on line seven I'm pretending that it is the
address, and this is actually OK. It turns out that an array
really can be treated as the address of the first
element in that array. The difference is that there's no
secret backslash zero anywhere. This is just part of
the phone number here, the ending in zero-- that's not
like a special backslash zero. So this is something we're going to
take advantage of too, before long. There's this interrelationship
between addresses and arrays that just generally allows you to
treat one as though it is the other, but the math is taken care of for you. Are any questions then on this before
we start to solve some bigger problems? Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Potentially. If you go beyond the end of an array,
you might get a segmentation fault. The problem is that that symptom
is sometimes nondeterministic, which means that sometimes it
will happen, sometimes it won't. It often depends on how far off the
end of the array you actually go. You'll often not induce
the segmentation fault if you just poke a little too
far, but if you go way too far it quite likely will. But we'll give you a tool today
actually for detecting and solving exactly that kind of situation. So let's go ahead now and do
something a little different in code, but that actually comes back
to that spoiler from earlier. Let me go ahead and create a program
called compare.c, and in this program I'm going to go ahead and
allow myself the CS50 library, not so much for string but so that
I can actually use GetInt still, which is way easier than the way we'll
see that C normally lets you get input. Let me give myself stdio.h,
do an int main(void), not worrying about command line
arguments today, and let me go ahead and get an int i using get int, and
ask the human for the value of i, then let me give myself an int j, ask
the user for another int, calling it j, and then let me go ahead and kind of
naively, but to your point earlier, if i equals equals j,
then let's go ahead and print out something like "same,"
backslash n, else let's go ahead and print out "different" if
they are not, in fact, the same. So that would seem to be a program that
compares the value of two integers. All right, so let's go
ahead and run make compare-- so far so good-- ./compare. OK, i will be 50, j will be 50-- they're the same. Let's do it once more. i will be 50, j will be 42. They are different. So so far, so good in this
first version of comparison. But as you might see
where I'm going with this, let's move away from integers and let's
actually change these things to char-- to strings. So I could do string s over here-- GetString s over here. Then I could do string t over
here, and GetString over here, asking the user for t this time, here. And then I can compare the two. If s equals equals t-- and this is a common convention. If you've used s for string already you
can use t for the next one, at least for simple demonstrations like this. I'm going to compare the two, just like
I did for ints, which worked great. Make compare-- so far
so good-- ./address-- oh, sorry. Wrong program-- ./compare. Let me go ahead and
type in something like hi, exclamation point and bye,
exclamation point, which of course should definitely be different. Let me run it again with hi, exclamation
point and hi, exclamation point. Different-- maybe I messed up. Let's maybe do it lowercase,
maybe that'll fix. But no, those two are different. So to come back to what I described
as a spoiler earlier, what's the fundamental issue here, to be clear? Why is it saying different
even though I'm pretty sure I typed the same thing twice. Yeah. Yeah, this is where it's now
useful to know that string has been an abstraction-- a training wheel, if
you will-- and if we take that away-- still use GetString because
that's convenient still-- but if I change string
to be char star, it's a little more explicit as to what s and
what t are. s is a pointer to a char, that is the address of
a char. t is a pointer to a char, that is
the address of a char. Specifically, the first character
in s and the first character in t, respectively. So if I'm comparing
these two it should stand to reason that they're
going to be different. Why? Because s might end up here in memory
and t might end up here in memory. Each time I call GetString, it is
not smart enough or advanced enough to know that, wait a minute--
you typed the same thing. I'm just going to hand
you back the same address. That doesn't happen because we
did not design GetString that way. Each time I call GetString,
it returns, apparently, a different copy of the
string that was typed in. A hi over here and a hi over here. They might look the same to
the human but to the computer they are different chunks of memory,
and therefore at different addresses. And here, too, we can reveal
what is GetString returning? Well, up until today it was
returning a string, so to speak. That's not really a thing. Technically, what
GetString has always been doing is returning the address
of the first char in a string and trusting that we put a backslash
zero at the end of whatever the human typed in, and that's enough now
for printf, for strlen, for you to know where a string begins and ends. So GetString has actually
always returned a pointer. It has not returned a quote
unquote string per se, but there are functions that can
solve this comparison for us. Recall that I could do
something like this. I could actually go
in here and I could-- let's see, where was it? So if I include str compare here and
use it to pass in two values, s and t, let's see now what happens
when I make compare. Implicitly declaring library
function str compare with type int-- and well, there's a star. So you might have seen this error before
and you might have ignored most of it, but there's some evidence of
stars or pointers going on here. It looks like I didn't include
the string.h header file, so that's an easy fix. Include string.h which, despite its
name, does not create a data type called string, it just has
string-related functions in it like str compare. Let's make compare again. Now it compiles, ./compare. Now let's type in hi, exclamation
point and even the same thing again. These are now-- oh, I used it wrong. OK, user error. That was supposed to be
impressive, but it's the opposite. What did I do wrong? What did I do wrong here? Yeah. Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah, it
returns three different values. Zero if they're the same, positive
1 becomes before the other, negative if the opposite is true. I just forgot that, so like
I did last week correctly, if I want to compare them for
equality per the manual page, I should be checking for
zero as the return value. Now make compare, ./compare, Enter. Let's try it one last time-- hi and hi. OK now, they're in fact the same. And Justin, thank you. And indeed, not that it's
returning same all the time. If I type in hi and
then bye, it's indeed noticing that difference as well. Well, let me go ahead and
do one other thing here. Let's do one other thing. Let me go ahead now and just reveal
more pictorially what's going on. Let's get rid of the string comparison
and let's just print these things out. The simple way to print this out would
be with %s and again, %s is special-- printf knows-- taking an address and start
there, print every character up until the backslash n, so let's
just hand it s and do that. And then let's do one more, %s,t. This is, again, sort of a
mix of week 1 and this week because I got rid of the word string. I'm using char star, but I'm still
using printf and %s in the same way. Let me go ahead and run compare
now, and if I type hi and hi, I should see the same thing twice. So they look the same, but here
now we have the syntax today to print out the actual
addresses of these things. So let me just change the s to a p,
because p means don't go to the address and print it, it means just
print the address as a pointer. So make compare, ./compare, and now
let's type in hi, and once more, and I should see, indeed, two
slightly different addresses given in hexadecimal. One's got a B at the end,
one's got an F at the end, and they are indeed a few bytes apart. So this is just confirming what
our suspicions have actually been. So what does this mean, perhaps
in the computer's memory? Well, let's take a look. I've zoomed out so I have a little
more squares to look at at once. Here might be s in memory when I do
string s equals, or char star s equals. I get a variable that's of size
1, 2, 3, 4, 5, 6, 7, 8, because I claimed earlier that on modern systems,
pointers are generally eight bytes nowadays so they can count even higher. And inside of the computer's
memory, also, might be hi. And I don't know where it ends
up so for the sake of discussion it ended up down here. That's what was free
when I ran the program. h-i exclamation point, backslash zero. Maybe it ended up, for the sake of
discussion, at 0x123, 4, 5, and 6. So to be clear, what is s
storing once the assignment operator copies from right to left? What is s storing if I
advance one more slide? Yeah. 0x123, the presumption
being that if a string is defined by the address of its first
char and that address of its first char is 0x123, then that's indeed
what should be in the variable s. And so technically, that's what's
been happening with that assignment operator from right to left. GetString indeed returns
a string, so to speak, but more properly it returns
the address of a char. What's been then copied from right to
left using that assignment operator all these weeks is indeed that address. Now technically, we don't really need
to care about where these addresses are. It suffices to just think about
them referentially, but let's first consider where t might be.
t is just another variable that I created on my second line of code. Maybe it ends up there,
maybe somewhere else. For the sake of discussion
I'll draw it left and right. Where did the second word
end up that I typed in? Well, suppose the second copy of
hi ended up at 0x456457458459. What ended up in t? I'll pluck this one off myself. 0x456, presumably. And so this is now a pictorial
representation of why, and let's abstract away everything else. When I compared s against t using
equal equals, based on the picture they're obviously not the same. One is over here, one is over here. And per a moment ago, one is
0x123, the other is 0x456. Yes, technically they're pointing
at something that's the same, but that just reveals
how str compare works. str compare is apparently
a function that takes in the address of
a string as its argument and the address of another
string as its argument, it goes to the first character in
each of those strings, respectively, and probably has a for
loop or a while loop and just goes from left to
right, comparing, looking for the same chars left and right, and
if it doesn't notice any differences, boom-- it returns zero. If it does notice a difference it
returns a positive or a negative value. And that's very similar, recall, to how
we implemented string length ourselves last week. I used a for loop, I was
looking for a backslash zero. str compare is probably a little similar
in spirit, looping from left to right but comparing, this
time not just counting. Are any questions then,
on string comparison and why it is that we use str
compare and not equals equals? Yeah. AUDIENCE: Do pointers have addresses? DAVID J. MALAN: Do
pointers have addresses? Yes. So we won't do that today, but I could
actually use the ampersand operator on s or on t. That would give me the
equivalent of a char star star that itself could be
stored elsewhere in memory. That's where it ends. We don't do that recursively forever. There's star and there's star
star, but yes, that is a thing and it's very often useful in the
context of two dimensional arrays, which we haven't really talked about,
but that is a feature of the language, too. But not today. Good question. All right, so what might we now
do to take things up a notch? Well let's go ahead and implement
a different program here that maybe tries copying some
values, just to demonstrate this. Let me open up a file
called, how about copy.c, and I'm going to start
off with a few includes. So let's include the CS50 library just
so we have a way of getting user input. Let's include-- how about stdio
as always, let's preemptively include string.h and maybe
one other in a moment. Let's do int main(void) as before. And then in here, let's get a
string from the user and just call it s for simplicity. And heck, we can actually just
call this char star if we want, or string, since we're
using the RS50 library. But we'll come back to that. Let's now make a copy
of s and do s equals t, using a single assignment operator and
then let's check something like this. Let's go into the first character
of t, which is t bracket zero, and then let's uppercase
it using that function that we've used in the past of
toupper t bracket zero, semicolon. And actually, I should go back up here. If I'm using toupper or if you use
tolower or isupper or islower-- I might not remember this offhand,
but it was in another header file called C type dot h. There was a bunch of helpful
functions in that library as well. Now at the very last line of the program
let's just print out what both s and t are by simply printing out %s for each
of them, and t is %s also, not %t, of course, and let's
see what happens here. So let me make copy-- oh my God, so many mistakes. What did I do wrong? Oh. OK, that was unintended. String t equals s, sorry, so
I'm creating two variables, s and t respectively,
and I'm copying s into t. Make copy, Enter. There we go. ./copy, and let's
now type in, for instance, how about hi exclamation point
in all lowercase this time, and now what gets printed? I don't think that's what I
intended, so to speak, here. Because notice that I got s from
the user, so that checks out. I then copied t into
s, which looks correct. That's what we always
use assignment for. Then I uppercase the first
letter in t, but not s-- at least in my code-- then I printed s and t and then
noticed, apparently, both s and t got capitalized. So if you're starting to get a
little comfortable with what's going on underneath the hood,
what's the fundamental problem here? Why did both get capitalized? Why did both get capitalized? Yeah, over here. AUDIENCE: Could it be they're
referencing the same address? DAVID J. MALAN: Yeah, they're
representing the same address. So C is really literal. If you create another variable called
t and you assign it the value of s, you are literally assigning
it the value in s, which is 0x123 or something like that. And so at that point in the
story both s and t presumably have a value of 0x123,
which means they technically point to the same h-i
exclamation point in memory. Nowhere did I tell the computer to give
me a copy of a h-i exclamation point per se, I literally said just copy s. So here's where an understanding of what
s literally is explains the situation. I'm only copying the pointers. So what actually went on in memory? Let's take a look here at this grid. If I created s initially,
maybe it ends up here. And I created hi in lowercase,
and it ended up down here. Then the address was, again, like
0x123456, 0x123 is what's in s. If then I create a
second variable called t, and I call it a string, a.k.a. char
star, maybe it again ends up here. But when I copy s into t by
doing t equals s semicolon, that literally just copies s into
t, which puts the value 0x123 there. So if we now abstract away all these
numbers and just think about a picture with arrows, what we've drawn in
the computer's memory is this. Two different pointers but storing
the same address, which means the breadcrumbs lead to the same place. And so if you follow the t breadcrumb
and capitalize the first letter, it is functionally the
same as copying the-- changing the first letter
in the version s as well. So what's the solution, then,
to this kind of problem? Even if you have no idea
how to do it in code, what's the gist of what I
really intended, which is, I want a genuine copy of s, called t. I want a new h-i exclamation
point backslash zero. What do I need to do
to make that happen? Thoughts? AUDIENCE: I think there's
a function called str copy. DAVID J. MALAN: So there is a
function called str copy, strcpy, which is a possible
answer to this question. The catch with stir copy is that you
have to tell it in advance not only what the source string is--
the one you want to copy-- you also need to pass in the
address of a chunk of memory into which you can copy the string, and
here's one thing we haven't seen yet, and we need one more building
block today, if you will. We haven't yet seen a way to
create new chunks of memory and then let some other
function copy into them. And for this, we're going to introduce
something called dynamic memory allocation. And this is the last and most
powerful feature perhaps, today, whereby we're going to introduce two
functions, malloc and free, where malloc means memory allocate,
which literally does just that. It's a function that takes a number
as input-- how many bytes of memory do you want the operating system to
find for you somewhere in that big grid? It's going to find it
and it's going to return to you the address of the first byte of
contiguous memory back to back to back, and then you can do anything you
want with that chunk of memory. free is going to do the opposite. When you're done using a chunk of
memory that malloc has given you, you can say free it, and that means you
hand it back to the operating system and then the operating system can
use it for something else later. So this is actually evidence of
a common problem in programming. If your Mac your PC has ever been in
the habit of starting to get really, really slow, or it's slowing to a
crawl-- heck, maybe it even freezes-- one of the possible
explanations could be that the program you're
running by Apple or Microsoft or whoever, maybe they're using
malloc or some equivalent, asking the operating system-- Mac OS or Windows-- for,
give me more memory. I need more memory. The user is creating more images. The user is typing a longer essay. Give me more memory, more memory. If the program has a bug and never
actually frees any of that memory, your computer might end up using
all of the available memory and honestly, humans are not very good
at handling corner cases like that. Very often programs, computers
just freeze at that point or get really, really slow because
they start trying to be creative when there's not enough memory left. So one of the reasons for a
computer really slowing down might be calling for malloc a lot, or
some equivalent, but never freeing it. Which is to say, you should
always use these two functions in concert and free memory
once you are done with it. So let me go ahead and do this in
code and solve this problem properly. Let me go ahead and do this. Before I copy s into t using
something like str copy, I first need to get a bunch
of memory from the computer. So to do that, let's make this super
clear that we're dealing with pointer, so I'm going to change my strings
to char stars for both s and t, and what I technically
am going to store in t is the address of an
available chunk of memory. To do that, I can ask the computer
to allocate memory for me, and how many bytes. If I want to create a copy
of h-i exclamation point, I need how many bytes? Good! Four! Because I need the h, the i, the
exclamation point, and additional space for the backslash zero. It's up to me to understand
that and ask for it. It's not going to happen magically. Nothing does in C. So I could
just naively type four there, and that would be correct
if I type in h-i exclamation point or any other three letter word
or phrase, but to do this dynamically I should probably do
something like strlen of s plus 1 for the additional
null character. Recall that string length
does it in the English sense-- it returns the length of the string
you see, plus 1 also takes into account the fact that I'm going
to need that backslash n. Now let me do this old
school style first. Let me go ahead and manually
copy the string s into t first. So for int i equals 0, i is less than
the string length of s, i plus plus. Then inside my for loop, I'm going
to do t bracket i equals s bracket i, but actually I want
the null character too, so I want to do the length
of the string plus 1 more, and heck, I think I learned
an optimization last time. If I'm doing this again
and again, I could really do n equals strlen of s plus 1
and then do i is less than n, just as a nice design optimization. I think this for loop will
actually handle the process, then, of copying every character from s into
every available byte of memory in t. Or I could get rid of all of that
and take your suggestion, which is to use str copy, which takes as
its first argument the destination and its second argument the source. So copy from right to left in this case,
too, that's going to do all of that automatically for me as well. Now I think I'm good. I can now capitalize safely. The first character in t, which
is now a different chunk of memory than s, and then I can print them both
out to see that one has not changed but the other has. So make copy-- all right,
what did I do wrong? Implicitly declaring library
function malloc dot, dot, dot. So we've seen this kind of error before. What is-- even if you don't
know quite how to solve it, what's the essence of the solution? What do I need to do to fix this
kind of problem involving implicitly declaring a library function? What did I forget? Yeah. I need to include the library. And I could look this up in the manual,
or I know it off the top of my head, I just forgot it. There's another library
we'll occasionally need now called standard lib-- standard library-- that contains
malloc and free prototypes and some other stuff, too. All right, let me just clear this
away and do make copy one more time. Now I'm good. ./copy, Enter, All right.
s, I'm going to type in hi, lowercase. t and s now come back as intended. s is untouched, it would seem,
but t is now capitalized. Are any questions, then, on
what we just did in code? Yeah. AUDIENCE: You said that
malloc and free go together. [INAUDIBLE] DAVID J. MALAN: Indeed. There's a few improvements
I want to make, so let me actually do those right now. Technically, I should practice what
I preached and I should indeed, when I'm done with t, free t. Fortunately, I don't have
to worry about how big t was-- the computer remembers how many
bytes it gave me and it will go free all of them, not just the first. I should do free t. I don't need to do free
s, and I shouldn't, because that is handled
automatically by the CS50 library. s, recall, came from
GetString, and we actually have some fancy code in
place that makes sure that at the end of your
program's execution we free any memory that we
allocated so we don't actually waste memory like I described earlier. But there's actually a
couple of other things if I really want to be
pedantic I should put in here. It turns out that
sometimes malloc can fail, and sometimes malloc doesn't
have enough memory available because maybe your
computer's doing so much stuff there's just no
more RAM available. So technically, I should
do something like this-- if t equals equals null,
with two L's today, then I should just return 1 or something
to say that there was a problem. I should probably print
an error message too, but for now I'm going to keep it simple. I should also probably check this. This is a little risky of me. If I'm doing t bracket zero, this is
assuming that there is a letter there. But what if the human just
hit Enter at the prompt and didn't even type h, let
alone h-i exclamation point? What if there is no t bracket zero? So technically, what I should probably
do here is, if the length of t is at least greater than zero,
then go ahead and safely capitalize the first letter of it. And then at the very
end if all goes well, I can return zero, thereby signifying
that indeed, this thing was successful. So yes, these two functions, malloc
and free, should be in concert. And so if you call malloc you
should call free eventually. But you did not call malloc for s,
so you should not call free for s. Yeah, other question. AUDIENCE: Here's a question. Why do we do malloc plus 1? DAVID J. MALAN: Why
did I do malloc plus 1? So malloc-- sorry, malloc
of string length of s plus 1-- the string length is the
literal length of the string as a human would perceive it in English. So h-i exclamation
point-- strlen gives me 3, but I know now as of last week and
this week what a string technically is and a string always has an extra byte. The onus is on me to
understand and apply that lesson learned so that I actually
give str copy enough room for that trailing null character. And here's just an annoying thing when
we called the backslash zero N-U-L last week, it turns out that
N-U-L-L is the same idea. It's also zero, but it's zero
in the context of pointer. So long story short, you never
really write N-U-L, I've just said it and we saw it on the screen. You will start writing N-U-L-L when you
want to check whether or not a pointer is valid or not. And what I mean by that is this. If malloc fails and there's just
not enough memory left inside of the computer for you, it's
got to return a special value, and that special value is
N-U-L-L in all capital letters. That signifies something went wrong. Do not trust that I'm giving
you a useful return value. Other questions on
these copies thus far? Yeah, over there. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Good question. Will str copy not work without malloc? You kind of need both in
this case because str copy, by definition-- if I pull up its
manual page-- needs a destination to put the copied characters. It's not sufficient just to
say char star t semicolon. That only gives you a pointer. But I need another
chunk of memory that's just as big as h-i exclamation
point backslash zero, so malloc gives me a
whole bunch of memory and then str copy fills it with h-i
exclamation point backslash zero. So again, that's why we're
going down to this lower level, because once you understand
what needs to be done you now have the functions to do it. So let's actually consider
what we just solved. So in this next version of the program
where I actually introduced malloc, t was initialized for the
return value of malloc, and maybe the memory that
I got back was here-- 0x456457458459. I've left it blank
initially because nothing is put there automatically by malloc. I just get a chunk of memory that
is now mine to use as I see fit. I then assign t to that return value,
which points t at the first address. Notice there's no backslash zero. This is not yet a string
it's just a chunk of memory-- four bytes-- an array of four bytes. What str copy eventually did
for me was it copied the h over, the i over, the exclamation point
over, and the backslash zero. And if I didn't want to use str copy or
I forgot that it existed, my for loop would have done exactly the same thing. Are any questions, then,
on these examples here. Any questions? Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Good question. After malloc, if I had then
still done just t equals s, it actually would have recreated
the same original problem by just copying 0x123 from s into t. So then I would have been left with
a picture that looked like this a few steps ago, I would have-- and
I can't quite do it live-- this arrow, if I did
what you just described, would now be pointing over here and so
I wouldn't have fundamentally solved the problem, I would have
just additionally wasted four bytes temporarily that
I'm not actually using. Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: You can-- do you always use malloc
and str copy together? Not necessarily. These are both solving
two different problems. malloc's giving me enough memory to
make a copy, str copy is doing the copy. However, you could actually use an
array, if you wanted, of characters, and you could use str copy on that, and
there's other use cases for str copy. But thus far, it's a
reasonable mental model to have that if you
want to copy strings, you use malloc and then str
copy, or your own homegrown loop. Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Say that once more. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: No. It will-- good question. If I had a-- str copy, per its documentation,
will copy the whole string plus the null character at the end. It just assumes there will be one there. It's therefore up to you to pass str
copy a long enough chunk of memory to have room for that. If I only ask malloc
for three bytes, that could have potentially
created a memory problem whereby str copy would just still
blindly copy one, two, three, four bytes, but technically it should
have only touched three of those. You do not yet have access to the
fourth one, or the rights to it, because you never asked malloc for it. Yeah. AUDIENCE: So the number inside
malloc would be the number of bytes. DAVID J. MALAN: Correct. The number inside malloc--
it's one argument. It's the number of bytes you want back. AUDIENCE: Does that mean you
have to remember [INAUDIBLE]?? DAVID J. MALAN: Yes, the onus
is on you, the programmer, to remember or frankly, use
a function to figure out how many bytes you actually need. That's why I did not ultimately
type in four manually, I used str length plus 1. So the plus 1 is necessary if you
understand how strings are represented, but using strlen means
that I can actually play around with any types of
inputs and it will dynamically figure out the length. So suffice it to say,
there's so many ways already where you can
start to break programs. Let's give you at least one tool for
finding mistakes that you might make. And indeed, in upcoming
problem sets you will use this to find bugs in your own code. Not just using printf, not just using
the built-in debugger, but another tool here as well. So let me go ahead and deliberately
write a program called memory.c that has some memory-related errors. Let me include stdio.h at the top and
let me include stdlib.h at the top so I have access to malloc now. Let me do int main(void) and then
inside of main, let me do this-- I want to allocate
maybe how about three-- space for three integers. Why? Just for the sake of discussion. So I'm going to go ahead and do malloc
of three, but I don't want three bytes. I want three integers and
an integer is four bytes, so technically I could do this-- 3 times 4, or I could do 12 but again,
that's making certain assumptions and if I run this program on
a slightly different computer, int might be a different size. so the better way to do this would be
3 times whatever the size is of an int. And this is just an operator you can use
any time if you just want to find out on this computer, how big is an int? How big is a float, or something else? So that's going to give me that many-- that much memory for three ints. What do I want to assign this to? Well, malloc returns an address. Pointers are addresses, so I'm going
to create a pointer to an int called x and assign it the value. So what am I doing here? This is a little less obvious,
but again go back to basics. The right hand side here gives me a
chunk of memory for three integers. malloc returns the address of
the first byte of that chunk. How do I store the address of anything? I need a pointer. The syntax for today
is type of data, star, where the type of data in question
is three ints, so I do int star x. Again, it's kind of purposeless, only
for sort of instructional purposes here, but this is equivalent now to
having a chunk of memory of size 12 in total, presumably, so I
can technically now do this. I can go into maybe the first
location and assign it the number 72 like the other day. Second location, the number 73, and the
third location, maybe the number 33. Now I've deliberately
made two mistakes here because I'm trying to trip
over my newfound understanding, or my greenness with
understanding pointers. One, I didn't remember that I
should be treating chunks of memory as zero indexed. malloc essentially returns an array,
if you want to think of it as that. An array of three ints,
or more technically, the address of a chunk of memory
that could fit three ints. So I can use my square bracket
notation, or I could be really cool and use pointer arithmetic, but
this is a little more user friendly. But I have made two mistakes. I did not start indexing
at zero, so line seven should have been x bracket zero. Line eight should have been x
bracket 1, and then line nine should have been x bracket 2. So first mistake. The second mistake that
I've made as a side effect, is I'm also touching
memory that I shouldn't. x bracket 3 would mean go to the
fourth int in the chunk of memory that came back. I only asked for enough
memory for three ints, not four, so this is what's
called a buffer overflow. I am accidentally, but
deliberately at the moment, going beyond the boundaries of
this array, this chunk of memory. So bad things happen,
but not necessarily by just running your program. Let me go ahead and just try this. Make memory, and you'll see here
that it compiles OK. ./memory, and it actually does
not segmentation fault, which comes back to that
point of nondeterminism. Sometimes it does, sometimes it
doesn't-- it depends on how bad of a mistake you made. But there's a program that can
spot these kinds of mistakes, and I'm going to go ahead and expand
my terminal window for a moment and I'm going to run not just ./memory,
but a program called Valgrind./memory. This is a command that comes
with a lot of computer systems that's designed to find
memory-related bugs in code. So it's a new tool in
your toolkit today, and you'll use it with
the coming problem sets. I'm going to run this now. It's output, honestly, it's hideous. But there's a few things
that will start to jump out and will help you with
tools and the problems sets to see these kinds of things. Here's the first mistake. Invalid write of size four. That's on memory.c line
nine, per my highlights. So let me go look at line nine. In what sense is this an
invalid write of size four? Well, I'm touching memory
that I shouldn't, and I'm touching it as though it's an int. And an int is four bytes-- size four. So again, this takes some practice to
get used to, the nomenclature here, but this is now a clue
for me, the programmer, that not only did I screw up, but
I screwed up related to memory and so this is just a hint, if you will. It's not going to necessarily
tell you exactly how to fix it, you have to wrestle with
the semantics, but invalid write of size four-- oh, OK. So I should not have indexed
past the boundary here. All right, so I
shouldn't have done that. So let me go ahead then and change this
to zero, one, and two, perhaps, here. All right, so let me go
ahead and recompile my code. Make memory, ./memory, still doesn't
seem to be broken but it is technically buggy. Let me go ahead and run Valgrind
again, so Valgrind of ./memory, Enter. And now there's fewer scary-- less scary output now, but
there's still something in there. Notice this-- 12 bytes in one blocks-- no regard for grammar
there-- are definitely lost in lost record one of one. Super cryptic, but this is hinting
at a so-called memory leak. The blocks of memory are lost in
the sense that I malloc'd them-- I asked for them but I never-- take a guess-- freed them. I have a memory leak. And this is the arcane way
of saying, you've screwed up. You have a memory leak. So this is an easy fix, fortunately. Once I'm done with this memory I
just need to free it at the end. So now let me go ahead
and rerun make memory, it's still runs fine so all the while
I might have thought, incorrectly, my code is correct. But let me run Valgrind one more time. Valgrin of ./memory, Enter. Now, this is pretty good. All heap blocks were
freed, whatever that means. No leaks are possible. And even though it's still a little
cryptic, there's no other error here and in fact, it's pretty explicit--
error summary, zero errors from zero contexts, dot, dot, dot. So even though this is one of
the most arcane tools we'll use, it's also one of the most
powerful because it can see things that you, the human, might not, and
maybe even that the debugger might not. It does a much closer
reading of your code while it's running to figure
out exactly what is going on. Any questions, then, on this tool? And we'll guide you after today
with actually using this, too. Just helps you find
memory-related mistakes that you might now be capable of making. All right, let's do one
other memory-related thing. Let me shrink my terminal window here. Let me create one other
file here called garbage.c. It turns out there's a term of ours
called garbage values in programming that we can reveal as follows. Let me include stdio.h,
and let me include-- how about stdlib.h, and
then let me give myself int main(void), and then in this
relatively short program let me give myself three
ints using last week's notation, just int scores bracket
3 for 3 quiz scores, or whatever. Then let me go ahead and do for
int i equals zero, i less than 3, i plus plus, then let me go ahead
and print out, %i backslash n, scores bracket i semicolon. That's it. This code, pretty sure is going
to compile and it's going to run, but what is my logical bug? I've forgotten a step even though the
code that's written is not so wrong. Yeah? Yeah, I didn't provide the
scores, so I didn't actually initialize the array called scores
to have any scores whatsoever. What's curious about this, though,
is that the computer technically doesn't mind. Let me go ahead and playfully
make garbage, Enter, and it's an apt description
because what I'm about to see are so-called garbage values. When you, the programmer, do not
initialize your codes variables to have values, sometimes, who knows
what's going to be there. The computer's been
doing some other things, there's a bit of work that happens even
before your code runs in the computer, so there might be remnants
of past ints, chars, strings, floats-- anything else in
there and what you're seeing is those garbage values, which is
to say you should never forget, as I just did, to initialize
the value of some variable. And this is actually
pretty dangerous, and there have been many examples of
software being compromised because of one of these issues
where a variable wasn't initialized and all of a sudden users, maybe people
on the internet in the context of web applications, could suddenly see the
contents of someone else's memory, or remnants. Maybe someone's password that
had been previously typed in or some other value like
a credit card number that had been previously typed in. There are different
defense mechanisms in place to generally make this not
so likely, but it's certainly very possible, at least
in this kind of context, to see values that you
probably shouldn't because they might be remnants from
something else that used them. So this is to say again, you have this
great power now to manipulate memory, but also now you have this great
hacking ability to poke around the contents of memory, and this is
exactly what hackers sometimes do when trying to find ways to exploit systems. Are any questions here? No? All right, let's go ahead and
take a quick five minute break and when we come back, we'll
build on these final topics. See you in five. We are back. First, just a little programmer
humor from XKCD, which hopefully now will make a little bit of sense to you. And what we'll also do next to take a
look at a short two minute video that animates with claymation, if you
will, from our friends at Stanford, exactly what happens now if you have
an understanding of what garbage values are and how they get there, and
what happens then if you misuse them. It's one thing just to print
them out as I just did, it's another if you actually mistake
a garbage value for a valid pointer, because garbage values are just zeros
and ones somewhere-- numbers, that is. But if you use that new
dereference operator, the star, and try to go to a garbage value
thinking incorrectly that it's a valid pointer, bad things can happen. Computers can crash or more familiarly,
segmentation faults can happen. So allow me to introduce, if we
could dim the lights for two minutes, our friend Binky from Stanford. SPEAKER 1: Hey Binky, wake up. It's time for pointer fun. BINKY: What's that? Learn about pointers? Oh, goody! SPEAKER 1: Well, to get
started, I guess we're going to need a couple of pointers. BINKY: OK, this code allocates two
pointers which can point to integers. SPEAKER 1: OK. Well, I see the two pointers, but they
don't seem to be pointing to anything. BINKY: That's right. Initially, pointers
don't point to anything. The things they point to are called
pointees, and setting them up is a separate step. SPEAKER 1: Oh, right, right. I knew that. The pointees are separate. So how do you allocate a pointee? BINKY: OK, well this code
allocates a new integer pointee, and this part sets x to point to it. SPEAKER 1: Hey, that looks better. So make it do something. BINKY: OK, I'll dereference the
pointer x to store the number 42 into its pointee. For this trick, I'll need my
magic wand of dereferencing. SPEAKER 1: Your magic
wand of dereferencing? That great. BINKY: This is what the code looks like. I'll just set up the number and-- SPEAKER 1: Hey, look. There it goes. So doing a dereference on x follows
the arrow to access its pointee, in this case to store 42 in there. Hey, try using it to store the number
13 through the other pointer, y. BINKY: OK. I'll just go over here to y
and get the number 13 set up, and then take the wand of
dereferencing and just-- whoa! SPEAKER 1: Oh hey, that didn't work. Say, Binky, I don't think
dereferencing y is a good idea because setting up the
pointee is a separate step and I don't think we ever did it. BINKY: Good point. SPEAKER 1: Yeah, we
allocated the pointer y, but we never set it
to point to a pointee. BINKY: Very observant. SPEAKER 1: Hey, you're
looking good there, Binky. Can you fix it so that y points
to the same pointee as x? BINKY: Sure, I'll use my magic
wand of pointer assignment. SPEAKER 1: Is that going to
be a problem, like before? BINKY: No, this doesn't
touch the pointees, it just changes one pointer to
point to the same thing as another. SPEAKER 1: Oh, I see. Now y points to the same place as x. So wait, now y is fixed. It has a pointee so you can try
the wand of dereferencing again to send the 13 over. BINKY: OK, here it goes. SPEAKER 1: Hey, look at that. Now dereferencing works on y. And because the pointers are sharing
that one pointee, they both see the 13. BINKY: Yeah, sharing. Whatever. So are we going to switch places now? SPEAKER 1: Oh look, we're out of time. BINKY: But-- That's from our friend
Nick Parlante at Stanford. So let's consider what
Nick did here as Binky. So here is all the code together. These first couple of lines were not
bad, and notice that in Stanford's code they move the stars to the left. That's fine. Again, more conventional
might be this syntax here. These two lines are fine. It's OK to create
variables, even pointers, and not assign them a value initially
so long as you eventually do. So we eventually do
here, with this line. We assign to x the return
value of malloc, which is presumably the address of something. To be fair, we should really
be checking for null as well, but that's not the biggest problem here. The biggest problem is
not even this next line, which means go to the memory location
in x and store the number 42 there. That's fine, because
again, malloc returns the address of some chunk of memory. This chunk of memory is
big enough for an int. x is therefore going to store
the address of that chunk that's big enough for an int. Star x recalls the dereference
operator, means go to that address and put 42 in it. It's like going to the mailbox
and putting the number 42 in it instead of taking the number
50 out, like we did before. But why is this line bad? This is where Binky lost
his head, so to speak. Why is this bad? Yeah. AUDIENCE: We haven't yet
allocated space for it. DAVID J. MALAN: Exactly. We haven't yet allocated space for y. There's no mention of malloc,
there's no assignment of y, even to that same memory. So this would be, go
to the address in y, but if there is no known address in
y, it is a so-called garbage value, which means go to some random address
that you have no control over, and boom-- that might cause what we've seen in the
past, perhaps as a segmentation fault. Now this, fortunately,
is the kind of thing that if you don't quite have the eye
for it yet, Valgrins, that new tool, could help you find as well. But it's just another example of
again, the sort of upside and downside of having control now
over memory at this level. All right. Well, let's go ahead
and do one other thing. Considering from last week
that this notion of swapping was actually a really common operation. We had all of our volunteers come
up, we had to swap a lot of things during bubble sorts and
even selection sort, and we just took for
granted that the two humans would swap themselves just fine. But there needs to be code
to do that if you actually implement bubble sort, selection sort,
or anything that involves swapping. So let's consider some code like this. We'll keep it simple
like last week, and where we wanted to swap some values like
int A and int B, for instance, here. Void because I'm not going to return
a value, but I have a function called swap. So here, for instance,
might be some code for this. But why is it so complicated? Here, let's actually take a step back. Why don't we do this here. I think we have time
for one more volunteer. Could we get someone to come on up? You have to be comfy
on camera and you're being asked to help with your-- oh,
I'll go with the friend, pointing. So whoever has their
friend doing this here-- no? Now they're pointing it over here. Now, literally an arm is being twisted. OK. Come on down. That backfired. Come on over. And what is your name? AUDIENCE: Marina. DAVID J. MALAN: Marina. Nice to meet you. Who were you trying to volunteer? AUDIENCE: My friend Jesse. DAVID J. MALAN: OK. So here we have for Marina two
glasses of liquid, orange and purple, just so that they're super obvious. And suppose that the problem
at hand, like last week, it's just to swap two values, as
though these two glasses represented two people and we want to swap them. But let's consider these glasses
to be like variables, or location in an array, and you know what? I'd really like you to swap the values. So orange has to go in there,
and purple has to go in there. How would you do it? And we'll see if we can
then translate that to code. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: OK, what-- say it a little louder. All right, yeah. So presumably, you're
struggling mentally with how you would do this without
having an extra cup, so good foresight here. Let me go ahead and we do have a
temporary variable, if you will. So if I hand you this, how would
you now solve this problem? AUDIENCE: I would go
like that, but it's-- DAVID J. MALAN: No, that's-- Oh. Well, OK. Go do it-- go with your instincts. OK. Sure, go ahead. Go to whatever your instincts are. Yeah, so a little-- so
strictly speaking, probably shouldn't have moved the
glasses just because that would be like moving
the array locations, so let's actually do it one
more time but the glasses now have to go back where
they originally are. So how would you swap these now,
using this temporary variable? OK, good. Otherwise we'd be completely
uprooting the array, for instance, by just physically moving it around. So you moved the orange into
this temporary variable, then you copied the purple
into where the orange was, and now, presumably, excellent. The orange is going to end
up where the purple once was and this temporary variable,
it stored up some extra memory. It was necessary at the time,
but not necessary, ultimately. But a round of applause if we could,
and thank you for doing that so well. So the fact that it
instantly occurred to Mariana that you need some temporary variable
is a perfect translation to code, and in fact this code here,
that we might glimpse now, is reminiscent of
exactly that algorithm, where A and B, at the end of the
day, are the same chunks of memory. Just like the second
time, the two glasses have to kind of stay put, even
though we're physically lifting them, but they're going back
to where they were, is kind of like having
two values, A and B, and you just have a temporary
variable into which you copy A, then you change A with
B, then you go and change B with whatever the
original value of A was, because you temporarily stored it
in this temporary variable, tmp. Unfortunately, this code doesn't
necessarily work as intended. So let me go over to my
VS Code here and open up a program called swap.c,
and in swap.c, let me whip up something really quickly
here with, how about include stdio.h, int main(void). Inside of main let me do something
like x gets 1 and y gets 2. Let me just print out as a
visual confirmation that x is %i, y is %i backslash n, plugging
in x and y, respectively. Then let me call a swap function
that we'll invent in just a moment. Swap x and y And then let me print out
again x is %i, y is %i backslash n, just to print out again what they are,
because presumably I should see 1, 2 first, then 2, 1 the second time. Now how is swap going to be implemented? Let me implement it exactly
as on the screen a moment ago. So void swap int x-- or let's call it int A
for consistency, int B. But I could always call
those anything I want. Int tmp gets A, A gets B, B gets tmp. So exactly as I proposed
a moment ago, and exactly as Mariana really implemented
it using these glasses of water. I need to now include my prototype,
as always, so nothing new there. And I'll just copy/paste that up here,
and now let's go ahead and run this. So make swap-- so far, so good-- swap-- x is now 1, y is 2, x is 1, y is 2. So there seems to be a bit of a
bug here, but why might this be? This code does not in fact work, even
though it obviously works in reality. Yeah? AUDIENCE: Because A and B have different
addresses than x and y [INAUDIBLE].. DAVID J. MALAN: Good,
and let me summarize. A and B do indeed have
different addresses of x and y, and in fact what happens when you
call a function like this on line 11, calling swap, passing in x and
y, you are calling a function by value, so to speak. And this is a term of
art that just means you are passing in copies of x and
y, respectively, and calling them A and B in the context of this
function, but they're indeed copies. Now technically, these
names are local only. I could have called this x,
I could have called this y, I could have changed this to x,
this to y, this to x, and this to y. The problem would still remain. Just because you use the same names
in one function as you do elsewhere, that doesn't mean they're the same. They just look the same to you. But indeed, swap is going to get copies
of this x and y, and in this context, this scope, so to speak-- x and y will be copies of the original. So for clarity, let me
revert this back to A and B just to make super clear that they're
indeed different, albeit copies, but there's indeed a problem there. This function actually works fine. In fact, notice this. Let me go ahead and print out
inside of this. printf A is %i, B is %i backslash n, and
then I'll print A and B. And let me do that same thing at the
beginning of this function before it does any work. Let me go ahead and rerun. Make swap, ./swap,
and this is promising. Initially, x is 1, y is 2, A
is 1, B is 2, A is 2, B is 1, but then nope-- x is 1, y is 2. So if anything, I've confirmed
that the logic is right-- Mariana's logic is right, but
there's something about C. There's something about using one
function versus another that's actually creating a problem here. The fact that I'm passing in copies of
these values is creating this problem. So what in fact is going on? Well again, inside of your computer's
memory there is these little chips, and we've been talking
about them abstractly, it's just this grid of memory locations. It turns out that your
computer uses this memory in a pretty conventional way. It's not just random, where it just
puts stuff wherever is available, it actually uses different parts of
the memory for different purposes. And you have control over a lot of
it, but the computer uses some of it for itself. And let's go ahead
and zoom out from this and consider that within your computer's
memory, what a computer will typically do is actually store initially,
all of the zeros and ones that you compiled in the top of
your computer's memory, so to speak. So when you compile a program and
then you run it with ./whatever, or on a Mac or PC you double
click on it, the computer first-- the operating system first-- loads all
of your program zeros and ones, a.k.a. Machine code, into just one big chunk
of memory at the top, so to speak. Below that it stores global
variables-- any variables you have created in your program
that are outside of main and outside of any functions. Generally, the top of your file. Globals tend to go at the top there. Then there's this chunk of memory
that's generally known as the heap-- and we saw that word
briefly in Valgin's output, and then there's this other
chunk of memory called the stack. And it turns out that up until this
week you were using the stack heavily. Any time you use local variables in
a function they end up on the stack. Any time you use malloc, that
memory ends up on the heap. Now as the arrow suggests,
this actually looks like a problem waiting to happen because
if you use more and more and more heap, and more and more
and more stack, it's like two things barreling down the
tracks at one another-- this does not end well. And that's actually a problem. If you've ever heard the phrase
stack overflow, or use the website, this is the origin of its name. When you start to use
more and more and more memory by calling lots
and lots of functions or using lots and lots
of local variables, you use a lot of this stack memory. Or if you use malloc a lot and keep
calling malloc, malloc, malloc, and never really, or rarely calling
free, you just use more and more memory and eventually these two things might
overflow each other, at which point you're just out of luck. The program will crash or
something bad will happen. So the onus is on you
just to don't do that. But this is the design,
generally, of what's going on inside of
your computer's memory. Now within that memory, though,
there are certain conventions focusing on here, the stack. And in fact, let me go
over here with a marker and say that this represents the
bottom of my memory, ultimately. And so here we have a whole bunch of
wooden blocks and each of these squares represents a byte of memory
and this, for instance, might represent four bytes
altogether-- good enough for an int, or something like that. So in my original code that I wrote
earlier, that is in fact, buggy, what is in fact going on
inside the swap function? We can visualize it like this-- when
you run ./swap or any program for that matter, main is the first function
to get called with a C program, and so I'm just going to label
this bottom row of memory as main. And what were the two variables I
had in main called in this code? Yeah. x and y. And each of those was an
int, so that's four bytes, so it's deliberate
that I reserved four-- a chunk of wood here that's four bytes. So let me just call this x, and I'm just
going to write the number 1 in this box here. And then I had my other variable y, and
I'm going to put the number 2 there. What happens when main calls swap
like it does in this code here? Well, it has two variables of its
own, A and B, and A initially is 1 and B is initially 2, but it
has a third variable, tmp, which is a local variable in
addition to the arguments A and B that are passed in, so I'm going
to call this tmp, tmp over here. And what is the value of tmp? Well, we have to look back at the code. tmp initially gets the value of A.
All right, the value of a was 1, so tmp initially gets 1. That's step one in my
three line program. OK, A equals B. So that is assigned
from the right to the left of the B into the A So B is 2, A is
this, so let me go ahead and erase this and just overwrite that. So at this moment in the story
you have two copies of two, so that's OK though, because
the third line of code says tmp gets copied
into B. So what's tmp-- 1, gets copied into B, so let
me overwrite this 2 with a 1, and now what happens? Now unfortunately, the code ends. swap doesn't actually do anything
with the result, and the problem in C is that I could have had a return value. I could go in there
and change void to int, but which one am I going to return? The A or the B? The whole goal is to
swap two values, and it seems kind of lame if you
can't write a function to do something as common per
last week sorting algorithms as swapping two values. But what really happens? Well, even though when this
program starts running, main is using this chunk of memory
at the bottom in the so-called stack, and the stack is just like
a cafeteria stack of trays-- it grows up, like this. Here's main's memory on the stack. Here's the swap function's
memory on the stack. It's using three ints instead of two-- instead of only two. What happens when the function
returns, whether it's void or not? The sort of recollection that
this is swap's memory goes away and garbage values are left. So, adorably, we get rid
of these values here, and there's still data there--
technically, the numbers 1, 1, and 2 are still there in the computer's
memory but they no longer belong to us because the function has now returned. So they're still in there and this
is kind of an example visually of why there's other stuff in memory
even though you didn't put it there, necessarily. Sometimes you did put
it there, but now once swap returns you only should be
touching memory inside of main. But we've never actually
copied one value into main. We haven't returned anything and we
haven't solved this fundamentally. So how could we do this? Well, what if we instead passed
into swap not copies of x and y, calling them A and B. What if they
passed in breadcrumbs to x and y, sort of a treasure map that
will lead swap to the actual x and to the actual y? Today we have that
capability using pointers. So suppose that we
use this code instead. There's a lot of stars going on
here, which is a bit annoying, but let's consider what it
is we're trying to achieve. What if we pass in not x and y, but
the address of x and the address of y, respectively--
breadcrumbs, if you will-- that will lead swap to
the original values. Then what we do is we still
give ourselves a tmp variable, like an empty glass. It's still a glass, so
we still call it an int, but what do we want to put
into that temporary variable? We don't want to put A into it,
because that's an address now. We want to go to that
address per the star and put whatever's at that address. What do we then want to do? Well, we want to then copy
into whatever's at location A, we want to copy over to
location A's contents whatever is at location B's
contents and then lastly, we want to copy tmp into
whatever's at location B. So again, we're very deliberately
introducing all of these stars because we don't want to
change any of these addresses, we want to go to these addresses
per the reference operator and put values there,
or get values from. So what does this actually mean? Well, if I kind of rewind in this story
and I go back here, I still have tmp, although I'm going to delete its
value to begin with, I still have B and I still have A, but
what's going to be different this time is how I use A and B.
So let me finish erasing those. That's A on the left,
this is B on the right. At this point in the
story, we're rerunning swap with this new and improved version,
and let's see what happens. Well, x is presumably at some address. Maybe it's like 0x123, as always. What then does A get
when I'm using this code? The value of A is 0x123. What is the value of B? Maybe y is that 0x456. What goes in B? Well, I'm going to put 0x456,
and the what am I going to do? Based on these three
lines of code, I'm going to store in tmp whatever is at the
address in A. What is the address in A? That's this thing here, so
I'm going to put 1 in tmp. Line two-- I'm going to go to B-- all right, B is 456, so
I'm going to B and I'm going to store 2 at whatever is
at location A, and at location A is 123, so that's this,
so what am I going to do? I'm going to change this 1 to a 2. Last line of code-- get the
value of tmp, which is 1, and then put it at whatever the
location B is, so B, 456, go there and change it to be the value
of tmp, tmp, which puts 1 here. That's it for the code. There's still no return value. swap returns, which means
these three temporary variables are garbage values now. They can be reused by
subsequent function calls but now, I've actually
swapped the values of x and y. Which is to say what came as naturally
as the real world here for Mariana is not quite as simply
done in C because again, functions are isolated from each other. You can pass in values but you
get copies of those values. If you want one function to affect the
value of a variable somewhere else, you have to 1, understand
what's going on but 2, pass things in as by a pointer here. So if I go back to my code here,
I need to make a few changes now. Let me get rid of these extra printf's. Let me go in and add all these stars. So I'm dereferencing these
actual addresses here and here, and I've got to make one more change. How do I now call swap if swap is
expecting an int star and an int star? That is, the address of an int
and the address of another int. What do I change on line 11 here? Yeah. Sorry, a little louder. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Sorry,
the address of operator. So up here on line 11, we do
ampersand x and ampersand y. So that yes, we're technically
passing in a copy of a value, but this time the copy we're passing
in is technically an address, and as soon as we have an address, just
like when I held up the fuzzy finger-- the foamy finger-- I can point at
that address, I can go to that address and actually get a value from the
mailbox or put a value into the mailbox if I even want. So let's cross our fingers
now and do make swap, Enter. Oh my God, so many mistakes. Oh, I didn't remember
to change my prototype, so let me go way up here and
add two more stars because I made that change already. Make swap, ./swap, and viola--
now I have actually swapped. Thank you. Thank you. The two values. All right, so what more can we do here? Well, let me consider
that all this time we've been deliberately using
GetString and GetInt and GetFloat and so forth, but for a reason. These aren't just training wheels
for the sake of making things easier, they're actually in place
to make your code safer. And to illustrate this, let me go
ahead and open up one other file here. How about a file called scanf.c. It turns out that the old
school way-- the way in C, really, of getting user input,
is via functions like scanf, and let me go ahead and include
stdio.h, int main(void), and without using the CS50 library at
all for strings or for any of those get functions. Let me give myself an int called x. Let me just print out what the value of
x is, even though it's going to be a-- or rather, ask the user for
the value by asking them for x. And I'm going to use a function
called scanf that's going to scan in an integer using %i, and I'm going
to store whatever the human types in at this location. And then I'm going to go ahead and,
just so we can see what happened, I'm going to print out with %i
whatever the human typed in as follows. All right, so line eight
is week 1 style code. Line five and six is week 1 style code. So the curiosity today is this new line.
scanf is another function in stdio.h, and notice what I'm doing. I'm using the same syntax
that I use for printf, which is kind of a little clue-- a
format code to tell scanf what it is I want to scan in, that is, read
from the human's keyboard-- and I'm telling it where to put
whatever the human typed in. I can't just say x, because we run into
the same darn problem as with swap. I have to give a little
breadcrumb to the variable where I want scanf to
put the human's integer. And so this just tells the
computer to get an int. This is what you would have
had to type, essentially, in week 1 just to get
an int from the user, and there's a whole bunch of
things that can go wrong still, but that's the cryptic syntax we
would have had to show you in week 1. Let me go ahead and make scanf here-- oops-- user error. Put the semicolon in the wrong place. Make scanf, Enter. Oh my God. Non void doesn't return a value. Oh, thank you. Strike two. OK. Make scanf. There we go. OK, so scanf-- I'm going to type in a number like
50 and it just prints it back out. So that is the traditional way of
implementing something like GetInt. The problem, though, is when you
start to get into strings, things get dangerous quickly. Let me delete all of
this and give myself a string s, although wait a
minute-- we don't call it strings anymore-- char star to store a string. Then let me go ahead and just prompt the
user for a string, using just printf. Then let me go ahead and use scanf, ask
them for a string this time with %s, and store it at that address. Then let me go ahead and print
out whatever the human typed in just by using the same notation. So here, line five is the same thing
as string s, but we've taken back that layer today so it's char star s. This is just week one this is
just week one, line seven is new. scanf will also read from the human's
keyboard a string and store it at s. But that's OK, because s is an address. It's correct not to do the ampersand. It's not necessary. A string is and has always
been a char star, a.k.a string. The problem, though, arises as follows-- if I do make scanf-- oh my God, what did I do wrong-- I can't-- OK, we have certain
defenses in place with make. Let me do clang of scanf.c, an
output of program called scanf. All right, so I'm overriding
some of our pedagogical defenses that we have in place with make. Let me now run scanf of this version,
Enter, and let me type in something like, how about hi again. So it didn't even store something
and it weirdly printed out null. This time it's in lowercase,
but that is somewhat related. What did I fundamentally
do wrong though, here? Why is this getting
more and more dangerous? And let me illustrate
the point even more. What if I type in not just something
like hello, which also doesn't work. What if I do like, hellooooo and
make a really long string, Enter-- that still works. Can I do this again? Let's try again. Right, a really long,
unexpectedly long string. This is the nondeterminism kicking in. Enter. All right, damn it. I was trying to trigger
a segmentation fault but it wouldn't, but
the point still remains. It's still not working, but what's
the essence of why this isn't working, and it's not storing my actual input? Yeah. AUDIENCE: Do you have to make a space? DAVID J. MALAN: We have
to make space for it. So what we're missing here is
malloc, or something like that. So I could do that, I could
do something like this. Well, let the human type in
at least a three letter word so I could do malloc of 3
plus 1 for the null character. So let me give them four characters,
and let me go ahead and do make scanf-- whoops. Nope, sorry. clang, I have to-- nope. Dammit. Oh, include stdlib.h-- there we go. That gives me malloc, now I'm
going to recompile this with clang, now I'm going to rerun it, and now I'm
going to type in my first thing, hi. That now works. And let me get a little aggressive now
and type in hello, which is too long. Still works, but I'm getting lucky. Let me try a hellooooooo. Damn it, that still works, too. Sort of. But it actually-- not quite. There's some weirdness
going on there already. It turns out I can also do this. I could actually just say
char star four and give myself an array of four characters. Let me try this one more time. So let me rerun clang ./scanf. Hellooooooo, clearly exceeding
the four characters-- there we go. Thank you, all right. So the point here, though, is
if we hadn't given you GetInt, you would have had to use the
scanf thing-- not a huge deal because it seemed to work. But if we hadn't given you GetString you
would have had to do stuff like this, knowing about malloc already or
knowing about strings being erased, and even now there's a danger. If the human types in five letters,
six letters, 100 letters-- this code, like with the Hello input, will
probably just crash, which is bad. So GetString also has
this functionality built in where we have a
fancy loop inside such that we allocate using malloc as
many bytes as you physically type in, and we use malloc
essentially every keystroke. The moment you type in h-e-l-l-o, we're
laying the tracks as we go and we keep allocating more and more memory so that
we theoretically will never crash with GetString even though
it's this easy to crack-- this easy to crash your code
using scanf if you again did it without the help of a library. So where are we all going with this? Well, let me show you a
few final examples that'll pave the way for what
will be problem set four. Let me go ahead and open
up from today's code-- which is available on
the course's website-- for instance, a program like
this, called phonebook.c, and I'm just going to give
you a quick tour of it, that you'll see more details on in
the context of p-set four itself. We're going to introduce a few
new functions you're going to see. You're going to see a function called
fopen, which stands for file open, and it takes two arguments-- the
name of a file to open like a CSV that you might manipulate in Excel or
Google Spreadsheets or the like-- comma separated values, and then something
like A for append, R for read, W for write, depending on whether
you want to add to the file, just open it up, or change it. We're going to introduce
you to a file pointer. You'll see that capital file-- which is a little bit
unconventional-- capital file is a pointer to an actual file
on the computer's hard drive so that you can actually access
something like a CSV file, or heck, even images. And we're going to see
down below that you're also going to have the ability to write
files as well, or print to files. You'll see functions like
printf printf for file printf. Or fwrite-- file write-- which now that
you will begin to understand pointers, you'll have the ability to
actually not only read files-- text files, images, other
things-- but also write them out. In fact for instance, just as a teaser
here, JPEGs will be one of the things we focus on this week where
we give you a forensic image and your goal is to
recover as many photographs from this forensic image of a
digital camera as you possibly can. And the way you're going to do
that is by knowing in advance that every JPEG in the world starts
with these three bytes, written in hexadecimal, but these three numbers. And so in fact, just as
a teaser, let me open up an example you'll see on the
course's website for today. If I scroll through here,
you'll see a program that does a little something like this. And again, more on this-- if we could hit the button-- there we go. So here we have the notion of a byte
we're going to create for ourselves. We'll see a data type called byte,
which is a common convention. This gives me three bytes. And you're going to learn
about a function called fread, which reads from a file some number
of bytes-- for instance, three bytes. We might then use code like this. If bytes bracket zero
equals equals 0xFF and bytes bracket 1 equals 0xD8 and bytes bracket
2 equals 0xFF, all three of those bytes I just claimed represent a
JPEG, you'll see an output like this. Let me go ahead and run
this program as follows. Let me copy jpeg.c into my
directory from today's distribution. Let me do make jpeg, and let me run
jpeg on a file which is available online called lecture.jpeg, and I
claim yes, it's possibly a JPEG. Well, what is that file? Let me open it up for us, called
lecture.jpeg, and here, for instance, is that same photo with which we began
class, namely implemented as a JPEG. But what we're also
going to do this week is start to implement our own sort
of filters a la Instagram, whereby we might take images and actually
run them through a program that creates different versions thereof. For instance, using a
different file format called BMP, which essentially lays out
all of its pixels from left to right, top to bottom, in a grid. You're going to see a struct-- a data struct in C that's
way more complicated than the candidate
structure from the past, or the person structure
from the past, that looks like this, which is just
a whole bunch more values in it, but we'll walk you through
these in the p-set. And we might take a
photograph like this and ask you to run a few different
filters on it a la Instagram, like a black and white filter,
or grayscale, a sepia filter to give it some old school feel, or
a reflection like this to invert it, or blur it, even in this way. And just to end on a note
here, I have a version of this code ready to go that doesn't
implement all of those filters, it just implements one filter initially. Let me go ahead and just ready
this on my computer here. I'm going to go into my
own version of filter and you'll see a few
files that will give you a tour of this coming week
in bitmap.h, for instance, is a version of this structure that
I claimed existed a moment ago. And let me show you this file here,
helpers.c, in which there is a function called filter that I've already
implemented in advance today. But the ones we give you for the piece
that won't already be implemented, this function called filter
takes the height of an image, the width of an image, and
a two dimensional array. So rows and columns
of pixels, and then I have a loop like this that iterates over
all of the pixels in an image from top to bottom, left to right. And then notice what
I'm going to do here. I'm going to change the blue
value to be zero in this case, and the green value to
be zero in this case. But why? Well, the image I have
here in mind is this one, whereby we have this
hidden image that simply has old school style-- a
secret message embedded in it. And if you don't happen to have in
your dorm one of these secret decoder glasses that essentially
make everything red-- getting rid of the green in the
world and the blue in the world-- you can actually-- I'm actually
probably the only one who can read this right
now-- see what message is hidden behind all of this red noise. But if using my code written here in
helpers.c I get rid of all the blue in the picture and I get rid of
all the green in the picture, essentially implementing
the idea of this filter-- this red filter where you only see red-- well, let's go ahead and
compile this program. Make filter, run ./filter
on this hidden message.bmp. I'm going to save it in a
new file called message.bmp, and with one final flourish
we're going to open up message.bmp, which is the result
of having put on these glasses, and hopefully now you
too will see what I see. All right, that's it for CS50! We'll see you next time. [MUSIC PLAYING] SPEAKER 1: All right. This is CS 50. And this is already week 5,
which means this is actually our last week in C together. In fact, in just a few days'
time, what has looked like this and much more cryptic
than this perhaps, is going to be distilled into
something much simpler next week. When we transition to a
language called Python. And with Python, we'll still have our
conditionals, and loops, and functions, and so forth. But a lot of the low-level plumbing
that you might have been wrestling with, struggling with, frustrated by,
over the past couple of weeks, especially, now that
we've introduced pointers. And it feels like you probably
have to do everything yourself. In Python, and in a lot
of higher level languages so to speak-- more modern,
more recent languages, you'll be able to do so much more
with just single lines of code. And indeed, we're going to start
leveraging libraries, all the more code that other people wrote. Frameworks, which is collections of
libraries that other people wrote. And on top of all that, will you be
able to make even better, grander, more impressive projects, that actually solve
problems of particular interest to you. Particularly, by way of
your own final project. So last week though, in week 4,
recall that we focused on memory. And we've been treating this
memory inside of your computer is like a canvas, right. At the end of the day, it's just
zeros and ones, or bytes, really. And it's really up to you
what you do with those bytes. And how you interconnect them, how
you represent information on them. And arrays, were like
one of the simplest ways. We started playing
around with that memory. Just contiguous chunks of memory. Back-to-back, to back. But let's consider, for a
moment, some of the problems that pretty quickly arise with arrays. And then, today focus on what more
generally are called data structures. Using your computer's memory as
a much more versatile canvas, to create even
two-dimensional structures. To represent information,
and, ultimately, to solve more interesting problems. So here's an array of size 3. Maybe, the size of 3 integers. And suppose that this
is inside of a program. And at this point in the story,
you've got 3 numbers in it already. 1, 2 and 3. And suppose, whatever the context,
you need to now add a fourth number to this array. Like, the number 4. Well, instinctively, where
should the number 4 go? If this is your computer's
memory and we currently have this array 1, 2, 3, from what. Left to right. Where should the number 4
just, perhaps, naively go. Yeah, what do you think? AUDIENCE: Replace number 1. SPEAKER 1: Sorry? AUDIENCE: Replace number 1. SPEAKER 1: Oh, OK. So you could replace number 1. I don't really like
that, though, because I'd like to keep number 1 around. But that's an option. But I'm losing, of course, information. So what else could I do if
I want to add the number 4. Over there? AUDIENCE: On the right side of 3. SPEAKER 1: Yeah. So, I mean, it feels like
if there's some ordering to these, which seems kind
of a reasonable inference, that it probably belongs
somewhere over here. But recall last week, as we started
poking around a computer's memory, there's other stuff
potentially going on. And if fill that in, ideally, we'd
want to just plop the number 4 here. If we're maintaining this kind of order. But recall in the context
of your computer's memory, there might be other stuff there. Some of these garbage
values that might be usable, but we don't really know
or care what they are. As represented by Oscar here. But there might actually
be useful data in use. Like, if your program has not
just a few integers in this array, but also a string that
says like, "Hello, world." It could be that your computer has
plopped the H-E-L-L-O W-O-R-L-D right after this array. Why? Well, maybe, you created the
array in one line of code and filled it with 1, 2, 3. Maybe the next line of
code used GET-STRING. Or maybe just hard coded a string
in your code for "Hello, world." And so you painted yourself
into a corner, so to speak. Now I think you might claim,
well, let's just overwrite the H. But that's problematic
for the same reasons. We don't want to do that. So where else could the 4 go? Or how do we solve this problem
if we want to add a number, and there's clearly memory available. Because those garbage values are junk
that we don't care about anymore. So we could certainly reuse those. Where could the 4, and
perhaps this whole array, go? OK. So I'm hearing we could
move it somewhere. Maybe, replace some of
those garbage values. And honestly, we have a lot of options. We could use any of these
garbage values up here. We could use any of these down
here, or even further down. The point is there is plenty
of memory available as indicated by these Oscars, where
we could put 4, maybe even, 5, 6 or more integers. The catch is that we
chose poorly early on. Or we just got unlucky. And 1, 2, 3 ended up back-to-back with
some other data that we care about. All right, so that's fine. Let's go ahead and assume that
we'll abstract away everything else. And we'll plop the new
array in this location here. So I'm going to go ahead
and copy the 1 over. The 2 over. The 3 over. And then, ultimately, once
I'm ready to fill the 4, I can throw away, essentially,
the old array at this point. Because I have it now
entirely in duplicate. And I can populate it with the number 4. All right. So problem solved. That is a correct potential
solution to this problem. But, what's the trade off? And this is something we're going to
start thinking about all the more. What's the downside of having
solved this problem in this way? Yeah. I'm adding a lot of running time. It took me a lot of effort to
copy those additional numbers. Now, granted, it's a small array. 3 numbers, who cares. It's going to be over
in the blink of an eye. But if we start talking
about interesting data sets, web application data sets,
mobile app data sets. Where you have not just a few, but
maybe a few hundred, few thousand, a few million pieces of data. This is probably a suboptimal
solution to just, oh, move all your data from
one place to another. Because who's to say
that we're not going to paint ourselves into a new corner. And it would feel like you're wasting
all of this time moving stuff around. And, ultimately, just costing
yourself a huge amount of time. In fact, if we put this now into
the context of our Big O notation from a few weeks back, what might
the running time now of Search be for an array? Let's start simple. A throwback a couple of weeks ago. If you're using an array, to
recap, what was the running time of a Search algorithm in Big O notation? So, maybe, in the worst case. If you've got n numbers, 3 in this
case or 4, but n more generally. Big O of what for Search? Yeah. What do you think? AUDIENCE: Big O of n. SPEAKER 1: Big O of n. And what's your intuition for that? AUDIENCE: [INAUDIBLE]. SPEAKER 1: OK. Yeah. So if we go through each element,
for instance, from left to right, then Search is going to take
this a Big O running time. If, though, we're talking about
these numbers, specifically. And now I'll explicitly stipulate
that, yeah, they're sorted. Does that buy us anything? What would the Big O notation be
for Searching an array in this case, be it of size 3, or 4,
or n, more generally. AUDIENCE: Big O of n. SPEAKER 1: Big O of, not n, but rather? AUDIENCE: Log n. SPEAKER 1: Log n, right. Because we could use per week zero
binary search on an array like this, we'd have to deal with some rounding. Because there's not a perfect
number of elements at the moment. But you could use binary search. Go to the middle roughly. And then go left or
right, left or right, until you find the
element you care about. So Search remains in Big O
of log n when using arrays. But what about insertion, now? If we start to think
about other operations. Like, adding a number to this array,
or adding a friend to your contacts app, or Google finding
another page on the internet. So insertion happens all the time. What's the running time of Insert? When it comes to inserting into
an existing array of size n. How many steps might that take? Big O of n. It would be, indeed, n. Why? Because in the worst case,
where you're out of space, you have to allocate, it
would seem, a new array. Maybe, taking over some of
the previous garbage values. But the catch is, even
though you're only inserting one new number,
like the number 4, you have to copy over all the darn
existing numbers into the new one. So if your original array of
size n, the copying of that is going to take Big O of n plus 1. But we can throw away the plus 1
because of the math we did in the past. So Insert now becomes Big O of n. And that might not be ideal. Because if you're in the habit
of inserting things frequently, that could start to add
up, and add up, and add up. And this is why computer programs,
and websites, and mobile apps could be slow. If you're not being mindful
of these trade offs. So what about, just for good
measure, Omega notation. And maybe, the best case. Well just to recap
here, we could get lucky and Search could just take one step. Because you might just get
lucky, and boom the number you're looking for is right there in
the middle, if using binary search. Or even linear search, for that matter. And insert 2. If there's enough room, and we didn't
have to move all of those numbers-- 1, 2, and 3, to a new location. You could get lucky. And we could have, as
someone suggested, just put the number 4 right there at the end. And if we don't get lucky,
it might take n steps. If we do get lucky, it might just take
the one, or constant number, of steps. In fact, let me go ahead and do this. How about we do something like this? Let me switch over to some code here. Let me start to make a
program called List.C. And in List.C, let's
start with the old way. So we follow the breadcrumbs we've
laid for ourselves as follows. So in this List.C, I'm going
to include standardio.h. Int main(void) as usual. Then inside of my code here, I'm
going to go ahead and give myself the first version of memory. So int list 3 is now implemented
at the moment, in an array. So we're rewinding for
now to week 2 style code. And then, let me just
initialize this thing. At the first location will be 1. At the next location will be 2. And at the last location will be 3. So the array is zero indexed always. I, for just the sake
of discussion though, am putting in the numbers 1, 2,
3, like a normal person might. All right. So now let's just print these out. 4 int i gets 0. I less than 3, i++. Let's go ahead now and
print out using printf. %i/n list [i]. So very simple program, inspired
by what we did in week 2. Just to create and then print
out the contents of an array. So let's Make List. So far, so good. ./list
And voila, we see 1, 2, 3. Now let's start to practice some of what
we're preaching with this new syntax. So let me go in now and get
rid of the array version. And let me zoom out a little bit
to give ourselves some more space. And now let's begin to
create a list of size 3. So if I'm going to do
this now, dynamically, so that I'm allocating these
things again and again, let me go ahead and do this. Let me give myself a list that's of type
int* equal the return value of malloc of 3 times the size of an int, so what
this is going to do for me is give me enough memory for that very first
picture we drew on the board. Which was the array
containing 1, 2, and 3. But laying the foundation
to be able to resize it, which was ultimately the goal. So my syntax is a little different here. I'm going to use malloc and get memory
from the so-called "heap", as we called it last week. Instead of using the stack by just
doing the previous version where I said, int list 3. That is to say this line of code from
the first version is in some sense identical to this line of
code in the second version. But the first line of
code puts the memory on the stack, automatically, for me. The second line of code,
that I've left here now, is creating an array of size 3,
but it's putting it on the heap. And that's important because it was only
on the heap and via this new function last week, malloc. That you can actually ask for more
memory, and even give it back. When you just use the
first notation int list 3, you have permanently given
yourself an array of size 3. You cannot add to that in code. So let me go ahead and do this. If list==null, something went wrong. The computers out of memory. So let's just return 1 and
quit out of this program. There's nothing to see here. So just a good error check there. Now let me go ahead and
initialize this list. So list [0] will be 1 again. List [1] will be 2. And list [2] will be 3. So that's the same kind
of syntax as before. And notice this equivalence. Recall that there's this relationship
between chunks of memory and arrays. And arrays are really just doing
pointer arithmetic for you, where the square bracket notation is. So if I've asked myself here, in line
5, for enough memory for 3 integers, it is perfectly OK to treat it now like
an array using square bracket notation. Because the computer will
do the arithmetic for me and find the first location,
the second, and the third. If you really want to be
cool and hacker-like, well, you could say list=1,
list+1=2, list+2=3. That's the same thing
using very explicit, pointer arithmetic, which we
looked at briefly last week. But this is atrocious to
look at for most people. It's just not very user friendly. It's longer to type, so
most people, even when allocating memory dynamically
as I did a second ago, would just use the more
familiar notation of an array. All right. So let's go on. Now suppose time passes
and I realize, oh shoot, I really wanted this array to
be of size 4 instead of size 3. Now, obviously, I could just
rewind and like fix the program. But suppose that this is
a much larger program. And I've realized, at
this point, that I need to be able to dynamically add more
things to this array for whatever reason. Well let me go ahead and do this. Let me just say, all
right, list should actually be the result of asking for 4
chunks of memory from malloc. And then, I could do something
like this, list [3]=4. Now this is buggy, potentially,
in a couple of ways. But let me ask first, what's really
wrong, first, with this code? The goal at hand is to start with
the array of size 3 with the 1, 2, 3. And I want to add a number 4 to it. So at the moment, in line 17, I've asked
the computer for a chunk of 4 integers. Just like the picture. And then I'm adding the number 4 to it. But I have skipped a few
steps and broken this somehow. Yeah. AUDIENCE: You don't know
exactly [INAUDIBLE].. SPEAKER 1: Yeah. I don't necessarily know where
this is going to end up in memory. It's probably not
going to be immediately adjacent to the previous chunk. And so, yes, even though I'm
putting the number for there, I haven't copied the 1, the 2, or
the 3 over to this chunk of memory. So well let me fix-- well, that's actually, indeed,
really the essence of the problem. I am orphaning the
original chunk of memory. If you think of the picture that
I drew earlier, the line of code up here on line 5 that allocates
space for the initial 3 integers. This code is fine. This code is fine. But as soon as I do this, I'm
clobbering the value of list. And saying no, don't point
at this chunk of memory. Point at this chunk of memory, at
which point I've forgotten if you will, where the original chunk of memory is. So the right way to do something like
this, would be a little more involved. Let me go ahead and give
myself a temporary variable. And I'll literally call it TMP. T-M-P, like I did last week. So that I can now ask the computer for
a completely different chunk of memory of size 4. I'm going to again say
if TMP equals null, I'm going to say bad
things happened here. So let me just return 1. And you know what,
just to be tidy, let me free the original list before I quit. Because remember from
last week, any time you use malloc you
eventually have to use free. But this chunk of code here
is just a safety check. If there's no more memory,
there's nothing to see here. I'm just going to clean
up my state and quit. But now, if I have asked
for this chunk of memory, now I can do this 4 int i gets 0. I is less than 3, i++. What if I do something like this? TMP [i] equals list [i]. That would seem to have the effect
of copying all of the memory from one to the other. And then, I think I need to
do one last thing TMP [3] gets the number 4, for instance. Again, I'm hard coding the numbers
for the sake of discussion. After I've done this,
what could I now do? I could now set list equals to TMP. And now, I have updated
my linked list properly. So let me go ahead and do this. 4 int i gets 0. I is less than 4, i++. Let me go ahead and print each of these
elements out with %i using list [i]. And then, I'm going to return 0 just
to signify that all is successful. Now so to recap, we
initialize the original array of size 3 and plug-in
the values 1, 2, 3. Time passes. And then, I realize, wait a
minute, I need more space. And so I asked the computer
for a second chunk of memory. This one of size 4. Just as a safety check, I make
sure that TMP doesn't equal null. Because if it does I'm out of memory. So I should just quit altogether. But once I'm sure that
it's not null, I'm going to copy all the values from
the old list into the new list. And then, I'm going to add my new
number at the end of that list. And then, now that I'm done playing
around with this temporary variable, I'm going to remember
in my list variable what the addresses of this
new chunk of memory. And then, I'm going to print
all of those values out. So at least, aesthetically, when I
make this new version of my list, except for my missing semicolon. Let me try this again. When I make lists, Oh OK. What did I do this time? Implicitly declaring a
library function malloc. What's my mistake any time
you see that kind of error? AUDIENCE: Library. SPEAKER 1: Yeah. A library. So up here, I forgot to do include
stdlib.h, which is where malloc lives. Let me go ahead and,
again, do make list. There we go. So I fixed that dot/list. And I should see 1, 2, 3, 4. But they're still a bug here. Does anyone see the
the-- bug or question? AUDIENCE: You forgot to free them. SPEAKER 1: I'm sorry, say again. AUDIENCE: You forgot to free them. SPEAKER 1: I forgot to
free the original list. And we could see this, even if not
just with our own eyes or intuition. If I do something like
Valgrind of dot/list, remember our tool from this past week. Let me increase the size of my
terminal window, temporarily. The output is crazy cryptic at first. But, notice that I have definitely
lost some number of bytes here. And indeed, it's even
pointing at the line number in which some of those bytes were lost. So let me go ahead and back to my code. And indeed, I think what I need to do
is, before I clobber the value of list pointing it at this new chunk
of memory instead of the old, I think I now need to
first, proactively, say free the old list of memory. And then, change its value. So if I now do Make List and do dot
/list, the output is still the same. And, if I cross my fingers
and run Valgrind again after increasing my window
size, hopefully here. Oh, still a bug. So better. It seems like less memory is lost. What have I now forgotten to do? AUDIENCE: You forgot to free the end. SPEAKER 1: I forgot to free
it at the very end, too. Because I still have a chunk of
memory that I got from malloc. So let me go to the very
bottom of the program now. And after I'm done senselessly
just printing this thing out, let me free the new list. And now let me do Make List, dot/list. It's still works, visually. Now let's do Valgrind
of dot/list, Enter. And now, hopefully, all
heap blocks were freed. No leaks are possible. So this is perhaps the best output
you can see from a tool like Valgrind. I used the heap, but I freed
all the memory as well. So there were 2 fixes needed there. All right. Any questions then on this array-based
approach, the first of which is statically allocating
an array, so to speak. By just hard coding the number 3. The second version now is
dynamically allocating the array, using not the stack but the heap. But, it too, suffers from the
slowness we described earlier, of having to copy all those
values from one to the other. OK. A hand was over here. AUDIENCE: Why do you not
have to free the TMP? SPEAKER 1: Good question. Why did I not have to free the TMP? I essentially did eventually. Because TMP was pointing
at the chunk of 4 integers. But on line 33 here,
I assigned list to be identical to what TMP was pointing at. And so, when I finally freed the list,
that was the same thing as freeing TMP. In fact, if I wanted to, I could say
free TMP here and it would be the same. But conceptually, it's wrong. Because at this point in the story, I
should be freeing the actual list, not that temporary variable. But they were the same at
that point in the story. Yeah. AUDIENCE: Is [? the line ?] part of it? SPEAKER 1: Good question. And long story short,
everything we're doing thus far is still in the world of arrays. The only distinction
we're making is that in version 1, when I said int list
[3], that was an array of fixed size. So-called statically allocated
on the stack, as per last week. This version now is still dealing with
arrays, but I'm flexing my muscles and using dynamic memory allocation. So that I can still use an
array per the first pictures we started talking about. But I can at least grow
the array if I want. So we haven't even now solved this, even
better in a sense, with linked lists. That's going to come next. Yeah. AUDIENCE: How are you able to free
list and then still make list? SPEAKER 1: How am I able to free list? I freed the original address of list. I, then, changed what list is storing. I'm moving its arrow to
a new chunk of memory. And that is perfectly reasonable
for me to now manipulate because now list is pointing
at the same value of TMP. And TMP is what was given the return
value of malloc, the second time. So that chunk of memory is valid. So these are just squares
on the board, right. There's just pointers inside of them. So what I'm technically
saying is, and I'm not pointing I'm not freeing
list per se, I am freeing the chunk of memory that begins
at the address currently in list. Therefore, if a few lines later, I
change what the address is in list. Totally reasonable to then touch that
memory, and eventually free it later. Because you're not freeing
the variable per se, you're freeing the
address in the variable. Good distinction. All right. So let me back up here and
now make one final edit. So let's finish this with
one final improvement here. Because it turns out,
there's a somewhat better way to actually resize an array
as we've been doing here. And there's another function in stdlib
that's called realloc, for re-allocate. And I'm just going to go in and
make a little bit of a change here so that I can do the following. Let me go ahead and
first comment this now, just so we can keep track of what's
been going on this whole time. So dynamically allocate
an array of size 3. Assign 3 numbers to that array. Time passes. Allocate new array of size 4. Copy numbers from old
array into new array. And add fourth number to new array. Free old array. Remember, if you will, new array
using my same list variable. And now, print new array. Free new array. Hopefully, that helps. And we'll post this code online after
2, which tells a more explicit story. So it turns out that we can reduce
some of the labor involved with this. Not so much with the printing
here, but with this copying. Turns out c does have a
function called realloc, that can actually handle the resizing
of an array for you, as follows. I'm going to scroll up
to where I previously allocated a new array of size 4. And I'm instead going to say this,
resize old array to be of size 4. Now, previously this wasn't
necessarily possible. Because recall that we
had painted ourselves into a corner with the
example on the screen where "Hello, world" happened to
be right after the original array. But let me do this. Let me use realloc, for re-allocate. And pass in not just the size
of memory we want this time, but also the address
that we want to resize. Which, again, is this array called list. All right. The code thereafter is
pretty much the same. But what I don't need to do is this. So realloc is a pretty handy
function that will do the following. If at the very beginning of class,
when we had 1, 2, 3 on the board. And someone's instinct was to just plop
the 4 right at the end of the list. If there's available memory,
realloc will just do that. And boom, it will just grow the array
for you in the computer's memory. If, though, it realizes, sorry, there's
already a string like "Hello, world" or something else there,
realloc will handle the trouble of moving that whole
array from 1 chunk of memory, originally, to a new chunk of memory. And then realloc will return to you,
the address of that new chunk of memory. And it will handle the process
of freeing the old chunk for you. So you do not need to do this yourself. So in fact, let me go ahead
and get rid of this as well. So realloc just condenses, a lot of what
we just did, into a single function. Whereby, realloc handles it for you. All right. So that's the final improvement
on this array-based approach. So what now, knowing
what your memory is, what can we now do with it that
solves that kind of problem? Because the world is
going to get really slow. And our apps, and our phones, and our
computers are getting really slow, if we're just constantly wasting
time moving things around in memory. What could we perhaps do instead? Well there's one new
piece of syntax today that builds on these 3 pieces
of syntax from the past. Recall, that we've
looked at struct, which is a keyword in C, that just lets
you invent your own structure. Your own variable, if you will,
in conjunction with typedef. Which lets you say a person has a name
and a number, or something like that. Or a candidate has a name
and some number of votes. You can encapsulate multiple pieces of
data inside of just one using struct. What did we use the Dot Notation
for now, a couple of times? What does the Dot operator do in C? AUDIENCE: Access the structure. SPEAKER 1: Perfect. To access the field
inside of a structure. So if you've got a person
with a name and a number, you could say something like
person.name or person.number, if person is the name
of one such variable. Star, of course, we've
seen now in a few ways. Like way back in week 1, we
saw it as like, multiplication. Last week, we began to see it
in the context of pointers, whereby, you use it
to declare a pointer. Like, int* p, or something like that. But we also saw it in
one other context, which was like the opposite, which
was the dereference operator. Which says if this is
an address, that is if this is a variable like a pointer,
and you put a star in front of it then with no int or no char,
no data type in front of it. That means go to that address. And it dereferences the pointer
and goes to that location. So it turns out that using
these 3 building blocks, you can actually start to now use
your computer's memory almost any way you want. And even next week, when
we transition to Python, and you start to get a
lot of features for free. Like a single line of
code will just do so much more in Python than it does in C. It
boils down to those basic primitives. And just so you've seen it already. It turns out that it's so
common in C to use this operator to go inside of a structure and
this operator to go to an address, that there's shorthand
notation for it, a.k.a. syntactic sugar. That literally looks like an arrow. So recall last week, I was in
the habit of pointing, even with the big foam finger. This arrow notation, a
hyphen and an angled bracket, denotes going to an address and
looking at a field inside of it. But we'll see this in
practice in just a bit. So what might be the
solution, now, to this problem we saw a moment ago whereby, we had
painted ourselves into a corner. And our memory, a few moments
ago, looked like this. We could just copy the whole existing
array to a new location, add the 4, and go about our business. What would another, perhaps
better solution longer term be, that doesn't require
constantly moving stuff around? Maybe hang in there for
your instincts if you know the buzz phrase we're looking for
from past experience, hang in there. But if we want to avoid
moving the 1, 2, and the 3, but we still want to be able
to add endless amounts of data. What could we do? Yeah. So maybe create some kind
of list using pointers that just point at a new location, right. In an ideal world, even
though this piece of memory is being used by this h in
the string "Hello, world", maybe we could somehow use
a pointer from last week. Like an arrow, that says after the
3, oh I don't know, go down over here to this location in memory. And you just stitch together
these integers in memory so that each one leads to the next. It's not necessarily the case
that it's literally back-to-back. That would have the
downside, it would seem, of costing us a little bit of space. Like a pointer, which recall,
takes up some amount of space. Typically 8 bytes or 64 bits. But I don't have to copy potentially
a huge amount of data just to add one more number. And so these things do have a name. And indeed, these things
are what generally would be called a linked list. A linked list captures
exactly that intuition of linking together things in memory. So let's take a look at an example. Here's a computer's
memory in the abstract. Suppose that I'm trying
to create an array. Let's generalize it as
a list, now, of numbers. An array has a very specific meaning. It's memory that's contiguous,
back, to back, to back. At the end of the day, I as the
programmer, just care about the data-- 1, 2, 3, 4, and so forth. I don't really care how it's stored. I don't care how it's stored
when I'm writing the code, I just wanted to work
at the end of the day. So suppose that I first
insert my number 1. And, who knows, it ends up,
up there at location, 0X123, for the sake of discussion. All right. Maybe there's something already here. And heck, maybe there's
something already here, but there's plenty of other options
for where this thing can go. And suppose that, for
the sake of discussion, the first available
spot for the next number happens to be over here at location
0X456, for the sake of discussion. So that's where I'm going
to plop the number 2. And where might the number 3 end up? Oh I don't know, maybe
down over there at 0X789. The point being, I don't know
what is, or really care about, everything else that's
in the computer's memory. I just care that there are at
least 3 locations available where I can put my 1, my 2, and my 3. But the catch is, now that
we're not using an array, we can't just naively assume that
you just add 1 to an index and boom, you're at the next number. Add 2 to an index, and boom
you're at the next, next number. Now you have to leave these little
breadcrumbs, or use the arrow notation, to lead from one to the other. And sometimes, it might be
close, a few bytes away. Maybe, it's a whole gigabyte away
in an even bigger computer's memory. So how might I do this? Like where do these pointers
go, as you proposed? All right. All I have access to here are bytes. I've already stored the
1, the 2, and the 3. So what more should I do? OK, yeah. So let me, you put the pointers
right next to these numbers. So let me at least plan ahead, so that
when I ask the computer like malloc, recall from last week, for some
memory, I don't just ask it now for space for just the number. Let me start getting
into the habit of asking malloc for enough space for the number
and a pointer to another such number. So it's a little more aggressive
of me to ask for more memory. But I'm planning ahead. And here is an example of a trade off. Almost any time in CS, when you start
using more space, you can save time. Or if you try to conserve space,
you might have to lose time. It's being that trade off there. So how might I solve this? Well let me abstract this away. And either next to or below, I'm
just drawing it vertically, just for the sake of discussion. So the arrows are a bit prettier. I've asked malloc for
now twice as much space, it would seem, than I previously needed. But I'm going to use this second chunk
of memory to refer to the next number. And I'm going to use this chunk
of memory to refer to the next, essentially, stitching
this thing together. So what should go in this first box? Well, I claim the number, 0X456. And it's written in hex because
it represents a memory address. But this is the equivalent of drawing
an arrow from one to the other. As a little check here, what
should go in this second box if the goal is to stitch these
together in order 1, 2, 3? Feel free to just shout this out. AUDIENCE: 0X789. SPEAKER 1: OK, that worked well. So 0X789, indeed. And you can't do that with the hands
because I can't count that fast. So 0X789 should go here because that's
like a little breadcrumb to the next. And then, we don't really have
terribly many possibilities here. This has to have a value, right. Because at the end of the day, it's
got to use its 64 bits in some way. So what value should go here,
if this is the end of this list? AUDIENCE: 0. SPEAKER 1: So it could be 0X123. The implication being that
it would be a cyclical list. Which is OK, but
potentially problematic. If any of you have accidentally
lost control over your code space because you had an infinite loop,
this would seem a very easy way to give yourself the accidental
probability of an infinite loop. What might be simpler than
that and ward that off? AUDIENCE: Null. SPEAKER 1: Say again? AUDIENCE: Null. SPEAKER 1: So just the null character. Not N-U-L, confusingly, which
is at the end of strings. But N-U-L-L, as we
introduced it last week. Which is the same as 0x0. So this is just a special value
that programmers decades ago decided that if you store the address
0, that's not a valid address. There's never going to be
anything useful at 0x0. Therefore, it's a sentinel
value, just a special value, that indicates that's it. There's nowhere further to go. It's OK to come back to your
suggestion of making a cyclical list. But we'd better be
smart enough to, maybe, remember where did the list start
so that you can detect cycles. If you start looping around
in this structure, otherwise. All right. But these addresses, who really
cares at the end of the day if we abstract this away. It really just now looks like this. And indeed, this is how most anyone
would draw this on a whiteboard if having a discussion at work. Talking about what data
structure we should use to solve some problem
in the real world. We don't care generally
about the addresses. We care that in code we can access them. But in terms of the concept
alone this would be, perhaps, the right way to think about this. All right, let me pause
here and see if there's any questions on this idea of creating
a linked list in memory by just storing, not just the numbers like 1,
2, 3, but twice as much data. So that you have little
breadcrumbs in the form of pointers that can lead you from one to the next. Any questions on these linked lists? Any questions? No? All right. Oh, yeah. Over here. AUDIENCE: So does this takes
time more memory than an array? SPEAKER 1: This does take
more memory than an array because I now need space
for these pointers. And to be clear, I technically
didn't really draw this to scale. Thus far, in the class, we've
generally thought about integers like, 1, 2 and 3, as
being 4 bytes, or 32 bits. I made the claim last week that
on modern computer's pointers tend to be 8 bytes or 64 bits. So, technically, this box should
actually be a little bigger. It was just going to look a
little stupid in the picture. So I abstracted it away. But, indeed, you're using
more space as a result. AUDIENCE: [INAUDIBLE]. SPEAKER 1: Oh, how does-- sorry. How does the computer identify
useful data from used data? So, for instance, garbage
values or non-garbage values. For now, think of that
as the job of malloc. So when you ask malloc for memory,
as we started to last week, malloc keeps track of the
addresses of the memory it has handed to as valid values. The other type of memory you
use, not just from the heap. Because recall we briefly
discussed that malloc uses space from the heap, which was drawn at the
top of the picture, pointing down. There's also stack memory, which is
where all of your local variables go. And where all of the memory
used by individual functions go. And that was drawn in the
picture is working its way up. That's just an artist's
rendition of direction. The compiler, essentially,
will also help keep track of which values are
valid or not inside of the stack. Or really the underlying
code that you've written will keep track of that for you. So it's managed for you at that point. All right. Good question. Sorry it took me a bit to catch on. So let's now translate
this to actual code. How could we implement this idea
of, let's call these things nodes. And that's a term of our NCS. Whenever you have some data structure
that encapsulates information, node, N-O-D-E, is the generic term for that. So each of these might
be said to be a node. Well, how can we do this? Well a couple of weeks ago, we saw
how we could represent something like a student or a candidate. And a student, or rather a person,
we said has a name and a number. And we used a few pieces of syntax here. One, we use the struct keyword,
which gives us a data structure. We use typedef, which defines the
name person to be our new data type representing that whole structure. So we probably have the
right ingredients here to build up this thing called a node. And just to be clear, what should
go inside of one of these nodes, do we think? It's not going to be a name
or a number, obviously. But what should a node have in
terms of those fields, perhaps? Yeah? AUDIENCE: [? Data. ?] SPEAKER 1: So a number like a
number and a pointer in some form. So let's translate this to actual code. So let's rename person to node
to capture this notion here. And the number is easy. If it's just going to
be an int, that's fine. We can just say int number,
or int n, or whatever you want to call that particular field. The next one is a little non-obvious. And this is where things
get a little weird at first, but, in retrospect, it
should all fit together. Let me propose that, ideally, we
would say something like node* next. And I could call the word
next anything I want. Next just means what comes after
me is the notion I'm using it at. So a lot of CS people would
just use next to represent the name of this pointer. But there's a catch here. C and C compilers are
pretty naive, recall. They only look at code top
to bottom, left to right. And any time they encounter
a word they have never seen before, bad things happen. Like, you can't compile your code. You get some cryptic
error message or the like. And that seems to be
about to happen here. Because if the compiler is reading
this code from top to bottom, it's going to say, oh,
inside of this struct should be a variable called next. Which is of type node*. What the heck is a node? Because it literally does
not find out until 2 lines later, after that semicolon. So the way to avoid this, which
we haven't quite seen before, is that you can temporarily name this
whole thing up here, struct node. And then, down here inside of the
data structure, you say struct node*. And then, you leave the rest alone. This is a workaround this is
possible because now you're teaching the compiler, from
the first line, that here comes a data structure called struct node. Down here, you're shortening the name
of this whole thing to just node. Why? It's just a little more convenient
than having to write struct everywhere. But you do have to write struct
node* inside of the data structure. But that's OK because it's
already come into existence now, as of that first line of code. So that's the only
fundamental difference between what we did last week
with a person or a candidate. We just now have to use this
struct workaround, syntactically. All right. Yeah, question. AUDIENCE: So [INAUDIBLE] have like
right next to the [INAUDIBLE] point to another [INAUDIBLE]. SPEAKER 1: Why is the next variable
a struct node* pointer and not an int star pointer, for instance? So think about the picture
we are trying to draw. Technically, yes, each of these
arrows I deliberately drew is pointing at the number. But that's not alone. They need to point at the
whole data structure in memory. Because the computer,
ultimately, and the compiler, in turn, needs to know that this
chunk of memory is not just an int. It is a whole node. Inside of a node is a number
and also another pointer. So when you draw these
arrows, it would be incorrect to point at just the number. Because that throws
away information that would leave the compiler
wondering, OK, I'm at a number. Where the heck is the pointer? You have to tell it that
it's pointing at a whole node so it knows a few bytes away
is that corresponding pointer. Good question. Yeah. AUDIENCE: How do you [INAUDIBLE]. SPEAKER 1: Really good question. It would seem that just as
copying the array earlier required twice as much memory,
because we copied from old to new. So, technically, twice as much
plus 1 for the new number. Here, too, it looks like we're
using twice as much memory, also. And to my comment earlier, it's
even more than twice as much memory because these pointers are 8 bytes, and
not just 4 bytes like a typical integer is. The differences are these. In the context of the array, you
were using that memory temporarily. So, yes, you needed
twice as much memory. But then you were quickly
freeing the original array. So you weren't consuming long-term,
more memory than you might need. The difference here, too, is
that, as we'll see in a moment, it turns out it's going to be
relatively quick for me, potentially, to insert new numbers in here. Because I'm not going to have
to do a huge amount of copying. And even though I might still have
to follow all of these arrows, which is going to take some
amount of time, I'm not going to have to be asking for
more memory, freeing more memory. And certain operations in the computer,
anything involving asking for or giving back memory, tends to be slower. So we get to avoid
that situation as well. There's going to be
some downsides, though. This is not all upside. But we'll see in a bit just what some
of those trade offs actually are. All right. So from here, if we go back to the
structure in code as we left it, let's start to now build up a
linked list with some actual code. How do you go about, in C,
representing a linked list in code? Well, at the moment, it would
actually be as simple as this. You declare a variable,
called list, for instance. That itself stores
the address of a node. That's what node* means. The address of a node. So if you want to store
a linked list in memory, you just create a variable
called list, or whatever else. And you just say that
this variable is going to be pointing at the first node in a
list, wherever it happens to end up. Because malloc is ultimately going
to be the tool that we use just to go get at any one particular
node in memory. All right. So let's actually do
this in pictorial form. When you write a line of
code, like I just did here-- and I do not initialize it to
anything with the assignment operator, an equal sign. It does exist in memory as a box,
as I'll draw it here, called list. But I've deliberately
drawn Oscar inside of it. Why? To connote what exactly? AUDIENCE: Garbage value. SPEAKER 1: It's a garbage value. I have been allocated the
variable in memory, called list. Which is going to give me 64 bits
or 8 bytes somewhere drawn here with this box. But if I myself have not
used the assignment operator, it's not going to get magically
initialized to any particular address for me. It's not going to even give me a node. This is literally just going to be an
address of a future node that exists. So what would be a solution here? Suppose that I'm beginning
to create my linked list, but I don't have any nodes yet. What would be a sensible thing to
initialize the list to, perhaps? AUDIENCE: Null. SPEAKER 1: Yeah, again. AUDIENCE: To null. SPEAKER 1: So just null, right. When in doubt with
pointers, generally it's a good thing to
initialize things to null, so at least it's not a garbage value. It's a known value. Invalid, yes. But it's a special
value you can then check for with a conditional, or the like. So this might be a better
way to create a linked list, even before you've inserted any
numbers into the thing itself. All right. So after that, how can we go about
adding something to this linked list? So now the story looks like this. Oscar is gone because inside
of this box is all zero bits. Just because it's nice and clean, and
this represents an empty linked list. Well, if I want to add the number 1
to this linked list, what could I do? Well, perhaps I could
start with code like this. Borrowing inspiration from last week. Let's ask malloc for enough
space for the size of a node. And this gets to your question earlier,
like, what is it I'm manipulating here? I don't just need space for an int and
I don't just need space for a pointer. I need space for both. And I gave that thing a name, node. So size of node figures out
and does the arithmetic for me. And gives me back the
right number of bytes. This, then, stores the address
of that chunk of memory in what I'll temporarily called n. Just to represent a generic new node. And it's of type node*. Because just like last week when I
asked malloc for enough space for an int and I stored it in an int* pointer. This week, if I'm asking
for memory for a node, I'm storing it in a node* pointer. So technically, nothing new
there except for this new term of art in data structure called node. All right. So what does that do for me? It essentially draws a
picture like this in memory. I still have my list variable from
my previous line of code initialize to null. And that's why I've drawn it blank. I also now have a
temporary variable called n, which I initialize to
the return value of malloc. Which gave me one of
these nodes in memory. But I've drawn it having
garbage values, too, because I don't know what int is there. I don't know what pointer is there. It's garbage values because malloc does
not magically initialize memory for me. There is another function for that. But malloc alone just says,
sure, use this chunk of memory. Deal with whatever is there. So how can I go about
initializing this to known values? Well, suppose I want to insert the
number 1 and then, leave it at that. A list of size 1, I could
do something like this. And this is where you have to
think back to some of these basics. My conditional here is asking the
question if n does not equal null. So that is, if malloc
gave me valid memory, and I don't have to quit altogether
because my computer's out of memory. If n does not equal null, but
is equal to valid address, I'm going to go ahead and do this. And this is cryptic looking syntax now. But does someone want to take a stab
at translating this inside line of code to English, in some sense? How might you explain what that
inner line of code is doing? *n. number equals 1. Let me go further back. Nope? OK, over here. Yeah. AUDIENCE: [INAUDIBLE]. SPEAKER 1: Perfect. The place that n is pointing
to, set it equal to 1. Or using the vernacular of going
there, go to the address in n and set it's number field to 1. However you want to think
about it, that's fine. But the * again is the
dereference operator here. And we're doing the
parentheses, which we haven't needed to do before because we
haven't dealt with pointers and data structures together until today. This just means go there first. And then once you're
there, go access number. You don't want to do one
thing before the other. So this is just enforcing
order of operations. The parentheses just like
in grade school math. All right. So this line of code is cryptic. It's ugly. It's not something most
people easily remember. Thankfully, there's that syntactic
sugar that simplifies this line of code to just this. And this, even though
it's new to you today, should eventually feel
a little more familiar. Because this now is shorthand
notation for saying, start at n. Go there as by following the arrow. And when you get there,
change the number field. In this case, to 1. So most people would not
write code like this. It's just ugly. It's a couple extra keystrokes. This just looks more like the artist's
renditions we've been talking about. And how most CS people would think about
pointers as really just being arrows in some form. All right. So what have we just done? The picture now, after setting number to
1, looks a little something like this. So there's still one step missing. And that's, of course, to
initialize, it would seem, the pointer in this new node
to something known like null. So I bet we could do this like this. With a different line
of code, I'm just going to say if n does not equal null,
then set n's next field to null. Or more pedantically, go
to n, follow the arrow, and then update the next field
that you find there to equal null. And again, this is just
doing some nice bookkeeping. Technically speaking,
we might not need to set this to null if we're going to keep
adding more and more numbers to it. But I'm doing it step-by-step so
that I have a very clean picture. And there's no bugs in
my code at this point. But I'm still not done. There's one last thing I'm
going to have to do here. If the goal, ultimately, was to insert
the number 1 into my linked list, what's the last step I
should, perhaps, do here? Just been English is fine. Yeah. AUDIENCE: Set the pointer value to null. SPEAKER 1: Yes. I now need to update the actual
variable, that represents my linked list, to point at this brand new node. That is now perfectly initialized as
having an integer and a null pointer. Yeah, technically, this
is already pointing there. But I describe this deliberately
earlier as being temporary. I just needed this to get it back from
malloc and clean things up, initially. This is the long term
variable I care about. So I'm going to want to do
something simple like this. List equals n. And this seems a little
weird that list equals n. But again, think about
what's inside this box. At the moment this is null
because there is no linked list at the beginning of our story. N is the address of the beginning, and
it turns out, end of our linked list. So it stands to reason that
if you set list equal to n, that has the effect of
copying this address up here. Or really just copying the
arrow into that same location so that now the picture looks like this. And heck, if this was a temporary
variable, it will eventually go away. And now, this is the picture. So an annoying number
of steps, certainly, to walk through verbally like this. But it's just malloc to
give yourself a node, initialize the 2 fields inside of
it, update the linked list, and boom, you're on your way. I didn't have to copy anything. I just had to insert
something in this case. Let me pause here to see if there's
any questions on those steps. And we'll see before long it all
in context with some larger code. AUDIENCE: So if the
statements [INAUDIBLE].. SPEAKER 1: Yes. I drew them separately just
for the sake of the voiceover of doing each thing very methodically. In real code, as we'll
transition to now, I could have and should
have just done it all inside of one conditional after
checking if n is not equal to null. I could set number to a value like 1. And I could set the pointer
itself to something like null. All right. Well let's translate, then,
this into some similar code that allows us to build up a linked
list now using code similar in spirit to before. But now, using this new primitive. So I'm going to go
back into VS Code here. I'm going to go ahead now and delete
the entirety of this old version that was entirely array-based. And now, inside of my main function,
I'm going to go ahead and first do this. I'm going to first give
myself a list of size 0. And I'm going to call that node* list. And I'm going to initialize that
to null, as we proposed earlier. But I'm also now going to have to
take the additional step of defining what this node is. So recall that I might do something
like typedef, struct node. Inside of this struct node, I'm
going to have a number, which I'll call number of type int. And I'm going to have
a structure called node with a * that says the next
pointer is called next. And I'm going to call this whole
thing, more succinctly, node, instead of struct node. Now as an aside, for those of you
wondering what the difference really is between struct and node. Technically, I could
do something like this. Not use typedef and not
use the word node alone. This syntax here would actually
create for me a new data type called, verbosely, struct node. And I could use this throughout
my code saying struct node. Struct node. That just gets a little tedious. And it would be nicer just to refer
to this thing more simplistically as a node. So what typedef has
been doing for us is it, again, lets us invent our own
word that's even more succinct. And this just has the effect
now of calling this whole thing node without the need, subsequently, to
keep saying struct all over the place. Just FYI. All right. So now that this thing exists in
main, let's go ahead and do this. Let's add a number to list. And to do this, I'm going to
give myself a temporary variable. I'll call it n for consistency. I'm going to use malloc to
give myself the size of a node, just like in our slides. And then, I'm going to
do a little safety check. If n equals equals null, I'm going
to do the opposite of the slides. I'm just going to quit
out of this program because there's nothing useful
to be done at this point. But most likely my computer is
not going to run out of memory. So I'm going to assume we can keep
going with some of the logic here. If n does not equal null, and that
is it's a valid memory address, I'm going to say n []-- I'm going to build this up backwards. Well let's do. That's OK, let's go ahead and do this. N [number] equals 1. And then n [arrow next] equals null. And now, update list to point
to new node, list equals n. So at this point in the
story, we've essentially constructed what was that first
picture, which looks like this. This is the corresponding code via
which we built up this node in memory. Suppose now, we want to add
the number 2 to the list. So let's do this again. Add a number to list. How might I do this? Well, I don't need to
redeclare n because I can use the same temporary variables before. So this time, I'm just going to say n
equals malloc and the size of a node. I'm, again, going to
have my safety check. So if n equals equals null, then let's
just quit out of this altogether. But, I have to be a
little more careful now. Technically speaking,
what do I still need to do before I quit out of my
program to be really proper? Free the memory that did
succeed a little higher up. So I think it suffices to free what
is now called list, way at the top. All right. Now, if all was well, though, let's
go ahead and say n [number] equals 2. And now, n [arrow next] equals null. And now, let's go ahead
and add it to the list. If I go ahead and do
list arrow next equals n, I think what we've just done is
build up the equivalent, now, of this in the computer's memory. By going to the list
field's next field, which is synonymous with the 1
nodes, bottom-most box. And store the address of what was n,
which a moment ago looked like this. And I'm just throwing away, in the
picture, the temporary variable. All right. One last thing to do. Let me go down here and say, add
a number to list, n equals malloc. Let's do it one more time. Size of node. And clearly, in a real program, we
might want to start using a loop. And do this dynamically or a function
because it's a lot of repetition now. But just to go through the
syntax here, this is fine. If n equals equals null, out
of memory for some reason. Let's return 1, but we
should free the list itself and even the second node, list [next]. But I've deliberately done this poorly. All right. This is a little more subtle now. And let me get rid of the highlighting
just so it's a little more visible. If n happens to equal equal
null, and something really just went wrong they're out of memory,
why am I freeing 2 addresses now? And again, it's not that I'm
freeing those variables per se. I'm freeing the addresses
at in those variables. But there's also a
bug with my code here. And it's subtle. Let me ask more pointedly. This line here, 43, what is
that freeing specifically? Can I go to you? AUDIENCE: You're freeing list 2 times. SPEAKER 1: I'm freeing, not so. That's OK. I'm not freeing list 2 times. Technically, I'm freeing
list once and list next once. But let me just ask the
more explicit question. What am I freeing with
line 43 at the moment? Which node? I think node number 1. Why? Because if 1 is at the
beginning of the list, list contains the address
of that number 1 node. And so this frees that node. This line of code, you might
think now intuitively, OK, it's probably freeing the node number 2. But this is bad. And this is subtle. Valgrind might help you catch this. But by eyeing it, it's
not necessarily obvious. You should never touch memory
that you have already freed. And so, the fact that I did
in this order, very bad. Because I'm telling the
operating system, I don't know. I don't need the list address anymore. Do with it what you want. And then, literally one line later,
you're saying, wait a minute. Let me actually go to
that address for a moment and look at the next
field of that first node. It's too late. You've already given up
control over the node. So it's an easy fix in
this case, logically. But we should be freeing
the second node first and then the first one
so that we're doing it in, essentially, reverse order. And again, Valgrind would
help you catch that. But that's the kind of thing one
needs to be careful about when touching memory at all. You cannot touch memory
after you freed it. But here is my last step. Let me go ahead and update
the number field of n to be 3. The next node of n to be null. And then, just like
in the slide earlier, I think I can do list
next, next equals n. And that has the effect now of
building up in the computer's memory, essentially, this data structure. Very manually. Very pedantically. Like, in a better world, we'd
have a loop and some functions that are automating this process. But, for now, we're doing it just
to play around with the syntax. So at this point, unfortunately,
suppose I want to print the numbers. It's no longer as easy as int
i equals 0, i less than 3, i++. Because you cannot just
do something like this. Because pointer arithmetic
no longer comes into play when it's you, who are stitching
together the data structure in memory. In all of our past examples
with arrays, you've been trusting that all of the bytes in
the array are back, to back, to back. So it's perfectly reasonable for
the compiler and the computer to just figure out, oh, well if you
want [0], that's at the beginning. [1], it's one location over. [2], it's one location over. This is way less obvious now. Because even though you might want to
go to the first element in the linked list, or the second, or the third, you
can't just jump to those arithmetically by doing a bit of math. Instead, you have to
follow all of those arrows. So with linked lists, you can't use
this square bracket notation anymore because one node might be here,
over here, over here, over here. You can't just use some simple offset. So I think our code is going
to have to be a little fancier. And this might look scary at
first, but it's just an application of some of the basic definitions here. Let me do a for-loop that actually
uses a node* variable initialized to the list itself. I'm going to keep doing this, so
long as TMP does not equal null. And on each iteration
of this loop, I'm going to update TMP to be
whatever TMP arrow next is. And I'll remind you in a moment
and explain in more detail. But when I print something here
with printf, I can still use %i. Because it's still a number
at the end of the day. But what I want to print out is the
number in this temporary variable. So maybe the ugliest
for-loop we've ever seen. Because it's mixing, not just
the idea of a for-loop, which itself was a bit cryptic weeks ago. But now, I'm using pointers
instead of integers. But I'm not violating the
definition of a for-loop. Recall that a for-loop has 3
main things in parentheses. What do you want to initialize first? What condition do you want to
keep checking again and again? And what update do you want to make
on every iteration of the loop? So with that basic
definition in mind, this is giving me a temporary
variable called TMP that is initialized to
the beginning of the loop. So it's like pointing my
finger at the number 1 node. Then, I'm asking the question,
does TMP not equal null? Well, hopefully, not because
I'm pointing at a valid node that is the number 1 node. So, of course, it
doesn't equal null yet. Null won't be until we get
to the end of the list. So what do I do? I started this TMP variable. I follow the arrow and go to
the number field they're in. What do I then do? The for-loop says,
change TMP to be whatever is at TMP, by following the arrow
and grabbing the next field. That, then, has the result of being
checked against this conditional. No, of course, it doesn't equal
null because the second node is the number 2 node. Null is still at the very end. So I print out the number 2. Next step, I update TMP one more
time to be whatever is next. That, then, does not yet equal null. So I go ahead and print
out the number 3 node. Then one last time, I update TMP to
be whatever TMP is in the next field. But after 1, 2, 3, that
last next field is null. And so, I break out of
this for-loop altogether. So if I do this in
pictorial form, all we're doing, if I now use my finger
to represent the TMP variable. I initialize TMP to be whatever
list is, so it points here. That's obviously not null
so I print out whatever is that TMP, follow the arrow
in number, and I print that out. Then I update TMP to point here. Then I update TMP to point here. Then I update TMP to point here. Wait, that's null. The for-loop ends. So, again, admittedly much more cryptic
than our familiar int i equals 0, and so forth. But it's just a different
utilization of the for-loop syntax. Yes. AUDIENCE: How does it happen that
you're always printing out the numbers. Because it seems to me that addresses- SPEAKER 1: Good question. How is it that I'm actually printing
numbers and not printing out addresses instead. The compiler is helping me here. Because I taught it, in the
very beginning of my program, what a node is. Which looks like this here. The compiler knows that a node has
a number of fields and a next field down here, in the for-loop. Because I'm iterating using a node*
pointer, and not an int* pointer, the compiler knows that any
time I'm pointing at something, I'm pointing at the whole node. Doesn't matter where specifically in
the rectangle I'm pointing per se. It's, ultimately, pointing
at the whole node itself. And the fact that I, then, use
TMP arrow number means, OK, adjust your finger slightly. So you're literally pointing at the
number field and not the next field. So that's sufficient information for
the computer to distinguish the 2. Good question. Other questions then
on this approach here. Yeah, in the back. AUDIENCE: How would you-- SPEAKER 1: How would I use a for-loop
to add elements to a linked list? You will do something like this,
if I may, in problem set 5. We will give you some of the
scaffolding for doing this. But in this coming weeks materials
will we guide you to that. But let me not spoil it just yet. Fair question, though. Yeah. AUDIENCE: So I had a
question about line 49. SPEAKER 1: OK. AUDIENCE: Is line 49
possible in line 43? SPEAKER 1: Good question. Is line 49 acceptable, even
if we freed it earlier. We didn't free it in line
43, in this case, right. You can only reach line 49,
if n does not equal null. And you do not return on line 45. So that's safe. I was only doing those freeing, if I
knew on line 45 that I'm out of here anyway, at that point. Good question. And, yeah. AUDIENCE: I had a quick question. Is TMP [INAUDIBLE]. SPEAKER 1: Correct You're asking
about TMP, because it's in a for-loop, does that mean you
don't have to free it? You never have to free pointers, per se. You should only free addresses that
were returned to you by malloc. So I haven't finished
the program, to be fair. But you're not freeing variables. You're not freeing like, fields. You are freeing specific
addresses, whatever they may be. So the last thing, and I
was stalling on showing this because it too is a little cryptic. Here is how you can free,
now, a whole linked list. In the world of arrays,
recall, it was so easy. You just say free list. You return 0 and you're done. Not with a linked list. Because, again, the
computer doesn't know what you have stitched together
using all of these pointers all over the computer's memory. You need to follow those arrows. So one way to do this
would be as follows. While the list itself is not null,
so while there's a list to be freed. What do I want to do? I'm going to give myself a
temporary variable called TMP again. And it's a different TMP because
it's in a different scope. It's inside of the while loop instead
the for-loop, a few lines earlier. I am going to initialize TMP to
be the address of the next node. Just so I can get one
step ahead of things. Why am I doing this? Because now, I can boldly
free the list itself, which does not mean the whole list. Again, I'm freeing the
address in list, which is the address of the number 1 node. That's what list is. It's just the address
of the number 1 node. So if I first use TMP
to point out the number 2 slightly in the middle of the picture,
then it is safe for me on line 61, at the moment, to free list. That is the address of the first node. Now I'm going to say, all right, once
I freed the first node in the list, I can update the list
itself to be literally TMP. And now, the loop repeats. So what's happening here? If you think about this picture, TMP
is initially pointing at not the list, but list arrow next. So TMP, represented by my right hand
here, is pointing at the number 2. Totally safe and reasonable to
free now the list itself a.k.a. the address of the number 1 node. That has the effect of just
throwing away the number 1 node, telling the computer you can
reuse that memory for you. The last line of code I wrote
updated list to point at the number 2, at which point my loop proceeded
to do the exact same thing again. And only once my finger is
literally pointing at nowhere, the null symbol, will the
loop, by nature of a while loop as I'll toggle back to, break out. And there's nothing more to be freed. So again, what you'll see,
ultimately, in problem set 5, more on that later, is an opportunity
to play around with just this syntax. But also these ideas. But again, even though the syntax
is admittedly pretty cryptic, we're still using basics like
these for-loops or while loops. We're just starting to now
follow explicit addresses rather than letting the computer do
all of the arithmetic for us, as we previously benefited from. At the very end of this thing, I'm
going to return 0 as though all is well. And I think, then, we're good to go. All right. Questions on this linked list code now? And again, we'll walk through this
again in the coming weeks spec. Yeah. AUDIENCE: Can you explain the while
loop [INAUDIBLE] starts in other ways? SPEAKER 1: Sure. Can we explain this while loop
here for freeing the list. So notice that, first, I'm just
asking the obvious question. Is the list null? Because if it is, there's
no work to be done. However, while the list is not
null, according to line 58, what do we want to do? I want to create a temporary variable
that points at the same thing that list arrow next is pointing at. So what does that mean? Here is list. List arrow next is whatever
this thing is here. So if my right hand represents
the temporary variable, I'm literally pointing at the
same thing as the list is itself. The next line of code,
recall, was free the list. And unlike, in our world of
arrays, like half an hour ago where that just meant
free the whole darn list, you now have taken over control over the
computer's memory with a linked list, in ways that you didn't with the array. The computer knew how to free
the whole array because you malloc the whole thing at once. You are now mallocing the
linked list one node at a time. And the operating system does
not keep track of for you where all these nodes are. So when you free list,
you are literally freeing the value of the list variable,
which is just this first node here. Then my last line of code, which I'll
flip back to in a second, updates list to now ignore the
free memory and point at 2. And the story then repeats. So, again, it's just a
very pedantic way of using this new syntax of star notation,
and the arrow notation, and the like, to do the equivalent of walking
down all of these arrows. Following all of these breadcrumbs. But it does take admittedly
some getting used to. Syntax, you only have to do one week. But, again, next week
in Python will we begin to abstract a lot of
this complexity away. But none of this
complexity is going away. It's just that someone else, the
authors of Python for instance, will have automated this stuff for us. The goal this week is
to understand what it is we're going to get for
free, so to speak, next week. All right. Questions on these length lists. All right. Just, yeah, in the back. AUDIENCE: So are the while
loops strictly necessary for the freeing [INAUDIBLE]. SPEAKER 1: Fair question. Let me summarize as, could we
have freed this with a for-loop? Absolutely. It just is a matter of style. It's a little more elegant to do it
in a while loop, according to me. But other people will
reasonably disagree. Anything you can do with a while
loop you can do with a for-loop, and vise versa. Do while loops, recall,
are a little different. But they will always
do at least one thing. But for-loops and while loops
behave the same in this case. AUDIENCE: Thank you. SPEAKER 1: Sure. Other questions? All right, well let's just
vary things a little bit here. Just to see what some of
the pitfalls might now be without getting into the weeds of code. Indeed, we'll try to save some of
that for problem set 5's exploration. But instead, let's imagine that we
want to create a list here of our own. I can offer, in exchange for a
few volunteers, some foam fingers to bring to the next game, perhaps. Could we get maybe just
one volunteer first? Come on up. You will be our linked
list from the get go. What's your name? AUDIENCE: Pedro. SPEAKER 1: Pedro, come on up. All right, thank you to Pedro. [AUDIENCE CLAPPING] And if you want to just
stand roughly over here. But you are a null pointer so
just point sort of at the ground, as though you're pointing at 0. All right. So Pedro is our linked list
of size 0, which pictorially might look a little something like this
for consistency with our past pictures. Now suppose that we want to go ahead
and malloc, oh, how about the number 2. Can we get a volunteer
to be on camera here? OK. You jumped out of your seat. Do you want to come up? OK, you really want
the foam finger, I say. All right. Round of applause, sure. [AUDIENCE CLAPPING] OK. And what's your name? AUDIENCE: Caleb. SPEAKER 1: Say again? AUDIENCE: Caleb. SPEAKER 1: Halen? AUDIENCE: Caleb. SPEAKER 1: Caleb. Caleb, sorry. All right. So here is your number
2 for your number field. And here is your pointer. And come on, let's say that there
was room for Caleb like, right there. That's perfect. So Caleb got malloced,
if you will, over here. So now if we want to insert Caleb and
the number 2 into this linked list, well what do we need to do? I already initialized you to 2. And pointing as you
are to the ground means you're initialized to
null for your next field. Pedro, what you should you-- perfect. What should Pedro do. That's fine, too. So Pedro is now pointing at the list. So now our list looks a
little something like this. So far, so good. All is well. So the first couple of these
will be pretty straightforward. Let's insert one more, if anyone
really wants another foam finger. Here, how about right in the middle. Come on down. And just in anticipation, how
about let's malloc someone else. OK, your friends are pointing at you. Do you want to come
down too, preemptively? This is a pool of memory, if you will. What's your name? AUDIENCE: Hannah. SPEAKER 1: Hannah. All right, Hanna. You are number 4. [AUDIENCE CLAPPING] And hang there for just a moment. All right. So we've just malloced Hannah. And Hannah, how about Hannah,
suppose you ended up over there in just some random location. All right. So what should we now do, if the
goal is to keep these things sorted? How about? So Pedro, do you have
to update yourself? AUDIENCE: No. SPEAKER 1: No. All right. Caleb, what do you have to do? OK. And Hannah what should you be doing? I would, it's just for you for now, so
point at the ground representing null. OK. So, again demonstrating the fact
that, unlike in past weeks where we had our nice, clean array
back, to back, to back, contiguously, these guys are
deliberately all over the stage. So let's malloc another. How about number 5. What's your name? AUDIENCE: Jonathan. SPEAKER 1: Jonathan. All right, Jonathan. You are our number 5. And pick your favorite place in memory. [AUDIENCE CLAPPING] OK. All right. So Jonathan's now over there. And Hannah is over there. So 5, we want to point
Hannah at number 5. So you, of course, are
going to point there. And where should you be pointing? Down to represent null, as well. OK. So pretty straightforward. But now things get a little interesting. And here, we'll use a chance
to, without the weeds of code, point out how order of operations
is really going to matter. Suppose that I next want to
allocate say, the number 1. And I want to insert the
number 1 into this list. Yes. This is what the code would look like. But if we act this out-- could
we get one more volunteer? How about on the end
there in the sweater. Yeah. Come on down. We have, what's your name? AUDIENCE: Lauren. SPEAKER 1: Lauren. OK. Lauren, come on down. [AUDIENCE CLAPPING] And how about, Lauren,
why don't you go right in here in front, if you don't mind. Here is your number. Here is your pointer. So I've initialized
Lauren to the number 1. And your pointer will be
null, pointing at the ground. Where do you belong if we're
maintaining sorted order? Looks like right at the beginning. What should happen here? OK. So Pedro has presumed
to point now at Lauren. But how do you know where to point? AUDIENCE: He's number 2. SPEAKER 1: Pedro's undoing
what he did a moment ago. So this was deliberate. And that was perfect that Pedro
presumed to point immediately at Lauren. Why? You literally just orphaned all of these
folks, all of these chunks of memory. Why? Because if Pedro was our only variable
pointing at that chunk of memory, this is the danger of using pointers,
and dynamic memory allocation, and building your own data structures. The moment you point
temporarily, if you could, to Lauren, I have no idea
where he's pointing to. I have no idea how to get back to Caleb,
or Hannah, or anyone else on stage. So that was bad. So you did undo it. So that's good. I think we need Lauren
to make a decision first. Who should you point at? AUDIENCE: Caleb. SPEAKER 1: So pointing at Caleb. Why? Because you're pointing at
literally who Pedro is pointing at. Pedro, now what are you safe to do? Good. So order of operations there matters. And if we had just done this line
of code in red here, list equals n. That was like Pedro's first
instinct, bad things happen. And we orphaned the rest of the list. But if we think through it logically and
do this, as Lauren did for us, instead, we've now updated the list to look
a little something more like this. Let's do one last one. We got one more foam finger
here for the number 3. How about on the end? Yeah. You want to come down. All right. One final volunteer. [AUDIENCE CLAPPING] All right. And what's your name? AUDIENCE: Miriam. SPEAKER 1: I'm sorry? AUDIENCE: Miriam. SPEAKER 1: Miriam. All right. So here is your number 3. Here is your pointer. If you want to go maybe in the middle of
the stage in a random memory location. So here, too, the goal is
to maintain sorted order. So let's ask the audience, who or what
number should point at whom first here? So we don't screw up and
orphan some of the memory. And if we do orphan memory, this is
what's called, again per last week, a memory leak. Your Mac, your PC, your
phone can start to slow down if you keep asking for memory but
never give it back or lose track of it. So we want to get this right. Who should point at whom? Or what number? Say again. AUDIENCE: 3 to 4. SPEAKER 1: 3 should point at 4. So 3, do you want to point at 4. And not, so, OK, good. And how did you know,
Miriam, whom to point at? AUDIENCE: Copying Caleb. SPEAKER 1: Perfect. OK, so copying Caleb. Why? Because if you look at where this
list is currently constructed, and you can cheat on the board
here, 2 is pointing to 4. If you point at whoever Caleb,
number 2, is pointing out, that, indeed, leads you
to Hannah for number 4. So now what's the next step
to stitch this together? Our voice in the crowd. AUDIENCE: 2 to 3. SPEAKER 1: 2 to 3. So, 2 to 3. So Caleb, I think it's now
safe for you to decouple. Because someone is already
pointing at Hannah. We haven't orphaned anyone. So now, if we follow
the breadcrumbs, we've got Pedro leading to 1,
to 2, to 3, to 4, to 5. We need the numbers back, but
you can keep the foam fingers. Thank you to our volunteers here. AUDIENCE: Thank you. Thank you. [AUDIENCE CLAPPING] SPEAKER 1: You can just
put the numbers here. AUDIENCE: Thank you. SPEAKER 1: Thank you to all. So this is only to say that when you
start looking at the code this week and in the problem set, it's
going to be very easy to lose sight of the forest for the trees. Because the code does get really dense. But the idea is, again, really do bubble
up to these higher level descriptions. And if you think about data
structures at this level. If you go off in program
after a class like CS50 and your whiteboarding something
with a friend or a colleague, most people think at
and talk at this level. And they just assume that,
yeah, if we went back and looked at our textbooks or class notes, we
could figure out how to implement this. But the important stuff
is the conversation. And the idea is up here. Even though, via this week, will we
get some practice with the actual code. So when it comes to analyzing
an algorithm like this, let's consider the following. What might be now the running time of
operations like searching and inserting into a linked list? We talked about arrays earlier. And we had some binary search
possibilities still, as soon as it's an array. But as soon as we have a linked list,
these arrows, like our volunteers, could be anywhere on stage. And so you can't just
assume that you can jump arithmetically to the middle
element, to the middle element, to the middle one. You pretty much have to follow all
of these breadcrumbs again and again. So how might that inform what we see? Well, consider this too. Even though I keep drawing all these
pictures with all of the numbers exposed. And all of us humans
in the room can easily spot where the 1 is, where the 2 is,
where the 3 is, the computer, again, just like with our lockers and arrays,
can only see one location at a time. And the key thing with a linked
list is that the only address we've fundamentally been remembering
is what Pedro represented a moment ago. He was the link to all
of the other nodes. And, in turn, each
person led to the next. But without Pedro, we would have lost
some of, or all of, the linked list. So when you start with
a linked list, if you want to find an element as via
search, you have to do it linearly. Following all of the arrows. Following all of the
pointers on the stage in order to get to the node in question. And only once you hit null can
you conclude, yep, it was there. Or no, it was not. So given that if a
computer, essentially, can only see the number 1, or the number
2, or the number 3, or the number 4, or the number 5, one
at a time, how might we think about the running time of search? And it is indeed Big O of n. But why is that? Well, in the worst case, the
number you might be looking for is all the way at the end. And so, obviously, you're going to
have to search all of the n elements. And I drew these things
with boxes on top of them. Because, again, even though
you and I can immediately see, where the 5 is for
instance, the computer can only figure that out by starting
at the beginning and going there. So there, too, is another trade off. It would seem that, overnight,
we have lost the ability to do a very powerful algorithm from
week 0 known as binary search, right. It's gone. Because there's no way in this
picture to jump mathematically to the middle node, unless
you remember where it is. And then, remember where
every other node is. And at that point,
you're back to an array. Linked list, by design, only
remember the next node in the list. All right. How about something like insert? In the worst case,
perhaps, how many steps might it take to insert
something into a linked list? Someone else. Someone else. Yeah. AUDIENCE: N squared. SPEAKER 1: Say again? AUDIENCE: N squared. SPEAKER 1: N squared. Fortunately, it's not that bad. It's not as bad as n squared. That typically means
doing n things, n times. And I think we can stay under
that, but not a bad thought. Yeah. AUDIENCE: Is it n? SPEAKER 1: Why would it be n? AUDIENCE: Because the [INAUDIBLE]. SPEAKER 1: OK. So to summarize, you're proposing n. Because to find where
the thing goes, you have to traverse,
potentially, the whole list. Because if I'm inserting the
number 6 or the number 99, that numerically
belongs at the very end, I can only find its location
by looking for all of them. At this point, though, in the term. And really, at this
point in the story, you should start to question these very
simplistic questions, to be honest. Because the answer is almost
always going to depend, right. If I've just got a link to
list that looks like this, the first question back to
someone asking this question would be, well does the list
need to be sorted, right? I've drawn it as sorted
and it might imply as much. So that's a reasonable
assumption to have made. But if I don't care about
maintaining sorted order, I could actually insert into a
linked list in constant time. Why? I could just keep inserting into
the beginning, into the beginning, into the beginning. And even though the
list is getting longer, the number of steps required to insert
something between the first element is not growing at all. You just keep inserting. If you want to keep it
sorted though, yes, it's going to be, indeed, Big O of n. But again, these kinds
of, now, assumptions are going to start to matter. So let's for the sake of
discussion say it's Big O of n, if we do want to maintain sorted order. But what about in the
case of not caring. It might indeed be a Big O of 1. And now these are the kinds of decisions
that will start to leave to you. What about in the best case here? If we're thinking about
Big Omega notation, then, frankly, we could just
get lucky in the best case. And the element we're looking for
happens to be at the beginning. Or heck, we just blindly insert to the
beginning irrespective of the order that we want to keep things in. All right. So besides then, how can we
improve further on this design? We don't need to stop at linked list. Because, honestly, it's
not been a clear win. Like, linked list allow us
to use more of our memory because we don't need massive
growing chunks of contiguous memory. So that's a win. But they still require Big O of
n time to find the end of it, if we care about order. We're using at least twice as
much memory for the darn pointer. So that seems like a sidestep. It's not really a step forward. So can we do better? Here's where we can now accelerate the
story by just stipulating that, hey, even if you haven't
used this technique yet, we would seem to have an ability to
stitch together pieces of memory just using pointers . And anything you could
imagine drawing with arrows, you can implement, it
would seem, in code. So what if we leverage
a second dimension. Instead of just stringing
together things laterally, left to right, essentially,
even though they were bouncing around on the screen. What if we start to leverage a
second dimension here, so to speak. And build more interesting
structures in the computer's memory. Well it turns out that
in a computer's memory, we could create a tree,
similar to a family tree. If you've ever seen or draw on a family
tree with grandparents, and parents, and siblings, and so forth. So inverted branch of a
tree that grows, typically when it's drawn, downward instead
of upward like a typical tree. But that's something we could
translate into code as well. Specifically, let's do something
called a binary search tree. Which is a type of tree. And what I mean by
this is the following. Notice this. This is an example of an
array from like week 2, when we first talked about those. And we had the lockers on stage. And recall that what was nice
about an array, if 1, it's sorted. And 2, all of its numbers
are indeed contiguous, which is by definition an array. We can just do some simple math. For instance, if there are 7 elements
in this array, and we do 7 divided by 2, that's what? 3 and 1/2, round down
through truncation, that's 3. 0, 1, 2, 3. That gives me the middle element,
arithmetically, in this thing. And even though I have to be
careful about rounding, using simple arithmetic, I can very quickly,
with a single line of code or math, find for you the middle of the
left half, of the left half, of the right half, or whatever. That's the power of arrays. And that's what gave us binary search. And how did binary search work? Well, we looked at the middle. And then, we went left or right. And then, we went left or right again,
implied by this color scheme here. Wouldn't it be nice if we
somehow preserved the new upsides today of dynamic memory
allocation, giving ourselves the ability to just add another
element, add another element, add another element. But retain the power of binary search. Because log of n was much better than
n, certainly for large data sets, right. Even the phone book
demonstrated as much weeks ago. So what if I draw this same
picture in 2 dimensions. And I preserve the color scheme,
just so it's obvious what came where. What are these things look like now? Maybe, like, things we
might now call nodes, right. A node is just a generic term
for like, storing some data. What if the data these nodes
are storing are numbers. So still integers. But what if we connected these
cleverly, like an old family tree. Whereby, every node has not one
pointer now, but as many as 2. Maybe 0, like in the leaves
at the bottom are in green. But other nodes on the interior
might have as many as 2. Like having 2 children, so to speak. And indeed, the vernacular
here is exactly that. This would be called
the root of the tree. Or this would be a parent,
with respect to these children. The green ones would be
grandchildren, respect to these. The green ones would be siblings
with respect to each other. And over there, too. So all the same jargon you
might use in the real world, applies in the world of data
structures and CS trees. But this is interesting because I think
we could build this now, this data structure in the computer's memory. How? Well, suppose that we defined
a node to be no longer just this, a number in a next field. What if we give ourselves
a bit more room here? And give ourselves a pointer called
left and another one called right. Both of which is a
pointer to a struct node. So same idea as before, but now we
just make sure we think of these things as pointing this way and
this way, not just this way. Not just a single direction, but 2. So you could imagine, in code, building
something up like this with a node. That creates, in essence,
this diagram here. But why is this compelling? Suppose I want to find the number 3. I want to search for the
number 3 in this tree. It would seem, just like Pedro was
the beginning of our linked list, in the world of trees,
the root, so to speak, is the beginning of your data structure. You can retain and remember this entire
tree just by pointing at the root node, ultimately. One variable can hang
on to this whole tree. So how can I find the number 3? Well, if I look at the root node and
the number I'm looking for is less than. Notice, I can go this way. Or if it's greater
than, I can go this way. So I preserve that
property of the phone book, or just assorted array in general. What's true over here? If I'm looking for 3, I can
go to the right of the 2 because that number is
going to be greater. If I go left, it's going
to be smaller instead. And here's an example
of actually recursion. Recursion in a physical sense
much like the Mario's pyramid. Which was recursively to find. Notice this. I claim this whole thing is a tree. Specifically, a binary search
tree, which means every node has 2, or maybe 1, or maybe 0 children. But no more than 2. Hence the bi in binary. And it's the case that every left
child is smaller than the root. And every right child
is larger than the root. That definition certainly
works for 2, 4, and 6. But it also works recursively for
every sub tree, or branch of this tree. Notice, if you think
of this as the root, it is indeed bigger
than this left child. And it's smaller than this right child. And if you look even at
the leaves, so to speak. The grandchildren here. This root node is bigger than
its left child, if it existed. So it's a meaningless statement. And it's less than its right child. Or it's not greater than, certainly,
so that's meaningless too. So we haven't violated the definition
even for these leaves, as well. And so, now, how many steps does
it take to find in the worst case any number in a binary
search tree, it would seem? So it seems 2, literally. And the height of this
thing is actually 3. And so long story short, especially,
if you're a little less comfy with your logarithms from yesteryear. Log base 2 is the number of times you
can divide something in half, and half, and half, until you get down to 1. This is like a logarithm
in the reverse direction. Here's a whole lot of elements. And we're having, we're
having until we get down to 1. So the height of this tree, that
is to say, is log base 2 of n. Which means that even in the worst case,
the number you're looking for maybe it's all the way at the
bottom in the leaves. Doesn't matter. It's going to take log base 2
of n steps, or log of n steps, to find, maximally, any
one of those numbers. So, again, binary search is back. But we've paid a price, right. This isn't a linked list anymore. It's a tree. But we've gained back binary search,
which is pretty compelling, right. That's where the whole class
began, on making that distinction. But what price have we paid to retain
binary search in this new world. Yeah. It's no longer sorted
left to right, but this is a claim sorted, according to
the binary search tree definition. Where, again, left child
is smaller than root. And right child is greater than root. So it is sorted, but it's sorted in
a 2-dimensional sense, if you will. Not just 1. But another price paid? AUDIENCE: [INAUDIBLE] nodes now. SPEAKER 1: Exactly. Every node now needs not one
number, but 2, 3 pieces of data. A number and now 2 pointers. So, again, there's that trade off again. Where, well, if you want
to save time, you've got to give something if
you start giving space. And you start using more
space, you can speed up time. Like, you've got it. There's always a price paid. And it's very often in space, or time,
or complexity, or developer time, the number of bugs you have to solve. I mean, all of these
are finite resources that you have to juggle them on. So if we consider now the code
with which we can implement this, here might be the node. And how might we actually
use something like this? Well, let's take a look at,
maybe, one final program. And see here, before we transition
to higher level concepts, ultimately. Let me go ahead here and let me just
open a program I wrote here in advance. So let me, in a moment, copy
over file called tree.c. Which we'll have on
the course's websites. And I'll walk you
through some of the logic here that I've written for tree.c. All right. So what do we have here first? So here is an implementation of
a binary search tree for numbers. And as before, I've played around and
I've inserted the numbers manually. So what's going on first? Here is my definition of a node for a
binary search tree, copied and pasted from what I proposed on
the board a moment ago. Here are 2 prototypes for
2 functions, that I'll show you in a moment,
that allow me to free an entire tree, one node at a time. And then, also allow me to
print the tree in order. So even though they're
not sorted left to right, I bet if I'm clever about
what child I print first, I can reconstruct the idea of
printing this tree properly. So how might I implement
a binary search tree? Here's my main function. Here is how I might
represent a tree of size 0. It's just a null pointer called tree. Here's how I might add
a number to that list. So here, for instance, is me
malllocing space for a node. Storing it in a temporary
variable called n. Here is me just doing a safety check. Make sure n does not equal null. And then, here is me initializing this
node to contain the number 2, first. Then, initializing the left
child of that node to be null. And the right child of
that null node to be null. And then, initializing the tree itself
to be equal to that particular node. So at this point in the story, there's
just one rectangle on the screen containing the number
2 with no children. All right. Let's just add manually
to this a little further. Let's add another number to the
list, by mallocing another node. I don't need to declare n as a node*
because it already exists at this point. Here's a little safety check. I'm going to not bother with my,
let me do this, free memory here. Just to be safe. Do I want to do this? We want a free memory too,
which I've not done here, but I'll save that for another time. Here, I'm going to
initialize the number to 1. I'm going to initialize the children
of this node to null and null. And now, I'm going to do this. Initialize the tree's
left child to be n. So what that's essentially
doing here is if this is my root node, the single rectangle
I described a moment ago that currently has no children, neither left nor right. Here's my new node with the number 1. I want it to become the new left child. So that line of code on the
screen there, tree left equals n, is like stitching these 2 together
with a pointer from 2 to the 1. All right. The next lines of code,
you can probably guess, are me adding another
number to the list. Just the number 3. So this is a simpler tree with
2, 1, and, 3 respectively. And this code, let me wave
my hands, is almost the same. Except for the fact that I'm
updating the tree's right child to be this new and third node. Let's now run the code before
looking at those 2 functions. Let me do make tree, ./tree. And while I'll 1, 2, 3. So it sounds like the data structure
is sorted, to your concern earlier. But how did I actually print this? And then, eventually,
free the whole thing? Well let's look at the
definition of first print tree. And this is where
things get interesting. Print tree returns nothing
so it's a void function. But it takes a pointer to a root element
as its sole argument, node* root. Here's my safety check. If root equals equals
null, there's obviously nothing to print, just return. That goes without saying. But here's where things
get a little magical. Otherwise, print your left child. Then print your own number. Then, print your right child. What is this an example of, even
though it's not mentioned by name here? What programming technique here? AUDIENCE: Recursion. SPEAKER 1: Yeah. So this is actually perhaps the most
compelling use of recursion, yet. It wasn't really that
compelling with the Mario thing because we had such an easy
implementation with a for-loop loop weeks ago. But here is a perfect application of
recursion, where your data structure itself is recursive, right. If you take any snip
of any branch, it all still looks like a tree,
just a smaller one. That lends itself to recursion. So here is this leap of faith where I
say, print my left tree, or my left sub tree, if you will, via
my child at the left. Then, I'll print my own root
node here in the middle. Then, go ahead and
print my right sub tree. And because we have this base case that
makes sure that if the root is null, there's nothing to do, you're
not going to recurse infinitely. You're not going to call yourself
again, and again, and again, infinitely, many times. So it works out and prints
the 1, the 2, and the 3. And notice what we could do, too. If you wanted to print the tree in
reverse order, you could do that. Print your right tree
first, the greater element. Then, yourself. Then, your smaller sub tree. And if I do make tree
here and ./tree, well now, I've reversed the order of the list. And that's pretty cool. You can do it with a
for-loop in an array. But you can also do it, even with
this 2-dimensional structure. Let's lastly look at
this free tree function. And this one's almost the same. Order doesn't matter in quite the
same way, but it does still matter. Here's what I did with free tree. Well, if the root of the tree is
null, there's obviously nothing to do. Just return. Otherwise, go ahead and free your
left child and all of its descendants. Then free your right child
and all of its descendants. And then, free yourself. And again, free literally just
frees the address in that variable. It doesn't free the whole darn thing. It just frees literally
what's at that address. Why was it important that
I did line 72 last, though? Why did I free the left
child and the right child before I freed myself, so to speak? AUDIENCE: [INAUDIBLE]. SPEAKER 1: Exactly. If you free yourself first, if I had
done incorrectly this line higher up, you're not allowed to touch the left
child tree or the right child tree. Because the memory address is
no longer valid at that point. You would get some
memory error, perhaps. The program would crash. Valgrind definitely wouldn't like it. Bad things would otherwise happen. But here, then, is an
example of recursion. And again, just a recursive use
of an actual data structure. And what's even cooler here
is, relatively speaking, suppose we wanted to
search something like this. Binary search actually gets pretty
straightforward to implement 2. For instance. here might be the prototype for a search
function for a binary search tree. You give me the root of a tree, and
you give me a number I'm looking for, and I can pretty easily now return true
if it's in there or false if it's not. How? Well, let's first ask a question. If tree equals equals null,
then you just return false. Because if there's no tree, there's no
number, so it's obviously not there. Return false. Else if, the number you're looking for
is less than the tree's own number, which direction should we go? AUDIENCE: Left. SPEAKER 1: OK, left. How do we express that? Well, let's just return the
answer to this question. Search the left sub tree,
by way of my left child, looking for the same number. And you just assume through
the beauty of recursion that you're kicking the can
and let yourself figure it out with a smaller problem. Just that snipped left tree instead. Else if, the number you're looking for
is greater than the tree's own number, go to the right, as you might infer. So I can just return the
answer to this question. Search my right sub tree
for that same number. And there's a fourth
and final condition. What's the fourth scenario we
have to consider, explicitly? Yeah. AUDIENCE: The number. SPEAKER 1: If the number,
itself, is right there. So else if, the number I'm looking
for equals the tree's own number, then and only then,
should you return true. And if you're thinking
quickly here, there's an optimization possible,
better design opportunity. Think back to even our scratch days. What could we do a little better here? You're pointing at it. AUDIENCE: Else. SPEAKER 1: Exactly. An else suffices. Because if there's logically
only 4 things that could happen, you're wasting your time by asking
a fourth gratuitous question. And else here suffices. So here to, more so than the
Mario example a few weeks ago, there's just this elegance
arguably to recursion. And that's it. This is not pseudocode. This is the code for binary
search on a binary search tree. And so, recursion tends
to work in lockstep with these kinds of data structures
that have this structure to them as we're seeing here. All right. Any questions, then, on binary search
as implemented here with a tree? Yeah. AUDIENCE: About like third years. [INAUDIBLE] SPEAKER 1: Good question. So when returning a Boolean value, true
and false are values that are defined in a library called Standard
Bool, S-T-D-B-O-O-L dot H. With a header file that you can use. It is the case that true is, it's
not well defined what they are. But they would map indeed, yes. To 0 and 1, essentially. But you should not compare
them explicitly to 0 and 1. When you're using true and false, you
should compare them to each other. AUDIENCE: I meant if
it's in a code return. SPEAKER 1: Oh, sorry. So if I am in my own code from
earlier, an avoid function, it is totally fine to return. You just can't return
something explicitly. So return just means that's it. Quit out of this function. You're not actually
handing back a value. So it's a way of short
circuiting the execution. If you don't like that,
and some people do frown upon having code return from functions
prematurely, you could invert the logic and do something like this. If the root does not equal
null, do all of these things. And then, indent all three
of these lines underneath. That's perfectly fine too. I happen to write it
the other way just so that there was explicitly a base case
that I could point to on the screen. Whereas, now, it's
implicitly there for us only. But a good observation too. All right. So let's ask the question as
before about running time of this. It would look like
binary search is back. And we can now do things in logarithmic
time, but we should be careful. Is this a binary search tree? Just to be clear. And again, a binary
search tree is a tree where the root is greater than its left
child and smaller than its right child. That's the essence. So you're nodding your head. You agree? I agree. So this is a binary search tree. Is this a binary search tree? [INTERPOSING VOICES] OK. I'm hearing yeses. Or I'm hearing just my delay
changing the vote it would seem. So this is one of those trick questions. This is a binary search
tree because I've not violated the definition
of what I gave you, right. Is there any example of a left child
that is greater than its parent? Or is there any example of a right
child that's smaller than its parent? That's just the opposite way
of describing the same thing. No, this is a binary search tree. Unfortunately, it also looks like,
albeit at a different axis, what? AUDIENCE: A linked list. SPEAKER 1: A linked list. But you could imagine
this happening, right. Suppose that I hadn't been as
thoughtful as I was earlier by inserting 2, And then 1, and then 3. Which nicely balanced everything out. Suppose that instead, because
of what the user is typing in or whatever you contrive in your
own code, suppose you insert a 1, and then a 2, and then a 3. Like, you've created a
problem for yourself. Because if we follow the same logic
as before, going left or going right, this is how you might implement
a binary search tree accidentally if you just blindly keep
following that definition. I mean, this would be
better designed as what? If we rotated the whole thing around. And that's totally fine. And those kinds of trees
actually have names. There's trees called AVL
trees in computer science. There are red-black black
trees in computer science. There are other types of
trees that, additionally, add some logic that tell you
when you got to pivot the thing, and rotate it, and snip off the
root, and fix things in this way. But a binary search
tree, in and of itself, does not guarantee that it
will be balanced, so to speak. And so, if you consider
the worst case scenario of even using a binary search tree. If you're not smart about
the code you're writing and you just blindly
follow this definition, you might accidentally create a
crazy, long and stringy binary search tree that essentially
looks like a linked list. Because you're not even using
any of the left children. So unfortunately, the literal
answer to the question here is what's the
running time of search? Well, hopefully, log n. But not if you don't maintain
the balance of the tree. Both, in certain search, could actually
devolve into instead of big O of log n, literally, big O of n. If you don't somehow take
into account, and we're not going to do the code for that here. It's a higher level thing you
might explore down the road. It can devolve into something
that you might not have intended. And so, now that we're
talking about 2 dimensions, it's really the onus
is on the programmer to consider what kinds of
perverse situations might happen. Where the thing devolves
into a structure that you don't actually
want it to devolve into. All right. We've got just a few structures to go. Let's go ahead and take one
more 5 minute break here. When we come back,
we'll talk at this level about some final applications of this. See you in 5. All right. So we are back. And as promised, we'll operate
now at this higher level. Where if we take for granted that, even
though you haven't had an opportunity to play with these techniques yet,
you have the ability now in code to stitch things together. Both in a one dimension
and even 2 dimensions, to build things like lists and trees. So if we have these building blocks. Things like now arrays,
and lists, and trees, what if we start to amalgamate
them such that we build things out of multiple data structures? Can we start to get some of the best
of both worlds by way of, for instance, something called a hash table. So a hash table is a Swiss
army knife of data structures in that it's so commonly used. Because it allows you to associate
keys with value, so to speak. So, for instance, it allows you to
associate a username with a password. Or a name with a number. Or anything where you have
to take something as input, and get as output a corresponding
piece of information. A hash table is often a
data structure of choice. And here's what it looks like. It's actually looks like
an array, at first glance. But for discussion's sake, I've
drawn this array vertically, which is totally fine. It's still just an array. But it allows you, a hash table, to
jump to any of these locations randomly. That is instantly. So, for instance, there's actually
26 locations in this array. Because I want to, for
instance, store initially names of people, for instance. And wouldn't it be nice if the
person's name starts with A, I have a go to place for it. Maybe the first box. And if it starts with Z,
I put them at the bottom. So that I can jump
instantly, arithmetically, using a little bit of
Ascii or Unicode fanciness, exactly to the location that
they want to they need to go. So, for instance, here's
our array 0 index. 0 through 25. If I think of this,
though, as A through Z, I'm going to think of
these 26 locations, now in the context of a hash table,
is what we'll generally call buckets. So buckets into which
you can put values. So, for instance, suppose that we
want to insert a value, one name into this data structure. And that name is say, Albus. So Albus starting with A. Albus might
go at the very beginning of this list. All right. And then, we want to
insert another name. This one happens to be Zacharias. Starting with Z, so it goes all
the way at the end of this data structure in location 25 a.k.a. Z. And then, maybe a third name like
Hermione, and that goes at location H according to that
position in the alphabet. So this is great because
in constant time, I can insert and conversely
search for any of these names, based on the first letter of their name. A, or Z, or H, in this case. Let's fast forward and assume we
put a whole bunch of other names-- might look familiar,
into this hash table. It's great because every
name has its own location. But if you're thinking of names
you don't yet see it on the screen, we eventually encounter a
problem with this, right. When could something go wrong
using a hash table like this if we wanted to insert even more names? What's going to eventually happen? Yeah. There's already someone with
the first letter, right. Like I haven't even mentioned
Harry, for instance, or Hagrid. And yet, Hermione's
already using that spot. So that invites the
question, well, what happens? Maybe, if we want to insert Harry
next, do we maybe cheat and put him at location I? But then if there's a location
I, where do we put them? And it just feels like the situation
could very quickly devolve. But I've deliberately
drawn this data structure, that I claim as a hash
table, in 2 directions. An array vertically, here. But what might this be hinting
I'm using horizontally, even though I'm drawing the rectangles
a little differently from before? AUDIENCE: An array. SPEAKER 1: Yeah. Maybe another array, to be fair. But, honestly, arrays are such a pain
with the allocating, and reallocating, and so forth. These look like the beginnings
of a linked list, if you will. Where the name is where the number
used to be, even though I'm drawing it horizontally now just
for discussion's sake. And this seems to be a pointer
that isn't pointing anywhere yet. But it looks like the array is 26
pointers, some of which are null, that is empty. Some of which are pointing at
the first node in a linked list. So that's really what a hash
table might be in your mind. An amalgam of an array, whose
elements are linked lists. And in theory, this gives you
the best of both worlds, right. You get random access with
high probability, right. You get to jump immediately to the
location you want to put someone. But, if you run into this perverse
situation where there's someone already there, OK, fine. It starts to devolve into a
linked list, but it's at least 26 smaller length lists. Not one massive linked list,
which would be Big O of n. And quite slow to solve. So if Harry gets inserted in Hagrid. Yeah, you have to chain them
together, so to speak, in this way. But, at least you've not
painted yourself into a corner. And in fact, if we fast forward and
put a whole bunch of familiar names in, the data structure
starts to look like this. So the chains not terribly long. And some of them are actually
of size 0 because there's just some unpopular letters of the
alphabet among these names. But it seems better than
just putting everyone in one big array, or
one big linked list. We're trying to balance these trade
offs a little bit in the middle here. Well, how might we represent
something like this? Here's how we could describe this thing. A node in the context of a
linked list could be this. I have an array called
word of type char. And it's big enough to fit the
longest word in the alphabet plus 1. And the plus 1 why, probably? AUDIENCE: The null. SPEAKER 1: The null character. So I'm assuming that longest word
is like a constant defined elsewhere in the story. And it's something big
like 40, 100, whatever. Whatever the longest word
in the Harry Potter universe is or the English dictionary is. Longest word plus 1 should be sufficient
to store any name in the story here. And then, what else does it
each of these nodes have? Well it has a pointer to another node. So here's how we might
implement the notion of a node in the context of storing
not integers, but names. Instead, like this. But how do we decide what
the hash table itself is? Well, if we now have a definition of a
node, we could have a variable in main, or even globally, called hash table. That itself is an array
of node* pointers. That is an array of pointers to nodes. The beginnings of linked lists. Number of buckets is to me. I proposed, verbally, that it be 26. But honestly, if you get a lot
of collisions, so to speak. A lot of H names trying
to go to the same place. Well, maybe, we need to be
smarter and not just look at the first letter of their name. But, maybe, the first and the second. So it's H-A and H-E. But wait, no,
then Harry and Hagrid still collide. But we start to at least make
the problem a little less impactful by tinkering with
something like the number of buckets in a hash table like this. But how do we decide where someone
goes in a hash table in this way? Well, it's an old school
problem of input and output. The input to the problem is going
to be something like the name. And the algorithm in
the middle, as of today, is going to be something
called a hash function. A hash function is
generally something that takes as input, a string, a
number, whatever, and produces as output a location in our context. Like a number 0 through 25. Or 0 through 16,000. Or whatever the number
of buckets you want is, it's going to just tell you where to
put that input at a specific location. So, for instance, Albus, according to
the story thus far, gave me back to 0 as output. Zacharias gave me 25. So the hash function, in the
middle of that black box, is pretty simplistic in this story. It's just looking at the Ascii
value, it seems, of the first letter in their name. And then, subtracting
off what capital A is 65. So like doing some math to get
back in number between 0 and 25. So that's how we got to
this point in the story. And how might we, then, resolve
the problem further and use this notion of hashing more generally? Well just for demonstration
sake here, here's actually some buckets, literally. And we've labeled, in advance,
these buckets with the suits from a deck of cards. So we've got some spades. And we've got diamonds here. And we've got, what else here? Clubs and hearts. So we have a deck of cards
here, for instance, right. And this is something you,
yourself, might do instinctively if you're getting ready to
start playing a game of cards. You're just cleaning up or
you want things in order. Like, here is literally
a jumbo deck of cards. What would be the easiest way
for me to sort these things? Well we've got a whole bunch of
sorting algorithms from the past. So I could go through like,
here's the 3 of diamonds. And I could, here let me
throw this up on the screen. Just so, if you're far in back. So here's diamonds. I could put this here. 3, 4. I could do this in order here. But a lot of us, honestly,
if given a deck of cards. And you just want to clean
it up and sort it in order, you might do things like this. Well here's my input, 3 of diamonds,
let's put it in this bucket. 4 of diamonds, this bucket. 5 of diamonds, this bucket. And if you keep going through the cards,
here's seven of hearts, hearts bucket. 8's bucket. Queen of spades over here. And it's still going
to take you 52 steps. But at the end of it, you
have hashed all of the cards into 4 distinct buckets. And now you have problems
of size 13, which is a little more tenable than
doing one massive 52 card problem. You can now do 4, 13 size problems. And so hashing is something that even
you and I might do instinctively. Taking as input some card, some name,
and producing as output some location. A temporary pile in which you
want to stage things, so to speak. But these collisions are inevitable. And honestly, if we kept going
through the Harry Potter universe, some of these chains would get
longer, and longer and longer. Which means that instead of
getting someone's name quickly, by searching for them
or inserting them, might start taking a decent amount of time. So what could we do instead to
resolve situations like this? If the problem, fundamentally, is
that the first letter is just too darn popular, H, we need
to take in more input. Not just the first letter but
maybe the first 2 letters. So if we do that, we
can go from A through Z to something more extreme like maybe
H-A, H-B, H-C, H-D, H-F, and so forth. So that now Harry and Hermione
end up at different locations. But, darn it, Hagrid
still collides with Harry. So it's better than before. The chains aren't quite as long. But the problem isn't
fundamentally gone. And in this case here, anyone
know how many buckets we just increased to, if we now look at not just
a through Z but AA through ZZ, roughly? AUDIENCE: 26 squared. SPEAKER 1: Yeah. OK, good. So the easy answer to
26 squared are 676. So that's a lot more buckets. And this is why I only showed
a few of them on the screen. So that's a lot more. And it spreads things out in particular. What if we take this one step further? Instead of H-A, we do like H-A-A,
H-A-B, H-A-C, H-Z-Z, and so forth. Well now, we have an
even better situation. Because Hermoine has her one spot. Harry has his one spot. Hagrid has his one spot. But there's a trade off here. The upside is now, arithmetically,
we can find their locations in constant time. Maybe, technically 3 steps. But 3 is constant, no matter how many
other names are in here, it would seem. But what's the downside here? Sorry, say again. AUDIENCE: Memory. SPEAKER 1: Memory. So significantly more. We're now up to 17,576 buckets, which
itself isn't that big a deal, right. Computers have a lot
of memory these days. But as you can infer,
I can't really think of someone whose name started with
H-E-Q, for instance, in the Harry Potter universe. And if we keep going,
definitely don't know of anyone whose name started with
Z-Z-Z or A-A-A. There's a lot of not useful combinations
that have to be there mathematically, so that you can do a bit of math
and jump to randomly, so to speak, the precise location. But they're just going to be empty. So it's a very sparsely
populated array, so to speak. So what does that really mean
for performance, ultimately? Well let's consider, again, in
the context of our Big O notation. It turns out that a hash
table, technically speaking, is still just going to give us
Big O of n in the worst case. Why? If you have some crazy perverse
case where everyone in the universe has a name that starts with A, or
starts with H, or starts with Z, you just get really unlucky. And your chain is massively long. Well then, at that point,
it's just a linked list. It's not a hash table. It's like the perverse
situation with the tree, where if you insert it without any mind for
keeping it balance, it just evolves. But there's a difference here
between a theoretical performance and an actual performance. If you look back at the
the hash table here, this is absolutely, in practice, going
to be faster than a single linked list. Mathematically, asymptotically,
big O notation, sure. It's all the same. Big O of n. But if what we're really caring about
is real humans using our software, there's something to be said
for crafting a data structure. That technically, if this data
were uniformly distributed, is 26 times faster than
a linked list alone. And so, there's this tension too
between systems, types of CS, and theoretical CS. Where yeah, theoretically,
these are all the same. But in practice, for
making real-world software, improving this speed by a factor of 26
in this case, let alone 576 or more, might actually make a big difference. But there's going to be a trade off. And that's typically some other
resource like giving up more space. All right. How about another data
structure we could build. Let me fast forward to
something here called a trie. So a trie, a weird
name in pronunciation. Short for retrieval,
pronounced trie typically. A trie is a tree that actually
gives us constant time lookup, even for massive data sets. What do I mean by this? In the world of a trie, you
create a tree out of arrays. So we're really getting into
the Frankenstein territory of just building things up with
spare parts of data structures that we have here. But the root of a trie
is, itself, an array. For instance, of size 26. Where each element in that
trie points to another node, which is to say another array. And each of those locations in
the array represents a letter of the alphabet like A through Z. So for instance, if you wanted to store
the names of the Harry Potter universe, not in a hash table, not in a linked
list, not in a tree, but in a trie. What you would do is hash on every
letter in the person's name one at a time. So a trie is like a multi-tier
hash table, in a sense. Where you first look
at the first letter, then the second letter, then the
third, and you do the following. For instance, each of these
locations represents a letter A through Z. Suppose I wanted to
insert someone's name into this that starts with the letter
H, like Hagrid for instance. Well, I go to the location
H. I see it's null, which means I need to malloc myself
another node or another array. And that's depicted here. Then, suppose I want to store the
second letter in Hagrid's name, an A. So I go to that
location in the second node. And I see, OK, it's currently null. There's nothing below it. So I allocate another node
using malloc or the like. And now I have H-A-G. And
I continue this with R-I-D. And then, when I get to the
bottom of this person's name, I just have to indicate
here in color, but probably with a Boolean value or something. Like a true value that
says, a name stops here. So that it's clear that the person's
name is not H-A, or H-A-G, or H-A-G-R, or H-A-G-R-I. It's H-A-G-R-I-D.
And the D is green, just to indicate there's like some
other Boolean value that just says, yes. This is the node in
which the name stops. And if I continue this logic, here's
how I might insert someone like Harry. And here's how I might
insert someone like Hermione. And what's interesting about the
design here is that some of these names share a common prefix. Which starts to get compelling
because you're reusing space. You're using the same nodes
for names like H-A-G and H-A-R because they share H and an A in common. And they all share an H in common. So you have this data structure
now that, itself, is a tree. Each node in the tree
is, itself, an array. And we, therefore, might implement
this thing using code like this. Every node is containing, I'll
do it in reverse order, an array. I'll call it children because
that's what it really represents. Up to 26 children for
each of these nodes. Size of the alphabet. So I might have used just
a constant for number 26, to give myself 26
letters of the alphabet. And each of those arrays
stores that many node stars. That many pointers to another node. And here's an example of the Bool. This is what I represented in
green on the slide a moment ago. I also need another piece of data. Just a 0 or 1, a true
or false, that says yes. A name stops in this node or it's just
a path to the rest of the person's name. But the upside of this is
that the height of this tree is only as tall as the
person's longest name. H-A-G-R-I-D or H-E-R-M-O-I-N-E. And
notice that no matter how many other people are in this data structure,
there's 3 at the moment, if there were 3 million, it would
still take me how many steps to search for Hermoine? H-E-R-M-I-O-N-E. So, 8 steps total. No matter if there's 2 other people,
2 million, 10 million other people. Because the path to her name
is always on the same path. And if you assume that there's a
maximum limit on the length of names in the human world. Maybe it's 40, 100, whatever. Whatever the longest
name in the world is. That's constant. Maybe it's 40, 100, but that's constant. Which is to say that with a
trie, technically speaking, it is the case that your lookup
time, Big O of n, a big O notation, would be big O of 1. It's constant time, because
unlike every other data structure we've looked at, with a trie, the amount
of time it takes you to find one person or insert one person is
completely independent of how many other pieces of data are
already in the data structure. And this holds true even if one
name is a prefix of another. I don't think there was a Daniel or
Danielle in the Harry Potter universe that I could think of. But, D-A-N-I-E-L could be one name. And, therefore, we have
a true there in green. And if there's a longer
name like Danielle. Then, you keep going
until you get to the E. So you can still have with
a trie, one name that's a substring of another name. So it's not as though we've
created a problem there. That, too, is still possible. But at the end of the day, it only
takes a finite number of steps to find any of these people. And again, that's what's
particularly compelling. That you effectively have
constant time lookup. So that's amazing, right. We've gone through this whole story
for weeks now of like, linear time. And then, it went up to n squared. And then, log n. And now constant time, what's the price
paid for a data structure like this? This so-called trie? What's the downside here? There's got to be a catch. And in fact, tries are not
actually used that often, amazing as they might sound
on some CS level here. AUDIENCE: Memory. SPEAKER 1: Memory. In what sense? AUDIENCE: Much like a [INAUDIBLE]. SPEAKER 1: Exactly. If you're storing all
of these darn arrays it's, again, a sparsely
populated data structure. And you can see it here. Granted there's only 3 names, but most
of those boxes, most of those pointers, are going to remain null. So this is an incredibly wide
data structure, if you will. It uses a huge amount of
memory to store the names. But again, you've got to pick a lane. Either you're going to minimize space
or you're going to minimize time. It's not really possible to get
truly the best of both worlds. You have to decide where
the inflection point is for the device you're writing
software for, how much memory it has, how expensive it is. And again, taking all of
these things into account. So lastly, let's do one
further abstraction. So even higher level to discuss
something that are generally known as abstract data structures. It turns out we could
spend like all day, all week, talking about
different things we could build with these data structures. But for the most part,
now that we have arrays. Now that we have linked lists
or their cousin's trees, which are 2-dimensional. And beyond that, there's
even graphs, where the arrows can go in multiple
directions, not just down, so to speak. Now that we have this ability
to stitch things together, we can solve all different
types of problems. So, for instance, a very
common type of data structure to use in a program, or even our
human world, are things called queues. A queue being a data structure
like a line outside of a store. Where it has what's
called a FIFO property. First In, First Out. Which is great for fairness,
at least in the human world. And if you've ever waited outside
of Tasty Burger, or Salsa Fresca, or some other restaurant
nearby, presumably, if you're queuing up at
the counter, you want them store to maintain a FIFO system. First in and first out. So that whoever's first in line gets
their food first and gets out first. So a queue is actually a
computer science term, too. And even if you're still in the
habit of printing things on paper, there are things you might
have heard called printer queues, which also do things in order. The first person to send
their essay to the printer should, ideally, be printed
before the last person to send their essay to the printer. Again, in the interest of fairness. But how can you implement a queue? Well, you typically have to
implement 2 fundamental operations, enqueue and dequeue. So adding something to it and
removing something from it. And the interesting thing here is
that how do you implement a queue? Well in the human world, you would
just have literally physical space for humans to line up from left
to right, or right to left. Same in a computer. Like a printer queue, if you send a
whole bunch of jobs to be printed, a whole bunch of essays
or documents, well, you need a chunk of memory like an array. All right. Well, if you use an
array, what's a problem that could happen in the world
of printing, for instance? If you use an array to store all of
the documents that need to be printed. AUDIENCE: It can be filled. SPEAKER 1: It could be filled, right. So if the programmer decided, HP or
whoever makes the printer decides, oh, you can send like a megabyte worth
of documents to this printer at once. At some point you might
get an error message, which says, sorry out of memory. Wait a few minutes. Which is maybe a reasonable
solution, but a little annoy. Or HP could write code that maybe
dynamically resizes the array or so forth. But at that point, maybe they
should just use a linked list. And they could. So there, too, you could
implement the notion of a queue using a linked list instead. You're going to spend more
memory, but you're not going to run out of space in your array. Which might be more compelling. This happens even in the physical world. You go to the store and you start having
to line up outside and down the road. And like, for a really busy store,
they run out of space so they make do. But in that case, it tends to
be more of an array just because of the physical notion
of humans lining up. But there's other data structures, too. If you've ever gone to the dining hall
and picked up like a Harvard or Yale tray, you're typically picking up
the last tray that was just cleaned, not the first tray that was cleaned. Why? Because these cafeteria trays
stack up on top of each other. And indeed a stack is another
type of abstract data structure. In the physical world, it's
literally something physical like a stack of trays. Which have what we would
call a LIFO property. Last In, First Out. So as these things
come out of the washer, they're putting the most
recent ones on the top. And then you, the human, are probably
taking the most recently cleaned one. Which means in the
extreme, no one on campus might ever use that very first tray. Which is probably fine
in the world of trays, but would really be bad in the world of
Tasty Burger lining up for food if LIFO were the property being implemented. But here, too, it could be an array. It could be a linked list. And you see this, honestly, every day. If you're using Gmail
and your Gmail inbox. That is actually a stack,
at least by default, where your newest message
last in are the first ones at the top of the screen. That's a LIFO data structure. And it means that you see
your most recent emails. But if you have a busy day,
you're getting a lot of emails, it might not be a good thing. Because now you're ignoring
the people who wrote you way earlier in the day or the week. So LIFO and FIFO are
just properties that you can achieve with these very
specific types of data structures. And the parliaments
in the world of stacks is to push something onto a
stack or pop something out. These are here, for instance,
as an example of why might you always wear the same color. Well, if you're storing all
of your clothes in a stack, you might not ever get
to the different colored clothes at the bottom of the list. And in fact, to paint this picture,
we have a couple of minute video here. Just to paint this here, made
by a faculty member elsewhere. Let's go ahead and dim the lights
for just a minute or 2 here. So that we can take a look
at Jack learning some facts. [VIDEO PLAYING] SPEAKER 2: Once upon a time,
there was a guy named Jack. When it came to making friends
Jack did not have the knack. So Jack went to talk to the
most popular guy he knew. He went up to Lou and
asked, what do I do? Lou saw that his friend
was really distressed. Well, Lou began, just
look how you're dressed. Don't you have any clothes
with a different look? Yes, said Jack. I sure do. Come to my house and
I'll showed them to you. So they went off the Jack's. And Jack showed Lou the box, where he
kept all his shirts, and his pants, at his socks. Lou said, I see you have
all your clothes in a pile. Why don't you wear some
others once in a while? Jack said, well, when I
remove clothes and socks, I wash them and put
them away in the box. Then comes the next
morning and up I hop. I go to the box and get
my clothes off the top. Lou quickly realized
the problem with Jack. He kept clothes, CDs,
and books in a stack. When he'd reached for
something to read or to wear, he chose a top book or underwear. Then when he was done he
would put it right back. Back it would go on top of the stack. I know the solution,
said a triumphant Lou. You need to learn to
start using a queue. Lou took Jack's clothes
and hung them in a closet. And when he had emptied
the box, he just tossed it. Then he said, now Jack, at the end of
the day, put your clothes on the left when you put them away. Then tomorrow morning when
you see the sunshine, get your clothes from the right,
from the end of the line. Don't you see, said
Lou, it will be so nice. You'll wear everything once
before you wear something twice. And with everything in queues
in his closet and shelf, Jack started to feel
quite sure of himself. All thanks to Lou and
his wonderful queue. SPEAKER 1: So just to help you realize
that these things are everywhere. [AUDIENCE CLAPPING] Even in our human world. If you've ever lined up at this place. Anyone recognize this? OK, so sweetgreen, little
salad place in the square. This is if you order
online or in advance, your food ends up according to
the first letter in your name. Which actually sounds awfully
reminiscent of something like a hash table. And in fact, no matter whether
you implement a hash table like we did, with an array and linked list. Or with 3 shelves like this. This is actually an abstract
data type called a dictionary. And a dictionary, just like in our
human world, has keys and values. Words and their definitions. This just has letters of the
alphabet and salads as their value. But here, too, there's
a real world constraint. In what kind of scenario does
this system at sweetgreen devolve into a problem, for instance? Because they, too, are using only
finite space, finite storage. What could go wrong? Yeah. AUDIENCE: Run out of space. SPEAKER 1: Yeah. If they run out of space
on the shelf and there's a lot of people whose names
start with D, or E, or whatever. And so, they just pile up. And then, maybe, they kind of
overflow into the E's or the F's. And they probably don't
really care because any human is going to come by, and just
eyeball it, and figure it out anyway. But in the world of a
computer, you're the one coding and have to be ever so precise. We thought we would lastly
do one final thing here. In advance, we prepared a linked
list of sorts in the audience. Since this has become a bit of a thing. I am starting to represent the
beginning of this linked list. And so far as I have a pointer
here with seat location G9. Whoever is in G9, would
you mind standing up? And what letter is on your sheet there? AUDIENCE: F15. SPEAKER 1: OK, so you
have S15 and your letter-- AUDIENCE: F15. SPEAKER 1: Say again? AUDIENCE: F. SPEAKER 1: F15. So I see you're holding
a C in your node. You are pointing to, if
you could physically, F15. F15, what do you hold? AUDIENCE: S. SPEAKER 1: You have an S. And
who should you be pointing at? AUDIENCE: F5. SPEAKER 1: F5. Could you stand up, F5. You're holding a 5, I see. What address? AUDIENCE: F12. SPEAKER 1: F12. Big finale. F12, if you'd like to stand up holding
a 0 and null, which means that was CS50. [AUDIENCE CLAPPING] All right. We'll see you next time. [MUSIC PLAYING] DAVID J. MALAN: All right, this is
CS50, and this is already week 6. And this is the week in which
you learn yet another language. But the goal is not just to
teach you another language, for languages sake,
as we transition today and in the coming weeks from C, where
we've spent the past several weeks, now to Python. The goal ultimately is to teach you all
how to teach yourselves new languages, so that by the end of this
course, it's not in your mind, the fact that you learned
how to program in C or learned some weeks back
how to program in Scratch, but really how you learned
how to program fundamentally, in a paradigm known as
procedural programming, as well as with some taste
today, and in the weeks to come, of other aspects of
programming languages, like object-oriented
programming, and more. So recall, though, back
in week zero, Hello, world looked a little something like this. And the world was quite simple. All you had to do was drag
and drop these puzzle pieces. But there were still functions and
conditionals and loops and variables and all of those kinds of primitives. We then transitioned, of course,
to a much more arcane language that looked a little something like this. And even now, some weeks
later, you might still be struggling with some of the
syntax or getting annoying bugs when you try to compile your
code, and it just doesn't work. But there, too, the
past few weeks, we've been focusing on functions and loops
and variables, conditionals, and really all of those same ideas. And so what we begin to do today
is to, one, simplify the language we're using, transitioning from C now
to Python, this now being the equivalent program in Python, and look
at its relative simplicity, but also transitioning
to look at how you can implement these
same kinds of features, just using a different language. So we're going to see
a lot of code today. And you won't have nearly as much
practice with Python as you did with C. But that's because so many of the
ideas are still going to be with us. And, really, it's going to be a
process of figuring out, all right, I want to do a loop. I know how to do it in C.
How do I do this in Python? How do I do the same with conditionals? How do I declare
variables, and the like, and moving forward, not just in
CS50, but in life in general, if you continue programming and learn
some other language after the class, if in 5-10 years, there's a new, more
popular language that you pick up, it's just going to be a
matter of googling and looking at websites like Stack
Overflow and the like, to look at just basic building
blocks of programming languages, because you already speak,
after these past 6 plus weeks, you already speak programming
itself fundamentally. All right, so let's do a few quick
comparisons, left and right, of what something might have
looked like in Scratch, and what it then looked
like in C, but now, as of today, what it's going
to look like in Python. Then we'll turn our attention
to the command line, ultimately, in order to
implement some actual programs. So in Scratch, we had
functions like this, say Hello, world, a verb or an action. In C it looked a little
something like this, and a bit of a cryptic mess the
first week, you had the printf, you had the double quotes. You had the semicolon, the parentheses. So there's a lot more syntax
just to do the same thing. We're not going to get rid of all
of that syntax now, but as of today, in Python, that same statement is going
to look a little something like this. And just to perhaps call
out the obvious, what is different or, now, simpler
in Python versus C, even in this simple example here? Yeah. AUDIENCE: Now print, instead of
printf would be, something like that. DAVID J. MALAN: Good, so it's
now print instead of printf. And there's also no semicolon. And there's one other
subtlety, over here. AUDIENCE: No new line. DAVID J. MALAN: Yeah,
so no new line, and that doesn't mean it's not
going to be printed. It just turns out that one
of the differences we'll see is that, with print, you
get the new line for free. It automatically gets outputted by
default, being sort of a common case. But you can override it,
we'll see, ultimately, too. How about in Scratch? We had multiple functions like
this, that not only said something on the screen, but
also asked a question, thereby being another function that
returned a value, called answer. In C we saw code that
looked a little something like this, whereby that first line
declares a variable called answer, sets it equal to the
return value of getString, one of the functions
from the CS50 library, and then the same double quotes
and parentheses and semicolon. Then we had this format code
in C that allowed us, with %S, to actually print out that same value. In Python, this, too, is going
to look a little bit simpler. Instead, we're going to have
answer equals getString, quote unquote "What's your
name," and then print, with a plus sign and a
little bit of new syntax. But let's see if we can't just
infer from this example what it is that's going on. Well, first missing on the left is what? To the left of the equal sign,
there's no what this time? Feel free to just call it out. AUDIENCE: Type. DAVID J. MALAN: So there's no type. There's no type, like
the word string, which even though that was a type in
CS50, every other variable in C did we use Int or string or
float, or Bool or something else. In Python, there are still
going to be data types, today onward, but you,
the programmer, don't have to bother telling the
computer what types you're using. The computer is going
to be smart enough, the language, really, is going to be
smart enough, to just figure it out from context. Meanwhile, on the right
hand side, getString is going to be a
function we'll use today and this week, which comes from a
Python version of the CS50 library. But we'll also start to take off
those training wheels, so that you'll see how to do things without
any CS50 library moving forward, using a different function instead. As before, no semicolon, but the rest
of the syntax is pretty much the same here. This starts, of course, to get
a little bit different, though. We're using print instead of printf. But now, even though this
looks a little cryptic, perhaps, if you've never
programmed before CS50, what might that plus be doing,
just based on inference here. What do you think? AUDIENCE: Adding answer
to the string Hello. DAVID J. MALAN: Yeah, so adding
answer to the string Hello, and adding, so to speak,
not mathematically, but in the form of joining
them together, much like we saw the joined block in Scratch, or
concatenation was the term of art there. This plus sign appends,
if you will, whatever's in answer to whatever is quoted here. And I deliberately left a space
there, so that grammatically it looks nice, after the comma as well. Now there's another way to do this. And it, too, is going to
look cryptic at first glance. But it just gets easier and
more convenient over time. You can also change this second
line to be this, instead. So what's going on here. This is actually a relatively new
feature of Python in the past couple of years, where now what
you're seeing is, yes, a string, between these
same double quotes, but this is what Python would
call a format string, or Fstring. And it literally starts with the letter
F, which admittedly looks, I think, a little weird. But that just indicates
that Python should assume that anything inside of
curly braces inside of the string should be interpolated, so to
speak, which is a fancy term saying, substitute the value of
any variables therein. And it can do some other things as well. So answer is a variable, declared,
of course, on this first line. This Fstring, then, says to Python,
print out Hello comma space, and then the value of Answer. If, by contrast, you
omitted the curly braces, just take a guess, what would happen? What would the symptom of that
bug be, if you accidentally forgot the curly braces, but
maybe still had the F there? AUDIENCE: It would print below it, too. DAVID J. MALAN: Yeah, it would literally
print Hello, comma answer, because it's going to take you literally. So the curly braces just kind
of allow you to plug things in. And, again, it looks
a little more cryptic, but it's just going to
save us time over time. And if any of you programmed in
Java in high school, for instance, you saw plus in that context,
too, for concatenation. This just kind of makes your code a
little tighter, a little more succinct. So it's a convenient
feature now in Python. All right, this was an example
in Scratch of a variable, setting a variable like
counter equal to 0. In C it looked like this, where
you specify the type, the name, and then the value, with a semicolon. In Python, it's going to look like this. And I'll state the obvious here. You don't need to mention the
type, just like before with string. And you don't need a semicolon. So it's a little simpler. If you want a variable, just write
it and set it equal to some value. But the single equal sign
still behaves the same as in C. Suppose we wanted to
increment counter by one. In Scratch, we use
this puzzle piece here. In C, we could do this, actually,
in a few different ways. There was this way, if
counter already exists, you just say counter
equals counter plus 1. There was the slightly less verbose
way, where you could say, oops, sorry. Let me do the first sentence first. In Python, that same
thing, as you might guess, is actually going to be almost the
same, you just throw away the semicolon. And the mathematics are ultimately
the same, copying from right to left, via the assignment operator. Now, recall, in C, that
we had this shorthand notation, which did the same thing. In Python, you can similarly do the same
thing, just no need for the semicolon. The only step backwards
we're taking, if you were a big fan of counter plus
plus, that doesn't exist in Python, nor minus minus. You just can't do it. You have to do the plus equals 1
or plus/minus or minus equals 1 to achieve that same result. All
right, how about in Python 2? Here in Scratch, recall,
was a conditional, asking a silly question like is x less
than y, and if so, just say as much. In C, that looked a little
something like this, printf and if with the parentheses, the curly
braces, the semicolon, and all of that. In Python, this is going to get a
little more pleasant to type, too. It's going to be just this. And if someone wants to call out
some of the obvious changes here, what has been simplified now in Python
for a conditional, it would seem? Yeah, what's missing, or changed? AUDIENCE: Braces. DAVID J. MALAN: So no curly braces. AUDIENCE: Colon is back. DAVID J. MALAN: I'm sorry? AUDIENCE: Using the colon instead. DAVID J. MALAN: And we're
using the colon instead. So I got rid of the
curly braces in Python. But I'm using a colon instead. And even though this is
a single line of code, so long as you indent subsequent
lines along with the printf, that's going to imply that everything,
if the if condition is true, should be executed below it, until you
start to un-indent and start writing a different line of code altogether. So indentation in Python is important. So this is among the reasons
we've emphasized axes like style, just how well styled your code is. And honestly, we've seen,
certainly, in office hours, and you've seen in your own code,
sort of a tendency sometimes to be a little lax when it
comes to indentation, right? If you're one of those folks
who likes to indent everything on the left hand side of the window,
yeah, it might compile and run. But it's not particularly
readable by you or anyone else. Python actually addresses this
by just requiring indentation, when logically needed. So Python is going to force you to start
inventing properly now, if that's been, perhaps, a tendency otherwise. What else is missing? Well, we have no semicolon here. Of course, it's print instead of printf. But otherwise, those seem to
be the primary differences. What about something larger in Scratch? If an if-else block, like
this, you can perhaps guess what it's going to look like. In C it looks like this, curly
braces semicolons, and so forth. In Python, it's going to now
look like this, almost the same, but indentation is important. The colons are important. And there's one other difference
that's now again visible here, but we didn't call it out a second ago. What else is different in Python
versus C for these conditionals? Yeah. AUDIENCE: You don't have any
parentheses around the condition. DAVID J. MALAN: Perfect. We don't have any parentheses
around the condition, the Boolean expression itself. And why not? Well, it's just simpler to type. It's less to type. You can still use parentheses. And, in fact, you might
want to or need to, if you want to combine thoughts and
do this and that, or this or that. But by default, you no longer need
or should have those parentheses. Just say what you mean. Lastly, with conditionals,
we had something like this, an if else if else statement. In C, it looked a little
something like this. In Python, it's going to
get really tighter now. It's just if, and this is the
curiosity, elif x greater than y. So it's not else if, it's literally
one keyword, elif, and the colons remain now on each of the three lines. But the indentation is important. And if we did want to
do multiple things, we could just indent below each
of these conditionals, as well. All right, let me pause
there first, to see if there's any questions on
these syntactic differences. Yeah. AUDIENCE: My thought is
maybe like, it's good, though, does it matter if there's
this in between thing like that, but and why. DAVID J. MALAN: In between,
between what and what? AUDIENCE: So like the left-hand
side and like the right side spaces? DAVID J. MALAN: Ah, good
question, is Python sensitive to spaces and where they go? Sometimes no, sometimes
yes, is the short answer. Stylistically, though, you should be
practicing what we're preaching here, whereby you do have spaces to the
left and right of binary operators, that they're called,
something like less than or greater than is a binary
operator, because there's two operands to the left
and to the right of them. And in fact, in Python,
more so than the world of C, there's actually formal
style conventions. Not only within CS50 have we had a
style guide on the course's website, for instance, that just dictates how you
should write your code so that it looks like everyone else's. In the Python community, they
take this one step further, and there's an actual standard whereby
you don't have to adhere to it, but generally speaking, in the real
world, someone would reprimand you, would reject your code, if you're trying
to contribute it to another project, if you don't adhere to these standards. So while you could be lax
with some of this white space, do make things readable. And that's Python theme, for the
code to be as readable as possible. All right, so let's take a look
at a couple of other constructs before transitioning
to some actual code. This, of course, in Scratch
was a loop, meowing forever. In C, the closest we could get was
doing something while true, because true never changes. So it's sort of a simple way
of just saying do this forever. In Python, it's pretty
much the same thing, but a couple of small differences here. The parentheses are gone. The colon is there. The indentation is there. No semicolon, and there's
one other subtle difference. What do you see? AUDIENCE: True is capitalized? DAVID J. MALAN: True is
capitalized, just because. Both true and false are
Boolean values in Python. But you've got to start
capitalizing them, just because. All right, how about a
loop like this, where you repeat something a finite number
of times, like meowing three times. In C, we could do this
a few different ways. There's this very mechanical way,
where you initialize a variable like i to zero. You then use a while loop and
check if i is less than 3, the total number of
times you want to meow. Then you print what you want to print. You increment i using this syntax,
or the longer, more verbose syntax, with plus equals or whatnot. And then you do it again
and again and again. In Python, you can do it
functionally the same way, same idea, slightly different syntax. You just don't bother saying
what type of variable you want. Python will infer from the fact
that there's a 0 right there. You don't need the parentheses. You do need the colon. You do need the indentation. You can't do the i plus plus, but
you can do this other technique, as we could have done in C, as well. How else might we do this, though, too? Well. it turns out in
C, we could do something like this, which, again, sort
of cryptic at first glance, became perhaps more familiar,
where you have initialization, a conditional, and then an update
that you do after each iteration. In Python, there isn't really an analog. There is no analog in
Python, where you have the parentheses and the multiple
semicolons in the same line. Instead, there is a for loop, but
it's meant to read a little more like English, for i in 0, 1, and 2. So we'll see in a bit, these square
brackets represent an array, now to be called a list in Python. So lists in Python are more like
link lists than they are arrays. More on that soon. So this just means for i and the
following list of three values. And on each iteration of this loop,
Python automatically, for you, it first sets i to zero. Then it sets i to one. Then it sets i to two, so that you
effectively do things three times. But this doesn't necessarily scale,
as I've drawn it on the board. Suppose you took this
at face value as the way you iterate some number of times
in Python, using a for loop. At what point does this approach
perhaps get bad, or bad design? Let me give folks just
a moment to think. Yeah, in back. AUDIENCE: If you don't know how
many times, last time, you know, you've got the link in there. DAVID J. MALAN: Sure, if you
don't know how many times you want to loop or iterate, you can't
really create a hard-coded list like that, of 0, 1, 2. Other thoughts? AUDIENCE: So you want to say raise
a large number of allowances. DAVID J. MALAN: Yeah, if you're
iterating a large number of times, this list is going to
get longer and longer, and you're just kind of
stupidly going to be typing out like comma 3, comma 4, comma 5, comma
dot dot dot, comma 99, comma 100. I mean, your code would start
to look atrocious, eventually. So there is a better way. In Python, there is a function,
or technically a type, called range, that essentially magically
gives you back a range of values from 0 on up to, but
not through a value. So the effect of this line of
code, for i in the following range, essentially hands you back
a list of three values, thereby letting you do
something three times. And if you want to do something
99 times instead, you, of course, just change the 3 to a 99. Question. AUDIENCE: Is there a way to start
the beginning point of that range at a number or an integer that's higher
than zero, or is there never a really any point to do so? DAVID J. MALAN: A really
good question, can you start counting at a higher number. So not 0, which is the implied default,
but something larger than that. Yes, so it turns out the range function
takes multiple arguments, not just one but maybe two or even three, that
allows you to customize this behavior. So you can customize where it begins. You can customize the increment. By default, it's one,
but if you want to do every two values, for like evens
or odds, you could do that as well, and a few other things. And before long, we'll take a
look at some Python documentation that will become your authoritative
source for answers like that. Like, what can this function do. Other questions on this thus far? Seeing none, so what else might
we compare and contrast here. Well, in the world of C, recall that
we had a whole bunch of built-in data types, like these here, Bool and char
and double and float, and so forth, string, which happened to
come from the CS50 library. But the language C itself certainly
understood the idea of strings, because the backslash 0, the support
for %S and printf, that's all native, built into C, not a CS50 simplification. All we did, and revealed,
as of a couple of weeks ago, is that string,
this data type, is just a synonym for a typedef for char star,
which is part of the language natively. In Python now, this list actually
gets a little shorter, at least for these common primitive data types. Still going to have bulls, we're
going to have floats, and Ints, and we're going to have strings,
but we're going to call them STRs. And this is not a CS50
thing from the library, STR, S-T-R, is, in fact,
a data type in Python, that's going to do a lot more than
strings did for us automatically in C. Ints and floats, meanwhile, don't need
the corresponding longs and doubles, because, in fact, among the
problems Python solves for us, too, Ints can get as big as you want. Integer overflow is no
longer going to be an issue. Per week 1, the language
solves that for us. Floating point
imprecision, unfortunately, is still a problem that remains. But there are libraries, code that
other people have written, as we briefly discussed in weeks past,
that allow you to do scientific or financial computing,
using libraries that build on top of these data types, as well. So there's other data types, too,
in Python, which we'll see actually gives us a whole bunch of
more power and capability, things called ranges,
like we just saw, lists, like I called out verbally,
with the square brackets, things called tuples, for
things like x comma y, or latitude, longitude,
dictionaries, or Dicts, which allow you to store keys and
values, much like our hash tables from last time, and then sets in the
mathematical sense, where they filter out duplicates for you, and you can
just put a whole bunch of numbers, a whole bunch of words or whatnot,
and the language, via this data type, will filter out duplicates for you. Now there's going to be a few functions
we give you this week and beyond, training wheels that we're then
going to very quickly take off, just because, as we'll see today, they
just simplify the process of getting user input correctly, without
accidentally writing buggy code, just when you're trying to get Hello,
World, or something similar, to work. And we'll give you functions, not
like, not as long as this list in C, but a subset of these,
get float, get Int, and get string, that'll
automate the process of getting user input in a way that's more
resilient against potential bugs. But we'll see what those bugs might be. And the way we're going to do
this is similar in spirit to C. Instead of doing include,
CS50.h, like we did in C, you're going to now
start saying import CS50. Python supports,
similar to C, libraries, but there aren't header files anymore. You just use the name of
the library in Python. And if you want to import CS50's
functions, you just say import CS50. Or, if you want to be more precise, and
not just import the whole thing, which could be slow, if you've got a really
big library with a lot of functionality in it, you can be more precise and
say from CS50, import get float. From CS50 import get Int,
from CSM 50 import get string, or you can just separate
them by commas and import 3 and only 3 things from a
particular library, like ours. But starting today and
onward, we're going to start making much more
heavy use of libraries, code that other people wrote, so that
we're no longer reinventing the wheel. We're not making our own linked lists,
our own trees, our own dictionaries. We're going to start standing
on the shoulders of others, so that you can get real work
done, so to speak, faster, by building your software on
top of others' code as well. All right, so that's it for the
syntactic tour of the language, and the sort of core features. Soon we'll transition
to application thereof. But let me pause here to see if there's
any questions on syntax or primitives or otherwise, or otherwise. Oh, yes, in back. AUDIENCE: Why don't Python
have the increment operators. DAVID J. MALAN: I'm sorry,
say it again, why doesn't Python have what kind of operators? AUDIENCE: Why doesn't Python
have the increment operator? DAVID J. MALAN: Sorry, someone coughed
when you said something operators. AUDIENCE: The increment. DAVID J. MALAN: Oh,
the increment operator? I'd have to check the history, honestly. Python has tended to be a
fairly minimus language. And if you can do something one
way, the community, arguably, has tended to not give you multiple
ways to do the same thing syntactically. There's probably a better answer. And I'll see if I can dig in and post
something online, to follow up on that. All right, so before we transition
to now writing some actual code, let me go ahead and consider exactly
how we're going to write code. In the world of C, recall that it's
generally been a 2-step process. We create a file called like Hello.c,
and then, step one, make Hello, step 2, ./Hello. Or, if you think back to week
two, when we sort of peeled back the layer of what Hello,
of what make was doing, you could more verbosely type out
the name of the actual compiler, Clang in our case, command line
arguments like dash Oh, Hello, to specify what name you want to create. And then you can specify the file name. And then you can specify what
libraries you want to link in. So that was a very verbose approach. But it was always a two-step approach. And so, even as you've been
doing recent problem sets, odds are you've realized that, any time
you want to make a change to your code, or make a change to your code
and try and test your code again, you're constantly doing those two steps. Moving forward in Python,
it's going to become simpler, and it's going to be just this. The file name is going to change,
but that might go without saying. It's going to be something like
Hello.py, P-Y, instead of Hello.c. And that's just a convention,
using a different file extension. But there's no compilation step per se. You jump right to the
execution of your code. And so Python, it turns out, is
the name, not only of the language we're going to start using, it's also
the name of a program on a Mac, a PC, assuming it's been pre-installed,
that interprets the language for you. This is to say that Python is generally
described as being interpreted, not compiled. And by that, I mean you get to skip,
from the programmer's perspective, that compilation step. There is no manual step in the world of
Python, typically, of writing your code and then compiling it to zeros and ones,
and then running the zeros and ones. Instead, these kind of
two steps get collapsed into the illusion of one, whereby you,
instead, are able to just run the code, and let the computer figure
out how to actually convert it to something the computer understands. And the way we do that is via this
old process, input and output. But now, when you have
source code, it's going to be passed into an
interpreter, not a compiler. And the best analog of this
is just to perhaps point out that, in the human world, if
you speak, or don't speak, multiple human languages, it can
be a pretty slow process from going from one language to another. For instance, here are step-by-step
instructions for finding someone in a phone book,
unfortunately, in Spanish. Unfortunately, if you don't
speak or read Spanish. You could figure this out. You could run this algorithm, but you're
going to have to do some googling, or you're going to have to open
up literal dictionary from Spanish to English and convert this. And the catch with translating
any language, human or computer or otherwise, is that you're going
to pay a price, typically some time. And so converting this in
Spanish to this in English is just going to take you
longer than if this were already in your native language. And that's going to be one of the
subtleties with the world of Python. Yes, it's a feature that you can
just run the code without having to bother compiling it manually first. But we might pay a price. And things might be a little slower. Now, there's ways to chip away at that. But we'll see an example thereof. In fact, let me transition now
to just a couple of examples that demonstrate how Python is
not only easier for many people to use, perhaps yourselves
too, because it throws away a lot of the annoying syntax,
it shortens the number of lines you have to write, and also it
comes with so many darn libraries, you can just do so much more without
having to write the code yourself. So, as an example of this,
let me switch over here to this image from problem set 4, which
is the Weeks Bridge down by the Charles River here in Cambridge. And this is the original
photo, pretty clear, and it's even higher res if we looked
at the original version of the photo. But there have been no filters, a
la Instagram, applied to this photo. Recall, for problem set four, you
had to implement a few filters. And among them might have been blur. And blur was probably among the
more challenging of the ones, because you had to iterate
over all of the pixels, you had to take into account what's
above, what's below, to the left, to the right. I mean, there was a lot
of math and arithmetic. And if you ultimately got it, it was
probably a great sense of satisfaction. But that was probably
several hours later. In a language like
Python, where there might be libraries that had been written
by others, on whose shoulders you can stand, we could
perhaps do something like this. Let me go ahead and run a program, or
write a program, called Blur.py here. And in Blur.py, in VS
Code, let me just do this. Let me import from a library,
not the CS50 library, but the Pillow library, so to
speak, a keyword called image and another one called image
filter, then let me go ahead and say, let me open the current
version of this image, which is called Bridge.bmp. So the before version
of the image will be the result of calling image.open
quote unquote "Bridge.bmp," and then, let me create
an after version. So you'll see before and after. After equals the before version
.filter of image filter. And there is, if I
read the documentation, I'll see that there's something
called a box blur, that allows you to blur in box
format, like one pixel above, below, left, and right. So I'll do one pixel there. And then, after that's done, let
me go ahead and save the file as something like Out.bmp. That's it. Assuming this library
works as described, I am opening the file
in Python, using line 3. And this is somewhat new syntax. In the world of Python, we're going to
start making use of the dot operator more, because in the
world of Python, you have what's called object-oriented
programming, or OOP, as a term of art. And what this means is that
you still have functions, you still have variables,
but sometimes those functions are embedded inside of the
variables, or, more specifically, inside of the data types themselves. Think back to C. When you wanted
to convert something to uppercase, there was a to upper function that takes
as input an argument that's a char. And you can pass in any char you
want, and it will uppercase it for you and give you back a value. Well, you know what, if that's
such a common paradigm, where upper-casing chars is a useful
thing, what the world of Python does is it embeds into the string
data type, or char if you will, the ability just to uppercase any char
by treating the char, or the string, as though it's a struct
in C. Recall that structs encapsulate multiple types of values. In object-oriented programming,
in a language like Python, you can encapsulate not just
values, but also functionality. Functions can now be inside of structs. But we're not going to
call them structs anymore. We're going to call them objects. But that's just a different vernacular. So what am I doing here? Inside of the image library,
there's a function called open, and it takes an argument, the
name of the file, to open. Once I have a variable called before,
that is a struct, or technically an object, inside of
which is now, because it was returned from this
function, a function called filter, that takes an argument. The argument here happens
to be image.boxblur1, which itself is a function. But it just returns the filter to use. And then, after, dot save
does what you might think. It just saves the file. So instead of using fopen and
fwrite, you just say dot save, and that does all of
that messy work for you. So it's just, what, four
lines of code total? Let me go ahead and go
down to my terminal window. Let me go ahead and show you
with LS that, at the moment, whoops, sorry, let me
not bother showing that, because I have other examples to come. I'm going to go ahead and do Python
of Blur.py, nope, sorry, wrong place. I did need to make a command. There we go. OK, let me go ahead and type LS
inside of my filter directory, which is among the sample code online today. There's only one file
called Bridge.bmp, dammit, I'm trying to get these
things ready at the same time. Let me rewind. Let me move this code into place. All right, I've gone ahead
and moved this file, Blur.py, into a folder called
filter, inside of which there's another file called Bridge.bmp,
which we can confer with LS. Let me now go ahead
and run Python, which is my interpreter, and also
the name of the language, and run Python on this file. So much like running
the Spanish algorithm through Google Translate,
or something like that, as input, to get back
the English output, this is going to translate the
Python language to something this computer, or this
cloud-based environment, understands, and then run the
corresponding code, top to bottom, left to right. I'm going to go ahead and Enter. No error message is
generally a good thing. If I type LS you'll now see out.bmp. Let me go ahead and open that. And, you know what, just to make
clear what's really happening, let me blur it even further. Let's make a box that's not
just one pixel around, but 10. So let's make that change. And let me just go ahead and
rerun it with Python of Blur.py. I still have Out.bmp. Let me go ahead and open Out.bmp
and show you first the before, which looks like this. That's the original. And now, crossing my fingers,
four lines of code later, the result of blurring it, as well. So the library is doing all
of the same kind of legwork that you all did for
the assignment, but it's encapsulated it all into a single
library, that you can then use instead. Those of you who might have
been feeling more comfortable, might have done a little
something like this. Let me go ahead and open up one
other file, called Edges.py. And in Edges.py, I'm again going
to import from the Pillow library the image keyword, and the image filter. Then I'm going to go ahead and
create a before image, that's a result of calling image.open
of the same thing, Bridge.bmp, then I'm going to go ahead and run a
filter on that, called image, whoops, image filter.find edges, which
is like a content, if you will, defined inside of this library for us. And then I'm going to do
after.save quote unquote "Out.bmp," using the same file name. I'm now going to run Python of
Edges.py, after, sorry, user error. We'll see what syntax error means soon. Let me go ahead and run
the code now, Edges.py. Let me now open that new file, Out.bmp. And before we had this, and now,
especially if what will look familiar if we did the more comfortable
version of P set 4, we now get this, after
just four lines of code. So again, suggesting the power
of using a language that's better optimized for the tool at hand. And at the risk of really
making folks sad, let's go ahead and re-implement, if we could,
problem set five, real quickly here. Let me go ahead and open
another version of this code, wherein I have a C
version, just from problem set five, wherein you
implemented a spell checker, loading 100,000 plus words into memory. And then you kept track of just
how much time and memory it took. And that probably took
a while, implementing all of those functions in Dictionary.c. Let me instead now go into a
new file, called Dictionary.py. And let me stipulate, for
the sake of discussion, that we already wrote
in advance, Speller.py, which corresponds to Speller.c. You didn't write either of those. Recall for problem set
five, we gave you Speller.c. Assume that we're going
to give you Speller.py. So the onus on us right now is only
to implement Speller, Dictionary.py. All right, so I'm going to go
ahead and define a few functions. And we're going to see now the syntax
for defining functions in Python. I want to go ahead and define
first, a hash table, which was the very first thing
you defined in Dictionary.c. I'm going to go ahead, then, and say
words gets this, give me a dictionary, otherwise known as a hash table. All right, now let me
define a function called check, which was the first function
you might have implemented. Check is going to take a word,
and you'll see in Python, the syntax is a little different. You don't specify the return type. You use the word Def instead to define. You still specify the name of the
function and any arguments thereto. But you omit any mention of types. But you do use a colon and indent. So how do I check if a word is in
my dictionary, or in my hash table? Well, in Python, I can
just say, if word in words, go ahead and return true, else
go ahead and return false, done, with the check function. All right, now I want to do like load. That was the heavy lift, where you
had to load the big file into memory. So let me define a function called load. It takes a string, the
name of a file to load. So I'll call that Dictionary,
just like in C, but no data type. Let me go ahead and open a file by
using an open function in Python, by opening that Dictionary in read mode. So this is a little similar to fopen,
a function in C you might recall. Then let me iterate over
every line in the file. In Python, this is pretty pleasant,
for line in file colon indent. How, now, do I get at the current
word, and then strip off the new line, because in this file of
words, 140,000 words, there's word backslash n,
word backslash n, all right? Well, let me go ahead and get
a word from the current line, but strip off, from the right end
of the string, the new line, which the Rstrip function
in Python does for me. Then let me go ahead and add to my
dictionary, or hash table, that word, done. Let me go ahead and close
the file for good measure. And then let me go ahead and
return true, because all was well. That's it for the load
function in Python. How about the size function? This did not take any arguments, it
just returns the size of the hash table or dictionary in Python. I can do that by returning the
length of the dictionary in question. And then lastly, gone from the
world of Python is malloc and free. Memory is managed for you. So no matter what I do,
there's nothing to unload. The computer will do that for me. So I give you, in these functions,
problem set five in Python. So, I'm sorry, we made
you write it in C first. But the implication now is that,
what are you getting for free, in a language like Python? Well, encapsulated in
this one line of code is much of what you wrote for
problem set five, implementing your array for all of your
letters of the alphabet or more, all of the linked lists that you
implemented to create chains, to store all of those words. All of that is happening. It's just someone else in the
world wrote that code for you. And you can now use it
by way of a dictionary. And actually, I can
change this a little bit, because add is technically not
the right function to use here. I'm actually treating the dictionary
as something simpler, a set. So I'm going to make one tweak, set
recall was another data type in Python. But set just allows it
to handle duplicates, and it allows me to just throw
things into it by literally using a function as simple as add. And I'm going to make
one other tweak here, because, when I'm checking a word,
it's possible it might be given to me in uppercase or capitalized. It's not going to necessarily come
in in the same lowercase format that my dictionary did. I can force every word to
lowercase by using word.lower. And I don't have to do it
character for character, I can do the whole darn string at
once, by just saying word.lower. All right, let me go ahead and
open up a terminal window here. And let me go into, first,
my C version, on the left. And actually I'm going to go ahead
and split my terminal window into two. And on the right, I'm going to go into
a version that I essentially just wrote. But it's also available online, if
you want to play along afterward. I'm going to go ahead and
make speller in C on the left, and note that it takes
a moment to compile. Then I'm going to be ready to
run speller of dictionaries, let's do like the Sherlock
Holmes text, which is pretty big. And then over here, let me get
ready to run Python of speller on texts/homes.txt2. So the syntax is a little
different at the command prompt. I just, on the left, have to
compile the code, with make, and then run it with ./speller. On the right, I don't
need to compile it. But I do need to use the interpreter. So even though the lines are
wrapping a little bit here, let me go ahead and run it on the right. And I'm going to count how
long it takes, verbally, for demonstration sake. One Mississippi, two Mississippi,
three Mississippi, OK, so it's like three
seconds, give or take. Now running it in
Python, keeping in mind, I spent way fewer hours implementing
a spell checker in Python than you might have in problem set five. But what's the trade-off going to be,
and what kinds of design decisions do we all now need to
be making consciously? Here we go, on the right, in Python. One Mississippi, two Mississippi,
three Mississippi, four Mississippi, five Mississippi, six Mississippi,
seven Mississippi, eight Mississippi, nine Mississippi, 10
Mississippi, 11 Mississippi, all right, so 10 or 11 seconds. So which one is better? Let's go to the group here, which
of these programs is the better one? How might you answer that question,
based on demonstration alone? What do you think? AUDIENCE: I think Python's
better for the programmer, more comfortable for the programmer,
but C is better for the user. DAVID J. MALAN: OK, so
Python, to summarize, is better for the programmer,
because it was way faster to write, but C is maybe better for the computer,
because it's much faster to run. I think that's a reasonable formulation. Other opinions? Yeah. AUDIENCE: I think it depends
on the size of the project that you're dealing with. So if it's going to be something
that's relatively quick, I might not care that it
takes 10 seconds to do it. And it could be way faster
to do it with Python. Whereas with C, if I'm dealing
with something like a massive data set or something huge, then that
time is going to really build up on, it might be worth it to put in the
upfront effort and just load it into C, so the process continually will run
faster over a longer period of time. DAVID J. MALAN: Absolutely,
a really good answer. And let me summarize, is it depends
on the workload, if you will. If you have a very large
data set, you might want to optimize your code to be as
fast and performant as it can be, especially if you're running
that code again and again. Maybe you're a company like Google. People are searching a
huge database all the time. You really want to squeeze
every bit of performance as you can out of the computer. You might want to have someone
smart take a language like C and write it at a very low level. It's going to be painful. They're going to have bugs. They're going to have to deal with
memory management and the like. But if and when it works correctly, it's
going to be much faster, it would seem. By contrast, if you have
a data set that's big, and 140,000 words is
not small, but you don't want to spend like 5 hours,
10 hours, a week of your time, building a spell
checker or a dictionary, you can instead leverage a different
language with different libraries and build on top of it, in order to
prioritize the human time instead. Other thoughts? AUDIENCE: Would you,
because with Python, doesn't it also like
convert the words, or like convert the words, for a lesson? When we convert that into
the same version again, do we just take that into view? DAVID J. MALAN: That's a perfect
segue to exactly the next point we wanted to make, which was, is
there something in between? And indeed there is. I'm oversimplifying what this
language is actually doing. It's not as stark a difference
as saying, like, hey, Python is four times slower than C.
Like that's not the right takeaway. There are absolutely ways that
engineers can optimize languages, as they have already done for Python. And in fact, I've configured
my settings in such a way that I've kind of dramatized
just how big the difference is. It is going to be slower,
Python, typically, than the equivalent C program. But it doesn't have
to be as big of a gap as it is here, because, indeed, among
the features you can turn on in Python is to save some intermediate results. Technically speaking, yes,
Python is interpreting Dictionary.py and these
other files, translating them from one language to another. But that doesn't mean it has to do that
every darn time you run the program. As you propose, you can save, or cache,
C-A-C-H-E, the results of that process. So that the second time and the third
time are actually notably faster. And, in fact, Python itself, the
interpreter, the most popular version thereof, itself is
actually implemented in C. So you can make sure that your
interpreter is as fast as possible. And what then is maybe
the high level takeaway? Yes, if you are going to try to
squeeze every bit of performance out of your code, and
maybe code is constrained. Maybe you have very small devices. Maybe it's like a watch nowadays. Or maybe it's a sensor that's installed
in some small format in an appliance, or in infrastructure, where you
don't have much battery life and you don't have much
size, you might want to minimize just how
much work is being done. And so the faster the code runs,
and the better it's going to be, if it's implemented something low level. So C is still very commonly used
for certain types of applications. But, again, if you just want
to solve real world problems, and get real work done, and your time
is just as, if not more, valuable than the device you're
running it on, long term, you know what, Python is among the
most popular languages as well. And frankly, if I were implementing
a spell checker moving forward, I'm probably starting with Python. And I'm not going to
waste time implementing all of that low-level stuff, because
the whole point of using newer, modern languages is to use abstractions
that other people have created for you. And by abstraction, I mean something
like the dictionary function, that just gives you a
dictionary, or hash table, or the equivalent version that I
used, which in this case was a set. All right, any questions,
then, on Python thus far? No, all right. Oh, yeah, in the middle. AUDIENCE: Could you
compile the Python code, or is there some, I'd imagine that
with the audience that can happen, but it feels like if you can just
come up with a Python compiler, that would give you the
best of both worlds. DAVID J. MALAN: Really good
question or observation, could you just compile Python code? Yes, absolutely, this idea of
compiling code or interpreting code is not native to the language itself. It tends to be native to the
conventions that we humans use. So you could actually
write an interpreter for C that would read it top to bottom, left
to right, converting it to, on the fly, something the computer understands, but
historically that's not been the case. C is generally a compiled language. But it doesn't have to be. What Python nowadays is actually
doing is what you described earlier. It technically is, sort
of unbeknownst to us, compiling the code, technically
not into 0's and 1's, technically into something called byte code,
which is this intermediate step that just doesn't take as much time as it
would to recompile the whole thing. And this is an area of research
for computer scientists working in programming languages, to
improve these kinds of paradigms. Why? Well, honestly, for you and I, the
programmer, it's just much easier to, one, run the code and not worry
about the stupid second step of compiling it all the time. Why? It's literally half as many
steps for me, the human. And that's a nice thing to optimize for. And ultimately, too, you might
want all of the fancy features that come with these other languages. So you should really
just be fine-tuning how you can enable these features, as
opposed to shying away from them here. And, in fact, the only time
I personally ever use C is from like September to October
of every year, during CS50. Almost every other month
do I reach for Python, or another language called JavaScript,
to actually get real work done, which is not to impugn C. It's just that
those other languages tend to be better fits for the amount of time I have to
allocate, and the types of problems that I want to solve. All right, let's go ahead and
take a five minute break here. And when we come back, we'll start
writing some programs from Scratch. All right. So let's go ahead and start writing
some code from the beginning here, whereby we start small
with some simple examples, and then we'll build our way up to
more sophisticated examples in Python. But what we'll do
along the way is first, look side by side at
what the C code looked like way back in week 1
or 2 or 3 and so forth, and then write the corresponding
Python code at right. And then we'll transition just
to focusing on Python itself. What I've done in advance today is
I've downloaded some of the code from the course's website,
my source 6 directory, which contains all of the pre-written
C code from weeks past. But it'll also have
copies of the Python code we'll write here together and look at. So first, here is
Hello.c back from week 0. And this was version 0 of it. I'm going to go ahead and do this. I'm going to go ahead and
split my code window up here. I'm going to go ahead and create
a new file called Hello.py. And this isn't something
you'll typically have to do, laying your code out side by side. But I've just clicked the
little icon in VS Code that looks like two columns, that
splits my code editor into two places, so that we can, in fact, see
things, for now, side by side, with my terminal window down below. All right, now I'm going to go ahead
and write the corresponding Python program on the right, which,
recall, was just print, quote unquote, "Hello, world," and that's it. Now down in my terminal
window, I'm going to go ahead and run Python of
Hello.py, Enter, and voila, we've got Hello.py working. So again, I'm not going to play
any further with the C code. It's there just to jog
your memory left and right. So let's now look at a second
version of Hello, world from that first week, whereby
if I go and get Hello1.c, I'm going to drag that
over to the right. Whoops, I'm going to go ahead and
drag that over to the left here. And now, on the right,
let's modify Hello.py to look a little more like this
second version in C, all right? I want to get an answer from
the user as a return value, but I also want to get
some input from them. So from CS50, I'm going to import the
function called getString for now. We're going to get rid
of that eventually, but for now, it's a
helpful training wheel. And then down here, I'm
going to say, answer equals getString quote
unquote, "What's your name"? Question mark, space. But no semicolon, no data type. And then I'm going to
go ahead and print, just like the first example on the slide,
Hello, comma space plus answer. And now let me go ahead and run this. Python, of Hello.py, all right,
it's asking me what's my name. David. Hello comma David. But it's worth calling attention to the
fact that I've also simplified further. It's not just that the
individual functions are simpler. What is also now glaringly omitted
from my Python code at right, both in this version,
and the previous version. What did I not bother implementing? AUDIENCE: The main code. DAVID J. MALAN: Yeah, so I didn't
even need to implement main. We'll revisit the main function,
because having a main function actually does solve problems sometimes. But it's no longer required. In C you have to have that to kick-start
the entire process of actually running your code. And in fact, if you were missing
main, as you might have experienced if you accidentally compiled
Helpers.c instead of the file that contained main, you would
have seen a compiler error. In Python it's not necessary. In Python you can just jump right
in, start programming, and boom, you're good to go. Especially if it's a
small program like this, you don't need the added overhead
or complexity of a main function. So that's one other difference here. All right, there are a few other
ways we could say Hello, world. Recall that I could use a format string. So I could put this whole thing in
quotes, I could use this f prefix. And then let me go ahead and
run Python of Hello.py again. You can perhaps see where
we're going with this. Let me type my name,
David, and here we go. OK, that's the mistake that
someone identified earlier, you need the curly braces. Otherwise no variables are
interpolated, that is substituted, with their actual values. So if I go back in and add those
curly braces to the F string, now let me run Python of Hello.py,
type in my name, and there we go. We're back in business. Which one's better? I mean, it depends. But generally speaking, making
shorter, more concise code tends to be a good thing. So stylistically, the F string is
probably a reasonable instinct to have. All right, well, what more
can we do besides this? Well, let me go ahead here and
let's get rid of the training wheel altogether, actually. So same C code at left. Let me get rid of the CS50
library, which we will ultimately, in a couple of weeks, anyway. I can't use getString,
but I can use a function that comes with Python called input. And, in fact, this is actually a
one-for-one substitution, pretty much. There's really no downside to
using input instead of getString. We implement getString
just for consistency with what you saw in C. Python of
Hello.py, what's your name, David. Still actually works the same. So gone are the CS50
specific training wheels. But we're going to bring
them back shortly, just to deal with integers or
floats or other values, too, because it's going to make
our lives a little simpler, with error checking. All right, any questions, before we
now pivot to revisiting other examples from week 1, but now in Python? All right, let me go
ahead and open up now. Let's say Calculator0.c, which was one
of the first examples we did involving math and operators like that, as
well as functions like getInt, let me go ahead and create a new
file now called Calculator.py, at right, so that I have
my C code at left still, and my Python code at right. All right, let me go dive into a
translation of this code into Python. I am going to use getInt
from the CS50 library. So let me import that. I'm going to go ahead now
and get an Int from the user. So x equals getInt, and I'll
ask them for an x value, just like we did weeks ago. No need to specify a semicolon,
though, or an Int for the x. It will just figure it out. Y is going to get
another Int via y colon, and then down here, I'm going to
go ahead and say print of x plus y. So this is already a bit new. Recall, the C version required that
I use this format string, as well as printf itself. Python is just a little
more user-friendly. If all you want to do is print out a
value, like x plus y, just print it. Don't futz with any percent
signs or format codes. It's not printf, it's
indeed just print now. All right, let me go ahead and
run Python of Calculator.py, Enter, just do a quick sample,
1 plus 2 indeed equals 3. As an aside, suppose I had
taken a different approach to importing the whole CS50 library,
functionally, it's the same. You're not to notice any
performance impact here. It's a small library. But notice what does not
work now, whereas it did work in C. Python of Calculator.py, Enter,
we see our first traceback deliberately here. So a traceback is just
a term of art that says, here is a trace back
through all of the functions that just got executed. In the world of C, you
might call this a stack trace, stack being the operative word. Recall that when we talked
about the stack and the heap, the stack, like a stack of trays,
was all of the functions that might get called, one after the other. We had main, we had swap, then swap went
away, and then main finished, recall. So here's a trace back of all of the
functions or code that got executed. There's not really any functions
other than my file itself. Otherwise there'd be more detail. But even though it's a little cryptic,
we can perhaps infer from the output here, name error, so something related
to the name of something, name, getInt is not defined. And this of course, happens
on line 3 over there. All right, so why is that? Well, Python essentially
allows us to namespace our functions that come from libraries. There was a problem in C. If
you were using the CS50 library, and thus had access
to getInt, getString, and so forth, you could
not use another library that had the same function names. They would collide, and
the compiler would not know how to link them
together correctly. In Python, and other languages
like JavaScript, and in Java, you have support for effectively
what would be called namespaces. You can isolate variables and
function names to their own namespace, like their own container in memory. And what this means is,
if you import all of CS50, you have to say that the getInt you
want is inside the CS50 library. So just like with the image
blurring, and the image edges before, where I had to specify image dot
and image filter dot, similarly here, am I specifying with a dot operator,
albeit a little differently, that I want CS50.getInt in both places. And now if I rerun Python
of Calculator.py, 1 and 2, now we're back in business. Which one is better? Generally speaking, it depends
on just how many functions you're using from the library. If you're using a whole bunch of
functions, just import the whole thing. If you're only using maybe one
or two, import them line by line. All right, so let's go ahead
and make a little tweak here. Let's get rid of this library
and take this training wheel off, too, as quickly as we introduced
it, though for the problems set six you'll be able to use all
of these same functions. Suppose I get rid of this, and
I just use the input function, just like I did by
replacing getString earlier. Let me go ahead now and run
this version of the code. Python of Calculator.py, OK,
how about 1 plus 2 equals 3. Huh. All right, obviously wrong, incorrect. Can anyone explain what just
happened, based on instincts? What just happened here. Yeah. AUDIENCE: You want an answer? DAVID J. MALAN: Sure, yeah. AUDIENCE: Say you have a number
of strings that don't have Ints, so you would part with them and
say, printing one, two, better. DAVID J. MALAN: Exactly, Python
is interpreting, or treating, both x and y as strings,
which is actually what the input function
returns by default. And so plus is now being interpreted
as concatenation, as we defined it earlier. So x plus y isn't x
plus y mathematically, but in terms of string
joining, just like in Scratch. So that's why we're getting
12, or really one two, which isn't itself a number. It, too, is another string. So we somehow need to convert things. And we didn't have this
ability quite as easily in C. We did have like the A to i
function, ASCII to integer, which did allow you to do this. The analog in Python is actually just
to do a cast, a typecast, using Int. So just like in C, you
can use the keyword Int, but you use it a little differently. Notice that I'm not doing parenthesis
Int close parenthesis before the value. I'm using Int as a function. So indeed, in Python, Int is a function. Float is a function, that
you can pass values into, to do this kind of conversion. So now, if I run Python
of Calculator.py, 1 and 2, now we're back in business,
and getting the answer of 3. But there's kind of a catch here. There's always going to be a trade-off. Like that sounds amazing that
it just works in this way. We can throw away the
CS50 library already. But what if the user accidentally
types, or maliciously types in, like a cat, instead of a number. Damn, well, there's one
of these trace backs. Like, now my program has crashed. This is similar in spirit
to the kinds of segfaults that you might have had in C. But they're not segfaults per se. It doesn't necessarily relate to memory. This time it relates to actual
runtime values, not being as expected. So this time it's not a name
error, it's a value error, invalid literal for Int with
base 10 quote unquote "cat." So, again, it's written for sort
of a programmer, more than sort of a typical person, because it's
pretty arcane, the language here. But let's try to interpret it. Invalid literal, a literal is just
something someone typed for Int, which is the function name, with base 10. It's just defaulting to decimal numbers. Cat is apparently not a decimal number. It doesn't look like it, therefore
it can't be treated like it. Therefore, there's a value error. So what can we do? Unfortunately, you would have
to somehow catch this error. And the only way to do
that in Python really is by way of another
feature that C did not have, namely, what are called exceptions. An exception is exactly what just
happened, name error, value error. They are things that can go wrong
when your Python code is running, that aren't necessarily going to be
detected until you run your code. So in Python, and in JavaScript, and in
Java, and other more modern languages, there's this ability to
actually try to do something, except if something goes wrong. And in fact, I'm going to
introduce a bit of syntax here, even though we won't
have to use this much just yet. Instead of just blindly converting
x to an Int, let me go ahead and try to do that. And if there's an exception,
go ahead and say something like print, that is not an Int. And then I'm going to do
something like exit, right there. And let me go ahead and do this here. Let me try to get y, except
if there's an exception. Then let me go ahead and say, again,
that is not an Int exclamation point. And then I'm going to exit
from there to, otherwise I'll go ahead and print x plus y. If I run Python of
Calculator.py now, whoops, oh, forgot my close quote, sorry. All right, so close quote, Python of
Calculator.py, 1 and 2 still work. But if I try to type in
something wrong like cat, now it actually detects the error. So what is the CS50
library in Python doing? It's actually doing that try and accept
for you, because suffice it to say, otherwise your programs for
something simple, like a calculator, start to get longer and longer. So we factored that kind of
logic out to the CS50 getInt function and get float function. But underneath the hood, they're
essentially doing this, try except, but they're being a little more precise. They're detecting a specific error,
and they are doing it in a loop, so that these functions will
get executed again and again. In fact, the best way to do this is to
say except if there's a value error, then print that error
message out to the user. And again, let's not get too into
the weeds here with this feature. We've already put into the CS50 library. But that's why, for instance,
we bootstrap things, by just using these
functions out of the box. All right, let's do something
more with our calculator here. How about this. In the world of C, we
had another version of this code, which actually
did some division by way of-- which actually did division of
numbers, not just the addition herein. So let me go ahead and close the C
version, and let's focus only on Python now, doing some of these
same lines of codes. But I'm going to go
ahead and just assume that the user is going to
cooperate and use proper input. So from CS50, import getInt, that
will deal with any errors for me. X gets getInt, ask the user
for an Int x, y equals getInt, ask the user for an Int y. And then, let's go ahead and do this. Let's declare a variable called
z, set it equal to x divided by y. Then let's go ahead and print z. Still no need for a format string, I
can just print out the variable's value. Let me go ahead and run
Python of Calculator.py. Let me do 1, 10, and I get 0.1. What did I get in C,
though, if you think back. What would we have happened in C? AUDIENCE: Zero? DAVID J. MALAN: Yeah, we
would have gotten zero in C. But why, in C, when you
divide one Int by another, and those Ints are like
1 and 10 respectively? AUDIENCE: It'll give
you an integer back. DAVID J. MALAN: It will give you what? AUDIENCE: An integer back. DAVID J. MALAN: It will give you an
integer back, and, unfortunately, 0.1, the integer part of it is indeed zero. So this was an example of truncation. So truncation was an
issue in C. But it would seem as though this is no
longer a problem in Python, insofar as the division operator
actually handles that for us. As an aside, if you want the old
behavior, because it actually is sometimes useful for
rounding or flooring values, you can actually use two slashes. And now you get the C behavior. So that now 1 divided by 10 is zero. So you don't give up that
capability, but at least it does a more sensible default. Most people, especially new programmers,
when dividing one value by another, would want to get 0.1,
not 0, for reasons that indeed we had to explain weeks ago. But what about another problem we
had with the world of floats before, whereby there is imprecision? Let me go ahead and, somewhat
cryptically, print out the value of z as follows. I'm going to format
it using an f-string. And I'm going to go ahead and format,
not just z, because this is essentially the same thing. Notice this, if I do Python
of Calculator.py, 1 and 10, I get, by default, just
one significant digit. But if I use this syntax in Python,
which we won't have to use often, I can actually do in
C like I did before, 50 significant digits
after the decimal point. So now let me rerun Python
of Calculator.py 1 and 10, and let's see if floating point
imprecision is still with us. Unfortunately, it is. And you can see as much here,
the f-string, the format string, is just showing us now 50 digits
instead of the default one. So we've not solved all problems. But we have solved at least some. All right, before we pivot away from
a mere calculator, any questions now on syntax or concepts or the like? Yeah. AUDIENCE: Do you think
the double slash you get has merit, how do you comment on that? DAVID J. MALAN: How do you what? Oh, how do you comment. Really good question, if you're
using double slash for division with flooring or truncation,
like I described, how do you do a comment in Python. This is a comment. And the convention is actually
to use a complete sentence, like with a capital T here. You don't need a period unless
there's multiple sentences. And technically, it should be above
the line of code by convention. So you would use a hash symbol instead. Good question. I haven't seen those yet. All right, let's go ahead and make
something else here, how about. Let me go ahead and
open up, for instance, an example called Points1.c,
which we saw a few weeks back. And let me go ahead on the other side
and create a file called Points.py. This was a program, recall, that
asked the user how many points they lost on the first assignment. And then it went ahead
and just printed out whether they lost fewer points
than me, because I lost two, if you recall the photo, more points
than me, or the same points as me. Let me go ahead and zoom out so
we can see a bit more of this. And let me now, on the top right here,
go about implementing this in Python. So I want to first prompt the
user for some number of points. So from CS50 let's import getInt,
so it handles the error-checking. Let's then do points
equals getInt, and ask the user, how many points
did you lose, question mark. Then let's go ahead and say, if points
less than two, which was my value, print, you lost fewer points than me. Otherwise, if it's else if points
greater than 2, go ahead and print, you lost more points than me. Else let's go ahead and handle
the final scenario, which is you lost the same number of points as me. Before I run this, does anyone want to
point out a mistake I've already made? Yeah. AUDIENCE: Else if has to be elif. DAVID J. MALAN: Yeah, so else if in
C is actually now elif in Python. It's a single word. So let me change this to elif, and now
cross my fingers, Python of Points.py, suppose you lost three
points on some assignment. You lost more points than my two. If you only lost one point,
you lost fewer points than me. So the logic is the same. But notice the code is much tighter. In 10 total lines, we did in
what was 24 lines, because we've thrown away a lot of the syntax. The curly braces are
no longer necessary. The parentheses are
gone, the semicolons. So this is why it just tends to
be more pleasant pretty quickly, using a language like this. All right, let's do
one other example here. In C, recall that we were able to
determine the parity of some number, if something is even or odd. Well, in Python, let me go ahead
and create a file called Parity.py, and let's look for a moment
at the C version at left. Here was the code in C that we used
to determine the parity of a number. And, really, the key
takeaway from all these lines was just the remainder operator. And that one is still with us. So this is a simple demonstration,
just to make that point, if in Python, I want to determine
whether a number is even or odd. Well, let's go ahead and from CS50,
import getInt, then let's go ahead and get a number like n from the user,
using getInt, and ask them for n. And then let's go ahead and say,
if n percent sign 2 equals 0, then let's go ahead and
print quote unquote "Even." Else let's go ahead and print
out Odd, but before I run this, anyone want to instinctively, even
though we've not talked about this, point out a mistake here? What I did wrong? AUDIENCE: Double equals. DAVID J. MALAN: Yeah, so double equals. Again, so even though some of the stuff
is changing, some of the same ideas are the same. So this, too, should
be a double equal sign, because I'm comparing for equality here. And why is this the right math? Well, if you divide a
number by 2, it's either going to have 0 or 1 as a remainder. And that's going to determine
if it's even or odd for us. So let's run Python of Parity.py,
type in a number like 50, and hopefully we get, indeed, even. So again, same idea, but now
we're down to eight lines of code instead of the 20. Well, let's now do something
a little more interactive and a little representative of tools
that actually ask the user questions. In C, recall that we had this
agreement program, Agree.c. And then let's go ahead and implement
a corresponding version in Python, in a file called Agree.py. And let's look at the C version first. On the left, we used get char here. And then we used the
double vertical bars to check if C is equal to
capital Y or lowercase y. And then we did the
same thing for n for no. And so let's go over here and
let's do from CS50, import get-- OK, get char is not a thing. And this here is another
difference with Python. There is no data type for
individual characters. You have strings, STRs,
and, honestly, those are fine, because if
you have a STR that's just one character, for
all intents and purposes, it is just a single character. So it's just a simplification. You don't have to think as much. You don't have to worry about
double quotes, single quotes. In fact, in Python, you can use
double quotes or single quotes, so long as you're consistent. So long as you're
consistent, the single quotes do not mean something
different, like they do in C. So I'm going to go ahead
and use getString here, although, strictly speaking, I
could just use the input function, as we saw before. I'm going to get a string from the
user that asks them this, getString, quote unquote, "Do you agree," like a
little checkbox or interactive prompt, where you have to say yes or no, you
want to agree to the following terms, or whatnot. And then let's translate the
conditionals to Python, now, too. So if S equals equals quote-unquote
"Y," or S equals equals lowercase y, let's go ahead and print out agreed,
just like in C, elif S equals equals N or S equals equals little n. Let's go ahead, then,
and print out not agreed. And you can already see, perhaps,
one of the differences here, too. Is Python a little more
English-like, in that you just literally use the English word
or, instead of the two vertical bars. But it's ultimately
doing the same thing. Can we simplify this code a bit, though. This would be a little
annoying if we wanted to add support, not just
for big Y and little y, but Yes or big Yes or little yes or
big Y, lowercase e, capital S, right? There's a lot of permutations
of Y-E-S or just y, that we ideally should tolerate. Otherwise, the user is going to
have to type exactly what we want, which isn't very user-friendly. Any intuition for how
we could logically, even if you don't know how to
do it in code, make this better? Yeah. AUDIENCE: Write way over
the list, and then up, it's like the things in the list. DAVID J. MALAN: Nice, yeah, we saw an
example of a list before, just 0, 1, 2. Why don't we take that same
idea and ask a similar question. If S is in the following list
of values, Y or little y, or heck, let me add to the list
now, yes, or maybe all capital YES. And it's going to get a
little annoying, admittedly, but this is still better than the
alternative, with all the or's. I could do things like
this, and so forth. There's a whole bunch more permutations. But let's leave this alone,
and let me just go into here and change this to, if S is in the
following list of N or little n or no, and I won't do as, let's just not
worry about the weird capitalizations there, for now. Let's go ahead and run this. Python of Agree.py, do I agree? Y. OK, how about yes? All right, how about big Yes. OK, that does not seem to work. Notice it did not say agreed,
and it did not say not agreed. It didn't detect it. So how can I do this? Well, you know what I could
do, what I don't really need the uppercase and lowercase. Let me tighten this
list up a little bit. And why don't I just
force S to be lowercase. S.lower, recall, whether
it's one character or more, is a function built into
STRs now, strings in Python, that forces the whole
thing to lowercase. So now, watch what I can do. Python of Agree.py, little y,
that works, big Y, that works. Big Yes, that works, big Y,
little e, big S, that also works. So we've now handled, in one fell
swoop, a whole bunch more logic. And you know what, we can
tighten this up a bit. Here's an opportunity, in Python,
for slightly better design. What have I done in here
that's a little redundant? Does anyone see an opportunity
to eliminate a redundancy, doing something more
times than you need. Is a stretch here, no. Yep. AUDIENCE: You can do S dot lower, above. DAVID J. MALAN: We could
move the S dot lower above. Notice that I'm using S dot lower twice. But it's going to give me
the same answer both times. So I could do a couple of things here. I could, first of all, get rid of
this lower, and get rid of this lower, and then above this, maybe I could
do something like this, S equal-- I can't just do this, because
that throws the value away. It does the math, but it doesn't
convert the string itself. It's going to return a value. So I have to say S equals s.lower. I could do that. Or, honestly, I can chain
these things together. And this is not something we saw in
C. If getString returns a string, and strings have functions
like lower in them, you can chain these functions
together, like this, and do dot this, dot that, dot this other thing. And eventually you want to stop,
because it's going to become crazy long. But this is reasonable,
still fits on the screen. It's pretty tight. It does in one place
what I was doing in two. So I think that's OK. Let me go ahead and do Python
of Agree.py one last time. Let's try it one last time. And it's still working as intended. Also if I tried those
other inputs as well. Yeah, question. AUDIENCE: Could you add on like a for
uppercase as well, for like upper, and then cover all the functions where
it's lowercase, for all the functions where it's uppercase as well, or
could you not just do this again. DAVID J. MALAN: Let me summarize. Could we handle uppercase and
lowercase together in some form? I'm actually doing that already. I just have to pick a lane. I have to either be all lowercase
in my logic or all uppercase, and not worry about
what the human types in, because no matter what
the human types in, I'm forcing their input to lowercase. And then I am using a
lowercase list of values. If I want to flip that, fine. I just have to be self-consistent. But I'm handling that already. Yeah. AUDIENCE: Are strings no
longer an array of characters? DAVID J. MALAN: A really
good loaded questions are strings no longer
an array of characters? Conceptually, yes,
underneath the hood, no. They're a little more
sophisticated than that, because with strings,
you have a few changes. Not only do they have
functions built into them, because strings are now
what we call objects, in what's called
object-oriented programming. And we're going to keep seeing
examples of this dot operator. They are also immutable, so
to speak, I-M-M-U-T-A-B-L-E. Immutable means they cannot be
changed, which means, unlike C, you can't go into a string and
change its individual characters. You can make a copy of the
string that makes a change, but you can't change the
original string itself. This is both a little
annoying, maybe, sometimes. But it's also pretty protective,
because you can't do screw-ups like I did weeks ago, when I was
trying to copy S and call it T. And then one affected the other. Python, underneath the hood, is
handling all of the memory management and the pointers and all of that. There are no pointers in Python. So If that wasn't clear, all of that
pain, if you will, all of that power, is now handled by the language
itself, not by us, the programmers. All right, so let's
introduce maybe some loops, like we've been in the habit of doing. Let me open up Meow.c, which was
an example in C, just meowing a bunch of times textually. Let me create a file called
Meow.py here on the right. And notice on the left,
this was correct code in C, but it was kind of poorly designed. Why? Because it was a missed
opportunity for a loop. Why say something three times
when you can say it just once? So in Python, let me do it
the poorly designed way first. Let me print out meow. And, like I generally should not,
let me copy, paste it three times, run Python of Meow.py, and it works. OK, but not good practice. So let me go ahead and
improve this a little bit. And there's a few ways to do this. If I wanted to do this three times, I
could instead do something like this. For i in range of 3, recall that
that was the better version, rather than arbitrarily enumerate
numbers yourself, let me go ahead and print out quote unquote "Meow." Now if I run Python of
Meow, still seems to work. So it's a little tighter,
and, my God, like, programs can't really get
much shorter than this. We're down to two lines of code, no
main function, no gratuitous syntax. Let's now improve the
design further, like we did in C, by introducing
a function called meow, that actually does the meowing. So this was our first
abstraction, recall, both in Scratch and in C. Let me focus
now entirely on the Python version here. Let me go ahead and
first define a function. Let me first go ahead and do
this, for i in range of 3, let's assume for the moment
that there's a meow function, that I'm just going to call. Let's now go ahead and define, using
the Def key word, which we saw briefly with the speller
demonstration, a function called meow that takes no arguments. And all it does for now is print meow. Let me now go ahead and run
Python of Meow.py Enter, huh, one of those trace backs. So this is another name error. And, again, name meow is not defined. What's your instinct here,
even though we've not tripped over this yet in Python? Where does your mind go here? Yeah. AUDIENCE: Does it read top
to bottom, left to right? I'm guessing we could find a new case. DAVID J. MALAN: Perfect, as smart,
as smarter as Python seems to be, it still makes certain assumptions. And if it hasn't seen a keyword
yet, it just doesn't exist. So if you want it to exist, we
have to be a little clever here. I could just put it, flip
it around, like this. But this honestly isn't
particularly good design. Why? Because now, if you, the reader
of your code, whether you wrote it or someone else, you
kind of have to go fishing now. Like where does this program begin? And even though, yes, it's obvious
that it begins on line four, logically, like, if the file were longer,
you're going to be annoyed and fishing visually for
the right lines of code. So let's reintroduce main. And indeed, this would
be a common paradigm. When you want to start having
abstractions in your own functions, just put your own code in main, so that,
one, you can leave it up top, and two, you can solve the problem
we just encountered. So let me define a function called
main that has that same loop, meowing three times. But now watch what happens. Let me go into my terminal and
run Python of Meow.py, Enter. Nothing. All right, investigate this. What could explain this symptom. I have not told you the answer yet. So all you have is
your instinct, assuming you've never touched Python before. What might explain this symptom,
where nothing is meowing? Yeah? AUDIENCE: Didn't run the main function. DAVID J. MALAN: Yeah, I
didn't run the main function. So in C, this is functionality
you get for free. You have to have a main function. But, heck, so long as you make
it, it will be called for you. In Python, this is just a convention,
to create a main function, borrowing a very common name for it. But if you want to call that
main function, you have to do it. So this looks a little
weird, admittedly, that you have to call your
own main function now, and it has to be at
the bottom of the file, because only once the interpreter
gets to the bottom of the file, have all of your functions
been defined, higher up. But this solves both problems. It keeps your code, that's
the main part of your code, at the very top of the file. So it's just obvious to you, and
a TF, or any reader in the future, where the program logically starts. But it also ensures that main is not
called until everything else, main included, has been defined. So this is another
perfect example of we're learning a new language
for the first time. You're not going to have heard
all of the answers before. Just apply some logic, as to, like, all
right, what could explain this symptom. Start to infer how the
language does or doesn't work. If I now go and run this, Python of
Meow.py, now we're back in business. And just so you have
seen it, there is a quote unquote "better" way of doing this,
that solves different problems that we are not going to encounter,
certainly in these initial days. Typically, you would see in
online tutorials or books, something that looks like this, where
you actually have a weird conditional with multiple underscores. That's functionally the same thing,
but it solves problems with libraries, if we ourselves were implementing a
library or something similar in spirit. But we're going to keep things simpler
and just write main at the bottom, because we're not going to
encounter that problem just yet. All right, let's make one change to
this, just to show how it's done. In C, the last version of meow also
took command line argument, sorry, also took arguments to the function meow. So suppose that I want
to factor this out. And I want to just call meow as a
better abstraction, where I just say meow this number of times. And I figure out how many times
by just, like, putting in number 3 or using getInt or something
like that, to figure out how many times to say meow. Well, now, I have to define
inside my meow function, in input, let's call it n, and then use that,
as by doing this, for i in range of n, let me go ahead and print
out meow that many times. So again, the only thing
that's different in C is we don't bother specifying return
types for any of these functions, and we don't bother specifying the
type of our arguments or our variables. So same ideas, simpler in some sense. We're just throwing away keystrokes. All right, let me run this one
final time, Python of Meow.py, and we still have the same program. All right, let me pause here. Any questions? And I know this is going fast. But hopefully, the C code
is still somewhat familiar. Yeah. AUDIENCE: Is there any difference
between global and local variables. DAVID J. MALAN: Good question. Is there any difference between
global and local variables? Short answer, yes, and we would
run into that same problem, if we declare a variable
in one function, another function is not
going to have access to it. We can solve that by
putting variables globally. But we don't have all of
the features we had in C, like there's no such thing
as a constant in Python. The mentality in the
Python community is, if you don't want some value
to change, don't touch it. Like just don't screw up. So there's trade-offs here, too. Some languages are stronger
or more defensive than that. But that, too, is part of the mindset
with this particular language. [SIREN] DAVID J. MALAN: Yeah. AUDIENCE: There is really
only one green line, in the-- DAVID J. MALAN: Oh, sorry, where's-- say it louder. AUDIENCE: There has only been
one green line printed at a time. DAVID J. MALAN: That
is an amazing segue. Let's come to that in just
a moment, because we're going to recreate also
that Mario example, where we had like the question marks for
the coins and the vertical bars. So let's come back to that in a second. And your question? AUDIENCE: If strings are immutable,
and every time you like make a copy. DAVID J. MALAN: Correct,
strings are immutable. Any time you seem to be modifying
it, as with the lower function, you're getting back a copy. So it's taking a little
more memory somewhere. But you don't have to deal with
it Python's doing that for you. AUDIENCE: So you don't free anything. DAVID J. MALAN: Say it again? You don't need what? AUDIENCE: You don't free
like taking leave on stuff. DAVID J. MALAN: You don't free anything. So if you weren't a big fan,
over the past couple of weeks, of malloc or free or
memory or addresses, or all of those low level
implementation details, Python is the language for
you, because all of that is handled for you automatically. Java does the same. JavaScript does the same. Yeah. AUDIENCE: Each up for the variable, you
put it before the name, use of the body before the name, correct? Well, if there isn't a main function in
Python, how do you define those words? DAVID J. MALAN: How do you
define a global variable if there's no main function in Python? Global variables, by definition, always
need to be outside of main, as well. So that's not a problem. If I wanted to have a
function that's outside of, and, therefore, global to
all of these, like global-- actually, don't use the word global,
that's a special word in Python-- variable equals Foo, F-O-O,
just as an arbitrary string value that a computer scientist would
typically use, that is now global. There are some caveats, though,
as to how you access that. But let's come back
to that another time. But that problem is solvable, too. All right. So let's go ahead and do this. To come back to the question about
the print command, let me go ahead and create a file now called Mario.py. Won't bother showing the C code anymore. We'll focus just on
the new language here. But recall that, in Python, in Mario, we
wanted to first do something like this. This was a random screen from
the side scroller version 1 of Super Mario Brothers. And we just want to print like three
hashes to represent those three blocks. Well, in Python, we could
do something like this, print, oh, sorry, for i in the range of
3, go ahead and print out quote unquote "hash." And I think this is
pretty straightforward. Python of Mario.py, we
get our three hashes. You could imagine
parameterizing this now, though, and getting actual user input. So let's do that. Let me go up here and let me go
and say from CS50, import getInt, and then let's get the
input from the user. So it actually is a
value n, like, all right, getInt the height of the column
of bricks that you want to do. And then, let's go ahead and print
out n hashes instead of three. So let me run this. Let's print out like five hashes. OK, one, two, three, four,
five, that seems to work, too. And it's going to work
for any positive value. But it's not going to work
for, how about negative 1? That just doesn't do anything. But that seems OK. But also recall that it's not going
to work if the user types in something weird, like, oh, sorry, it is going
to work if the user types in something weird like cat, why? We're using CS50's
getInt function, which is handling all of those headaches for us. But, what if the user indeed
types a negative number? We're tolerating that. So that was the bug I
wanted to highlight. It would be nice to re-prompt
them and re-prompt them. And in C, what was the
programming construct we used when we wanted to
ask the user a question. And then, if they didn't cooperate,
prompt them again, prompt them again. What was that? Yeah. AUDIENCE: Do while loop. DAVID J. MALAN: Yeah,
do while loop, right? That was useful, because it's
almost the same as a while loop. But instead of checking a
condition, and then doing something, you do something and
then check a condition, which makes sense with user
input, because what are you even going to check if the
user hasn't done anything yet? You need that inverted logic. Unfortunately in Python,
there is no do while loop. There is a for loop. There is a while loop. And frankly, those are
enough to recreate this idea. And the way to do this in
Python, the Pythonic way, which is another term of art in the
community, is to say this. Deliberately induce an infinite loop,
while True, with capital T for true. And then do what you got to do,
like get an Int from a user, asking them for the
height of this thing. And then, if that is what you want, like
a number greater than zero, go ahead and break out of the loop. So this is how, in Python, you could
recreate the idea of a do while loop. You deliberately induce
an infinite loop. So something's going to
happen at least once. Then, if you get the answer
you want, you break out of it, effectively achieving the same logic. So this is the Pythonic way
of doing a do while loop. Let me go ahead and run Python
of Mario.py, type in 3 this time. And now I get back just
the 3 hashes as well. What if, though, I wanted to
get rid of, how about ultimately that CS50 library function, and
also encapsulate this in a function. Well, let's go ahead and
tweak this a little bit. Let me go ahead and
remove this temporarily. Give myself a main function, so
I don't make the same mistake as I did initially earlier. And let me give myself a function called
get height that takes no arguments. And inside of that function
is going to be that same code. But I don't want to break in
this case, I want to return n. So, recall, that if you return
from a function, you're done, you're going to exit
from right at that point. So this would be fine. You can just say return
n inside of the loop, or, if you would prefer
to break out, you could do something like this instead. Break, and then down here,
you could return, down here, you could return n as well. And let me make one point here
before we go back up to main. This is a little different
from C. And this one's subtle. What have I done here that in C would
have been a bug, but is apparently not, I claim, in Python. It's super subtle, this one. Yeah. AUDIENCE: So aren't we like
defining mostly object, like we're using it
first, defining an object? [INAUDIBLE] DAVID J. MALAN: So similar, it's
not quite that we're using it first. So it's OK not to declare a
variable with like the data type. We've addressed that before, but on line
9, we're assigning n a value, it seems. And then we return n on line 12. But notice the indentation. In the world of C, if we had declared
a variable inside of a loop, on line 9, it would have been scoped
to that loop, which means as soon as you get out of that
loop, like further down in the program, n would not exist. It would be local to the
curly braces therein. Here, logically, curly braces
are gone, but the indentation makes clear that n is still inside of
this loop, between lines 8 through 11. But n is actually still
in scope in Python. The moment you create a variable
in Python, for better or for worse, It is available everywhere within
that function, even outside of the loop in which you defined it. So this logic is actually OK in Python. In C, recall, to solve
this same problem, we would have had to do something
a little hackish like this, like define n up here on line 8,
so that it exists, now, on line 10, and so that it exists on line 13. That is no longer an
issue or need, in Python. Once you create a variable,
even if it's nested, nested, nested inside of
some loops or conditionals, it still exists within
the function itself. All right, any questions then on this,
before we now run this and then get rid of the CS50 library again? OK, so let me go ahead and
get the height from the user. Let's go ahead and create a
variable in main called height. Let's call this get height function. And then let's use that height value,
instead of something hardcoded there. And let me see if this all works now. Python of Mario.py. Hopefully, I haven't
messed up, but I did. But this is an easy fix now. Yeah. AUDIENCE: Got to call main. DAVID J. MALAN: I got to call main. So again, I deleted that earlier. But let me bring it back. So I'm actually calling main. Let me rerun Python of
Mario.py, there we go, height 3. Now it seems to be working. So let's do one last
thing with Mario, just to tie together that idea now
of exceptions from before. Again, exceptions are
a feature of Python, whereby you can try to do something. And if there's a problem, you can
handle it in any way you see fit. Previously, I handled it by just yelling
at the user that that's not an Int. But let's actually use this to
re-implement CS50's own getInt function. Let me throw away
CS50's getInt function. And now let me go ahead and
replace getInt with input. But it's not sufficient
to just use input. What do I have to add to
this line of code on line 8? If I want to get back an Int? AUDIENCE: The Int function. DAVID J. MALAN: Yeah, I
have to cast it to an Int by calling the Int
function around that value, or I could do it on a separate
line, just to be clear. I could also do n equals Int of n. That would work too, but it's
sort of an unnecessary extra line. This is not sufficient, because
that does not change the value. It creates the value. But then it throws it away. We need to assign it. So the conventional way to do this
would probably be in one line, just to keep things nice and tight. So that works fine now. If I run Python of Mario.py, I can
still type in 3, and all as well. I can still type in negative 1, because
that is an Int that I am handling. What I'm not yet handling
is weird input like cat or some string that is
not a base 10 number. So here, again, is my traceback. And notice that here, let
me scroll up a little bit, here we can actually see
more detail in the traceback. Notice that, just like in C, or just
like in the debugger in VS Code, you can see a few things. You can see mention of module, that
just means your file, main, which is my main function, and get height. So notice, it's kind of backwards. It's top to bottom instead
of bottom up, as we drew it on the board the other
day, and as we envisioned stacks of trays in the cafeteria. But this is your stack,
of functions that have been called, from top to bottom. Get height is the most recent,
main is the very first, value error is the problem. So let's try to do, let's try to do this
literally, except if there's an error. So what do I want to do? I'm going to go in here, and I'm
going to say, try to do the following. Whoops, try to do the following, except
if there's a value error, value error, then go ahead and say something,
well, like before, print, that's not an integer exclamation point. But the difference this time is
because I'm in a loop, the user is going to have a chance
to recover from this issue. So if I run Mario.py, 3
still works as before. If I run Mario.py and type
in cat, I detect it now, and because I'm still in that loop,
and because the program hasn't crashed, because I've caught, so to speak, the
value error, using this line of code here, that's the way in Python
to detect these kinds of errors, that would otherwise end up
being on the user's own screen. If I type in cat, dog,
that doesn't work. If I type in, though, 2, I get my two
hashes, because that's, indeed, an Int. Are any questions on
this, and we're not going to spend too much time on
exceptions, but just wanted to show you what's involved with
getting rid of those training wheels. Yeah. AUDIENCE: Then the hash marks in line. DAVID J. MALAN: OK, so let's do this. That actually comes to
the earlier question about printing the
hashes on the same line, or maybe something like this,
where we have the little bricks in the sky, or little question marks. Let's recreate this idea,
because the problem with print, as was noted earlier, is you're
automatically printing out new lines. But what if we don't want that. Well, let's change
this program entirely. Let me throw away all the functions. Let's just go to a simpler world,
where we're just doing this. So let me start fresh in Mario.py. I'm not going to bother with
exceptions or functions. Let's just do a very simple program, to
create this idea, for i in range of 4 this time, because there are
four of these things in the sky. Let's go ahead and just
print out a question mark to represent each of those bricks. Odds are you know this not going to end
well, because these are unfortunately, as you've predicted, on separate lines. So it turns out that the
print function actually takes in multiple arguments, not
just the thing you want to print, but also some additional arguments,
that allow you to specify what the default line ending should be. But what's interesting
about this is that, if you want to change the line
ending to be something like, quote unquote, "that is
nothing," instead of backslash n, this is not sufficient,
because in Python, you can have two types of
arguments, or parameters. Some arguments are positional, which
is the fancy way of saying it's a comma separated list of arguments. And that's what we did all the time
in C. Something comma, something comma, something, we did
it in printf all the time, and in other functions that
took multiple arguments. In Python, you have, not
only positional arguments, where you just separate them by commas,
to give one or two or three or more arguments. There are also named arguments,
which looks weird but is helpful for reasons like this. If you read the
documentation, you will see that there is a named argument
that Python accepts, called end. And if you set that
equal to something, that will be used as the end
of every line, instead of the default, which the
documentation will also say is quote unquote backslash n. So this line here has no effect
on my logic at the moment. But if I change it to just quote
unquote, essentially overriding the default new line character, and
now run Mario again, now I get all four on the same line. There's a bit of a bug, though. My prompt is not meant
to be on the same line. So I can fix that by
just printing nothing. But, really, it's not nothing,
because you get the new line for free. So let me run Python of
Mario.py again, and now we have what I intended in the first
place, which was a little something that looked like this. And this is just one example
of an argument that has a name. But this is a common
paradigm in Python 2, to not just separate things by
commas, but to be very specific, because the print function might take
5, 10, even 20 different arguments. And my God, if you had to
enumerate like 10 or 20 commas, you're going to screw up. You're going to get
things in the wrong order. Named arguments allow you to
be resilient against that. So you only specify
arguments by name, and it doesn't matter what order they are in. All right, any questions, then, on
this, and the overriding of new line. And to be clear, you can do
something like, very weird, but logically expected, like this, by
just changing the line ending, too. But the right way to
solve the Mario problem would be just to override
it to be nothing like this. All right, how about this for cool. And this is why a lot
of people like Python. Suppose you don't really like loops. You don't really like
three-line programs, because that was kind of three
times longer than it needs to be. What if you just printed out
a question mark four times? Python, whoops, Python of
Mario.py, that also works. So it turns out that, just like
the plus operator in Python can join things together,
the multiply operator is not arithmetic in this case. It actually means, take this and
concatenate it four times over. So that's a way of just
distilling into one line what would have otherwise taken multiple
lines in C, fewer, but still multiple lines in Python, but is really
now rather succinct in Python, by doing that instead. Let's do one last Mario example, which
looked a little something like this. If this is another part
of the Mario interface, this is like a grid of like
3 by 3 bricks, for instance. So two dimensions now, just not just
vertical, not horizontal, but now both. Let's print out something
like that, using hashes. Well, how about, how do I do this. So how about for i in range of 3. Then I could do for j in range of
3, just because j comes after I and that's reasonable for counting. I could now print out a hash symbol,
well, let's see what this does. Python of Mario.py, OK, that's
just one crazy long column. What do I need to fix and where
here, to make this look like this? So 3 by 3 bricks, instead
of one long column. Any instincts? AUDIENCE: Why don't we create
a line and then we'll skip it. DAVID J. MALAN: OK, so after
printing 3, we want to skip a line. So maybe like print
out a blank line here. OK, let's try that. I like that instinct, right, print
3, new line, print 3, new line. Let's go ahead and run
Python of Mario.py. OK, it's more visible, what
I'm doing, but still wrong. What can I, what's the
remaining fix, though? Yeah. AUDIENCE: So right behind the two. DAVID J. MALAN: Yeah, I'm
getting an extra new line here, which I don't want
while I'm on this row. So let me do n equals quote unquote,
and now, together, your solutions might take us the whole way there. Python of Mario.py, voila, now
we've got it, in two dimensions. And even this, we can tighten up. Like, we could just use the
little trick we learned. So we could just say,
print a hash times 3 times, and we can get rid of one
of those loops altogether. All it's doing is, whoops, all it's
doing is automating that process. But, no, I don't want to do that. What do I, how do I fix this here. I don't think I want
this anymore, right? Because that's giving
me an extra new line. So now this program is
really tightened up. Same thing, two lines of code. But we're now implementing this
same two dimensional structure here. All right, any questions here on these? Yeah. AUDIENCE: Is there any practical reason
why when we write n, n is, I mean, the print function, you
don't put any spaces in it. DAVID J. MALAN: If I
print n, any spaces. Say that once more. AUDIENCE: Whenever we
write n, for example, the print function
is, you know, in order to stop it from going to a new
line, it seems like any spaces, we did like n equals and then too close. There were no spaces. Did you do that on purpose? DAVID J. MALAN: Oh. yes, good question. I see what you're saying. So in a previous version, let me
rewind in time, when we had this, I did not put spaces. The convention in Python
is not to do that. Why? It just starts to add too much space. And this is a little
inconsistent, because, earlier, when we talked about
like pluses or spaces around the less than or equal
signs, I did say add it. Here it's actually
clearer and recommended to keep them tighter together. Otherwise it just becomes harder
to read where the gaps are. Good observation. All right, let's do, how about,
another five minute break. Let's do that. And then we're going to dive into
some more sophisticated problems, and then ultimately build with some
audio and visual examples, as well. See you in five. All right, so almost all
of the examples we just did were recreations of
what we did in week 1. And recall that week 1 was like
our most syntax-heavy week. It was when we were first learning
how to program in C. But after week 1, we began to focus a bit
more on ideas, like arrays, and other higher-level constructs. And we'll do that again here, condensing
some of those first early weeks into a fewer set of examples in Python. And we'll culminate by actually
taking Python out for a spin, and doing things that
would be way harder to do, and way more time-consuming to do in C,
even more so than the speller example. But how do you go about figuring
out what functions exist, if you didn't hear it in
class, you don't see it online, but you want to see it officially, you
can go to the Python documentation, docs.python.org here. And I will disclaim that, honestly,
the Python documentation is not terribly user-friendly. Google will often be your
friend, so googling something you're interested in, to find your way
to the appropriate page on Python.org, or StackOverflow.com is
another popular website. As always, though, the
line should be googling things like, how do I convert
a string to lowercase. Like that's reasonable to Google. Or how to convert to uppercase or
how implement function in Python. But googling, of course, things like
how to implement problem set 6 in CS50, of course, crosses the line. But moving forward, and really with
programming in general, like Google and Stack Overflow are
your friends, but the line is between the reasonable
and the unreasonable. So let me officially use the
Python documentation search, just to search for something
like the lowercase function. Like, I know I can
lowercase things in Python. I don't quite remember how. So let me just search
for the word lower. You're going to get, often, an
overwhelming number of results, because Python is a pretty big
language, with lots of functionality. And you're going to want to
look for familiar patterns. For whatever reason,
string.lower, which is probably more popular or more commonly used than
these other ones, is third on the list. But it's purple, because I clicked
it a moment ago, when looking for it. So str.lower is probably
what I want, because I am interested at the moment
in lower casing strings. When I click on that, this is an example
of what Python's documentation tends to look like. It's in this general format. Here's my str.lower function. This returns a copy of
the string, with all of the cased characters
converted to lowercase, and the lower-casing
algorithm, dot dot dot. So that doesn't give me much. It doesn't give me sample code. But it does say what the function does. And if we keep looking, you'll see
mention of Lstrip, which is left strip. I used its analog, Rstrip before, right
strip, which allows you to remove, that is strip, from the end of a
string, something like white space, like a new line, or even something else. And if you scroll through
string, this web page here. And we're halfway down the page already. If you see my scroll
bar, tiny on the right, there's a huge amount of functionality
built into string objects, here. And this is just testament to just
how rich the language itself is. But it's also reason to
reassure that the goal, when playing around with some new
language and learning it, is not to learn it exhaustively. Just like in English
or any human language, there's always going to be
vocab words you don't know, ways of presenting the same
information in some language. That's going to be the case with Python. And what we'll do today and this
week in problem set 6 is really get your footing with this language. But you won't know all of Python,
just like you won't know all of C. And, honestly, you won't know all of
any of these languages on your own, unless you're, perhaps, using
them full time professionally, and even then, there's more libraries
than one might even retain themselves. So let's actually now
pivot to a few other ideas, that we'll implement
in Python, in a moment. Let me switch back over to VS Code here. And let me whip up, say, a recreation
of our scores example from week two, where we averaged like
three scores together. And that was an opportunity
in week 2 to play with arrays, to realize how constrained arrays are. They can't grow or shrink. You have to decide in advance. But let's see what's
different here in Python. So let me do Scores.py, and let
me give myself an array in Python called scores, sorry, let me give myself
a variable in Python called scores. Set it equal to a list
of three scores, which are the same ones we've used
before, 72, 73, 33, in this context meant to be scores, not ASCII values. And then let's just do
the average of these. So average will be another variable. And it turns out I can do, well,
how did I sum these before? I probably had a for loop to add
one, then I knew how long they were. Turns out in Python, you
can just say sum of scores divided by the length of scores. That's going to give me my average. So sum is a function that takes
a list, in this case, as input, and it just does the sum for
you, with a for loop or whatever underneath the hood. Len gives you the length of the
list, how many things are in it. So I can dynamically figure that out. Now let me go ahead and print out,
using print, the word average, and then, in curly braces, the actual
average, close quote. All right, so let's run this
code, Python of Scores.py. And there is my average, in this
case, 59.33333 and so forth, based on the math. Well, let's actually, now,
change this a little bit and make it a little more interesting,
and actually get input from the user rather than hard coding this. Let me go back up here and
use from CS50 import getInt, because I don't want to deal with
all the exceptions and the loops. Like, I just want to use
someone else's function here. Let me give myself an
empty list called scores. And this is not something we
were able to do in C, right? Because in C, if you tried
to make an empty array, well, that's pretty stupid,
because you can't add things to it. It's a fixed size. So it wouldn't even let you do that. But I can just create
an empty list in Python, because lists, unlike arrays,
are really lengthless. They'll grow and shrink. But you and I are not dealing with
all the pointers underneath the hood. Python's doing that for us. So now, let's go ahead and get a
whole bunch of scores from the user. How about three of them in total. So for i in range of 3, let's go
ahead and grab a score from the user, using getInt, asking them for score. And then let's go ahead and append, to
the scores list, that particular score. So it turns out that a list,
and I could read the Python documentation to confirm as much,
lists have a function built into them, and functions built into objects
are generally known as methods, if you've heard that term before. Same idea, but whereas a function
kind of stands on its own, a method is a function built
into an object, like a list here. That's going to achieve the
same result. Strictly speaking, I don't need the variable. Just like in C, I could tighten this
up and do something like this as well. But, I don't know, I
kind of like it this way. It's more clear, to me, at least, that
what I'm doing here, getting the score and then appending it to the list. Now the rest of the
code can stay the same. Python of Scores.py,
score will be 72, 73, 33. And I get back the math. But now the program's a little
more dynamic, which is nice. But there's other
syntax I could use here. Just so you've seen it, Python does
have some neat syntactic tricks, whereby, if you don't
want to do scores.append, you can actually say scores
plus equals this score. So you can actually concatenate
lists together in Python 2. Just as we used plus to
join two strings together, you can use plus to
join two lists together. The catch is, you need
to put the one score I'm adding here in a list of its
own, which is kind of silly. But it's necessary, so that this
thing and this thing are both lists. To do this more verbosely,
which most programmers wouldn't do, but just for clarity,
this is the same thing as saying scores plus this score. So now maybe it's a little more
clear that scores and brackets score plural, sorry, singular, are both
lists themselves, being concatenated or joined together. So two different ways, not sure
one is better than the other. This way is pretty common, but .append
is also quite reasonable as well. All right, how about another
example from week two. This one was called uppercase. So let me do this in
Uppercase.py, though, this time. And let me import from
CS50, get string again. And let me go ahead and say,
before will be my first variable. Let me get a string from the user,
asking them for a before string. And then let me go ahead and say,
after, just to demonstrate some changes, upper-casing to this string. Let me change my line ending to
be that, using our new trick. And this is where things get cool
in Python, relatively speaking. If I want to iterate over all
of the characters in a string, and print them out in uppercase,
one way to do that would be this. For c in the before string, go ahead and
print out C.uppercase, sorry, C.upper, but don't end the line yet, because I
want to keep these all on the same line until I'm all done. So what am I doing? Python of Uppercase.py, let me
type in Hello in all lowercase. I've just upper-cased the whole string. How? I first get string, calling it before. I then just print out some fluffy
text that says after colon, and I get rid of the line ending,
just so I can kind of line these up. Notice I hit the spacebar
a couple of times just so letters line up to be pretty. For c and before, this is new. This is powerful in C,
sorry, in Python, whereby you don't have to do like Int i
equals 0 and i less than this, you could just say, for c in the
string in question, for c and before. And then here is just upper-casing
that specific character, and making sure we don't
output a new line too soon. But this is actually more
work than I need to do. Based on what we've seen thus far,
like from our agreement example, can I tighten this up further? Can I collapse lines 5 and 6,
maybe even 7, all together? If the goal of this program is just
to uppercase the before string, how might I do this? Yeah, in back. AUDIENCE: Would it be str.upper? DAVID J. MALAN: Str.upper,
yeah, so I could do something like this, after gets before.upper. So it's not stir
literally dot upper, stir just represents the string in question. So it would be before.upper,
but right idea otherwise. And so let me go ahead and just tweak
my print statement a little bit. Let me just go ahead and print out the
after variable here, after creating it. So this line is the same, I'm
getting a string called before. I'm creating another variable
called after, and, as you propose, I'm calling upper on the whole
string, not one character at a time. Why? Because it's allowed. And, again, in Python, there aren't
technically characters individually. There's only strings, anyway. So I might as well do them all at once. So if I rerun the code now,
Python of Uppercase.py. Now I'll type in Hello in all
lowercase, and, oh, so close, I think I can get rid of
this override, because I'm printing the whole thing out at
once, not character by character. So now if I type in Hello before,
now I have an even tighter version of the program here. All right, any questions,
then, on lists or on strings, and what this kind of function,
upper, represents, with its docs. No? All right, so a couple other
building blocks before we start. Oh. Where was that? AUDIENCE: To the right. DAVID J. MALAN: To the right, right. Yes, thank you. AUDIENCE: Could you write, very close to
variable string, and then print upper, you start creating a variable upper. DAVID J. MALAN: Yes, do I have
to create this variable, upper? No, I don't. I could actually tighten
this up, and, if you really want to see something neat,
inside of the curly braces, you don't have to just put
the names of variables. You can put a small
amount of logic, so long as it doesn't start to look stupid and
kind of overwhelmingly complex, such that it's sort of bad
design at that point. I can tighten this up like this. And now we're in Python of
Uppercase.py, writing Hello again. And that, too, works. But I would be careful about this. You want to resist the temptation of
having like a long line of code that's inside the curly braces, because
it's just going to be harder to read. But, absolutely, you
could indeed do that, too. All right, how about command line
arguments, which was one thing we introduced in week two also, so
that we could actually have the ability to take input from the user, whoops. So we could actually take input
from the user at the command line, so as to take literally
command line arguments. These are a little different,
but it follows the same paradigm. There's no main by default.
And there's no Def main int arg c char, or we called it string,
argv by default. There's none of this. So if you want access to the
argument vector, argv, you import it. And it turns out, there's another
module in Python, or library in Python called CIS, and you can import from
the system this thing called argv. So same idea, different place. Now I'm going to go ahead and do this. Let's write a program that just requires
that the user types in two, a word, after the program's
name, or none at all. So if the length of argv equals 2,
let's go ahead and print out, how about, Hello comma argv bracket 1 close quote,
else if they don't type two words total at the prompt, let's just say
the default's, like we did weeks ago, Hello, world. So the only thing that's new here
is we're importing argv from CIS, and we're using this fancy f-string
format, which kind of to your point, too, it's putting more complex
logic in the curly braces. But that's OK. In this case, it's a list called argv,
and we're getting bracket 1 from it. Let's do Python of Argv.py,
Enter, Hello, world. What if I do Argv.py
David at the command line. Now I get Hello, David. So there's one curiosity here. Python is not included in
argv, whereas in C, dot slash whatever was the first thing. If the analog in Python is that
the name of your Python program is the first thing, in bracket 0,
which is why David is in bracket 1, the word Python does not appear in
the argv list, just to be clear. But otherwise, the
idea of these arguments is exactly the same as before. And in fact, what you can
do, which is kind of cool, is, because argv is a list,
you can do things like this. For arg in argv, go ahead
and print out each argument. So instead of using a
for loop and i and all of this, if I do Python of argv Enter,
it just writes the program's name. If I do Python of argv Foo,
it puts Argv.py and Foo. If I do, sorry, if I do Foo and
bar, those words all print out. If I do Foobar baz, those print out too. And Foo and bar or baz are like
a mathematician's x and y and z for computer scientists, when you
just need some placeholder words. So this is just nice. It reads a little more like English, and
a for loop is just much more concise, allows you to iterate very quickly
when you want something like that. Suppose I only wanted the real
words that the human typed after the program's name. Like, suppose I want to ignore Argv.py. I mean I could do something
hackish like this. If arg equals Argv.py,
I could just ignore, you know, let's invert the logic. I could do this, for instance. So if the arg does not
equal the program name, then go ahead and print out the word. So I get Foobar and baz only. Or, this is what's kind of neat
about Python 2, let me undo that. And let me just take a slice of
the array of the list instead. So it turns out, if argv is
a list, I can actually say, you know what, go into that list,
start at element 1, instead of 0, and then go all the way to the end. And we have not seen this
syntax in C. But this is a way of slicing a list in Python. So now watch what happens. If I run Python of
Argv.py, Foo bar baz Enter, I get only a subset of the
list, starting at position 1, going all of the way to the end. And you can even do
kind of the opposite. If, for whatever reason, you
want to ignore the last element, you can say colon, we
could say colon negative 1, and use a negative number,
which we've not seen before, which slices off the end
of the list, as well. So there's some syntactic tricks
that tend to be powerful in Python 2, even if at first glance, you might
not need them for typical things. All right, let's do one
other example with exit, and then we'll start actually
applying some algorithms, to make things interesting. So in one last program here, let's do
Exit.py, just to do one more mechanic, before we introduce some algorithms. And let's do this. Let's import from CIS, import argv. Let's now do this. Let's make sure the user gives
me one command line argument. So if the length of argv does not
equal 2 in total, then let's go ahead and print out something like
missing command line argument, just to explain what the problem is. And then let's do this. We can exit. But I'm going to use a
better version of exit here. Let me import two functions from CIS. Turns out the better way to do this is
with CIS.exit, because I can then exit specifically 2, with this exit code. Otherwise, down here, I'm going
to go ahead and print out, something like Hello, comma
argv bracket 1, same as before. And then I'm going to exit with zero. So, again, this was a
subtle thing we introduced in week two, where you can
actually have your programs exit, with some number, where
0 signifies success, and anything else signifies error. This is just the same idea in Python. So if I, for instance, just run the
program like this, oops, I screwed up. I meant to say exit here and exit here. Let me do that again. If I run this like this, I'm
missing a command line argument. So let me rerun it with
like my name at the prompt. So I have exactly two command line
arguments, the file name and my name, Hello comma David. And if I do David Malan, it's
not going to work either, because now argv does not equal 2. But the difference here is
that we're exiting with 1, so that special programs can detect an
error, or 0 in the event of success. And now there's one other
way to do this, too. Suppose that you're
importing a lot of functions, and you don't really want
to make a mess of things and just have all of these
function names available, without it being clear
where they came from. Let's just import all of CIS. And let's just change our syntax,
kind of like I proposed for CS50, where we just prepend to all
of these library functions, CIS, just to be super-explicit
where they came from, and if there's another
exit or argv value that we want to import from a library,
this is one way to avoid collision. So if I do it one last time here,
missing command line argument. But David still actually worked. All right, only to demonstrate how
we can implement that same idea. Let's now do something more
powerful, like a search algorithm, like binary search. I'm going to go ahead and open
up a file called Numbers.py, and let's just do some searching
or linear search, rather, on a list of numbers. Let's go ahead and do this. How about import CIS as before. Let me give myself a list of
numbers, like 4, 6, 8, 2, 7, 5, 0, so just a bunch of integers. And then let's do this. If you recall from week three,
we searched for the number 0 at the end of the lockers on stage. So let's just ask that
question in Python. No need for a loop or
anything like that. If 0 is in the numbers, go
ahead and print out found. And then let's just exit successfully,
with 0, else, if we get down here, let's just say print not found. And then we'll CIS exit with 1. So this is where Python
starts to get powerful again. Here's your list. Here is your loop, that's doing
all of the checking for you. Underneath the hood, Python
is going to use linear search. You don't have to implement it yourself. No while loop, no for loop,
you just ask a question. If 0 is in numbers,
then do the following. So that's one feature
we now get with Python, and get to throw away
a lot of that code. We can do it with strings, too. Let me open a file
called Names.py instead, and do something that was
even more involved in C, because we needed Str Comp and
the for loop, and so forth. Let me import CIS for this file. Let's give myself a bunch
of names like we did in C. And those were Bill and Charlie
and Fred and George and Ginny, and two more, Percy, and lastly Ron. And recall, at the
time, we looked for Ron. And so we had to iterate
through the whole thing, doing Str Comp and i plus
plus and all of that. Now just ask the question, if Ron
is in names, then let's go ahead and, whoops, let me hide that. I hit the command too soon. Let me go ahead and say
print, found, as before. CIS exit 1, just to indicate
success, and then down here, if we get to this point,
we can say not found. And then we'll just CIS exit 1 instead. So, again, this just does linear search
for us by default, Python of Names.py, we found Ron, because, indeed, he's
there, and at the end of the list. But we don't need to deal with
all of the mechanics of it. All right, let's take
things one step further. In week three, we also
implemented the idea of a phone book, that actually
associated keys with values. But remember, the phone book in
C, was kind of a hack, right? Because we first had two arrays,
one with names, one with numbers. Then we introduced structs, and
so we gave you a person structure. And then we had an array of persons. You can do this in Python, using
objects and things called classes. But we can also just use a
general purpose dictionary, because just like in P set 5, you
can associate keys with values, using a hash table, using a try. Well, similarly, can
Python just do this for us. From CS50, let's import get string. And now let's give myself
a dictionary of people, D-I-C-T () open paren closed
paren gives you a dictionary. Or you can simplify
the syntax, actually, and a dictionary again is just keys
and values, words and definitions. You can also just use
curly braces instead. That gives me an empty dictionary. But if I know what I want to put in it
by default, let's put Carter in there, with a number of plus 1-617-495-1000,
just like last time, and put myself, David, with plus 1-949-468-2750. And it came to my attention,
tragically, after class that day, that we had a bug in
our little Easter egg. If today, you would like to call
me or text me, at that number, we have fixed the code that
underlies that little Easter egg. Spoiler ahead. All right, so this now
gives me a variable called people, that's
associating keys with values. There is some new syntax here in
Python, not just the curly braces, but the colons, and the quotes
on the left and the right. This is a way, in Python,
of associating keys with values, words with definitions,
anything with anything else. And it's going to be a super-common
paradigm, including in week seven, when we look at CSS and HTML and
web programming, keys and values are like this omnipresent idea in
computer science and programming, because it's just a really useful way
of associating one thing with another. So, at this point in the story, we
have a dictionary, a hash table, if you will, of people, associating
names with phone numbers, just like a real world phone book. So let's write a program that gets
a string from the user and asks them whose number they would like to look up. Then, let's go ahead and say, if that
name is in the people dictionary, go ahead and print out
that person's number, by going into the people
dictionary and going to that specific name, within there,
using an f-string for the whole thing. So this is similar in spirit to before. Linear search and dictionary lookups
will just happen automatically for you in Python, by just asking the
question, if name and people. And this line is just
going to print out, whoever is in the people
dictionary, at that name. So I'm using square brackets, because
here's the interesting thing in Python, just like you can index into
an array, or a list in Python, using numbers, 0, 1, 2, you
can very conveniently index into a dictionary in Python,
using square brackets, as well. And just to make clear what's
going on here, let me go and create a temporary variable,
person equals people bracket name. And then let's just, or, sorry, let's
say, number equals people bracket name. And that will just print
out the number in question. In C, and previously in Python,
anything with square brackets like this would have been go to a location in
a list or an array, using a number. But that can actually be a string,
like a word the human has typed. And this is what's amazing
about dictionaries, it's not like a big
line, a big linear thing. It's this table, that you can
look up in one column the name, and get back in the
other column the number. So let's go ahead and run
Python of Phonebook.py, found, not that, oh, wait. That's not what's
supposed to happen at all. I think I'm in the wrong play. Phonebook.py. What's going on? Print found. I am confused. OK, let's run this again. Python of Phonebook.py, what the-- OK, stand by. [KEYS CLICKING] What the heck? What am I not understanding here? OK, Roxanne, Carter, do you
see what I'm doing wrong? AUDIENCE: I don't. DAVID J. MALAN: What the-- [LAUGHTER] Say again? SPEAKER 47: When you found the test
results, it was doing both commands. DAVID J. MALAN: Oh, yeah, found,
OK, we're going to do this. One sec. [KEYS CLICKING] Whoa, OK. All this is coming out of the video. So. [LAUGHTER] [APPLAUSE] Thanks. All right. I will try to figure out
what was going wrong. The best I can tell, it was
running the wrong program. I don't quite understand why. So we will diagnose this later. I just put the file into a temporary
directory, for now, to run it. So let me go ahead and just run
this, Python of Phonebook.py, type in, for instance, my name. And there's my corresponding number. Have no idea what was just happening. But I will get to the
bottom of it and update you, if we can put our finger on it. So this was just an example, now,
of implementing a phone book. Let's now consider what we
can do that's a little more powerful, in these examples,
like a phone book that actually keeps this information around. Thus far, these simple phone book
examples throw the information away. But using CSV files,
comma separated values, maybe we could actually keep
around the names and numbers, so that, like on your
phone, you can actually keep your contacts around long-term. So I'm going to go ahead now and
do a slightly different example. And let me just hide this
detail, so it's not confusing. Whoops, I'm going to change
my prompt temporarily. So let me go ahead now and
refine this example as follows. I'm going to go into
Phonebook.py, and I'm going to import a whole
library called CSV. And this is a powerful
one, because Python comes with a library that just
handles CSV files for you. A CSV file is just a file
with comma separated values. And, in fact, to demonstrate
this, let me check on one thing here, just to make this
a little more real. To demonstrate this, let's
go ahead and do this. Let me import the CSV library from CS50. Let me import getString. Let me then open a file,
using the open function, open a file called
Phonebook.csv, in append format, in contrast with read
format and write format. Write just blows it away if it exists,
append adds to the bottom of it. So I keep this phone book
around, just like you might keep adding contacts to your phone. Now let me go ahead and get a
couple of values from the user. Let me say getString and
ask the user for a name. Then let me getString again, and
ask the user for their number. And now, let me go ahead and do this. And this is new, and
this is Python-specific. And you would only know this
by following a tutorial, or reading the documentation. Let me give myself a
variable called writer, and ask the CSV library
for a writer to that file. Then, let me go ahead and
use that writer variable, use a function or a method
inside of it, called write row, to write out a list containing
that person's name and number. Notice the square brackets
inside the parentheses, because I'm just printing a list
to that particular row in the file. And then I'm just going
to close the file. So what is the effect of all of this? Well, let me go ahead and run
this version of Phonebook.py, and I'm prompted for a name. Let's do Carter's first, plus
1-617-495-1000, and then, let's go ahead and LS. Notice in my current directory,
there's two files now, Phonebook.py, which I wrote, and
apparently Phonebook.csv. CSV just stands for
comma separated values. And it's like a very simple way
of storing data in a spreadsheet, if you will, where the comma represents
the separation between your columns. There's only two columns
here, name and number. But, because I'm writing to
this file in append mode, let me run it one more time,
Python of Phonebook.py, and let me go ahead and do David
and plus 1-949-468-2750, Enter. And notice what happened
in the CSV file. It automatically updated,
because I'm now persisting this data to the file in question. So if I wanted to now
read this file in, I could actually go ahead and
do linear search on the data, using a read function to
actually read from the CSV. But, for now, we'll just leave
it a little simply as write. And let me make one refinement here. It turns out that, if you're in
the habit of re-opening a file, you don't have to even
close it explicitly. You can instead do this. You can instead say, with the opening
of a file called Phonebook.csv in append mode, calling the thing file,
go ahead and do all of these lines here. So the with keyword is
a new thing in Python. And it's used in a few different
ways, but one of the ways it's used is to tighten up code here. And I'm going to move my
variables to the outside, because they don't need to be
inside of the with statement, where the file is open. This just has the effect of
ensuring that you, the programmer, don't screw up, and accidentally
don't close your file. In fact, you might
recall, from C, Valgrind might have complained at you, if you had
a file that, you didn't close a file, you might have had a memory leak
as a result. The with keyword takes care of all of
that for you, as well. How about let's do, want to do this. How about, let's do one other thing. Let's do this. Let me go ahead and propose,
that on your phone or laptop here, or online, go to this URL here,
where you'll find a Google form. And just to show that these CSVs
are actually kind of omnipresent, and if you've ever
like used a Google Form or managed a student group,
or something where you've collected data via Google
Forms, you can actually export all of that data via CSV files. So go ahead to this URL here. And those of you
watching on demand later, will find that the form
is no longer working, since we're only doing this live. But that will lead to
a Google Form that's going to let everyone input
their answer to a question, like what house do you
want to end up into, sort of an approximation of the
sorting hat in Harry Potter. And via this form, will we then
have the ability to export, we'll see, a CSV file. So let's give you a moment to do that. In just a moment, I'll share
my version of the screen, which is going to let me actually
open the file, the form itself. And in just a moment, I'll switch over. OK, so this is now my
version of the form here, where we have 200 plus responses
to a simple question of the form, what house do you belong in, Gryffindor,
Hufflepuff, Ravenclaw, or Slytherin. If I go over to responses, I'll see all
of the responses in the GUI form here. So graphical user interface,
and we could flip through this. And it looks like, interestingly,
40% of Harvard students want to be in Gryffindor, 22%
in Slytherin, and everyone else in between the others. But you might have noticed,
if ever using a Google Form, this Google Spreadsheets link. So I'm going to go ahead and click that. And that's going to automatically open,
in this case, Google Spreadsheets. But you can do the same thing
with Office 365 as well. And now you see the raw
data as a spreadsheet. But in Google Spreadsheets, if I go
to File and then I go to Download, notice I can download this as
an Excel file, a PDF, and also a CSV, comma separated values. So let me go ahead and do that. That gives me a file in my
Downloads folder on my computer. I'm going to now go back
to my code editor here. And what I'm going to go
ahead and do is upload this file, from my
Downloads folder to VS Code, so that we can actually
see it within here. And now you can see this open file. And I'm going to shorten its name,
just so it's a little easier to read. I'm going to rename this using the
MV command, to just Hogwarts.csv. And then we can see, in the file, that
there's two columns, timestamp column house, where you have a
whole bunch of time stamps when people filled out the form,
with someone very early in class. And then everyone else
just a moment ago. And the second value, after each
comma, is the name of the house. Well, let me go ahead here
and implement a program in a file called Hogwarts.py,
that processes this data. So in Hogwarts.py, let's
just write a program that now reads a CSV, in
this case not a phone book, but everyone's sorting hat information. And I'm going to go
ahead and Import CSV. And suppose I want to answer a
reasonable question, ignoring the fact that Google's GUI or graphical
user interface, can do this for me. I just want to count up who's
going to be in which house. So let me give myself a dictionary
called houses, that's initially empty, with curly braces. And let me pre-create a few keys. Let me say Gryffindor is
going to be initialized to 0, Hufflepuff will be initialized
to 0 as well, Ravenclaw will be initialized to 0. And finally, Slytherin
will be initialized to 0. So here's another example of
a dictionary, or a hash table, just being a very
general-purpose piece of data. You can have keys and values. The keys, in this case, are the houses. The values are initially zero,
but I'm going to use this, instead of like four separate variables,
to keep track of everyone's answer to this form. So I'm going to do this. With opening Hogwarts.csv, in read mode,
not append, I don't want to change it. I just want to read it, as
file as my variable name. Let's go ahead and create
a reader this time, that is using the reader function in
the CSV library, by opening that file. I'm going to go ahead and ignore
the first line of the file, because, recall, that the first
line is just timestamp and house. I want to get the real data. So this next function
is just a little trick for ignoring the first line of the file. Then let's do this. For every other row in the
reader, that is line by line, get the current person's house,
which is in row bracket 1. This is what the CSV reader
library is doing for us. It's handling all of the
reading of this file. It figures out where the comma is,
and, for every row in the file, it hands you back a list of size 2. In bracket 0 is the time stamp,
in bracket 1 is the house name. So, in my code, I can say
house equals row bracket 1. I don't care about the time
stamp for this program. And then let's go into my dictionary
called houses, plural, index into it at the house location, by
its name, and increment that 0 to 1. And now, at the end
of this block of code, that has the effect of iterating
over every line of the file, updating my dictionary
in four different places, based on whether someone typed
Gryffindor or Slytherin or anything else. And notice that I'm using the name of
the house to index into my dictionary, to essentially go up to this little
cheat sheet and change the 0 to a 1, the 1 to a 2, the 2 to
a 3, instead of having like four separate
variables, which would just be much more annoying to maintain. Down at the bottom, let's
just print out the results. For each house in those
houses, iterating over the keys they're in
by default in Python, let's go ahead and print
out an f-string that says, the current house has the current count. And count will be the result of indexing
into houses, for that given house. And let me close my quote. So let's run this to summarize
the data, Hogwarts.py, 140 of you answered Gryffindor, 54 Hufflepuff,
72 Ravenclaw, and 80 of you Slytherin. And that's just my now way
of code, and this is, oh, my God, so much easier than C, to
actually analyze data in this way. And one of the reasons that Python is so
popular for data science and analytics, more generally, is that it's actually
really easy to manipulate data, and run analytics like this. And let me clean this up slightly. It's a little annoying that
I just have to know and trust that the house name is in bracket
1 and timestamp is in bracket 0. Let's clean this up. There's something called a
Dictionary Reader in the CSV library that I can use instead. Capital D, capital R, this means
I can throw away this next thing, because what a dictionary
reader does is it still returns to me every row from
the file, one after the other, but it doesn't just give me a list
of size 2 representing each row. It gives me a dictionary. And it uses, as the keys in that
dictionary, timestamp and house, for every row in the
file, which is just to say it makes my code a little
more readable, because instead of doing this little
trickery, bracket 1, I can say quote unquote "Bracket
House" with a capital H, because it's capitalized
in the Google Form itself. So the code now is
just minorly different, but it's way more resilient, especially
if I'm using Google Spreadsheets, and I'm moving the columns around
or doing something like that, where the numbers might get messed up. Now I can run this on Hogwarts.py
again, and I get the same answers. But I now don't have to worry about
where those individual columns are. All right, any questions on
those capabilities there. And that's a teaser of sorts,
for some of the manipulation we'll do in P set 6. All right, so some final
examples and flair, to intrigue with what you can do with Python. I'm going to actually switch over
to a terminal window on my own Mac, so that I can actually use
audio a little more effectively. So here's just a terminal
window on Mac OS. I before class have preinstalled
some additional Python libraries, that won't really work
in VS Code in the cloud, because they require audio that the
browser won't necessarily support. But I'm going to go ahead
and write an example here that involves writing a speech-based
program, that actually does something with speech. And I'm going to go ahead
and import a library, that, again, I pre-installed,
called Python text to speech, and I'm going to go ahead
and, per its documentation, give myself a speech engine, by
using that library's init function, for initialize. I'm then going to use this
engine's save function to do something fun, like Hello, world. And then I'm going to go ahead and
tell this engine to run and wait, while it says those words. All right, I'm going to save this file. I'm not using VS Code at the moment. I'm using another popular program
that we used in CS50 back in my day, called Vim, which is a
command line program that's just in this black and white window. Let me go ahead now and run
Python of Speech.py, and-- COMPUTER: Hello, world. DAVID J. MALAN: All right, so
it's a little computerized, but it is speech that has been
synthesized from this example. Let's change it a little
bit to be more interesting. Let's do something like this. Let's ask the user for their name,
like what's your name question mark. And then, let's use the little F
string, and say, not Hello, world, but Hello to that person's name. Let me save my file, run
Python of Speech.py, Enter. David. COMPUTER: Hello, David. DAVID J. MALAN: All right,
so we pronounce my name OK, might struggle with different
names, depending on the phonetics. But that one seemed to be OK. Let's do something else with
Python, using similarly, just a few lines of code. Let me go into today's examples. And I'm going to go into a folder
called Detect, whoops, a folder called Faces.py. Sorry, Faces. And in this folder, that
I've written in advance, are a few files,
Detect.py, Recognize.py, and two full of photos,
Office.jpeg and Toby.jpeg. If you're familiar with the
show, here, for instance, is the cast photo from The Office here. So here's a photo as input. Suppose I want to do
something very Facebook-style, where I want to analyze
all of the faces, or detect all of the faces in there. Well, let me go ahead
and show you a program I wrote in advance,
that's not terribly long. Much of it is actually comments. But let's see what I'm doing. I'm importing the Pillow library,
again, to get access to images. I'm importing a library called face
recognition, which I downloaded and installed in advance. But it does what it says. According to its documentation,
you go into that library and you call a function
called load image file, to load something
like Office.jpeg, and then you can use the
line of code like this. Call a function called face
locations, passing the images input, and you get back a list of
all of the faces in the image. And then down here, a for loop,
that iterates over all of those face locations. And inside of this loop, I
just do a bit of trickery. I figure out the top, right, bottom,
and left corners of those locations. And then, using these
lines of code here, I'm using that image library,
to just draw a box, essentially. And the code looks cryptic. Honestly, I would have to look
this up to write it again. But per the documentation, this
just draws a nice little box around the image. So let me go ahead and zoom out here,
and run this now on Office.jpeg. All right, it's analyzing, analyzing,
and you can see in the sidebar here, here's the original. And here is every face that my,
what, 10 lines of Python code found, within that file. What's a face? Presumably the library
is looking for something, maybe without a mask, that has
two eyes, a nose, and a mouth, in some kind of arrangement,
some kind of pattern. So it would seem pretty reliable, at
least on these fairly easy-to-read faces here. What if we want to look
for someone specific, for instance, someone that's
always getting picked on. Well, we could do something like this. Recognize.py, which is taking two files
as input, that image and the image of one person in particular. And if you're trying to
find Toby in a crowd, here I conflated the program,
sorry, this is the version that draws a box around the given face. Here we have Toby as identified. Why? Because that program, Recognize.py,
has a few more lines of code, but long story short, it additionally
loads as input Toby.jpeg, in order to recognize
that specific face. And that specific face is a
completely different photo, but it looks similar enough to the
person, that it all worked out OK. Let's do one other that's a
little sensitive to microphones. Let me go into, how about my listen
folder here, which is available online, too. And let's just run Python of Listen0.py. I'm going to type in like David. Oh, sorry, no, I'm going to-- Hello, world. Oh, no, that's the wrong version. [CHUCKLES] OK, I looked like an idiot. OK, hello, there we go. Hello to you, too. And if I say goodbye, I'm talking
to my laptop like an idiot, OK. Now it's detecting what I'm saying here. So this first version of the program is
just using some relatively simple, if elif elif, and it's just asking
for input, forcing it to lowercase. And that was my mistake
with the first example. And then, I'm just checking,
is Hello in the user's words? Is how are you in the user's words? Didn't see that, but it's there. Is goodbye in the user's words? Now let's do a cooler version, using a
library, just by looking at the effect. Python of Listen1.py. Hello, world. Huh. Let's do version 2 of this, that
uses an audio speech-to-text library. Hello, world. OK, so now it's artificial intelligence. Now let's do something a
little more interesting. The third version of this program that
actually analyzes the words that are said. Hello, world, my name is David. How are you? OK, so that time, it not
only analyzed what I said, but it plucked my name out of it. Let's do two final examples. This one will generate a QR code. Let me go ahead and
write a program called QR.py, that very simply does this. Let me import a library called OS. Let me import a library called QR code. Let me grab an image
here, that's QRcode.make. And let me give you the URL of like a
lecture video on YouTube, or something like that, with this ID. Let me just type this,
so I don't get it wrong. OK, so if I now use this URL here,
of a video on YouTube, making sure I haven't made any
typos, I'm now going to go ahead and do two
lines of code in Python. I'm going to first save that as
a file called QR.png, which is a two dimensional barcode, a QR code. And, indeed, I'm going
to use this format. And I'm going to use the OS.system
library to open QR.png automatically. And if you'd like to take
out your phone at this point, you can see the result of my barcode,
that's just been dynamically generated. Hopefully from afar that will scan. [UPROAR] And I think that's an
appropriate line to end on. So that's it for CS50. We will see you next time. [APPLAUSE] [MUSIC PLAYING] [MUSIC PLAYING] DAVID J. MALAN: This is CS50. And this is week 7, the
week, here, of Halloween. Indeed, special thanks to
CS50's own Valerie and her mom for having created this very festive
scenery, and all past ones as well. Today, we pick up where
we left off last time, which, recall, we introduced Python. And that was our big transition
from C, where suddenly things started to look new again,
probably, syntactically. But also, probably things
hopefully started to feel easier. Well, with that said, problem set
6 certainly added some challenges, and you did some new things. But hopefully you've begun to appreciate
that with Python, just a lot more stuff is easier to do. You get more out of the box
with the language itself. And that's going to be so
useful over the coming weeks as we transition further to introducing
something called databases today, web programming next
week and the week after. So that by term's end, and perhaps
even for your final project, you really are building
something from scratch using all of these various
tools somehow together. So before we do that,
though, today, let's consider what we weren't really able to
do last week, which was actually create and store data ourselves. In Python, we've played around with the
CSV, comma-separated values library. And you've been able to
read in CSVs from disk, so to speak, that is, from files
in your programming environments. But we haven't necessarily started
saving data, persisting data ourselves. And that's a huge limitation, because
pretty much all of the examples we've done thus far with
a couple of exceptions have involved my providing input
at the keyboard or even vocally. But then nothing happens to it. It disappears the moment
the program quits, because it was only
being stored in memory. But today, we'll start to focus all
the more on storing things on disk, that is, storing things
in files and folders so that you can actually
write programs that remember what it is the human did last time. And ultimately, you can
actually make mobile or web apps that actually begin to grow, and
grow, and grow their data sets, as might happen if you get more and
more users, for instance, on a website. To play, then, with this new capability
of being able to write files, let's go ahead and
just collect some data. In fact, those of you
here in person, if you want to pull up this URL
on your phone or laptop, that's going to lead
you to a Google Form. And that Google Form is going to
ask you in just a moment for really just your favorite TV show. And it's going to ask
you to categorize it according to a genre, like comedy,
or drama, or action, or musical, or something like that. And this is useful,
because if you've ever used a Google Form before, or
Microsoft's equivalent with Office 365, it's a really useful mechanism at
just collecting data from users, and then ultimately, putting
it into a spreadsheet form. So this is a screenshot of
the form that those of you here in person or tuning in on
Zoom are currently filling out. It's asking only two questions. What's the title of
your favorite TV show? And what are one or more genres
into which your TV show falls? And I'll go ahead and
pivot now to the view that I'll be able to see as the
person who created this form, which is quite simply a Google spreadsheet. Google Forms has this nice
feature, if you've never noticed, that allows you to export your
data to a Google Spreadsheet. And then from there, we
can actually grab the file and download it to my
own Mac or your own PC so that we can actually play around
with the data that's come in. So in fact, let me go
ahead and slide over to this, the live Google Spreadsheet. And you'll see, probably, a whole
bunch of familiar TV shows here, all coming in. And if we keep scrolling, and
scrolling, and scrolling-- only 46, 47. There we go, up to 50 plus already. If you need that URL again
here, if you're just tuning in, you can go to this URL here. And in just a moment,
we'll have a bunch of data with which we can start to experiment. I'll give you a moment or so there. All right. Let me hang in there a little longer. OK, we've got over 100 submissions. Good. Good, even more coming in now. And we can see them coming in live. Here, let me switch
back to the spreadsheet. The list is growing, and
growing, and growing. And in just a moment-- let me give Carter a moment to
help me export it in real time. Carter, just give me a heads
up when it's reasonable for me to download this file. All right, and I'll begin
to do this very slowly. So I'm going to go up to the File
menu, if you've never done this before. Download-- you can download a whole
bunch of formats, one in Excel. But more simply, and the one
we'll start to play with here, is comma-separated values. So CSV files we used this past
week, why are they useful? Now that you've played with them
or used them in past real world, what's the utility of a CSV file versus
something like Excel, for instance? Why CSV in the first place? Any instincts? Yeah? AUDIENCE: Because it's just a text file? DAVID J. MALAN: OK, so
storage is compelling. A simple text file with ASCII or
Unicode text is probably pretty small. I like that. Other thoughts? AUDIENCE: Structure of it? DAVID J. MALAN: Yeah, well said. It's just a simple text
format, but using conventions like commas you can represent the
idea of columns using new lines, backslash ends invisibly
at the end of your lines, you can create the idea of rows. So it's a very simple
way of implementing what we might call a flat-file database. It's a way of storing
data in a flat, that is, very simple file that's just
pure ASCII or Unicode text. And more compellingly, I dare
say, is that with a CSV file, it's completely portable. Something is portable in
the world of computing if it means you can use it on a Mac
or a PC running this operating system, or this other one. And portability is nice because if
I were to download an Excel file, there'd be a whole bunch of
people in this room and online who couldn't download it because
they haven't bought Microsoft Excel or installed it. Or if they have a Mac, or if it's
a .numbers file in the Mac world, a PC user might not be
able to download it. So a CSV is indeed very portable. So I'm going to go ahead and
download, quite simply, the CSV version of this file. That's going to put it onto
my own Mac's Downloads folder. And let me go ahead here, and in just a
moment, let me just simplify the name. Because it actually downloads
it at a pretty large name. And give me just one moment here,
and you'll see that, indeed, on my Mac I have a file
called favorites.csv. I shortened the name real quick. And now what I'm going to do is go
over to VS Code, and in VS Code, I'm going to open my File Explorer. And if I minimize my window here for
a moment, a handy feature of VS Code is that you can just drag and drop a
file, for instance, into your Explorer. And voila, it's going to
automatically upload it for you. So let me go ahead and full
screen here, close my Explorer, temporarily close my Terminal window. And you'll see here a
CSV file, favorites.csv. And the first row, by
convention, has whatever the columns were in Google
Spreadsheets, or Office 365, in Excel online, timestamp,
comma, title, comma, genres. Then, we have timestamps,
which indicates when people started submitting. Looks like a couple of people
were super eager to get started an hour or two ago. And then, you have the
title next, after a comma. But there's kind of a
curiosity after that. Sometimes I see the genre
like comedy, comedy, comedy, but sometimes it's like crime, comma,
drama, or action, comma, crime, comma, drama. And those things are quoted. And yet, I didn't do any quotes. You probably didn't type any quotes. Where are those quotes
coming from in this CSV file? Why are they there if we infer? Yeah? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah, so you
have a corner case, if you will. Because if you're using
commas, as you described, to separate your data into what
are effectively columns, well, you've painted yourself into
a corner if your actual data has commas in it itself. So what Google has done, what
Microsoft does, what Apple does is, they quote any strings
of text that themselves have commas so that these are
now English grammatical commas, not CSV specific commas. So it's a way of escaping
your data, if you will. And escaping just means to call
out a symbol in a special way so it's not misinterpreted
as something else. All right, so this is
all to say that we now have all of this data with which we
can play in the form of what we'll start calling a flat-file database. So suppose I wanted to now
start manipulating this data, and I want to store it ultimately,
indeed, in this CSV format. How can I actually
start to read this data, maybe clean it up, maybe
do some analytics on it and actually figure out, what's the most
popular show among those who submitted here over the past few minutes? Well, let me go ahead and close this. Let me go ahead, then, and open up,
for instance, just my Terminal window. And let's code up a file
called favorites.py. And let's go ahead and iteratively start
simple by just opening up this file and printing out what's inside of it. So you might recall that we can do
this by doing something like import CSV to give myself some CSV
reading functionality. Then, I can go ahead and do something
like with open, the name of the file that I want to open in read mode. Quote, unquote, "r" means to read it. And then, I can say as
file, or whatever other name for a variable to say that
I want to open this file, and essentially store some kind of
reference to it in that variable called file. Then, I can give myself a
reader, and I can say csv.reader, passing in that file as input. And this is the magic of that library. It deals with the process of opening
it, reading it, and giving you back something that you can just
iterate over, like with a for loop I do want to skip the first row,
and recall that I can do this. Next, reader, is this little trick
that just says, ignore the first row. Because the first one is special. It said timestamp, title, genres. That's not your data, that was mine. But this means now that
I've skipped that first row. Everything hereafter is going
to be the title of a show that you all like, so let me do this. For row in the reader, let's go
ahead and print out the title of the show each of you typed in. How do I get at the title of
the show each of you typed in? It's somewhere inside of row. Row recalls a list. So what do I want to
type next in order to get at the title of the current
row just as a quick check here? What do I want to type to
get at the title of the row, keeping in mind, again, that it
was timestamp, title, genres? Yeah? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: So row
bracket 1 would give me the second column, 0 index, that is,
the one in the middle with the title. So this program isn't
that interesting yet, but it's a quick and dirty way to
figure out, all right, what's my data look like? Let me actually just do a
little bit of a check here and see if it contains
the data I think it does. Let me maximize my Terminal window here. Let me run Python of
favorites.py, hitting Enter. And you'll see now a purely
textual list of all of the shows you all seem to like here. But what's noteworthy about it? Specific shows aside,
judgment aside as to people's TV tastes, what's interesting or
noteworthy about the data that might create some problems for us
if we start to analyze this data, and figure out what's the most popular? How many people like this or that? What do you think? Yeah? AUDIENCE: User errors [INAUDIBLE]. DAVID J. MALAN: Yeah,
there might be user errors, or just stylistic differences that
give the appearance that one show is different from the other. For instance, here. Let's see if I can see an
example on the screen here. Yeah, so friends here is an all
lowercase, Friends here is capitalized. No big deal. We can sort of mitigate that. But this is just a tiny example
of where data in the real world can get messy fast. And that probably wasn't even a typo. It was just someone not caring as much
to capitalize it, and that's fine. Your users are going to type
what they're going to type. So let's see if we can't now begin
to get at more specific data, and maybe even clean
some of this data up. Let me go back into my file
called favorites.py here, and let's actually do something a
little more user friendly for me. Instead of a reader, recall that there
was this dictionary reader that's just a little more user friendly. And it means I can type in dictionary
reader here, passing in the same file. But now, when I iterate over this
reader variable, what is each row? When using a DictReader instead
of a reader, recall, and this is just a peculiarity
of the CSV library, this gives me back, not a list
of cells, but what instead, which is marginally more
user friendly for me? Yeah? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah. I can now use open bracket,
quotes, and the title. Because what's coming back
now is a dict object, that is, a dictionary which has keys and values. The keys of which are
the column headings. The values of which are the
data I actually care about. So this is just marginally
better because, one, it's just way more obvious to me, the
author of this code, what it is I'm getting at. I don't remember what
column the title was. Was it 0? Was it 1? Was it 2? That's something you're
going to forget over time. And God forbid someone changes the
data by just dragging and dropping the columns in Excel, or Apple
Numbers, or Google Spreadsheets. That's going to break all
of your numeric indices. And so a dictionary
reader is arguably just better design because it's
more robust against changes and potential errors like that. Now the effect of this change isn't
going to be really any different. If I run Python of favorites.py,
voila, I get all of the same results. But I've now not made any assumptions
as to where each of the columns actually is numerically. All right. Well, let's go ahead and now
filter out some duplicates. Because there's a lot of commonality
among some of the shows here, so let's see if we can't filter out duplicates. If I'm reading a CSV file top to bottom,
what intuitively might be the logic I want to implement to
filter out duplicates? It's not going to be quite as simple as
a simple function that does it for me. I'm going to have to build this. But logically, if you're reading
a file from top to bottom, how might you go about, in
Python or just any context, getting rid of duplicate values? Yeah, what do you think? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Sure. I could use a list and I could
add each title to the list, but first check if I put
this into the list before. So let's try a little
something like that. Let me go ahead and create a variable
at the top of my program here. I'll call it titles, for instance,
initialize to an empty list, open bracket, close bracket. And then, inside of my loop
here, instead of printing it out, let's start to make a decision. So if the current row's
title is in the titles list I don't want to put it there. And actually, let me invert the logic
so I'm doing something proactively. So if it's not the case that
row bracket title is in titles, then, go ahead and do something like
titles.append the current row's title. And recall that we saw
.append a week or so ago, where it just allows you to
append to the current list. And then, what can I do at
the very end, after I'm all done reading the whole file? Why don't I go ahead and
say, for title in titles, go ahead and print
out the current title? So it's two loops now, and we can come
back to the quality of that design. But let me go ahead here and
rerun Python of favorites.py. Let me increase the size of my Terminal
window so we can focus just on this, and hit Enter. And now, I'm just skimming. I don't think I'm seeing
duplicates, although I am seeing some near duplicates. For instance, there's Friends again. And if we keep going, and
going, and going, and going, there's Friends again. Oh, interesting, so that's curious
that I seem to have multiple Friends, and I have this one here, too. So how might we clean this up further? I like your instincts, and
it's a step closer to it. What are we going to have
to do to really filter out those near duplicates? Any thoughts? AUDIENCE: You could set
everything to lower [INAUDIBLE].. DAVID J. MALAN: Yeah. What are the common
mistakes to summarize? We could ignore the capitalization
altogether and maybe just force everything to lowercase,
or everything to uppercase. Doesn't matter which, but
let's just be consistent. And for those of you who might have
accidentally or instinctively hit the spacebar at the beginning of
your input or even at the end, we can strip that off, too. Stripping whitespace is a common
thing just to clean up user input. So let me go back into my
code here, and let me go ahead and tweak the title a little bit. Let me say that the current
title inside of this loop is not going to be just
the current row's title. But let me go ahead and strip off,
from the left and the right implicitly, any whitespace. If you read the documentation for the
strip function, it does just that. It gets rid of whitespace to the
left, whitespace to the right. And then, if I want to force
everything to maybe uppercase, I can just uppercase the entire string. And remember, what's handy about Python
is you can chain some of these function calls together by just
using dots again and again. And that just takes
whatever just happened, like the whitespace got stripped
off, then, it additionally uppercases the whole thing as well. So now, I'm going to just check whether
this specific title is in titles. And if not, I'm going to go
ahead and append that title, massaged into this different
format, if you will. So I'm throwing away some information. I'm sacrificing all of the
nuances of your grammar and input to the form itself. But at least I'm trying to
canonicalize size, that is, standardize what the
data actually looks like. So let me go ahead and run Python
of favorites.py again and hit Enter. Oh, and this is just user error. Maybe you haven't seen this before. This just looks like
a mistake on my part. I meant to say not even uppercase. That's completely wrong. The function is called upper,
now that I think of it. All right. Let's go and increase the size
of the Terminal window again. Run Python of favorites.py. And now, it's a little more overwhelming
to look at because it's not sorted yet and it's all capitalized. But I don't think I'm seeing
multiple Friends, so to speak. There's one Friends
up here and that's it. I'm back up at my prompt already. So we seem now to be
filtering out duplicates. Now, before we dive in further and
clean this up further than this, what else could we have done? Well, it turns out that
in Python 2 you often do get a lot of functionality
built into the language. And I'm kind of implementing
myself the idea of a set. If you think back to
mathematics, a set is typically something with a bunch of values
that has duplicates filtered out. Recall that Python
already has this for us. And we saw it really briefly
when I whipped up the dictionary implementation a couple of weeks back. So I could actually define my titles
to be a set instead of a list, and this would just modestly allow
me to refine my code here, such that I don't have to bother
checking for duplicates anyway. I can instead just say
something like, titles.add the current title, like this. Marginally better design if you know
that a set exists because you're just getting more functionality out of this. All right, so let's clean
the data up further. We've now gone ahead and fixed
the problem of case sensitivity. We threw away whitespace in case
someone had hit the spacebar with some of the input. Let's go ahead now and sort these
things by the titles themselves. So instead of just printing out
the titles in the same order you all inputted them, but filtering
out duplicates as we go, let me go ahead and use another function in
Python you might not have seen, which is literally
called sorted, and will take care of the process of
actually sorting titles for you. Let me go ahead and increase
the font size of my Terminal, run Python of favorites.py,
and hit Enter. And now you can really see how many of
these shows start with the word "the" or do not. Now it's a little easier
to wrap our minds around, just because it's at least
sorted alphabetically. But now you can really see some of
the differences in people's inputs. So far, so good. But a few of you decided to stylize
Avatar in three different ways here. Brooklyn 99 is a couple
of different ways here. And I think if we keep going we'll see
further and further variances that we did not fix by focusing on
whitespace and capitalization alone. So already here, this is only,
what, 100 plus, 200 rows. Already real-world data
starts to get messy quickly, and that might not bode
well when we actually want to keep around real
data from real users. You can imagine an actual
website or a mobile application dealing with this kind
of thing on scale. Well, let's go ahead and do this. Let's actually figure out the
popularity of these various shows by now iterating over my data, and
keeping track of how many of you inputted a given title. We're going to ignore the problems
like Brooklyn 99 and the Avatar. Sorry, yeah, Avatar,
where there was things that were different beyond just
whitespace and capitalization. But let's go ahead and
keep track of, now, how many of you inputted
each of these titles. So how can I do this? I'm still going to take this
approach of iterating over the CSV file from top to bottom. We've used a couple of
data structures thus far, a list to keep track of titles,
or a set to keep track of titles. But what if I now want to keep
around a little more information? For each title, I want to keep around
how many times I've seen it before. I'm not doing that yet. I'm throwing away the total
number of times I see these shows. How could I start to keep that around? AUDIENCE: Use a dictionary. DAVID J. MALAN: We could
use a dictionary, and how? Elaborate on that. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Perfect,
really good instincts. Using a dictionary,
insofar as it lets us store keys and values, that is,
associate something with something else. This is why a dictionary
or hash tables more generally are such a useful,
practical data structure. Because they just let you remember
stuff in some kind of structured way. So if the keys are going
to be the titles I've seen, the values could be the number of
times I've seen each of those titles. And so it's kind of like just
having a two-column table on paper. For instance, if I were going
to do this on a piece of paper, I might just have two
columns here, where maybe this is the title that I've
seen, and this is the count over here. This is, in effect, a
dictionary in Python. It's two columns, keys on the
left, values on the right. And this, if I can implement
in code, will actually allow me to store this data, and
then maybe do some simple arithmetic to figure out which is the most popular. So let's do this. Let me go ahead and change my titles
to not be a list, not be a set. Let's have it be a dictionary instead,
either doing this, or more succinctly, two curly braces that are empty gives
me an empty dictionary automatically. What do I now want to do? I think most of my
code can stay the same. But down here, I don't want
to just blindly add titles to the data structure. I somehow need to keep
track of the count. And unfortunately, if I just
do this-- let's do titles, bracket, title, plus equals 1. This is a reasonable
first attempt at this. Because what am I doing? If titles is a dictionary and I want
to look up the current title therein, the syntax for that, like before,
is titles, bracket, and then the key you want to use to
index into the dictionary. It's not a number in this case,
it's an actual word, a title. And you're just going
to increment it by one, and then eventually I'll come
back and finish my second loop and do things in terms of the order. But for now, let's just keep
track of the total counts. Let me go ahead and
increase my Terminal window. Let me do Python of
favorites.py and hit Enter. Huh. How I Met Your Mother is
giving me a key error. What does that mean? And why am I seeing this? And in fact, just to give a
little bit of a breadcrumb here, let me zoom out here. Let me open up the CSV
file again real quickly. And wow, we didn't even get
past the second row in the file or the first show in the file. Notice that How I Met Your
Mother, somewhat lowercased, is the very first show in therein. What's your instinct for
why this is happening? AUDIENCE: You don't
have a starting point. DAVID J. MALAN: I don't
have a starting point. I'm adding one to what? I'm blindly indexing into the dictionary
using a key, How I Met Your Mother, that doesn't yet exist
in the dictionary. And so Python throws
what's called a key error because the key you're trying
to use just doesn't exist yet. So logically, how could we fix this? We're close. We got half of the problem solved,
but I'm not handling the obvious, now, case of nothing being there. Yeah? AUDIENCE: Creating a counter. DAVID J. MALAN: Creating a-- AUDIENCE: Counter. DAVID J. MALAN: Creating
the counter itself. So maybe I could do something like this. Let me close my Terminal window
and let me ask a question first. If the current title is in the
dictionary already, if title in titles, that's going to give me a
true-false answer it turns out. Then, I can safely say, titles,
bracket, title, plus equals 1. And recall, this is just shorthand
notation for the same thing as in C, title plus 1. Whoops, typo. Don't do that. That's the same thing as this
but it's a little more succinct just to say plus equals 1. Else, if it's logically not the case
that the current title is in the titles dictionary, then I probably want to
say titles, bracket, title equals? Feel free to just shout it out. AUDIENCE: Zero. DAVID J. MALAN: Zero. I just have to put some value there
so that the key itself is also there. All right. So now that I've got this
going on, let me go ahead and undo my sorting temporarily. And now let me go ahead and do this. I can, as a quick check, let me
go ahead and just run the code as is, Python of favorites.py. I'm back in business. It's printing correctly, no key
errors, but it's not sorted. And I'm not seeing any of the counts. Let me just quickly add
the counts, and there's a couple of ways I could do this. I could, say, print out the title, and
then, maybe, let's do something like-- how about just, comma,
titles, bracket, title? So I'm going to print
two things at once, both the current title
in the dictionary, and whatever its value
is by indexing into it. Let me increase my Terminal window. Let me run Python of
favorites.py, Enter, and OK. Huh. Huh. None of you said a whole
lot of TV shows, it seems. What's the logical error here? What did I do wrong if I
look back at my code here? Yeah? Why so many 0s? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Exactly. To summarize, I initialized the
count to 0 the first time I saw it, but I should have initialized it at
least to 1 because I just saw it. Or I should change my code a bit. So for instance, if I go back
in here, the simplest fix is probably to initialize to 1,
because on this iteration of the loop, obviously, I'm seeing this
title for the very first time. Or I could change my logic a little bit. I could do something like this instead. If the current title is not in titles,
then I could initialize it to 0. And then I could get rid of
the else, and now blindly index into the titles dictionary. Because now, on line 11, I
can trust that lines 9 and 10 took care of the initialization
for me if need be. Which one is better? I don't know. This one's a little nicer, maybe
because it's one line fewer. But I think both approaches are
perfectly reasonable and well-designed. But the key thing, no
pun intended, is that we have to make sure the key exists
before we presume to actually incrue. Oh, this is wrong. This is incorrect code. What did I do wrong? OK, yes. There we go. So otherwise, everyone would have
liked this show once, and no matter how many people said the same thing. Now the code is as it should be. So let me go ahead and open
up my Terminal window again. Let me run Python of favorites.py,
and now we see more reasonable counts. Some shows weren't that popular. There's just 1s and maybe 2s. But I bet if we sort these things we
can start to see a little more detail. So how else can we do this? Well, turns out, when dealing
with a dictionary like this-- let's go ahead and just
sort the titles themselves. So let's reintroduce the sorted function
as I did before, but no other changes. Let me go ahead now and
run Python of favorites.py. Now it's just a little easier
to wrap your mind around it because at least it's alphabetical. But it's not sorted by
value, it's sorted by key. But sure enough, if we scroll
down, there's something down here, for instance, like,
let's see, The Office. That's definitely
going to be a contender for most popular, 15 responses. But let's see what's actually
going to bubble up to the top. Unfortunately, the sorted function
only sorts dictionaries by keys by default, not by values. But it turns out, in Python,
if you read the documentation for the sorted function,
you can actually pass in other arguments that
tell it how to sort things. For instance, if I want to
do things in reverse order, I can add a second parameter to
the sorted function called reverse. And it's a named parameter. You literally say,
reverse equals true, so that the position of it in the
comma-separated list doesn't matter. If I now rerun this after
increasing my Terminal window, you'll see now that it's
in the opposite order. Now adventure and Anne
with an E is at the bottom of the output instead of the top. How can I tell it to sort
by values instead of by key? Well, let's go ahead and do this. Let me go ahead and define a function. I'm just going to call it
f to keep things simple. And this f function is going
to take a title as input. And given a given title, it's going
to return the value of that title. So actually, maybe a better name
for this would be get value, and/or we could come up
with something else as well. The purpose of the get
value function, to be clear, is to take it as input a title and
then return the corresponding value. Why is this useful? Well, it turns out that the
sorted function in Python, according to its documentation,
also takes a key parameter, where you can pass in, crazy
enough, the name of a function that it will use in order to determine
what it should sort by, by the key, or by the value, or in other cases,
even other types of data as well. So there's a curiosity here,
though, that's very deliberate. Key is the name of the
parameter, just like reverse was the name of this other parameter. The value of it, though,
is not a function call. It's a function name. Notice I am not doing
this, no parentheses. I'm instead passing in get value,
the function I wrote, by its name. And this is a feature of Python
and certain other languages. Just like variables, you can
actually pass whole functions around so that they can be called
for you later on by someone else. So what this means is that the
sorted function written by Python, they didn't know what you're
going to want to sort by today. But if you provide them with a function
called get value, or anything else, now their sorted function
will use that function to determine, OK, if you don't want to
sort by the key of the dictionary, what do you want to sort by? This is going to tell
it to sort by the value by returning the specific
value we care about. So let me go ahead now and rerun this
after increasing my Terminal, Python of favorites.py, Enter. Here we have now an example
of all of the titles you all typed in, albeit forced to uppercase
and with any whitespace thrown out. And now, The Office is
an easy win over Friends, versus Community, versus Game of
Thrones, Breaking Bad, and then a lot of variants thereafter. So there's a lot of steps to go through. This isn't that bad once
you've done it once, and you know what these
functions are, and you know that these parameters exist. But it's a lot of work. That's 17 lines of code
just to analyze a CSV file that you all created by way of
those Google Form submissions. But it took me a lot of work just
to get simple answers out of it. And indeed, that's going
to be among the goals for today, ultimately, is, how
can we just make this easier? It's one thing to learn
new things in Python, but if we can avoid writing
code, or this much code, that's going to be a good thing. And so one other technique
we can introduce here that does allow us to
write a little less code is, we can actually get
rid of this function. It turns out, in Python, if you
just need to make a function but it's going to be used and
then essentially thrown away, it's not something you're going
to be reusing in multiple places-- it's not like a library function
that you want to keep around-- you can actually just do this. You can change the value
of this key parameter to be what's called a
lambda function, which is a fancy way of saying a function
that technically has no name. It's an anonymous function. Why does it have no name? Well, it's kind of stupid that
I invented this name on line 13. I used it on line 16, and
then I never again used it. If there's only being used in one place,
why bother giving it a name at all? So if you instead, in
Python, say lambda, and then type out the
name of the parameter you want this anonymous
function to take, you can then say, go ahead
and return this value. Now let's notice the
inconsistencies here. When you use this special lambda
keyword that says, hey Python, give me an anonymous function,
a function with no name, it then says, Python, this anonymous
function will take one parameter. Notice there's no parentheses. And that's deliberate, if confusing. It just tightens things up a little bit. Notice that there's no return keyword,
which similarly tightens things up a bit, albeit inconsistently. But this line of code
I've just highlighted is actually identical in
functionality to this. But it throws away the word [INAUDIBLE]. It throws away the word get value. It throws away the parentheses, and
it throws away the return keyword just to tighten things up. And it's well suited
for a problem like this where I just want to pass in
a tiny little function that does something useful. But it's not something
I'm going to reuse. It doesn't need multiple
lines to take up space. It's just a nice, elegant one liner. That's all a lambda function does. It allows you to create an anonymous
function right then and there. And then the function you're passing it
to, like sorted, will use it as before. Indeed, if I run Python of favorites.py
after growing my Terminal window, the result is exactly the same. And we see at the bottom here
all of those small results. Are any questions, then, on
this syntax, on these ideas? The goal here has been to write
a Python program that just starts to analyze or clean up data like this. Yeah? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Could you use the lambda
if it's just returning immediately? It's really meant for one
line of code, generally. So you don't use the return keyword. You just say what it
is you want to return. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Good question. Could you do more in
that one line if it's got to be a more involved algorithm? Yes, but you would just ultimately
return the value in question. In short, if it's getting
at all sophisticated you don't use the lambda
function in Python. You go ahead and actually
just define a name for it, even if it's a one-off name. JavaScript, another language
we'll look at in a few weeks, makes heavier use, I dare
say, of lambda functions. And those can actually be
multiple, multiple lines, but Python does not
support that instinct. All right. So let's go ahead and
do one other thing. Office was clearly popping out
of the code here quite a bit. Let's go ahead and write a
slightly different program that maybe just focuses on
The Office for the moment, just focuses on The Office. So let me go ahead and throw most of
this code away, up until this point when I'm inside of my inner loop. And let me go ahead, and I don't
even want the global variable here. All I want to do is focus
on the current title. How could I detect if
someone likes The Office? Well, I could say something like-- how about this? So counter equals 0. We'll just focus on The Office. If title equals, equals The Office,
I could then go ahead and say, counter plus equals 1. I don't need a key. There's no dictionary involved now. It's just a simple integer variable. And then, down here
I'll say something like, number of people who like The
Office is, whatever this value is. And I'll put in counter in
curly braces, and then I'll turn this whole thing into an F string. All right, let me go ahead and run this. Python of favorites.py, Enter. Number of people who
like The Office is 15. All right, so that's great. But let's go ahead now and
deliberately muddy the data a bit. All of you were very nice in
that you typed in The Office. But you can imagine
someone just typing Office, for instance, maybe there, maybe there. And many people might just
write Office, you could imagine. Didn't happen here, but
suppose it did, and probably would have if we had even more
and more submissions over time. Now let's go ahead and rerun this
program, no changes to the code. Now only 13 people like The Office. So let's fix this. The data is now as I mutated it to have
a couple Offices, and many The Offices. How could I change my Python code to
now count both of those situations? What could I change up here in
order to improve this situation? Any thoughts? Yeah? AUDIENCE: You write
the title [INAUDIBLE].. DAVID J. MALAN: Yeah, so I could
just ask two questions like that. If title equals The Office,
or title equals, equals just Office, for instance. And I'm still don't have to
worry about capitalization. I don't have to worry about spaces
because I at least threw that all away. Now I can go ahead and rerun this code. Let me go run it a third time. OK, so we're back up to 15. So I like that. You could imagine this
not scaling very well. Avatar had three different
permutations, and there were some others if we dug deeper that there
might have been more variants. Could we do something a
little more general purpose? Well, we could do something like this. If Office in the title-- this is kind of a cool thing
you can do with Python. It's very English-like, just ask
the question, albeit tersely. This, interesting, just
got me into trouble. Now, all of a sudden, we're up to 16. Does anyone know what the other one is? AUDIENCE: Someone put V Office. DAVID J. MALAN: What Office? AUDIENCE: Someone entered
a V Office, [INAUDIBLE].. DAVID J. MALAN: Oh, interesting. Yes, so they hit The. OK. [APPLAUSE] DAVID J. MALAN: OK. Someone did that, sure. So The V Office. OK, this one's actually going
to be hard to correct for. I can't really think of a general-- well, this is actually a good
example of data gets messy fast. And you could imagine doing
something where, OK, we could have like 26 conditions if someone
said The A Office, or The B Office, right? You could imagine doing that. But then there's surely going to
be other typos that are possible. So that's actually a hard one to fix. But it turns out we got lucky and now
this is actually the accurate count. But the data is itself messy. Let me show another way that just
adds another tool to our toolkit. It turns out that there's this feature
in many programming languages, Python among them, called regular expressions. And this is actually a
really powerful technique that we'll just scratch
the surface of here. But it's going to be really useful,
actually, maybe toward final projects, in web programming, any time you want
to clean up data or validate data. And actually, just to make
this clear, give me a moment before I switch screens here. And let me open up a
Google Form from scratch. Give me just a moment to
create something real quick. If you've never noticed this
before when creating a Google Form, you can do a question. And if you want the user
to type in something very specific as a short
text answer like this, you might know that there's toggles
like this in Google's world, like you can require it. Or you can do response validation. You could say, what's your email? And then you could say something
like, text is an email. So here's an example in Google Forms
how you can validate users' input. But a feature most of you have probably
never noticed, or cared about, or used, is this thing called a
regular expression, where you can actually define a pattern. And I could actually reimplement that
same idea by doing something like this. I can say, let the user type in anything
represented by .star, then an at sign, then something else, then a
literal period, then, for instance, something else. So it's very cryptic,
admittedly, at first glance. But this means any
character 0 more times. This means any character 0 more times. This means a literal
period, because apparently dot means any character in
the context of these patterns. Then this thing means any
character 0 more times. So I should actually be
a little more nitpicky. You don't want 0 or more times,
you want 1 or more times. So this with the plus means
any character 1 or more time. So there has to be something there. And I think I want the same thing
here 1 or more times, 1 or more times. Or heck, if I want to restrict this
form in some sense to edu addresses, I could change that last
thing to literally .edu. And so long story short,
even though this looks, I'm sure, pretty cryptic, there's
this mini language built into Python, and JavaScript, and Java, and other
languages that allows you to express patterns in a standardized way. And this pattern is actually something
we can implement in code, too. And let me switch back to
Python for a second just to do the same kind of idea. Let me toggle back to my code here. Let me put up, for instance, a
summary of what it is you can do. And here's just a quick summary
of some of the available symbols. A period may represent any character.
.star or .asterisks means 0 or more characters. So the dot means anything,
so it can be A or nothing. It can be B or nothing. It can be A, B, A, B, C. It can be any
combination of 0 or more characters. Change that to a plus and you now
express one or more characters. Question mark means
something is optional. Caret symbol means start matching at
the beginning of the user's input. Dollar sign means stop matching
at the end of the user's input. So we won't play with
all of these just now. But let me go over here and
actually tackle this Office problem. Let me go ahead and import a new library
called the regular expression library, import re. And then, down here, let me say this. If re.search, this pattern. Let's just search for Office, quote,
unquote, in the current title. Then we're going to go ahead
and increase the counter. So it turns out that the
regular expression library has a function called search that
takes as its first argument a pattern, and then, as its second
argument the string you want to analyze for that pattern. So it's sort of looking for a needle
in this haystack, from left to right. Let me go ahead now and run this
version of the program, Enter. And now I screwed up because I forgot
my colon, but that's old stuff. Enter. Huh. Number of people who
like The Office is now 0. So this seems like a big-- thank you-- big step backwards. What did I do wrong? Yeah? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah. I forced all my input to uppercase,
so I probably need to do this. So we'll come back to
other approaches there. Let me rerun it now. OK, now we're back up to 16. But I could even, let's say-- I could tolerate just The Office. How about this, or how about
something like, or The Office? Let me do this instead. And let me use these
other special characters. This caret sign means the
beginning of the string. This dollar sign weirdly
represents the end of the string. I'm adding in some parentheses just
like in math, just to add another symbol here, the or symbol here. And this is saying start matching
at the beginning of the user string. Check if the beginning of the string is
Office, or the beginning of the string is The Office. And then, you better be
at the end of the string. So they can't keep typing words
before or after that input. Let me go ahead and rerun the program. And now we're down to 15, which
used to be our correct answer, but then we noticed The V Office. How can we deal with that? It's going to be messier
to deal with that. How about if I tolerate any
character represented by dot in between The and Office? Now if I rerun it, now I really
have this expressive capability. So this is only to say, there are so
many ways in languages, in general, to solve problems. And some of these tools are
more sophisticated than others. This is one that you've actually
probably glanced at but never used in the context of Google
Forms for years if you're in the habit of creating these for
student groups or other activities. But it's now something
you can start to leverage. And we're just scratching the surface
of what's actually possible with this. But let's now do one final example
just using some Python code here. And let's actually
write a program that's a little more general purpose that
allows me to search for any given title and figure out its popularity. So let me go ahead and simplify this. Let's get rid of our
regular expressions. Let's go ahead and continue
capitalizing the title. And let's go ahead to-- at the beginning of this program,
and first ask the user for the title they want to search for. So title equals, let's
ask the user for input, which is essentially the same thing
as our CS50 get_string function. Ask them for the title. And then whatever they type in,
let's go ahead and strip whitespace and uppercase the thing again. And now, inside of my loop, I
could say something like this. If the current row's title after
stripping whitespace and forcing it to uppercase, too, equals
the user's title, then, go ahead and maybe increment a counter. So I still need that counter back. So let me go ahead and define this
maybe in here, counter equals 0. And then, at the very
end of this program, let me go ahead and print
out just the popularity of whatever the human typed in. So again, the only difference is
I'm asking the human for some input this time. I'm initializing my
counter to 0, then I'm searching for their
title in the CSV file by doing the same massaging of the
data by forcing it to uppercase and getting rid of the whitespace. So now, when I run Python
of favorites.py, Enter, I could type in the office all lowercase
even, and now we're down to 13. 13, why? Oh, that's correct. Because I'm the one that went in and
removed those The keywords a bit ago. If we fixed those, we
would be back up to 15. If we added support for The V
Office, we would be up to 16 as well. All right, any questions then
on these various manipulations? And if you're feeling
like, oh, my god, this is so much Python code just to do
simple things, that's the point. And indeed, even though
it's a powerful language and can solve these kinds of problems,
we had to write almost 20 lines of code just to ask a single question like this. But any questions on how we did
this, or on any of these building blocks along the way? Anything here? No? All right. That was a lot. Let's take a five-minute break here. When we come back, we'll do it better. So we are back. And the rest of today
is ultimately about, how can we store, and manipulate,
and change, and retrieve data more efficiently than we might
by just writing raw code? This isn't to say that you shouldn't
use Python to do the kinds of things that we just did. And in fact, it might be super common
if you're getting a lot of messy input from users that you might
want to clean it up. And maybe the best way to do that is
to write a program so that step-by-step you can make all of the
requisite changes and fixes like we did with The Office,
for instance, again and again, and reuse that code, especially
if more and more submissions are coming through. But another theme of
today, ultimately, is that sometimes there are different,
if not better tools for the same job. And in fact, now at
this point in the term, as we begin to introduce not
just Python, but in a moment a language called SQL, and next
week, a language called JavaScript, and the week after that, synthesizing
a whole lot of these languages together is to just kind
of paint a picture of how you might decide what the trade-offs are
between using this tool, or this tool, or this other tool. Because undoubtedly you can
solve problems moving forward in many different ways
with many different tools. So let's give you another
tool, one with which you can implement a proper
relational database. What we just saw in
the form of CSV files are what we might call
flat-file databases. Again, just a very simple file, flat
in that there's no hierarchy to it. It's just rows and columns. And that is all ultimately
storing ASCII or Unicode text. A relational database, though,
is something that's actually closer to a proper spreadsheet program. A CSV is an individual
sheet, if you will, from a spreadsheet when you export it. If you had multiple
sheets in a spreadsheet, you would have to export multiple CSVs. And that gets annoying
quickly in code if you have to open up this CSV,
this CSV, all of which represent different sheets or
tabs in a proper spreadsheet. A relational database is more
like a spreadsheet program that you, a programmer,
now can interact with. You can write data to it. You can read data from it, and you
can have multiple sheets, a.k.a., tables storing all of your data. So whereas Excel and numbers
in Google spreadsheet are meant to be reused really by humans
with their mouse and their keyboard, clicking, and pointing, and
manipulating things graphically, a relational database
using a language called SQL is one in which the programmer
has similar capabilities, but doing so in code. Specifically, using a language
called SQL, and at a scale that's much grander
than spreadsheets alone. In fact, if you try on your Mac
or PC to open a spreadsheet that's got tens of thousands
of rows, it'll probably work fine, hundreds of thousands
of rows, millions of rows, no way. At some point your Mac or
PC is going to struggle to open particularly large data sets. And that, too, is where
proper databases come into play and proper
languages for databases come into play, when it's all about scale. And indeed, most any mobile app or
web app today that you or someone else might write should probably plan
on lots of data if it's successful. So we need the right
tools for that problem. So fortunately, even though we're
about to learn yet another language, it only does four things fundamentally,
known by this silly acronym, CRUD. SQL, this language for
databases, supports the ability to create data, read data,
update data, and delete data. That's it. There's a few more keywords that
exist in this language called SQL that we'll soon see. But at the end of the
day, even if you're starting to feel like this
is a lot very quickly, it all boils down to these
four basic operations. And the four commands
in SQL, if you will, functions in a sense that implement
those four ideas happen to be these. They're almost the same but
with some slight variance. The ability to create or insert data
is the C. The ability to select data is the R, or read. Update is the same. Delete is the same, but drop
is also a keyword as well. So we'll see these and
a few other keywords in SQL that, at the end of the day, just
allow you to create, read, and update data using verbs, if
you will, like these. So to do that, what's
the syntax going to be? Well, we won't get into the
weeds too quickly on this. But here's a representative
syntax of how you can create using this
language called SQL, in your very own database, a brand new table. This is so easy in Excel, and Google
Spreadsheets, and Apple Numbers. You want a new sheet, you
click the plus button. You get a new tab. You give it a name,
and boom, you're done. In the world of programming, though, if
you want to create the analogue of that spreadsheet in the computer's memory,
you create something called a table, like a sheet, that has a name, and then
in parentheses has one or more columns. But unlike Google Spreadsheets,
and Apple Numbers, and Excel, you have to decide as the
programmer what types of data you're going to be storing
in each of these columns. Now even though Excel,
and Google Spreadsheets, and Numbers does allow you to format
or present data in different ways, it's not strongly typed data like it
is, for instance, when we were using C. And heck, even in Python
there's underlying data types. Even if you don't have
to type them explicitly, databases are going to want to
know, are you storing integers? Are you storing real numbers or floats? Are you storing text? Why? Because especially as your
data scales, the more hints you give the database about your
data, the more performance it can be, the faster it can help you
get at and store that data. So types are about to
be important again, but there's not going to be
that many of them, fortunately. Now how can I go about converting,
for instance, some real data, like that from you,
my favorites.csv file, into a proper relational database? Well, it turns out that
using SQL I can do this in VS Code on my own Mac,
or PC, or in the cloud here by just importing
the CSV into a database. We'll see eventually
how to do this manually. For now, I'm going to use
more of an automated process. So let me go over to VS Code here. Let me type ls to see
where we left off before. I had two files favorites.csv, which
I downloaded from Google Spreadsheets. Recall that I made a couple of changes. We deleted a couple of Thes
from the file for The Office. But this is the same file
as before, and then we have favorites.py, which
we'll set aside for now. I'm going to go ahead now
and run a command SQLite3. So in the world of
relational databases, there's many different products out there,
many different software that implements the SQL language. Microsoft has their own. There's something called MySQL
that's been very popular for years. Facebook, for instance,
used it early on. PostgreSQL, Microsoft
Access Server, Oracle, and maybe a whole bunch
of other product names you might have encountered
over time, which is to say there's many different
types of tools, and servers, and software in which you can use SQL. We're going to use a very lightweight
version of the SQL language today called SQLite. This is the version of
SQL that's generally used on iPhones and
Android devices these days. If you download an app that stores
data like your own contacts, typically is stored using SQLite. Because it's fairly lightweight,
but you can still store hundreds, thousands, even tens of
thousands of pieces of data even using this lightweight
version thereof. SQLite3 is like version 3 of this tool. We're going to go ahead and run SQLite3
with a file called favorites.db. It's conventional in the world of
SQLite to name your file something.db. I'm going to create a
database called favorites.db. Once I'm inside of the program, now I'm
going to go ahead and enter CSV Mode. Again, not something
you have to memorize, just something you
can look up as needed. And then, I'm going to
import favorites.csv into a table, that is, a sheet, if
you will, called favorites as well. Now I'm going to hit Enter and I'm
going to go ahead and exit the program altogether and type ls. Now I have three files
in my current directory-- the CSV file, the Python file
from before, and now favorites.db. But if I did this right, all of the
data you all typed into the CSV file has now been loaded into a proper
database where I can now use this SQL language to access it instead. So let's go ahead again and run SQLite3
of favorites.db, which now exists. And now, at the SQLite
prompt I can start to play around and
see what this data is. For instance, I can
look, by typing .schema, at what the schema is of
my data, what's the design. Now no thought was put into the
design of this data at the moment because I automated the whole process. Once we start creating
our own databases we'll give more thought to the data
types and the columns that we have. But we can see what SQLite
presumed I wanted just by importing the data by default. What the import command did for me a
moment ago is essentially the syntax. It automated the process of creating
a table, if it doesn't exist, called favorites. And then notice, in parentheses
it gave me three columns-- timestamp, title, and genres, which
were inferred, obviously, from the CSV. All three of which have
been decreed to be text. Again, once we're more comfortable
we'll create our own tables, choose our own types and column names. But for now, I just automated
the whole process just to get us started by using this
built-in import command as well. All right. So what now can I begin to do? Well, if I wanted to, for instance,
start playing around with data therein, I might execute a couple
of different commands. Let me find the right one here--
one of which would be select. Select being one of our
most versatile tools to select data from this database. So if I have these three
columns here-- timestamp, title, and genres, suppose I
want to select all of the titles. Doing that earlier in Python
required importing the CSV library, opening the file, creating a reader or
a DictReader, iterating over every row, adding every title to a dictionary
or just printing it out, and dot, dot, dot. There was a dozen or so lines
of code when we first began. Now, how about this? Select title from
favorites, semicolon, done. So now, with this particular
language, the output is very textual and it's simulating what it looks like
if it were more graphical by creating this table, so to speak. Select title from
favorites is a distillation in a different language called
SQL of all the lines of code I wrote early on when we first
started playing with favorites.py. SQL is therefore optimized for
reading, and creating, and updating, and ultimately, deleting data. So here's perhaps a better tool
for the job once you have the data. Tossing it into a more
powerful, versatile format might allow you now to get
more work done more quickly without having to reinvent the wheel. Someone else has figured out
how to select data like this. What more can I do here? Well, let me go ahead and pull
up, in a moment, just a little bit of a cheat sheet here. Give me one second to find this. So suppose I want to now select
data a little more powerfully. So here's what I just
did in a canonical way. So select typically works like this. You select columns from a
specific table, semicolon. Unfortunately, stupid
semicolons are back. Select columns from table then, is
the generic form of what I just did. More specifically, I selected one
column called title from favorites. Favorites is the name of the table. Semicolon ends my thought. Suppose I wanted to get two things, like
the genres that each of you inputted. I could instead do select title,
comma, genres from favorites, and then, a semicolon, and Enter. It's going to look a
little ugly on my screen because some of these titles and-- OK, one of you really went
all out with Community. You can see that it's just
wrapping in an ugly way, but it's just now
showing me two columns. If we scroll up to the very top
again, the left most of one, Black Mirror went all out, too. Thank you. And now, OK, we're going to
have to clean some of these up. Game of Thrones, good comedy, yes. Keep going, keep going, keep going. So now we've selected two of
the columns that we care about. There it is. OK, so it's crazy wide because
of all of those genres. But it allows me to select
exactly the data I want. Let's go back to the titles, though,
and perhaps start playing around with some modifiers here. For instance, it turns out, using
SQL there's a lot of functionality built into the language. You've got a lot of functions, similar
to Excel or Google Spreadsheets where you can have formulas. SQL provides you with some
of the same heuristics that allow you to apply operations
like these on entire columns. For instance, you can take
averages, count the total, get the distinct values, force
things to lowercase, uppercase, min, and max, and so forth. So let's try distinct, for instance. Let me go back to my Terminal,
and let's say, select, how about the distinct titles
from the favorites table? Enter. I didn't bother selecting
the genres because I want it to be a little prettier. And you can see here that we
have just the distinct titles, except for issues of formatting. So whitespace is going
to be an issue again. Capitalization is going
to be a thing again. So there's a trade-off. One of the things I was doing in Python
was forcing everything to uppercase and then getting rid of whitespace. But we could combine some of these. I could do something like
force every title to uppercase, then get the distinct value. And that's actually going to get
rid of some of those values as well. And again, I did it all in
one simple line that was fast. So let me pull up at the
bottom of the screen again. I selected distinct upper
titles from favorites, and that did everything for
me at once in just one breath. Suppose I want to get the total
number of counts of titles. How about select count of all
of those titles from favorites? Semicolon, Enter, and now
you get back a mini table that contains just your
answer, 158 in this case. So that's the total
number of, not distinct, but total titles that
we had in the file. And we could continue to manipulate
the data further using, again, functions like these here. But there's also additional
filtration we can do. We can also qualify our selections by
saying where some condition is true. So just as in Scratch, and C, and
Python, you have Boolean expressions, you can have the same in SQL as well,
where I can filter my data where something is true or false. Like allows me to do approximations. If I want to get something
that's like The Office but not necessarily
T-H-E, space, Office, I could do pattern
matching using like here. Order by, limit, and grouped by are
other commands I can execute, too. So let me go back and do
a couple of these here. How about, let me just get, oh, I don't
know, all of the titles from favorites but limit it to 10 results. That might be one thing that's helpful
to see if you just care about some of the data at the top there instead. How about, select all of the titles
from favorites, where the title itself is like, quote, unquote, "Office?" And this will give me only two answers. Those are the two rows, recall, that I
mutated by getting rid of the word The. Notice that like allows me too
tolerate uppercase and lowercase. Because if I instead
just use the equal sign, and in SQL a single equal sign
does, in fact, mean equality. For comparison's sake,
it's not doing assignment. This is not how you assign data in SQL. I got back no answers there. So indeed, the equal sign
is giving me literal answers that searches just for what I typed in. How could I get all of these? Well, similar in spirit to regular
expressions but not quite as powerful in SQL, I could do something like this. I can select the title from favorites
where the title is like, quote, unquote, "Office." But I can add, a bit weirdly, percent
signs to the left and the right. So the language SQL supports the
same notion of pattern matching but much more limited out of the box. If we want more powerful
regular expressions we probably do want
to use Python instead. But the percent sign here
means 0 or more characters on the left, 0 or more
characters on the right. So this will just grab any title that
contains O-F-F-I-C-E in it in that order. And now I get all 16, it would
seem, of those results, again. How do I know it's 16? Well, I can just get the
count of those titles and get back that
answer instead as well. So again, it takes some
getting used to, the vocabulary and the syntax that you can use. There's these building
blocks and others. But SQL is really designed, again,
for creating, reading, updating, and deleting data. For instance, I've never really
been a fan of Friends, for instance. So right now if I do select,
how about title from favorites where title like, quote, unquote,
Friends with the percent signs? We can see that there's
a whole bunch of them. That's how many exactly. Let's just do a quick count. So that's nine of them. Well, delete from favorites. OK, you and me, delete from favorites,
where title like Friends, Enter. Nothing seems to happen,
but bye-bye Friends. [APPLAUSE] DAVID J. MALAN: Thank you. So now we've actually changed the data. And this is what's compelling
about a proper database. Yes, you could technically write Python
code that not only reads the CSV file, but also writes it. You can change using quote,
unquote, "A" for append, or quote, unquote, "W" for
write, instead of quote, unquote, "R" for read alone. But it's definitely a little more
involved to do that in Python. But with SQL, you can update
the data in real time. And if I were actually running a
web application here or a database for a mobile app, that
change, theoretically, would be reflected everywhere
on your own devices if you're somehow talking
to this application. So that's the direction we're headed. This other thing has been bothering me. So select, how about title from
favorites, where title equals, what was it? The V Office, was it? Yeah, it was that one. How about we update
favorites by setting title equal to The Office, where title
equals quote, unquote, "The V Office" semicolon? And now, if I select
the same thing again I can go up and down with
my arrow keys quickly. Now there is no The V Office. We've actually changed that value. How about genres? Select genres from favorites,
where the title is title equals Game of Thrones, semicolon. These were kind of long, and I
don't really agree with all of that. So how about we update favorites,
set genres equal to, sure, action, adventure, sure, drama? OK, so it's a decent list. Fantasy, sure, thriller, war. OK, anything really but
comedy, I would say. Let's go ahead and hit Enter now. And now, if I select genres again, same
query, now we've canonicalized that. We've thrown data away. So whether or not that
is right is probably a bit subjective and argumentative. But I have at least cleaned up my
data, which is, again, the U in CRUD. Create, read, update, delete,
you can do it that easily. Beware using delete. Beware worse using drop, whereby
you can drop an entire table. But via these kinds of
commands, can we actually now manipulate our data much more
rapidly and with single thoughts. And in fact, if you're an aspiring
statistician, or data scientist, or analyst in the real world, SQL
is such a commonly used language because it allows you to really
dive into data quickly, and ask questions of the data, and get
back answers quite quickly. And this is a simple data set. You can do this with much larger
data sets as we soon will, too. Or any questions on what
we've seen of SQL thus far? Only scratched the
surface, but again, it boils down to creating, reading,
updating, and deleting data. Questions here? All right. Well, let's consider
the design of this data. Recall that if I do .schema, that
shows me the design of my table, the so-called schema of my data. This is OK. It gets the job done, and frankly,
everything the user typed in was arguably text, including the
timestamp, which is the date and time. But so the data set
itself is somewhat simple. But if we look at the data set itself,
especially genres, let's do this. Select genres from favorites. And let me point out one other
thing stylistically, too. I am very deliberately capitalizing
all of the special SQL keywords, and I'm lowercasing all of the
column names and the table names. This is a convention, and
honestly, it just helps you read, I think, the code when you're
co-mingling your names for columns and tables with proper SQL keywords. But I could just as easily do
select genres from favorites, but again, the SQL specific keywords
don't quite jump out as much. So stylistically, we would
recommend this, selecting genres from favorites, semicolon. So here is where-- oh. OK, that was not intended. I accidentally made
every show, including The Office about action, adventure,
drama, fantasy, thriller, and war. How did I do that accidentally? What did I do wrong? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah. So beware, this is funny. I think I did say
beware around this time. So the SQL database took me--
literally, I updated favorites, setting genres equal to that,
semicolon, end of thought. I really wanted to say
where title equals, quote, unquote, "Game of Thrones." Unfortunately, there isn't an
undo command or time machine with a SQL database, so
the best we can do here is, let's actually get
rid of favorites.db. Let's run SQLite of favorites.db
again, which now will be recreated. Let me change myself into CSV mode. Let me import, into my
favorites table, the CSV file. And now, Friends is back,
for better or for worse, but so are all of our genres. If I now reload the file
and do select, star, from-- sorry. Select genres from favorites,
that was the result I was getting. It's much messier, but that's
because some of these are quite long. But now we're back to the original data. Lesson here, be sure
to back up your work. All right. So what more can we
now do with this data? Well, I don't love the design of the
genres table for a couple of reasons. One, we didn't have
any sort of validation, but user input is going to be messy. There's just a lot of
redundancy in here. Let's go ahead and do this. Let me select all the
comedies you all typed in. So select title from
favorites, where genres equals, quote, unquote, "comedy." OK, so there's all of the shows
that are explicitly comedies. But I think there might
actually be others. Let me scroll back up here. Comedy, drama. What was a comedy and a drama? How about let's search for the-- oops,
let me copy paste comedy, comma, drama. OK, so The Office, in this case, was
considered comedy and drama, Billions, It's Always Sunny in Philadelphia,
and Gilmore Girls as well. But notice that I get many more
when I just search for comedy. So the catch here is that, because I
have all of these genres implemented the way Google did, as
a comma-separated list, it's actually really hard and messy
to get at any show, all of the shows that are somewhere described as comedy. Because if I search for quote,
unquote, "comedy," the only answers I'm going to get are this one, whatever
that show is, this one, whatever that show is, this one. But I'm not going to get this one. I'm not going to get this one. Why? If I'm searching for, where genres
equals, quote, unquote, "comedy," why am I missing those other shows? Why am I missing? Yeah? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Exactly. It's not just a comedy,
it's a comedy and a drama, and a comedy or a news
show, and so forth. So I have to search for these commas,
so this gets messy quickly, right? Let me copy this so I can do this. Let me search for where
genres equals comedy. How about, or genres equals
comedy, drama, or genres equals this whole thing,
comedy, news, talk show? I'm going to get more and more results. But that's not going to scale well. What could I do instead
of enumerating with ors all of the different permutations
of genres, do you think? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah. So I could use the keyword is,
similar in Python to the word in. I could use the like
keyword so that so long as the genres is like
comedy somewhere in there, that's going to give me all of them,
so long as the word comedy is in there. But let me go ahead and just
open the form from earlier. Let me see if I can open this
real quick before I toggle over. If we look back at the
form, recall that there were all of those radio buttons
asking for the specific genres into which something fell. And if I open this, let me full screen
here and now open the original form. You'll see all of the
genres here, none of which are that worrisome except for a
corner case is jumping out at me. Where might the like keyword
alone get me into trouble? It's not with comedy. I'm OK with comedy. AUDIENCE: Music and musical? DAVID J. MALAN: Yeah, music and musical
are deliberately on the list here. Because, one, they're separate genres. But if I just search for
something that's like music, I'm going to accidentally suck
in all of the musicals, which might not be what I intend. If music is a music video or
whatever, and musical is actually a different type of show, I
don't want to just do that. So it seems just very messy. I could probably hack something together
with-- maybe add some commas in there, or something like this. But this is just not a
good design for the data. Google has done it this
way because it's just simple to actually keep the user's
data all in a single column, and just as they did,
separate it by commas. But this is a real
messy way to use CSV is by putting comma-separated values
in your comma-separated values. Arguably, the folks at
Google probably just did this because it's just simpler. And they didn't want to
give people multiple sheets or complicate things using some other
weirder character than commas alone. But I bet there's a better
way for us to do this. And let me go ahead and do this. Let me go back into my code here. And in just a moment, I'm
going to grab a program that I wrote in advance that's going
to use Python to open up the CSV file, iterate over all of the rows, and load
the data into two tables this time, two tables, one called
shows, and one called genres, so as to actually separate
these two things out. Give me just a moment to grab the code. And when I run this, I'll
only have to run it once. Let me go ahead and
run Python in a moment, and I'll reveal the results in a sec. This is going to be version
8 of the code online. When I do this, let me go
ahead and open up this file. Give me a second to move
it into this directory. Version 8, OK. So here we have version 8 of
this that's available online that's going to do the following. And I'll gloss over some
of the details just so that we don't get stuck in the
weeds of some of this code. I'm going to be using, at
the top of this program, as we'll soon see, a CS50 library,
not for the sake of get_string, or get_int, or get_float, but
because there's some built-in SQL functionality that we didn't discuss
a couple of weeks back with the CS50 library itself. But inside of the CS50 library we'll
see there is a special function called SQL that gives you the ability using
this weird URL-like looking thing, technically called a URI, that allows
me to open a file called favorites.db. And long story short, all
of the subsequent code is going to iterate over this
favorites.csv file that we downloaded. And it's going to import it
into the SQLite database, but it's going to use two
tables instead of just one. So give me just a moment
to run this, and then I'll reveal the actual results. This is going to be
run on favorites.csv. And taking a look here,
give me just a moment. Oh, give me a sec. Come on. Come on. This program should not
be taking this long. Sorry. Let's open this real fast. Whoops, not that file. OK. Let me just skim this code real
quick to see where we've gone wrong. [INAUDIBLE] reader. Reader, title, show ID
in certain two shows. [INAUDIBLE] genres split, DB execute. All right. This is me debugging in real time. All those times we encourage you to use
print, this is me actually using print. We'll see how quickly I
can recover from this. Python of favorites version 8. OK, so here's me debugging in real time. It's printing it. Oh, maybe I just didn't
wait long enough. OK, so here we go. What I'm doing is printing out
the dictionary that represents each row that you all typed in. And we're actually making progress. All right. I was too impatient and
didn't wait long enough. So in a moment-- there we go. All right, so all we have
to do sometimes is wait. Let me go ahead now and open
this file using SQLite3. So in SQLite3 I now have a
different version of favorites.db. I named it number 8 for consistency. Once I've run the program I can
do .schema to look inside of it. And here's what the two tables in
this database are going to look like. I've created a table called shows, this
time to represent all of the TV shows that are favorites,
that has two columns. One is called ID, one is called Title. But now I'm going to start
taking out for a spin some of the other features of SQL. And besides there being text, it turns
out there's a data type called integer. Besides there being a
data type called text, there's also a special key
phrase that you can specify that the title can never be null. Think back to our use
of null in C. Think back to the keyword none in Python. This is a database constraint that
allows you to ensure that none of you can't have of favorite TV show. If you submit the form, you have
to have typed in a title for it to end up in our database here. And you'll notice one other new feature. It turns out, on this
table I'm defining what's called a primary key,
specifically to be the ID column. More on that in just a moment. Meanwhile, the second table my code
has created for me, as we'll soon see, gives me a column called
show ID, and then, a genre, the value of which is text
that can also not be null. And then more on this in a moment. This table has what we're
going to call a foreign key, specifically the show ID column
that references shows ID. So before we get into
the weeds of this, this is now a way of creating the
relation in relational database. If I have two tables now, not
just one, they can somehow be linked together by a common column. In other words, the shows column-- shows table is going to give
me a table with two columns-- an ID and a title. Every title you gave me, I'm
going to assign a unique value. The genre's table, meanwhile, is
going to associate individual genres singular with that same idea. And the result of this, to pop back to
the Terminal here, is, let's do this. Select star from shows
of this new database, and you'll see that I've given,
indeed, all of the shows you all typed in unique identifiers. I didn't filter out duplicates or do
anything beyond just forcing everything to uppercase. So there's going to be some
duplicates here because I didn't want to get rid of anyone's data. But you'll see that,
indeed, I've given everyone a unique identifier, from
the very first person who typed How I Met Your Mother, all
the way down to input number 158. Meanwhile, if I do select star from
genres, which is now a table, not just a column in the original
data, now you'll see a much better design for this data. Notice what I've done here. Let me go all the way to the top and
you'll see two columns, one of which is called show ID, the other
of which is called genre. And again, I wrote some
code to do this because I had to take Google's messy output where
everything was separated by commas. I had to tear away the commas and
then put each genre into this table by itself. Even though we haven't
introduced the syntax via which we can reconstitute the
data and reassociate your genres with your
titles, why, at a glance, might this be a better design now? Even though I've doubled the
number of tables from one to two, why is this probably on the
direction toward a better design? What might your instincts be? Why is this cleaner? Again, first time with SQL,
why is it better, perhaps, that we've done this
with our genre's table? Can I come to you? Why might this be better? Yeah. Oh, just because we had the
conversation before about the commas. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Exactly. It's as simple as that. We've cleaned up the data by giving
every genre, every word in the genres column in the original
Google Spreadsheet its own cell in this table, if you will. And now notice show ID
might appear multiple times. Whoever typed in How I Met Your Mother,
they only associated one genre with it. And so we see that
show ID 1 is a comedy. But whoever typed in-- I forget the name of
the second show offhand. But that person, whoever was
assigned show ID 2 checked off a whole bunch of the genre's boxes. That happened again with show ID 3, 4. Persons 5, 6, 7 only checked one box. And so you can see now that we've
associated the data with what we might call a one-to-many relationship. A one-to-many relationship, whereby
for every one show in the show's table, it can now have many genres
associated with it, each of which is represented by a separate row here. So again, if I go ahead and
select star from shows-- let's limit it to the first 10 just
to focus on a subset of the data. How I Met Your Mother, The Sopranos
was the second input there. It would seem that now that I've
created the data in this way, I could ideally somehow search the
data, but a little more correctly. I don't have to worry about the commas. I don't have to worry about
the hackish approach of music being a substring of musical. But how can I actually
get back at this data? Well, let's go ahead and do this. Suppose I did want to get back
maybe all of the comedies. All of the comedies, no matter whether
the person checked just the comedy box or multiple boxes instead. How now, given that I
have two tables, could I go about selecting only
the titles of comedies? I've actually made the
problem a little harder, but again, SQL is going to
give me a solution for this. The problem is that if I
want to search for comedies, I have to check the genres table first. And then what's that going to give me? If I search the genres
table for comedies, what's that going to
give me back potentially? Yeah? AUDIENCE: Show ID. DAVID J. MALAN: Maybe show ID. So let me try that. Let me do select show ID from genres,
where the genre in a given row equals quote, unquote, "comedy." No commas, no like, no percent signs. Because literally, that column now is
singular words, like comedy, or drama, or the like. Let me go ahead and hit Enter here. OK, so I got back a whole
bunch of ID numbers. Now this could very
quickly get annoying. It looks like show ID 1, 2, 4, 5, 6,
7, 9, and so forth, are all comedies. So I could do something really
crazy like, select title from shows, where ID equals 1, or ID equals 2. This is not going to
scale very well, but this is why SQL is especially powerful. You can actually compose one
SQL question from multiple ones. So let's do this. Why don't I select the title
where the ID of the show is in the following list of IDs? Select show ID from genres, where the
specific genre is, quote, unquote, "comedy." So I've got two SQL queries. One is deliberately nested
inside of parentheses. That's going to give me back
that whole list of show IDs. But that's exactly what
I want to then look up the titles for by selecting title
from shows where the ID of the show is in that big, tall list. And so now if I hit Enter,
I get back only those shows that were somehow flagged as
comedy, whether you in the audience checked one box for comedy,
two boxes, or all of the boxes. Somehow we teased out
comedy, again, just by using that Python script,
which loaded this data not into one big table, but instead, two. And if we want to clean this
up, let's do a couple of things. Let's, outside of the
parentheses, do order by title. This is a way of sorting
the data in SQL very easily. Now we have a whole list of the
same titles that are now sorted. And what was the keyword with which
I could filter out duplicates? Yeah, distinct. So let's try this. Same query, but let's select only the
distinct titles from that whole query. And notice, I've very
deliberately done it this way. And to this day, any
time I'm using SQL, I don't just start at the beginning
and type out my whole thought, and just get it right on the first try. I very commonly start
with the subquery, if you will, the thing in
parentheses, just to get myself one step toward what I care about. Then I add to it. Then I add to it. Then I add to it, just like
we've encouraged in Python and C, taking baby steps in order to get to
the answer you actually care about, like this one now. And other than this
mistake, which we didn't fix because I re-imported the data after
accidentally changing everyone's genre, we now have an alphabetized
list of all of the same data. But now it's better designed, because we
have it split across these two tables. Oh, thank you. OK, just thanks. What questions do we have, if any here? Questions on this approach? Yeah? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Oh, now
that we have a database, how do we transfer it to a CSV? There are ways to do that. And in fact, there's a
command within SQLite that allows you to export
your data back to a CSV file. If you want to email
it to someone and you want them to be able to open it in
Excel, or Google Spreadsheets, or Apple Numbers, or the like, you can
go in the other direction. Generally though, once
you're in the world of SQL you're probably storing
your data there long term. And you're probably updating it,
maybe deleting it, adding to it, and so forth. For instance, the one command
I did not show earlier is, suppose someone forgot a show. Let's see, did I see this in the output? All right, so Curb Your Enthusiasm. Saw that last night. It was just, yeah. Did anyone see it last night? No? All right, well, just the one person
that checked that box, so you and me. What's another show that
didn't make the list? How about Seinfeld? It's now on Netflix, apparently. So insert into shows. What do we want to insert? Well, we want to insert
maybe an ID and a title. But I don't actually
care what the ID is, so I'm just going to insert a title. And the value I'm going
to give to that title is going to be, quote,
unquote, "Seinfeld." And then, let me go
ahead and hit semicolon. Nothing seems to happen, but
let me rerun the big query from before looking for comedies. And unfortunately, Seinfeld has
not yet been flagged as a comedy, so let's get this right, too. What intuitively I'm going to
have to do to associate, now, Seinfeld with my comedies? I just inserted into the show's table. What more needs to happen before
we can flag Seinfeld as a comedy? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Say again? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah. So I need to insert into the
genres table two things now, a show ID, like this, and
then, the name of the genre, which presumably is comedy. What values do I want to insert? Well, the show ID, I better grab that. Oh, I don't even know what it is. I'm going to have to
figure out what that is. So I could do this in a couple of ways. Let me do a one-time thing. Select star from shows,
where title equals, quote, unquote,
"Seinfeld" semicolon 159. So now I could do, insert
into genres a show ID and a genre name, the values 159, and,
quote, unquote, "comedy" semicolon, Enter. And now, if I scroll back in my history
and execute that really big query again, looking for
all distinct comedies, now Seinfeld has made the list. But I did this manually so I
didn't actually capitalize it. Let's clean that up. Let's do update. Let's do update my shows. Set title equals to Seinfeld semicolon. No? OK, thank you, where title equals,
quote, unquote, "Seinfeld." Let's not make that mistake again. Enter. And now, if I execute that really
big query, now Seinfeld is, indeed, considered a comedy. So where are we going with this? Well, thus far we've been doing
all of this pretty manually. And this is absolutely what an
analyst, a data scientist type person might do if just manipulating
a pretty large data set just to get at interesting answers
that might be across one, two, or even many more tables. Eventually, in a few weeks, we're
going to start to automate all of this by writing code in Python
that generates SQL to do this. If you go to most any website
on the internet today, and you, for instance, log in, odds are
you're typing a username and password, clicking Submit. What's then happening? Well, the website might not
be implemented in Python but it's probably implemented in some
language, Python, JavaScript, Java, Ruby, something else. And that language is probably using
something like a relational database to use SQL to get your
username, get your password, and compare the two against
what you've typed in. And actually, it's hopefully not
getting your actual password, but something called the hash thereof. But there's probably a
database involved doing that. When you buy something on
Amazon.com and you click Check Out, odds are there's some
code on Amazon's server that's looking at what it is
you added to your shopping cart, and then maybe using a for loop of some
sort, in Python or another language. It's doing a whole bunch of SQL
inserts to store in their database what it is you bought. There's other types of databases,
too, but SQL databases, or relational databases
are quite popular. So let's go ahead and write
one other program here in Python that now merges these
two languages together, whereby I'm going to use SQL
inside of a Python program so I can implement my logic
of my program in Python, step-by-step, line-by-line. But when I want to get at some data I
can actually talk to a SQL database. So let me go ahead
and open favorites.py. And let me go ahead and throw away
some of what we did earlier and really just now add a SQL to the mix. From the CS50 library, let's
import the SQL function. This will be useful to use
because most third-party libraries that deal with SQL and Python are
more complicated than they need to be. So I think you'll find
this library easier to use. Let's then do the following. Create a variable
called db for database. But I could call it anything I want. Let's use that you URI, which is
a fancy way of saying something that looks like a URL, but that actually
opens up a database on disk, that is, in the current folder. Let's now ask the user for a title by
prompting them for a, quote, unquote, "title" like this. And let's strip off any whitespace
just so that the data is not messy. And then, let's go ahead and do this. And this is the new logic. I'm going to go ahead now and write
a line of code that uses Python to talk to the original favorites.db. So again, I'm not using the two-table
database, which is in favorites8.db. I'm using the original that we
imported from your own data, and I'm going to do the following. I'm going to use db.execute to execute
a SQL command inside of Python. I'm going to select the count
of shows from the favorites table, where the title the user
typed in is like this question mark. And why I'm doing that is as follows. Just like in C, when we had
percent S, in SQL for now, the analogue is going
to be a question mark. So same idea, different syntax. Instead of percent S,
it's just a question mark. And using a comma outside of this
first string, using CS50's execute function I can pass in
a SQL string, a command, then any arguments I want to plug
into the question marks therein. So the goal at hand is to
actually write a program that's going to search favorites.csv, a.k.a.,
favorites.db for the total number of people that liked a particular show. So this is going to select the count
of people from the favorites table where the title they typed in is like
whatever the user has just now typed in. This db execute function returns a list. It returns a list of rows. And you would only know that
by my telling you or reading the documentation. And therefore, if I want to
get back to the total count, I'm going to go ahead and grab
the first row from those rows. Because it's only going
to give me back the count. And then I'm going to go ahead and
print out that row's first value. But it's going to be a little weird. Technically the column is going to be
called "count" star, quote, unquote, which is a little weird. Let me add one more feature to the mix. You can actually give
nicknames to columns that are coming back, especially if they
are the result of functions like this. I can just call that column
counter, in all lowercase. That means I can now say get back the
counter key inside of this dictionary. So just to recap, what have we done? We've imported the CS50
library SQL function. We've, with this line of
code, opened the favorites.db file that you and I created earlier
by importing your CSV into SQLite. I'm now just asking the user for
a title they want to search for. I'm now executing this SQL
query on that database, plugging in whatever the
human typed in as their title in order to get back a total count. And I'm giving the count a
nickname, an alias of counter, just so it's more self-explanatory. This function, db execute, no matter
what, always returns a list of rows, even if there's only
one row inside of it. So this line of code just gives
me the first and only row. And then, this goes inside of that row,
which it turns out is a dictionary, and gives me the key counter
and the value it corresponds to. So what, to be clear, is this doing? Let's go ahead and run this manually
in my Terminal window first. Let me run SQLite3 on favorites-- Well, let's do this. On favorites.db, let me
import the data again. So mode csv.import in from
favorites.csv into a favorites table. So I've just recreated the
same data set that you all gave me earlier in favorites.db. If I were to do this manually,
let's search for The Office again. Select, count star from favorites,
where title like, and let's just manually type it
in for now, The Office. We'll search for the one
with the word The, semicolon. I get back 12. But technically, notice what I get back. I technically get back a miniature
table containing one column and one row. What if I want to rename that column? That's where the as keyword comes in. So select count star as counter. Notice what happens, Enter. I just get back-- same
simple table, but I've renamed the column to be counter
just because it's a little more self-explanatory as to what it is. So what am I doing
with this line of code? This line of code is returning to
me that miniature temporary table in the form of a list of dictionaries. The list contains one
row, as we'll see, and it contains one column, as we'll
see, the key for which is counter. So let's now run the code itself. I'm going to get out of SQLite3 and I'm
going to run Python of favorites.py. Enter. I'm being prompted for a title. I'm going to type in The Office and
cross my fingers, and there's that 12. Why is it 12? Well, there's a typo again
because I re-imported the CSV. I had deleted two of the Thes, so
we're back at the original data set. So there's 12 total that have,
quote, unquote, "The Office" in the title like that. So what have we done? We've combined some
Python with some SQL, but we've relegated all of the
complexity of searching for something, the selecting of something,
gotten rid of all of the with keyword, the
open keyword, the for loop, the reader the DictReader,
and all of that. And it's just one line of SQL now,
using the best of both worlds. All right, any questions on what we've
just done here or how any of this works? Any questions here? Yeah? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: When does this
function return more than one row? Was that the question? AUDIENCE: Yeah. DAVID J. MALAN: Yeah. So let's do that by changing
the problem at hand. This program was designed just
to select the total count. Let's go ahead and
select, for instance, all of the ways you all typed in The Office
by selecting the title this time. If I do this in SQLite3, let
me go ahead and do this again after increasing my Terminal window. Let's do it manually. Select title from favorites,
where the title is like, quote, unquote, "The Office," semicolon. I get back all of these different rows,
and we didn't even notice this one. There's actually another
little typo in there with some capitalization of the
E, and the C, and the E. That would be an example of a query
that gives me back therefore for multiple rows. So let's now change my Python program. If I now, in my Python program, do
this, I get back a whole bunch of rows containing all of those titles. I can now do, for row in rows, I can
print out the current row's title, and now manipulate all
of those things together. Let me keep both on the screen. Let me run Python of favorites.py. And that for loop now should
iterate, what, 10 or more times, once for each of those titles. And indeed, if I type in
The Office again, Enter. Whoops. Row title. What did I do wrong? Oh, I should not be renaming
title to counter this time. So that's just a dumb
mistake on my part. Let me rerun it again. And now I should see after
typing in The Office, Enter, a whole bunch of The Offices. And because I'm using
like, even the missed capitalizations are coming through,
because like is case insensitive. It doesn't matter if it's
uppercase or lowercase. Whereas had I used the equal sign
I would get back only the same ones capitalized correctly. All right, any questions on this next? All right, so let's transition
to a larger, juicier data set, and consider some
of the issues that arise when actually now using SQL and
skating toward a world in which we're using SQL for mobile apps, web
apps, and generally speaking, very large data sets. So let's start with a larger
data set just like that. Give me just a moment to switch screens
over to what we have for you today, which is an actual relational
database that we've created out of a real-world data set from IMDb. So InternetMovieDatabase.com
is a website where you can search for TV
shows, and movies, and actors, and so forth, all using their
database behind the scenes. IMDb wonderfully makes their data
set available as not CSV files, but TSV files, tab-separated values. And so what we did is, before class
we downloaded those TSV files. We wrote a Python program
similar to my favorites8.py file earlier that read in
all of those TSV files, created some SQL tables
in an IMDb database for you in SQLite that has multiple
tables and multiple columns. So let's go and wrap our minds around
what's actually in this data set. Let me go back to VS Code
here, and in just a moment, I'm going to go ahead and copy the
file, which we've named shows.db. And I'm going to go ahead and increase
my Terminal and do SQLite3 of shows.db. Whenever playing around with a
SQLite database for the first time, typing .schema is perhaps a good
place to start to give you a sense of what's in there. And things just escalated quickly. There's a lot in this data
set, because, indeed, there's going to be tens of hundreds of
thousands of rows in this data set, and also problem set 7, where we'll
look at the movie side of things and not just the TV shows. So what is the schema that
we have created for you from IMDb's actual real-world data? One, there's a table called shows. And notice we've just added whitespace
by hitting Enter a bunch of times to make it a little more
stylistically readable. The shows table has an ID
column, a title column, a year, and the total number of
episodes for a given show. And the types of those columns are
integer, text, numeric, and integer. So it turns out there's
actually a few different data types that are worth being aware of when
it comes to creating tables themselves. In fact, in SQLite there's
five data types, and only five, fortunately, one of which is, indeed,
integer, negative or positive, numeric, which is kind of a
catchall for dates and times, things that are numeric
but are not just integers, and not just real numbers, for instance. Real number is what we've generally
thought of as float up until now. Text, of course, is
just text, but notice that you don't have to
worry about how big it is. Like in Python, it will size to fit. And then there's BLOB, which
is binary large object, which is for just raw 0s and 1s, like
for files or things like that. But we'll generally use
the other four of these. And so, indeed, when we
imported this data for you we decided that every show would be
given an ID, which is just an integer. Every show has, of course, a
title, which should not be null. Otherwise, why is it in the database? Every show has a year,
which is numeric according to that definition a moment ago. And the total number of episodes for
a show is going to be an integer. What now is with these primary keys
that we mentioned earlier, too? A primary key is the column that
uniquely identifies all of the data. In our case, with the
favorites, I automatically gave each of your submissions a unique
ID so that even if two or more of you typed in The Office,
your submission still had a unique identifier, a number
that allowed me to then correlate it with your genres, just
as we saw a moment ago. In this version of IMDb,
there's also genres. But they don't come from
us, they come from IMDb.com. And so a genre has a show ID, and
a genre just like our database. But these are real-world genres
with a bit more filtration. Notice, though, just like my
version, there's a foreign key. A foreign key is the appearance
of another table's primary key in its own table. So when you have a table
like genres, which is somehow cross referencing the original shows
table, if shows have a primary key called ID, and those same numbers
appear in the genres table under the column called show ID, by
definition, show ID is a foreign key. It's the same numbers but
it's foreign in the sense that the number is being
used in this table, even though it's officially defined
primarily in this other table. This is what we mean by
relational databases. You have multiple tables with some
column in common, numbers typically. And those numbers allow you to line
the two tables up in such a way that you can reconnect the
shows with their genres, just like we did with our
smaller data set a moment ago. This logic is extended further. Notice that the IMDb database we've
created for you has a stars table, like TV show stars, the actors therein. And that table, interestingly,
has no mention of people and no mention of shows, per se. It only has a column called
show ID, which is an integer, and a person ID, which is an integer. Meanwhile, if we scrolled
down to the bottom, you will see a table called people. And we have decided in IMDb's world
that every person in the TV show world will have a unique identifier that's
a number, a name that's text, a birth date, which is numeric, and
then, again, specifying that ID is going to be their primary key. So what's going on here? Well, it turns out that TV stars and
writers are both types of people. So using this relational database,
notice the road we're going down. We're factoring out commonalities. And if a person can be
different things in life, well, we're defining them
first and foremost as people. And then, notice these two
tables are almost the same. The stars table has a show
ID, which is a number, and a person ID, which
is a number, which allows us via this middleman table, if
you will, to link people with TV shows. Similarly, the writers table allows
us to connect shows with people, too, by just recording those numbers. So if we go into this data
set, let's do the following. Let's do select star
from people semicolon. So a huge amount of data is coming back. This is hundreds of thousands of rows
now based on the ID numbers alone. So this is real-world data
now flying across the screen. There's a lot of people in the TV show
business, not just actors and writers, but others as well. It's still going. There's a lot of data there. So my god, if you had to do
anything manual in this data set it's probably not going
to work out very well. And actually, we're up to, what,
a million people in this data set, plus, which would mean
this probably isn't even going to open very well in Excel, or
Google Spreadsheets, or Apple Numbers. SQL probably is the
better approach here. Let's search for someone
specific, like select star from people, where name equals
Steve Carell, for instance, sticking with comedies. All right, so there's Steve Carell. He is person number
136,797, born in 1962. And that's as much data as
we have on Steve Carell here. How do we figure out what
shows, for instance, he's in? Well, let's see, select
star from shows, semicolon. There's a crazy number of shows
out there in the IMDb database. And you can see it here again
flying across the screen. Feels like we're going to have to
employ some techniques in order to get at all of Steve Carell's shows. So how are we going to do that? Well, god, this is a lot of data here. And in fact, yeah, we
have, what, 15 million shows plus in this data set, too. So doing things efficiently is
now going to start to matter. So let's actually do this. Let me select a specific show. Select star from shows where title
equals, quote, unquote, "The Office." And there presumably shouldn't
be typos in this data because it comes from the
real website IMDb.com. Let's get back to show. Turns out there's been a lot of
The Offices out in the world. The one that started in 2005
is the one that we want, presumably the most
popular with 188 episodes. How can we get just that? Maybe we could do and year
equals, how about 2005? All right, so now we've got
back just the ID of The Office that we care about. And let's do this, too. Let me turn on a timer
within SQLite just to get a sense of running time now. Let me do that again. Select star from shows, where
title equals The Office, and year equals 2005. And let's keep it simple. Let's just do titles for now. Enter. All right, so not terribly long. It found it pretty fast, but it looks
like it took how much real time? 0.02 seconds, not bad for just a title. But just to plant a seed, it
turns out that we can probably speed even this up. Let me do this. Let me create something called an
index, which is another use of the C in CRUD for creating something. And I'm going to call this title index. And I'm going to create
it on the shows table, specifically on the title column. And we'll see in a moment what
this is going to do for me. Enter. Took a moment, like 0.349 seconds,
to create something called an index. But now watch, if I select star from
shows searching for The Office again, previously it took me 0.021 seconds. Not bad, but now, wow. Literally no time at all, or so low
that it wasn't really measurable. And I'll do it again just
to get a sense of things. Still quite low. Now even though 0.021 seconds, not crazy
long, imagine now having a lot of data, a lot of users running a real
website or real mobile app. Every millisecond we can start to
shave off is going to be compelling. So what is it we just did? Well, we actually just created
something called an index. And this is a nice way
to tie in, now, some of our week 5 discussion
of data structures, and our week 3 discussion
of running times. An index in a database is
some kind of fancy data structure that allows the database
to do better than linear search. Literally, as you just saw, these
tables are crazy long or tall right now, very linear, that is. And so when I first
searched for The Office, it was literally doing linear search,
top to bottom, looking at as many as, what, a million plus rows. That's relatively slow. It's not that slow, 0.021 seconds. But that's relatively slow just
theoretically, algorithmically, doing anything linearly. But if you instead create
an index using syntax like this, which I just did, creating an
index on the title column of the show's table, that's like giving the
database a clue in advance saying, hey, I know I'm going to search on
this column in this table a lot. Do something with data
structures to speed things up. And so if you think back to our
discussion of data structures, maybe it's using a tree. Maybe it's using a trie or a hash
table, some fancier two-dimensional data structure is generally going to lift
the data up creating right maybe a tree structure. So it's just much faster
to find data, especially if it's sorting it now
based on title, and not just storing it in one long list. And in fact, in the world
of relational databases, the type of structure that's
often used in a database is something called a B-tree. It's not a binary tree. Different use of the letter B, but it
looks a little something like the trees we've seen. It's not binary because
some of the nodes might have more than
two children or fewer, but it's a very wide but
relatively shallow tree. It's not very tall. And the upside of that is that if
your data is stored in this tree, the database can find it more quickly. And the reason it took half a second,
a third of a second to build the index is because SQLite needed to take
some non-zero amount of time to just build up this tree in memory. And it has algorithms for doing so based
on alphabetization or other techniques. But you spend a bit of time
up front, a third of a second. And then thereafter, wow. Every subsequent query, if I
keep doing it again and again, is going to be crazy
low, 0.000, maybe 0.001. But an order of magnitude, a
factor of 10 or 100 faster than it previously was earlier. So we have these indexes which
allow us to get at data faster. But what if we want to
actually get data that's now across these multiple tables? How can we do that? And how might these indices
or indexes help further? Well, it turns out there is
a way that we've seen already indirectly to join two tables together. Previously, when I selected
the ID of The Office, and then I searched for it in the other
table using select in a nested query, I was joining two tables together. And it turns out there's a
couple of ways to do this. Let's go ahead now and, for instance,
find all of Steve Carell's TV shows. Not just The Office
but all of them, too. Unfortunately, if we look at our schema,
shows up here have no mention of TV-- oh, shows over here has no
mention of the TV stars in them. And people have no mention of shows. We somehow need to use this
table here to connect the two. And this is called a join table, in the
sense that using two integer columns-- it joins the two tables
together logically. And so if you're savvy enough with SQL,
you can do what I did with my hands earlier and like recombine
tables by using these common IDs, these integers together. So let me do this. Let me go ahead and figure out,
step-by-step, Steve Carell's shows. So how am I going to do this? Well, if I select star from people,
where name equals Steve Carell, fortunately, there's only one of them. So this gives me back his name,
his ID, and his birth year. But it's really only his
ID that I care about. Why? Because in order to get back his shows,
I need to link person ID with show ID. So I need to know his ID number. So what could I do with this? Well, remember the schema
and the stars table. I've just gotten, from the
people table, Steve Carell's ID. I bet by transitivity I could
now use his person ID, his ID, to get back all of his show IDs. And then once I've got all of his show
IDs, I can take it one step further and get back all of his shows' titles. So the answer is actually English
words and not just random, seemingly, integers. So let me go ahead and do this. Let me, again, get Steve
Carell's ID number, but not star. Star represents everything. It's a wildcard character in SQL. Let me just select the
ID of Steve Carell. And that gives me back 136,797. And it's only giving me back one value. The thing called ID is just
the column heading up above. Now, suppose I want to
select all of the show IDs that Steve Carell is affiliated with. Let me select Show ID from stars,
where the person ID in stars happens to equal Steve Carell's ID. So again, I'm building up my answer in
reverse and taking these baby steps. On the right, in parentheses,
I'm getting Steve Carell's ID. On the left, I am now
selecting all of the show IDs that have some connection with
that person ID in the stars table. This answer, too, is not
going to be that illuminating. It's just a whole bunch of integers
that have no meaning to me as a human. But let's take this one step further. And even though my
code is getting long, I could hit Enter and format
it nicely, especially if I were doing this in a code file. But I'm just doing it
interactively for now. Let's now select all of the
titles from the shows table, where the ID of the show is in
this following previous query. So again, the query is getting long. But notice, it's the
third and last step. Select title from the shows
table, where the ID of the show is in the list of all
of the show IDs that came back from the stars table
searching for Steve Carell's person ID. How did we get that person ID? Let me scroll to the end. Well, I selected, in my innermost
parentheses, Steve Carell's own ID. So now, when I hit Enter, voila. I get all of Steve Carell's
TV shows up until now. And if I want to tidy this up further,
I can use the same tricks as before. Order by title, semicolon. Now I've got it all
alphabetized as before. So again, with SQL comes
the ability to search-- I mean, look how quickly
we do this, 0.094 seconds to search across three different
tables to get back this answer. But my data is now all neatly
designed in individual tables, which is going to be important
now that the data set is so large. But let me take this one step further. Let me go ahead and do this. Let me go ahead and point
out that with this query, notice that I'm searching on-- let's say I'm searching
on a person ID here. And at the end here, I'm
searching on a name column here. So let me actually go ahead and do this. Let me go ahead and see
if we can't speed this up. This query at the moment
takes 0.092 seconds. Let's see if we can't speed this
up further by just quickly creating a few more of those B-trees
in the databases memory. Create an index called person index, and
I'm going to do this on the stars table on the person ID column. Enter. It's taking a moment, taking a moment. That's almost a full second
because that's a big table. Let's create another index called
show index on the stars table. Why? Because I want to search
by the show ID also. That was part of my big query. Takes a moment. OK, just more than
about 2/3 of a second. Now let's create one last one,
another index called name index, but I could call these things
anything I want, on the people table. Why? Because I'm also searching
on the name column. So in short, I'm
creating indexes on each of the columns that are somehow
involved in my search query, going from one table to the other. Now let's go back to the previous
query, which, recall, took-- I think I erased it, 0.091. All right. Well, it was roughly
this order of magnitude. We're not seeing the data now. But let me go ahead and run
my original big query once. And boom, we're down to almost nothing. So again, creating
these indexes in memory has the effect of rapidly
speeding up our computation time. Now if you've ever used, for instance,
the my.harvard course shopping tool here on campus, or Yale's analogue, you
might wonder, why is the thing so slow? This could be one of the reasons why
large data sets with thousands of rows, thousands of courses
tend to be slow, if, and I'm only conjecturing, if the
database isn't properly indexed. If you're building your
own web application and you're finding that users
are waiting and waiting, and things are spinning and spinning,
what might be among the problems? Well, it could absolutely just be bad
algorithms and bad code that you wrote. Or it might be that you
haven't thought about, well, what column should be optimized
for searches and filtration like I've done here in order
to speed up subsequent queries? Again, from the outside
in, we can only conjecture. But ultimately, this is
just one of the things that explains performance problems as well. All right, let's point out just a
couple of final syntactic things, and then we'll consider,
bigger picture, some problems that might arise in this world. If these nested, nested queries
start to get a little much, there are other ways,
just so you've seen it, that you can execute
similar logic in SQL. For instance, if I
know in advance that I want to connect Steve Carell to
his show IDs and to their titles, we can do something more like this. Select title from the people table,
joined with the stars table on people ID equals stars.personID. So what am I doing? New syntax. And again, this is not something you'll
have to memorize or ingrain right away. But just so you've seen other
approaches, select title from people join stars. This is an explicit way to say, take
the people table in one hand, the stars table in the other hand,
and somehow join them as I keep doing with my fingertips here. How to join them? Join them so that the people, the ID
column in the people table lines up with the person ID in the stars table. But that's not quite everything. I could also say, join
further on the shows table, where the stars show ID
equals the shows ID column. So what am I doing here? That's saying, go further and join
the stars table with the show's table, joining the show ID
column with the ID column. Again, this starts to get a
little messy to think about. But now I can just say, where name
equals, quote, unquote, "Steve Carell." I can do in one query what previously
took me three nested queries and get back the same answers. And I can still add in my order
by title to get back the result. And if I do this a little more
neatly, let me type this out a little differently. Let me type this out by adding a
new line-- ah, I can't do that here. I'm going to leave it alone for now. We can type it on multiple
lines in other contexts. And let me do one last thing. Do I want to show that? I'm going to show it,
but this is not something you should ingrain just yet either. Select title from
people, stars, and shows. If you know in advance that you want
to do something with all three tables, you can just enumerate them,
one table name after the other. And then you can say where
people.ID equals stars.personID. And now I'm hitting
Enter so that it formats a little more readably on my screen. And stars.showID equals shows.ID,
and lastly, name equals Steve Carell. In short, you specify that you
want to select data from all three of these tables. And then you tell the database how to
combine foreign keys with primary keys, that is, the columns that
have those integers in common. If I hit Enter now, I get
the same exact results, ever more so if I also add
in an order by title. Oops. All right. That's why I didn't
want to do this earlier. I have to go back through my history
multiple times to actually get back the multi-line query this time. All right. That was a lot all at once. But this is only to say that, even
as we make the design of the data more sophisticated, and we put
some of it over here, some of it over here, some of it over here so as to
avoid duplication of data, weird hacks like putting commas in the data, we
can still get back all of the answers that we might want across
these several tables. And using indexes, we can
significantly speed up these processes so as to handle 10 times as
many, a 100 times as many users on the same actual database. There is going to be a downside. And thinking back to our
discussion of algorithms and data structures in past weeks, what might be
a downside of creating these indexes? Because as of now, I created four
separate indexes on the name column, the title column, and
some other columns, too. Why wouldn't I just go
ahead and index everything if it's clearly speeding things up? Memory, so space. Any time you're starting to benefit
time wise in computer science, odds are you're sacrificing
space, or vice versa. And probably indexing absolutely
everything is a little dumb because you're going to waste way more
space than you might actually need. So figuring out where the
right inflection point is is part of the process of designing and
just getting better at these things. Now unfortunately, a whole lot of
things can go wrong in this world, and they continue to in the real
world with people using SQL databases. And in fact, here on
out, if you're reading something technical about SQL databases,
and websites being hacked in some form, and passwords leaking out,
unfortunately, all too often it is because of what are
called SQL injection attacks. And just to give you a
sense now to counterbalance, maybe [INAUDIBLE] enthusiasm
for like, oh, that was neat how we can
do things so quickly. With great power comes
responsibility in this world, too. And so many people introduce
bugs into their code by not quite appreciating how it is the
data is getting into your application. So what do I mean by that? Here, for instance, is a
typical login screen for Yale. And here's the analogue for
Harvard where you're prompted, every day probably, for your
username and your password, your email address and
your password here. Suppose, though, that
behind this login page, whether Harvard's or Yale's,
there's some website. And that website is using
SQL underneath the hood to store all of the
Harvard or Yale people's usernames, passwords, ID
numbers, courses, transcripts, all of that stuff. So there's a SQL database
underneath the website. Well, what might go
wrong with this process? Unfortunately, there's
some special syntax in SQL just like there is in C and Python. For instance, there are
comments in SQL, too. If you do two hyphens, dash,
dash, that's a comment in SQL. And if you, the programmer, aren't
sufficiently distrustful of your users, such that you defend against
potentially adversarial attacks, you might do something like this. Suppose that I somewhat
maliciously or curiously log in by typing my username,
[email protected], and then maybe a single quote and a dash, dash. Why? Because I'm trying to suss out
if there is a vulnerability here to a SQL injection attack. Do not do this in general. But if I were the owner of the website
trying to see if I've made any mistake, I might try using potentially
dangerous characters in my input. Dangerous how? Because single quote is used for
quoting things in SQL, as we've seen-- single quotes or double quotes. Dash, dash, I claim now,
is used for commenting. But let's now imagine what
the code underneath the hood might be for something like
Yale's login or Harvard's login. What if it's code that looks like this? So let me read it from left to right. Suppose that they are using something
like CS50's own execute function, and they've got some SQL
typed into the website that says select star from users,
where username equals this, and password equals that. And they're plugging in
username and password. So what am I doing here? Well, when the user types their
username password, hits Enter, I probably want to select
that user from my database to see if the username
and passwords match. So the underlying SQL
might be, select star from users, where username
equals question mark, and password equals question mark. Users is the table. One column is username. One column is password. All right. And if we get back one row,
presumably
[email protected] exists with that password. We should let him proceed
from there on out. So that's some pseudo code, if
you will, for this scenario. What if, though, this code is not
as well written as it currently is, and isn't using question marks? So the question mark syntax
is a fairly common SQL thing, where the question marks
are used as placeholders, just like in printf, percent S was. But this function, db.execute
from CS50's library and third-party libraries
as well, is also doing some good stuff
with these question marks, and defending against
the following attack. Suppose that you were not using
a third-party library like ours and you were just manually constructing
your SQL queries like this. You were to do something like this
instead using an f-string in Python. You're comfortable with
format strings now. You've gotten into the habit of using
curly braces and plugging in values. Suppose that you, the
aspiring programmer, is just using techniques
that you've been taught. So you have an f-string
with select star from users, where username equals, quote,
unquote, "username" in curly braces. And password equals, quote,
unquote, "password" in curly braces. As of what, two weeks
ago, this was perfectly legitimate technique in Python
to plug in values into a string. But notice if you are using
single quotes yourself and the user has typed in single
quotes to their input, what could go wrong here? Where are we going with this if you're
just blindly plugging user input into your own prepared string of text? Yeah? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah. Worst case, they could insert what is
actually SQL code into your database as follows. Generally speaking, if you're using
special syntax like single quotes to surround the user's
input, you'd better hope that they don't have
an apostrophe in their name. Or you better hope that they
don't type a single quote as well. Because what if their single quote
finishes your single quote instead, and then the rest of
this is somehow ignored? Well, let's consider
how this might happen. Let me go ahead in here. This got a little
blurry here, but let me plug in here-- wow, that looks awful. Let me fix the red. Just change this to white
so it's more readable. What happens if the
user does this instead? They type in, like I
did into the screenshot, '
[email protected],'
single quote, dash, dash. What has just happened
logically, even though we've only just begun with SQL today? Well, select star from users, where
username equals
[email protected], end quote. What's bad about the rest of this? Dash, dash, I claim,
means a comment, which means my color coding is going
to be a little blurry again. But everything after the
dash, dash is just ignored. The logic, then, of
the SQL query, then, is to just say, select
[email protected] from the database, not even checking the password anymore. Therefore, you will get
back at least one row. So length of rows will equal 1, and so
presumably the rest of the pseudo code logs the user in, gives them
access to my my.harvard account, or whatever it is. And they've pretended to be me simply
by using a single quote and a dash, dash in the username field. Again, please don't go
start doing this later today on Harvard, Yale, or other websites. But it could be as simple as that. Why? Because the programmer
practiced what they were taught, which was just to
use curly braces to plug in, in f-strings, values. But if you don't understand how the
user's input is going to be used, and if you don't distrust your users
fundamentally, for every good person out there there's going
to be, unfortunately, some adversary who just wants to try
to find fault in your code or hack into your data set. This is what's known as
a SQL injection attack, because the user can type something
that happens to be or look like SQL, and trick your database into doing
something it didn't intend to, like, for instance, logging the user in. Worst case, they could
even do something else. Maybe the user types a semicolon, then
the word drop, or the word update. You could imagine doing semicolon
update table grades, where name equals Malan, and set the
grade equal to A instead of B, or something like that. The ability to inject
SQL into the database means you can do anything you want with
the data set, either constructively, or worse, destructively. And now, just a quick, little
cartoon that should now make sense. OK, to, like, one of us, two of us. Awkwardly somewhat funny. All right, so let's move
on to one last condition. There's one other problem
that can go awry here. Oh, and I should explain this. So this is an allusion to the son,
Robert, having typed in semicolon. The word drop, table, students, and
doing some of the same technique. This is humor that only
CS people would understand because it's the mom realizing,
oh, her son's doing a SQL injection attack onto the database. Less funny when you explain it, but once
you notice the syntax, that's all this is an allusion to. All right. So one final threat, now
that you are graduating to the world of proper databases
and away from CSV files alone. Things can go wrong
when using databases, and honestly, even using CSV
files if you have multiple users. And thus far, you and
I have had the luxury in almost every program we've written
that it's just me using my code. It's just you using your code. And even if your teaching fellow
or TA is using it, probably not at the same time. But the world gets interesting if you
start putting your code on phones, on websites, such that now you might
have two users literally trying to log in at the same time,
literally clicking a button at the same, or nearly the same time. What happens, then, if
a computer is trying to handle requests from two
different people at once, as might happen all
the time on a website? You might get what are
called race conditions. And this is a problem in computing in
general, not just with SQL, not just with Python, really just any
time you have shared data, like a database, as follows. This apparently is one of the
most liked Instagram posts ever. It is literally just
a picture of an egg. Has anyone clicked on this egg? Like, a couple? Oh, OK. Wow. All right, so yes. So go search for this photo if you'd
like to add to the likes on Instagram. The account is world_record_egg. This is just a screenshot of
Instagram of that picture of an egg. If you're in the habit
of using Instagram, or like any social media site, there's
some equivalent of a like button or a heart button these days. And that's actually a
really hard problem. Such a simple idea to count
the number of likes something has, but that means
someone has to click on it. Your code has to detect the click. Your code has to update the database,
and then do it again and again, even if multiple people are perhaps
right now clicking on that same egg. And unfortunately, bad things can
happen if two people try to do something at the same time on a computer. How might this happen? So here's some more code, half
pseudocode, half Python code here, as follows. Suppose that what happens when you,
literally, right now, maybe click on the like button on
the Instagram post. Suppose that code, like the following,
is executed on Facebook servers. db.execute of select likes from
posts where ID equals question mark. All right. So what am I assuming here? I'm assuming that that
photograph has a unique ID. It's some big integer, whatever
it was, randomly assigned. I'm assuming that when
you click on the heart the unique ID is somehow
sent to Instagram servers so that their code can call it ID. And I'm assuming that Instagram
is using its SQL database and selecting, from a posts
table, the current number of likes of that egg for that given ID number. Why? Because I need to know how many likes it
already has if I want to add one to it and then update the database. I need to select the data, then
I need to update the data here. All right. So in some Python code here,
let's store, in a variable called likes, whatever comes back in the
first row from the likes column. Again, this is new syntax
specific to our library, but a common way of getting back
first row and the column called likes therein. So at this point in the
story, likes is storing the total number of likes, in
the millions or whatever it is, of that particular egg. Then I do this. Execute update posts,
set the number of likes equal to this value, where the
ID of the post equals this value. What do I want to update the likes to? Whatever likes currently is plus
1, and then plugging in the ID. So a simple idea, right? I'm checking the value of
the likes, and maybe it's 10. I'm changing 10 to 11 and
then updating the table. But a problem can arise
if two people have clicked on that egg at roughly the
same time, or literally, the same time. Why is that? Well, in the world of
databases and servers, and the Instagrams of the world have
thousands of physical servers nowadays. So they can support millions,
billions even, of users nowadays. What can go wrong? Well, typically code like this
is not what we'll call atomic. To be atomic means that it all
executes together or not at all. Rather, code typically is executed,
as you might imagine, line by line. And if your code is running on a server
that multiple people have access to, which is absolutely the case
for an app like Instagram, if you and I click on the
heart at roughly the same time, for efficiency, the computer,
the server, owned by Instagram, might execute this line of code for me. Then it might execute
this line of code for you. Then this line of code for me,
then this line of code for you, then this line of code for me,
then this line of code for you. That is to say, our queries might
get intermingled chronologically. Because it'd be a little obnoxious
if, when you're using Instagram, I'm blocked out while you're
interacting with the site. It'd be a lot nicer for efficiency
and fairness if somehow they do a little bit of work for me,
a little bit of work for you, and back and forth, and back and
forth, equitably on the server. So that's what typically happens
by default. These lines of code get executed independently. And they can happen in alternating
order with other users. You can get them combined like this. Same order top to bottom, but other
things might happen in between. So suppose that the number of
likes at the very beginning was 10. And suppose that Carter and I both click
on that egg at roughly the same time. And suppose this line of
code gets executed for me, and that gives me a value
in likes, ultimately, of 10. Suppose, then, that the computer takes
a break from dealing with my request, does the same code for
Carter, and gets back what value for the
current number of likes? Also 10 for Carter. Because mine has not been recorded yet. At this point in the story,
somewhere in the computer's memory there's a likes variable
for me, storing 10. There's a likes variable
storing 10 for Carter. Then this line of code executes for me. It updates the database to be likes
plus 1, which stores 11 in the database. Then Carter's code is executed,
updating the same row in the database to 11, unfortunately. Because his value of likes happened
to be the same value of mine. And so the metaphor here, that if we
had a refrigerator on stage we would actually act out, is something that was
taught to me years ago in an operating systems class, whereby the most similar
analogue in the real world would be if you've got a mini
fridge in your dorm room. And one of you and your roommates comes
home, opens the fridge, and realizes, oh, we're out of milk, was
how the story went in my day. So you close the refrigerator, and
you walk across the street, go to CVS, and get in line to buy some milk. Meanwhile, your roommate comes home. They, too, inspect the state of your
refrigerator, a.k.a., a variable, open the door, and realizes,
oh, we're out of milk. I'll go get more milk. Close the fridge, go
across the street, and head to maybe a different store,
or the line is long enough that you don't see each
other at the store. So long story short, you both eventually
get home, open the door, and damn it, now there's milk from
your other roommate there because you both
made a decision on this based on the state of a variable
that you independently examined. And you didn't somehow communicate. Now in the real world, this
is absolutely solvable. How would you fix this or avoid
this problem in the real world? Literally, own roommate, own fridge. AUDIENCE: Text your
roommate [INAUDIBLE].. DAVID J. MALAN: Perfect. Let them know, so somehow communicate. And in fact, the terminology
here would be multiple threads can somehow intercommunicate
by having shared state, like the iMessage thread on your phone. You could leave a note. You could, more dramatically,
lock the refrigerator somehow, thereby making the milk
purchasing process atomic. The fundamental problem is
that for efficiency, again, computers tend to
intermingle logic that needs to happen when it's happening across
multiple users just for fairness' sake, for scheduling sake. You need to make sure that all
three of these lines of code execute for me, and then
for Carter, and then for you if you want to ensure that
this count is correct. And for years, when social media
was first getting off the ground, this was a super hard problem. Twitter used to go down all
of the time, and tweets, and retweets were a thing
that were similarly happening with a very high frequency. These are hard problems to solve. And thankfully, there are solutions. And we won't get into the weeds
of how you might use these things, but know that there are
solutions in the form of things called locks, which I use that
word deliberately with the fridge. Software locks can allow you to
protect a variable so no one else can look at it until you're done with it. There are things called
transactions, which allow you to do the equivalent of
sending a message to, or really locking out your roommate from accessing
that same variable, too, but for slightly less amount of time. There are solutions to these problems. So for instance, in Python,
the same code now in green might look a little something like this. When you know that something
has to happen all at once, altogether, you first begin a
transaction, and you do your thing, and then you commit the
transaction at the very end. Here, too, though, there's
going to be a downside. Typically, the more you use
transactions in this way, potentially the higher
the probability is that you're going to box someone out or
make Carter's request a little slower. Why? Because we can't interact
at the same time. Or you might make his request
fail if he tries to update something that's already been updated. So you generally want to
have as few lines of code together in between these transactions
so that you get in and you get out. And you go to CVS and you get
back really fast so as to not cause these kind of performance things. So things indeed
escalated quickly today. The original goal was just to solve
problems using a different language more effectively than Python. But as soon as you have these
more powerful techniques, a whole new set of problems arises. Takes practice to get comfortable with. But ultimately, this is all leading
us toward the introduction next week of web programming with HTML,
CSS, and some JavaScript. The week after, bringing Python
and SQL back into the mix. So that by term's end,
we've really now used all of these different languages
for what they're best at. And over the next few weeks, the goal
is to make sure you're understanding and comfortable with what each of
these things is good and bad for. Let's go ahead and wrap here. I'll stick around for questions. We'll see you next time. [MUSIC PLAYING] SPEAKER 1: All right. This is CS50, and this
is already week 8. And if we think back to
the past several weeks now, recall that things started pretty
interestingly, pretty interactively, in like week 0, when
we were using Scratch, because with Scratch we had a
GUI, a graphical user interface. So even as we explored variables and
loops and conditionals and all of that, you had kind of a fun environment
in which to express those ideas. And then in week 1,
we sort of took a lot of that away, when we introduced C, and
a terminal window, and a command line, because now, all of your programs became
very textual, very keyboard-based, and gone was the mouse, the
animations, the menus, and so forth. And so now, fast
forward to week 8, we're going to bring those kinds of
user interface, UI, elements back, in the form of web programming. And this goes beyond
just laying out websites. This will, to this week and next week,
combine elements of the back-end server stuff that we've been doing
for the past several weeks, using Python, using
SQL, and now introducing a couple of other languages,
on the so-called client side, on your own Mac, your own PC,
your own phone, that's going to talk to those back-end services. So indeed, at this end of
CS50, does everything rather come together into a user interface
that's just super familiar. All of us are on our phones,
desktops, laptops, every day. And increasingly, even the mobile
apps that you all are using are implemented, not necessarily
in languages like Swift or Java, if you're familiar with
those, but with languages called HTML, CSS, and JavaScript,
which we'll focus on here today. But before we do that, let's provide a
foundation on which these apps can run, because indeed, we'll start to look
underneath the hood of how the internet itself works, albeit quickly, so that
we have kind of a mental model for where all of this code is running, how you
can troubleshoot issues, and how, really, ultimately, after
CS50, you can learn, by just poking around other actual websites. So the internet, we're all on it. Literally, right now, what
is it, in your own words? What is the internet? It's this utility nowadays, that
we all rather take for granted. How would you describe it? AUDIENCE: Big storage. SPEAKER 1: OK, big
storage, and indeed, that's how the cloud is described, which is
kind of an abstraction if you will, for a whole lot of wires
and cables and hardware. And the internet, other
formulations of the term, how else? AUDIENCE: Bunch of
data that we can reach. SPEAKER 1: OK, a bunch
of data that we can all reach, by way of being interconnected
somehow with wires or wirelessly. And so really, the internet,
too, is a hardware thing. There's a whole lot of servers out
there, that are somehow interconnected, via physical cables, via
internet service providers, via wireless connectivity, and the like. And once you start to have
networks of networks of networks, do you get the internet. Indeed, Harvard has its own network
and Yale has its own network, and your own home probably
has its own network. But once you start
connecting those networks, do you get the interconnected network
that is the internet as we now know it? So there's this whole
alphabet soup that goes with the internet, some of
whose acronyms and terms you've probably seen before. But let's at least peel
back some of those layers and consider what some of
the building blocks are. So here's a picture of the internet
before it was known as the internet, back in 1969, when it
was something called ARPANET, from the Advanced
Research Projects Agency. And the intent, originally, was just
to Interconnect a few universities here in Utah and California, literally
servers, or computers, in each of those areas, somehow
interconnected with wires, so that people could
start to share data. A year later, it expanded to
include MIT and Harvard and others. And now fast forward to
today, you have a huge number of systems around the world
that are on this same network. And, in fact, if I
just pull up a web page here, that's sort of
constantly changing, a visualization of the internet as
it might now be today, this here, in the abstract, all of these
lines and interconnections represent just how interconnected
the world is today. And it just means that there's all the
more servers, all the more cabling, all of the more hardware giving
us this underlying infrastructure. But if we focus, really, on just
these nodes, these individual dots, whether back in 1970, or now in 2021,
each of these dots you can think of as, yes, a server, but a certain type
of server, namely known as a router. And a router, as the
name implies, just routes data left to right, top to
bottom, from one point to another. And so there's all these servers here
on campus at Harvard, on Yale's campus, in Comcast's network, Verizon's
network, your own home network, you have your own routers out
there, whose purpose in life is to take in data and then
decide, should I send it this way, or this way, or this way, so
to speak, assuming there are multiple options with multiple cables. You, in your home, probably have just
one cable coming in or going out. But certainly, if you're a place like
Harvard or Yale or Comcast or the like, there's probably a whole
bunch of interconnections that the data can then
travel across ultimately. So how do we get data
among these routers? For instance, if you want
to send an email to someone at Stanford, in California,
from here, on the East Coast, or if you want to visit
www.stanford.edu, how does your laptop, your phone, your desktop, actually
get data from point A to point B? Well, essentially, your
laptop or phone knows when it boots up at the beginning of
the day, what the local router is, what the address of that local router is. So if you want to send an
email from my laptop over here, my laptop is essentially going to
hand it to the nearest Harvard router. And then, from there, I
don't know, I don't care how it gets the rest of the distance. But hopefully, within some
small number of steps later, Harvard's router is going to
send it to maybe Boston's router is going to send it to
California's router is going to send it to Stanford's router, until
finally it reaches Stanford's email server. And we can depict this, actually,
how about a bit playfully. Thankfully, the course's
staff kindly volunteered to create a visualization for
this, using a familiar technology. So here we have some of our TFs
and TAs and CAs present and past. Let me go ahead and full
screen this window here. Give me just a moment to
pull it up on my screen here. And we'll consider what happens if we
want to send a packet of information from one person or router,
namely Phyllis in this case, in the bottom right hand corner,
up to Brian, in this case, in the top left hand corner. So each of the staff members
here represents exactly one of these routers on the internet. [MUSIC PLAYING] [APPLAUSE] The applause is appreciated. It actually took us a
significant number of attempts to get that ultimately right. So when, what was it the
staff were all passing here? Here we have just, physically, what
it was the staff were passing around. So Phyllis started with an
envelope, inside of which was that email, presumably,
on the East Coast, and she wanted to send it to Brian on
the West Coast, top left hand corner. And so she had all of these different
options, different connections, between her and point B, namely Brian. She could go up, down, in her case, and
then each of those subsequent routers could go up, down, left, or right,
until it finally reaches Brian. And long story short,
there's algorithms that figure out how you decide
to send a packet up, down, left, or right, so to speak. But they do so by taking an input, and
in the form of input is this envelope. And there's at least a couple of
things on the outside of this, because all of these routers and,
in turn, all of our Macs and PCs and phones these days,
speak something called TCP/IP, a set of
acronyms you've probably seen somewhere on your
phone, your Mac or PC, in print somewhere, which refers
to two protocols, two conventions, that computers use to
inter-communicate these days. Now what's a protocol? A protocol is like a set
of rules, that you behave. In healthier times, I might
extend my hand and someone like Carter might extend his hand,
thereby interacting with me, based on a human protocol of like
literally physically shaking hands. Nowadays, we have mask protocols,
whereby what you need to do is wear a mask indoors. But that, too, is just a set of rules
that we all follow and adhere to, that's somewhere
standardized and documented. So computers use protocols
all the time to govern how they are sending information
and receiving information. And TCP and IP are two such protocols
that standardize this as follows. What TCP/IP tells someone
like Phyllis to do, if she wants to send an email to Brian,
is put the email in a virtual envelope, so to speak. But on the outside of that virtual
envelope, put Brian's unique address. And I'll describe this as destination
on the middle of the envelope, just like in our human world,
you would write the destination address on the envelope. And then she's going to put her own
source address in the top left hand corner, just like you, the
sender, would put your own source address in the human world. But, instead of these addresses
being like something Kirkland Street, Cambridge, Massachusetts 02138, USA,
you probably know that computers on the internet have unique addresses
of their own, known as IP addresses. And an IP address is
just a numeric identifier on the internet, that allows
computers, like Phyllis and Brian, to address these envelopes
to and from each other. And you've probably seen
the format at some point. Typically, the format of IP
addresses is something dot something dot something dot something. Each of those somethings,
represented here with a hash symbol, is a number from 0 through 255. And, based on that little
hint, if each of these hashes represents a number from 0
to 255, each of those hashes is represented with
how many bytes or bits? Eight bits or one byte, which is to
say, we can extrapolate from there, an IP address must use
32 bits or 4 bytes, if we rewind now to some of the
primitives we looked at in week 0. And what that means is,
at least at a glance, it looks like we have 4 billion some
odd IP addresses available to us. Now, unfortunately,
there's a huge number of humans in the world these days,
all of whom have, many of whom have multiple devices, certainly
in places like this, where you have a laptop, and a phone, and you have
other internet of things-type devices, all of which need to be addressed. So there's another type
of IP address that's starting to be used more commonly. This is version 4 of IP. There's also version 6
which, instead of 32 bits, uses 128 bits, which gives us a
crazy number of possible addresses for computers, so we can at least handle
all of the additional devices we now have today. So this is to say, what ultimately
is going on this envelope is the destination address, that is
Brian's IP address, and the source address, that is Phyllis's IP address,
so that this packet can go from point A to point B, and if need
be, back, by just flipping the source and the destination. But on the internet, you presumably know
that there's not just email servers. There's web servers, there's chat
servers, video servers, game servers. Like there's all of these different
functions on the internet nowadays. And so, when Brian
receives that envelope, how does he know it's an email, versus
a web page, versus a Skype call, versus something else altogether. Well, it turns out that we
can look at the other part of this acronym, the TCP in TCP/IP. And what TCP allows us
to do, for instance, is specify a couple of things. One, the type of service whose
data is in this envelope, that is, it does this with a numeric identifier. And I'm going to go ahead and write down
a colon, and the word port, P-O-R-T. And I'm going to write that in the
source address, too, colon and port. So technically, now,
what's on this envelope is not just the addresses,
but also a unique number that represents what kind of service
is being sent from point A to point B, whether it's email, or web traffic,
or Skype, or something else. These numbers are standardized, and here
are just two of the most common ones, not even in the context of email,
but in the context of the web. Port 80 is typically used
whenever an envelope contains a web page, or a request
therefor, or the number 443, when that request is actually
encrypted, using that thing you probably know, in URLs, known as HTTPS,
where the S literally means secure. More on what the HTTP means later. If it's email, the number
might be 25 or 465, or 587. These are the kinds of things you
Google if you ultimately care about. But if you've ever had to configure,
like, Outlook or even Gmail to talk to another account,
you might very well have seen these numbers, by typing
in something like SMTP.Gmail.com and then a number, which is only to
say these numbers are omnipresent. But they're typically
not things you and I have to care about, because
servers and computers nowadays automate much of this process. But that's all it takes, ultimately, for
Phyllis to get this message to Brian. But what if it's a really big message? If it's a short email, It might
fit perfectly in one single packet, so to speak. But suppose that Phyllis wants
to send Brian a picture of a cat, like this, or worse, a video of a cat. It would be kind of inequitable
if no one else could do anything on the internet, just
because Phyllis wants to send Brian a really big picture,
a really big video of a cat. It would be nice if we could kind
of time-share the interconnections, across these routers, so that
we can give a little bit of time to Phyllis, a little bit
of time to someone else, a little bit of time to someone else,
so that eventually, Phyllis' entire cat gets through the internet. But in terms of fairness, she
doesn't monopolize the bandwidth of the network in question. And this, then, allows us to
do one other feature of TCP/IP, which is fragmentation,
where we can temporarily, and Phyllis's computer would
do this automatically, fragment the big packet in question,
or the big file in question, and then use, not just a single
envelope, but maybe a second, a third, and a fourth, or more. If we do that, though,
we're probably going to need one other piece of information,
just logically, on these envelopes. Like, if you were implementing this,
chopping up this picture of a cat into four parts, like,
intuitively, what might you want to put virtually on the
outside of this envelope now? Yeah. AUDIENCE: The order. SPEAKER 1: The order of them, somehow. So probably something like part
one of four, part two of four, part three of four, and so forth. So I'm going to write one more thing
in like the memo line of the envelope here. I put some kind of
sequence number, that's just a little bit of a
clue to Brian, to know in what order to
reassemble these things. And even more powerfully
than that, this actually gives us this simple primitive of
just using INTs on these envelopes, in these packets. If Brian receives envelopes like these,
with numbers like these in the memo field, what other feature
does TCP apparently enable Brian and Phyllis to implement? This is a bit subtle. But it's not just the
ordering of the packets. What else might be useful about
putting numbers on these things, might you think? What might be useful here? Yeah, in back. AUDIENCE: How about if you like missed. SPEAKER 1: If you missed something
that was intended to be sent, if I heard that correct. So short answer, exactly, yes, TCP,
because of this simple little integer that we're including, can quote
unquote "guarantee" delivery. Why? Because if Brian receives one
out of four, two out of four, four out of four, but
not three out of four, he now knows, predictably, that
he needs to ask Phyllis, somehow, to resend that packet. And so this is why pretty much
always, if you receive an email, you either receive the whole
thing, or nothing at all. Like sentences and words and
paragraphs should never really be missing from an email. Or if you download a
photograph on the web, it shouldn't just have a
blank hole in the middle, just because that packet of
information happened to be lost. TCP, if it is the protocol being used to
transmit data from point A to point B, ensures that it either all gets there,
or ultimately, none of it at all. So this is an important property,
but, just as a teaser there's other protocols out there. There's something called UDP,
which is an alternative to TCP, that doesn't guarantee delivery. And just as a taste of why you might
ever not want to guarantee delivery, maybe you're watching like a streaming
video, like a sports event online. You probably don't
necessarily want the thing to buffer and buffer and buffer, just
because you have a slow connection, because you're going to
start to miss things. And then you're going to be the
only one in the world watching the game that ended 20 minutes ago, when
everyone else is sort of up to speed. Similarly for a voice call,
it would be really annoying if our voice is constantly buffered. So UDP might be a good
protocol for making sure that, even if the person on the other
end sounds a little crappy, at least you can hear them. It's not pausing and
resending and resending, because that would really slow down
that sort of human interaction. So, in short, IP handles the
addressing of these packets, and standardizes numbers that every
computer, your own included, gets, and TCP handles the standardization
of like what services can be used, between points A and
point B. All right, this is great, but presumably, when Phyllis
sends a message to Brian, she doesn't really know
and probably shouldn't care what his IP address is, right? These days it's, like, I don't
know most of the phone numbers that my friends have. I instead look them up in some way. And, indeed, when you visit a
website, what do you type in? It's typically not something
dot something dot something dot something, where each of
those somethings is a number. What do you typically
type in to a browser? So a domain name, right? Something like Stanford.edu,
Harvard.edu, Yale.edu, gmail.com, or any other such domain name. And so, thankfully,
there's another system on the internet, one more acronym for
today, called DNS, domain name system. And pretty much every network on the
internet, Harvard's, Yale's, Comcast's, your own home network, somewhere,
somehow has a DNS server. You probably didn't have
to configure it yourself. Someone else did, your campus, your
job, your internet service provider. But there is some server connected
somehow to the network you're on, via wires or wirelessly, that just
has a really big table in its memory, a big spreadsheet, if you
will, or, if you prefer, a hash table, that has at least
two columns of keys and values respectively. Where on the left hand
side is what we'll call domain name,
something like Harvard.edu, Yale.edu, an IP address on the
right hand side, that is to say, a DNS server's purpose in life
is just to translate domain names to IP addresses. And vice versa, if you want
to go in the other direction, and technically, just to be precise, it
translates fully qualified domain names to IP addresses. And we'll see what those
are in just a moment. But again, all of this just
kind of happens magically when you turn on your
phone or your laptop today, because all of these things
are pre-configured for us nowadays. So how can we actually start to
see some of these things in action? Well, let's go ahead and poke around,
for instance, at a couple of URLs here. Let's see what we can actually do
now with these basic primitives. If we now have the ability to
move data from point A to point B, and what can be in that envelope
could be, yes, an email, but today, onward, it's really
going to be web content. There's going to be content
that you're requesting, like give me today's home page. And there's content
you're sending, which would be the contents of
that actual home page. And so, just to go one level deeper,
now that we have these packets that are getting from point A to
point B using TCP/IP, let's put something specific inside of them,
not just an email and a bunch of text, but something called HTTP, which
stands for hypertext transfer protocol. You've seen this for
decades now, probably, in the form of URLs, so much so that you
probably don't even type it nowadays. Your browser just adds
it for you automatically, and you just type in Harvard.edu,
or Yale.edu, or the like. But HTTP is just a final
protocol that we'll talk about here, that just
standardizes how web browsers and web servers inter-communicate. So this is a distinction now
between the internet and the web. The internet is really like
the low-level plumbing, all of the cables, all of a
technology that just moves packets from left to right, right to left, top
to bottom, that gets data from point A to point B. You can do anything you
want on top of that internet nowadays, email and web and video and chat
and gaming, and all of that. So HTTP, or the web,
is just one application that is conceptually on top of,
built on top of the internet. Once you take for granted
that there is an internet, you can do really
interesting things with it, just like in our physical world,
once you have electricity, you can just assume you can do really
interesting things with that, too, without even knowing
or caring how it works. But now that you'll be
programming for the web, it's useful to understand how
some of these things indeed work. So let's take a peek at the
format of the things that go inside of these messages. These days, it's usually
actually HTTPS that's in play, where, again,
the S just means secure. More on that later, but the HTTP is
what standardizes what kinds of messages go inside of these envelopes. And wonderfully, it's just
textual information, typically. There is a simple text format
that humans decided on years ago, that goes inside of these
envelopes, that tells a browser how to request information from a server,
and how to respond from the server to that client with information. So here's, for instance, a canonical
URL, https://www.example.com. What might you see at the end of this? You might sometimes see a slash. Browsers nowadays kind of simplify
things and don't show it to you. But slash, as we'll see, just
represents like the default folder, the root of the
web server's hard drive, like whatever the base is of it. It's like C colon backslash on
Windows, or it's my computer on Mac OS. But a URL can have more than that. It can have slash path,
where path is just a word, or multiple words, that sort of
describe a longer part of the URL. That path could actually be
a specific file, we'll see, like something called file.html. More on HTML in just a bit, or
it can even be slash folder, maybe with another slash, or
maybe it can be /folder/file.html. Now these days Safari, and even Chrome
to some extent, and other browsers, are in the habit of trying to hide
more and more of these details from you and me. Ultimately, though, it'll
be useful to understand what URLs you're at, because
it maps directly to the code, that we're ultimately going to write. But this is only to say that
all this stuff in yellow refers to, presumably, a specific
file and/or folder on the web server, on which you're programming. All right, what's this? Example.com, this is the domain
name, as we described it earlier. Example.com is the
so-called domain name. This whole thing, www.example.com,
is the fully qualified domain name. And what the WW is referring
to is specifically the name of a specific server in that domain. So back in the day, there was
a www.example.com web server. There might have been a
mail.example.com mail server. There might have been a
chat.example.com chat server. Nowadays, this hostname, or
subdomain, depending on the context, can actually refer to a whole
bunch of servers, right? When you go to www.facebook.com,
that's not one server, that's thousands of servers nowadays. So long story short,
there's technology that somehow get your data
to one of those servers, but this whole thing is what we
meant by fully qualified domain name. This thing here, hostname,
in the context of an email address it might alternatively
be called a subdomain. This thing here, top
level domain, you probably know that dot com means commercial,
although anyone can buy it these days. Dot org is similar, dot net. Some of them are a bit restricted,
dot mil is just for the US military, dot edu is just for accredited
educational institutions. But there are hundreds, if
not more, top level domains nowadays, some more popular than others. CS50's tools, for instance, use CS50.io. IO sort of connotes input-output. It actually belongs, though, to
a small island nation, a country, whose country code is .io, and you see
other two letter top level domains that are country specific. Indeed, it's something.uk,
something.jp, and the like typically refer to countries. But some of them have been
rather co-opted, .tv as well, because they have these
meanings in English as well. Lastly, this is what
we'll call the protocol. That specifies how the server uses
this URL to get data from point A to point B. So what is
inside of this envelope? Let's now start poking
around a little bit more. What is inside of this envelope? It's essentially, for our
purposes today, one of two verbs, either GET or POST. And if any of you have dabbled
with HTML or made your own website, you might have seen some
of these terms before. But these two verbs describe
just how to send information from you to the server. Long story short, more
on this next week, GET means put any user input
in the URL, POST means hide it, so that things you're searching for,
credit card numbers you're typing in, usernames and passwords you're
inputting, don't show up in the URL, and are therefore visible
to anyone with access to your computer and
your search history, but rather they're somehow provided
elsewhere, deeper into that envelope. But for now, we'll focus
almost entirely on GET, which is perhaps the most common
one that we're always going to use. And what we're going to do is this. Let me switch over just
to a blank screen here. And if we assume that little
old me is this laptop here, and I'm connected to the cloud, and
in that cloud is some server that I want to request the web page
of, Harvard.edu or Yale.edu, it's really going to
be a two-step process. There's going to be a request,
that goes from point A to point B, and then, hopefully,
the server that hears that request is going to reply with
what we'll typically call a response. And other terms that are
relevant here, is my laptop is the so-called client,
Harvard.edu, Yale.edu, whatever it is, is the so-called server. And just like in a restaurant, where
you might request something to eat, the server might bring it to you. It's, again, that kind of
bidirectional relationship. One request, one response, for
each such web page we request. All right, so what's inside these
envelopes, and what do we actually see? Well, this arrow, this line I
just drew from left to right, representing the request, technically
looks a little more like this. When you visit a web
page, using your browser, on your phone, laptop, or desktop,
what's going inside that envelope, and the textual message your Mac or PC
or phone is automatically generating, looks a little something like this. The verb GET, the URL, or rather
the path that you want to get, slash represents the
default page on the website. HTTP/1.1 is just some mention of
what version of HTTP you're speaking. Now we're up to version 2, and
version 3, but 1.1 is quite common. And the envelope contains some
mention of the host that was typed in, the fully qualified domain name. This is because single servers can
actually host many different websites. If you're using Squarespace or Wix or
one of these popular hosting websites nowadays, you don't get your own
personal server, most likely. You're on the same server as
dozens, hundreds of other customers. But when your customers,
your users' browsers, include a little mention of your
specific, fully qualified domain name in the envelope,
Squarespace and Wix just know to send it to your web page or
my web page or some other customer altogether. Dot dot dot, there's
some other stuff there. But that's really the essence
of what's in these requests. Hopefully, then, when your browser
requests this web page from the server, what comes back? Well, hopefully, a response
that looks like this, HTTP/1.1, so the same version, some
status code, like a number 200, and then literally a short phrase like
OK, which means exactly that, like, OK, this request was satisfied. Then it contains some other
information, like the type of content that's coming back. And we'll see that this,
too, is standardized. Text/HTML means here comes some
HTML, which is just a text language. It could instead be image/jpeg
or Image/png, or video/mp4, there are these different content
types, otherwise known as MIME types, that uniquely identify types
of files, that come back, similar in spirit to file
extensions, but a little more standardized this way. Then there's some more
stuff, dot dot dot. But in general, what you see here, are
a familiar pattern, keys and values. These keys and values are
otherwise known as HTTP headers. And your browser has been sending
these every time you visit a website. And, indeed, we can see
this right now ourselves. Let me go over, in just a
second, to Chrome on my computer, though you can do this kind of
thing with most any browser today. I'll go ahead and visit
HTTP://Harvard.edu, Enter. And, voila, I'm at Harvard's
home page for today. The content often changes. But this is what it
looks like right now. Well, I typed in the URL, but
notice it changed a little bit. It actually sent me to
HTTPS and added the www, even though I didn't type that. But it turns out we can poke around
at what my browser is actually doing. Let me open another page. I'm going to start to use incognito
mode this time, not because I care that people know
I'm visiting Harvard.edu, but because it throws away
any history that I just did. So that every request is going
to look like a brand new one, and that's just useful
diagnostically, because we're always going to see fresh information. My browser is not going to remember
what I previously already requested. But I'm going to go
up to View, developer, developer tools, which is something
that all of you have, if you use Chrome. And there's something
analogous for Firefox and Edge and Safari and other browsers. Developer tools is going to
open up these tabs down here. I don't really care what's new, so I'm
going to close the bottom thing there. And I'm going to hover over the
Network tab for just a moment. And now I'm going to go and
say HTTP://Harvard.edu, so the shorter version. I'm going to hit Enter,
and a whole bunch of stuff just flew across the screen. And it's still coming in. And if I zoom in down here, my God,
visiting Harvard.edu, still going, is downloading, what 17, 18,
19 megabytes, 20 megabytes, millions of bytes of information,
over 111 HTTP requests. In other words, a bit of a
simplification, but my browser, unbeknownst to me, sent one
envelope initially with the request. Then the server said,
OK, by the way, there's 110 other things you need, 112
other things you need to get. So my computer went back and forth,
requesting even more content for me. Why? Well, inside of Harvard's web
page is a whole bunch of images, and maybe sound files and
videos and other stuff, that all need to be
downloaded and to compose what is ultimately the web page. But I don't care about like
100 plus of these things. Let's focus on the very first one first. The very first request
I sent was up here. And I'm going to click on this
row, under the Network tab. And then I'm going to see a
bit of diagnostic information. To an average person using the
web, they needn't care about this, just as you probably didn't
care about it until right now. And even then, perhaps not. But if I scroll down to
request headers, you will see, if I click View source, literally
everything that was in the request my Mac just sent to Harvard.edu. Two of the lines are familiar,
get/http1.1, host:harvard.edu, and then other stuff that, for now,
it's not that interesting for us. But let's look at the response
that came back from the server. I'm going to scroll up now and
see response headers, view source. And this is interesting. It is not OK. There's no 200, there's no word OK. Curiously, harvard.edu
has moved permanently. What does that mean? Well, there's a whole
bunch of stuff here that's not that interesting for us. But this line, location, is interesting. This is an HTTP header, a
standardized key value pair, that's part of the HTTP
protocol, that is, conventions. And if I highlight just
this one, it's telling me, mm-mmm, Harvard is not
at HTTP://Harvard.edu, Harvard's website is now, and perhaps
forever, at HTTPS://www.harvard.edu. So what's the value here? Probably someone at Harvard wants
you to use a secure connection. So they redirected you
from HTTP to HTTPS. Maybe the marketing people want you to
be at www instead of just Harvard.edu. Why? Just to standardize things,
but there are technical reasons to use a hostname, and not
just the raw domain name. And all this other stuff is sort
of uninteresting for our purposes, now, because a browser that
receives a 301 response knows, by design, by the definition of HTTP,
to automatically redirect the user. And that's why, in my browser, all of
this happened in like a split second, because I didn't really know or
care about all of those headers. But that's why and how I
ended up at this URL here. My browser was told to go
elsewhere via that new location. And the browser just
followed those breadcrumbs, if you will, at which point it
downloaded all of the other images and files, and so forth, that
compose this particular page. Well, let me zoom out. And let me actually go
into VS Code, if only because it's a little more pleasant to
do things in just a terminal window, without actually using
a full-fledged browser. So now let's just use
an equivalent program. It's called Curl, for
connecting to a URL, that's going to allow me to play with
websites and just see those headers, without bothering to download
all the images and text and so forth from the website. It's going to allow me to
do something like this. Let me go ahead and run, for instance,
Curl-I-xget, which is just the command line arguments that says
simulate a GET request textually, as though you're a browser. And let's go to
HTTP://Harvard.edu Enter. Now, by way of how Curl, works,
I'm just seeing the headers. It didn't bother downloading
the whole website. And you see exactly the same
thing, 301 moved permanently. Location is, indeed, this one here. So that's kind of interesting. But let's follow it manually now. Let's now do what it's telling me to do. Let's go to the location, with
HTTPS and the www and hit Enter. And now, what's a good
sign with this output? Most of it's irrelevant. AUDIENCE: Migrate? SPEAKER 1: 200 OK, that
means I'm seeing, presumably, if I were using a real browser,
the actual content of the web page. Looks like Harvard's version of HTTP
is even newer than the one I'm using. It's using HTTP version
2, which is fine. But 200 is indeed indicative
of things being OK. Well, what if I try
visiting some bogus URL, like Harvard.edu, when this file does
not exist, something completely random, probably doesn't exist, and hit Enter. What do you see now, that's perhaps
familiar, in the real world? Yeah. AUDIENCE: Error 404. SPEAKER 1: Yeah, error 404. All of us have seen
this probably endlessly, from time to time, when you
screw up by mis-typing a URL, or someone deletes the
web page in question. But all that is is a
status code that a browser is being sent from the server,
that's a little clue as to what the actual problem is,
underneath the hood. So instead of getting
back, for instance, something like OK, or moved permanently,
what I've just gotten back, quite simply, is 404 not found. Well, it turns out there's
other types of status codes that you'll start to see over time,
as you start to program for the web. 200 is OK. 301 is moved permanently. 302, 304, 307 are all similar in spirit. They're related to redirecting the
user from one place to another. 401, 403, unauthorized or forbidden. If you ever mess up
your password, or you try visiting a URL you're
not supposed to look at, you might see one of these
codes, indicating that you just don't have authorization for those. 404 not found, 418, I'm a
teapot, was an April Fool's joke by the tech community years ago. 500 is bad. And, unfortunately,
all of you are probably on a path now to creating
HTTP 500 errors, once, next week, we start writing
code, because all of us are going to screw up. We're going to have typos, logical
errors, and this is on the horizon, just like segfaults were in the world of
C, but solvable with the right skills. 503 service unavailable, means
maybe the server is overloaded, or something like that. And there's other codes there. But those are perhaps some
of the most common ones. Has anyone, we can get away with
this here, less so in New Haven, has anyone ever visited
SafetySchool.org? HTTP://SafetySchool.org,
dare we do this, Enter. Oh, look at that. Where did we end up? [LAUGHTER] OK, so-- [APPLAUSE] --so this has been like a
joke for like 10 or 20 years. Someone out there has been
paying for the domain name, safetyschool.org, just for
this two second demonstration. But we can now infer, how did this work? The person who bought that domain
name and somehow configured DNS to point to their web server,
the IP address of their web server, what is their web server
presumably spitting out, whenever a browser requests the page? What status code, perhaps? Well, we can simulate this. Let me go over to VS Code. Let me go back over here. Let me increase my terminal window. Let me do Curl-I-xget
HTTP://safetyschool.org Enter, and that's all this website does. There's not even an
actual website there. No HTML, no CSS languages
we're about to see. It literally just exists on the
internet to do that redirect there. In fairness, there are others. Let me actually do another one here. Instead of safetyschool.org,
turns out someone, some years ago, bought
HarvardSucks.org Enter. And when we do this, you'll see that,
oh, they don't need us to be secure, but I do need the www. Let's do this one, Enter. That one is not found. This demo actually
worked for so many years. But someone has stopped paying
for the Squarespace account recently, apparently. So-- [APPLAUSE] OK, so, fortunately, we
did save the YouTube video to which this thing refers. And so, just to put this
into context, since it's been quite a few years,
Harvard and Yale, of course, have this long-standing rivalry. There is this tradition
of pranking each other. And, honestly, hands down, one of the
best pranks ever done in this rivalry was by Yale to Harvard. It's about a three-minute retrospective. It's one of the earliest
videos, I dare say, on YouTube, so the quality is
representative of that. But let me go ahead and
full screen my page here. And what used to live at
HarvardSucks.org is this video here. If we could dim the lights
for about three minutes. [VIDEO PLAYBACK] [MUSIC PLAYING] [CHEERING] - Actually we're going
all the way to the top. And you pass it down. - This is for you, Yale. - We love you, Yale. - We're here to trip up Harvard. - Go Harvard! - Go Harvard! - Pass from the top one, pass it down. - Pass them down. - It's nice to say the ERA sucks. - Let's go Harvard. - Where does? - You see that [BEEP]? Where they're passing. It's going to have to happen. - It's actually going to happen. I can't [BEEP] believe this. - What do you think of Yale? - They don't think good. - Hah-hah. - Because they don't have it. Doesn't run out of stuff. - OK. - Is there another stuff? - Probably that's going
to be legible, very small. - Garbage. - I know, but-- - Well, what houses? - Says, are we in boats now? - How many extras? - How many extra are there? - Sometimes. - Yeah. [CHEERING] - OK. - You guys are from Harvard, right? - No, vote for. Full timer. - Yeah, these are '05s. - Just make sure everyone has one. - They're probably crummy. - We're still passing. - All the cards needed. - Oh, no. My bad. - Yeah. All right, cue it up. [CHEERING] Go, Harvard. [APPLAUSE] - Hold up your signs. [BEEP] - You suck. You suck. You suck. You suck. You suck. You suck. You suck. You suck. You suck. You suck. You suck. You suck. You suck. You suck. You suck. [SCREAMING] - What do you think of Yale, sir? - Going to be, do one more time. - One more time. One more time. [SCREAMING] - Oh, there it goes again. - Oh. - Harvard sucks. Harvard sucks. Harvard sucks. Harvard sucks. Harvard sucks. Harvard sucks. Harvard sucks. [END PLAYBACK] SPEAKER 1: All right, so thanks to
our friends at Yale for that one. Let's go ahead here and consider, in
just a moment, what further is deeper down inside of the
envelope, because we now have the ability to get data from,
oh, OK, YouTube autoplay again. Got to stop doing that. Let's consider for just a moment
that, let's consider for just a moment that we now have this ability to
get data from point A to point B. And we have the ability to
specify in those envelopes what it is we want from the website. We want to get the home page. We want to get back the HTML. But what is that HTML? In fact, we don't yet have the language
with which the web pages themselves are written, namely HTML and CSS. But let's go ahead and take
a five minute break here. And when we come back, we'll
learn those two languages. All right, we are back. So we've got three
languages to look at today. But two of them are not
actually programming languages. What makes something a programming
language, like C or Python and SQL, is that there are these constructs via
which you can express conditionals. You might have variables, you
might have looping constructs. You have the ability,
ultimately, to express logic. HTML and CSS aren't so much about
logic as they are about structure, and the aesthetics of a page. And so we're going to create
the skeleton of a web page using this pair of languages, HTML and CSS. And then toward the
end of the today, we'll introduce an actual
programming language, that actually is pretty similar
in spirit and syntactically to both C and Python, but that's going
to allow us to make these web pages not just static, things that you look at,
but interactive applications as well. And then next week again, in week 9,
will we reintroduce Python and SQL, tie all of this together, so that you
can actually have a browser or a phone talking to a back-end
server, and creating the experience that you and I now take
for granted for most any app or website today. Well, let's go ahead and do this. Let's quickly whip up something
in this language called HTML. I'm in VS Code here. I'm going to go ahead and create a
file quite simply called, Hello.html. The convention is typically to
end your file names in dot html. And I'm going to go ahead
and bang this out real quick. But then we'll more slowly step
through what the constructs are herein. So I'm going to say doctype
html open bracket html, and then notice I'm going to do open
bracket slash html close bracket. And I'm leveraging a feature of VS
Code and programming environments more generally, to do a bit of autocomplete. So you'll see that there's this symmetry
to much of what I'm going to type, but I'm not typing all of these things. VS Code is automatically generating the
end of my thought for me, if you will. Let me go ahead and
say, Open the head tag. Open the title tag. I'll say something cute
like, Hello, title. And then down here, I'm going to
create the body of this web page and say something like Hello, body. And let me specify at the very top,
that all of this is really in English, Lang equals en. So at this moment, I have a file in my
VS Code environment called Hello.html. VS Code as we're using it,
of course, is cloud-based. We're using it in a browser,
even though you can also download it and run it on a Mac and PC. So we are in this weird
situation where I'm using the cloud to
create a web page, and I want that web page to also live in
the cloud, that is, on the internet. But the thing about VS
Code, or really any website that you might use in a browser, by
default that website is using probably TCP port number 80 or
TCP port number 443, which is HTTP and HTTPS respectively. But here I am, sort of
a programmer myself, trying to create my own
website on an existing website. So it's a bit of a weird situation. But that's OK, because
what's nice about TCP is that you and I can just pick port
numbers to use and run our own web server, on a web server. That is, we can control
the environment entirely, by just running our own web server
via this command, HTTP-server, in my terminal window. This is a command that we
preinstalled in VS Code here. And you'll notice a pop-up just came up. Your application running
on port 8080 is available. That's a commonly used TCP port
number, when 80 is already used, and 443 is already used,
you can run your own server on your own port, 8080 in this case. I've opened that tab in advance, and
if I go into another browser tab here, here I see a so-called directory
listing of the web server I'm running. So I don't see any of my other files. I don't see anything
belonging to VS Code itself. I only see the file that I've created
in my current directory, called Hello.html. And so if I click on this file
now, I should see Hello, body. I don't see the title. But that's because the
title of a web page nowadays is typically
embedded in the tab. And if I'm full screen in my
browser, there are no tabs. So let me minimize the window a bit. And now you can see just in this
single browser window, in my own URL here, that Hello, body, is
in the top left hand corner. And if I zoom in, there's Hello, title. So what have I done here? I have gone ahead and created
my own web page in HTML, in a file called Hello.html. And then I have opened up
a web server of my own, configured it to listen
on TCP port 8080, which just says to the internet, hey,
listen for requests from web browsers, not on the standard port number,
80 or 443, listen on 8080. And this means I can develop a website
using a web-based tool, like this one here, which is
increasingly common today. All right, so now let's consider
what it is I actually just typed out. HTML is characterized really by just
two features, two vocab words, tags and attributes. Most of what I just typed were tags,
but there was at least one attribute already. Here's the same source code that I
typed out in HTML, from top to bottom. Let's consider what this is. The very first line of
code here, doctype html, is the only anomalous one. It's the only one that starts with
an open bracket, a less than sign, and an exclamation point. There's no more exclamation
points thereafter, for now. This is the document type declaration,
which is a fancy way of saying, it's just got to be there nowadays. It's like a little breadcrumb
at the beginning of a file that says to the browser, you are about to
see a file written in HTML version 5. That line of code has changed
over time, over the years. The most recent version of it
is nice and succinct like this, and it's just a clue to the
browser as to what version of HTML is being used by you, the programmer. All right, what comes after that? Well, after that, and I've
highlighted two things in yellow, this is what we're going to
start calling an open tag, or a start tag, open bracket HTML
then something, close bracket, is the so-called start or open tag. Then the corresponding close
or end tag is down here. And it's almost the same. You use the same tag number, you
use the same angled brackets. But you do add a slash, and
you don't repeat yourself with any of the things
called attributes, because, what is this thing here? Lang equals quote unquote
"en," means the language of my page is written
in the English language. The humans have standardized
two and three letter codes for every human language, right now. And so this is just a clue to the
browser for like automatic translation and accessibility purposes,
what language the web page itself is written in. Not the tags, but the words, like
Hello, title and Hello, body, which while minimalist,
are indeed in English. So when you close a tag, you close
the name of it with the slash and the angle brackets. You don't repeat the attribute. That would just be annoying to
have to type everything again. But notice the pattern here. It's new syntax. But this is another example of
key value pairs in computing. The key is Lang, the
value is E-N for English. The attribute is called
Lang, the value is called, it is E-N. So again,
it's just key value pairs, in just yet another context. Probably the browser's using a
hash table underneath the hood, to keep track of this stuff, like a
two column table, with keys and values. Again, humans keep using the same
paradigm in different languages. What's inside of that? The nesting is important
visually, not to the computer, but to us, the humans,
because it implies that there's some hierarchy here. And, indeed, what is inside
of the HTML tag here? Well, we have what
we'll call the head tag. The head tag says, hey, browser,
here comes the head of the page. And then the body tag
says, hey, browser, here comes the body of the page. The body is like 99% of the user's
experience, the big rectangular window. The head is really just the address
bar and other such stuff at top, like the title that we saw a moment ago. Just to introduce the vernacular,
then, the HTML tag, otherwise known as an element, has two children,
the head child and the body child, which is to say that head
and body are now siblings. So you can use the same kind of
family tree terminology that we used, when talking about trees, weeks ago. If we look at the head tag, how
many children does it seem to have? I'm seeing one, and,
indeed, at least if we ignore all the white space, the
spaces or tabs or new line characters, there's just one child, a title element. And an element is the terminology that
includes the start tag and the end tag, and everything in between. So this is the title element. And the title element has one
child, which is just pure text, otherwise known as a text node. Recall, node, from our discussions
of data structures weeks ago. If we jump then to the body, which
is the other child of the HTML tag, it too has one child, which is just
another chunk of text, a text node, that says, quote unquote "Hello, body." What's nice about this indentation,
even though the browser technically is not going to care, is that it
implies this kind of structure. And this is where we connect,
like weeks 5 and now weeks 8, here is the tree structure we began to
talk about, even in our world of C. It's not a binary tree,
even though this one happens to have no more than two children. It's an arbitrary tree that can
have 0 or any number of children. But if we have a special node
here that refers to the document, the root node, so to speak, is
HTML, drawn with a rectangle here, just for discussion's sake. It has two children, head
and body, also rectangles. Head has a title child,
and then it and body have text nodes, which I've
drawn with ovals instead. Which is only to say that when your
browser, Chrome, Safari, whatever, downloads a web page,
opens up that envelope, and sees the contents that
have come back from the server, it essentially reads the
code that someone wrote, the HTML code, top to
bottom, left to right, and creates in the browser's
memory, in your Mac or your PC or your phone's memory or RAM,
this kind of data structure. That's what's going on
underneath the hood. And that's why aesthetically,
it's just nice, as a human, to indent things stylistically,
because it's very clear then to you, and to other programmers,
what the structure actually is. So that's it for like
the fundamentals of HTML. We'll see a bunch of tags
and a bunch of examples now. But HTML is just tags and attributes. And it's the kind of thing that
you look them up when you need to. Eventually, many of them get ingrained. I constantly check the reference
guides or stack overflow if I'm trying to figure out,
how do I lay something out. It's really just these
building blocks that allow you to assemble the
structure of a web page. This one is being super simple,
but it's just tags and attributes. Any questions on this
framework, before we start to add more tags, more
vocabulary, if you will? In the middle, yeah. AUDIENCE: What would happen
if we put the title tag? SPEAKER 1: If we put the hello tag
around body, that's a good question. Let's try it. So let me actually go to this,
and say open bracket title, whoops, sometimes you don't want
it to finish your thought for you. But it did that time. I've gone ahead and changed the file. Let me go and open up, give me a
second to open my terminal window, and go back to the URL that has my page. Give me a second. There's my Hello.html. Let me zoom in on this. Let me zoom in on this. And let me go ahead now
and click on Hello.html. And in this case, it looks like
we don't actually see anything. So the browser is hiding it. Technically speaking, browsers
tend to be pretty generous. And half the time, when
you make mistakes in HTML, it will display, it might display-- not display as you intend it. It might not display the same on
Macs or PCs or Chrome or on Firefox. There is a tool, though,
that we'll see, that can help answer this question for you. For instance, if I go
to Validator.w3.org, W3 is the World Wide
Web Consortium, a group of people that standardize
this kind of stuff, I can click on Validate
by direct input, and just copy paste my sample HTML into
this box, and click Check. And I should see,
hopefully, that indeed, it's an error, what you proposed that I do. The browser just did its
best to do something, which was to show me nothing at least,
rather than the incorrect information. But if I revert that change, and
let me undo what we just did, let me copy my original code back
into this text box, and click Check, now you can see, conversely,
my code is now correct. And there's automated
tools to check that. But we'll encourage you, for
problem sets and projects, to use that particular manual tool. All right, so let's go ahead
and enhance this a little bit by introducing a whole
bunch of tags, just to give you a sense of some
of the building blocks here. So I'm going to go ahead and create
a new file called Paragraphs.html. And I'm just going to do a bunch of
copy/paste just to start things off, so I'm not constantly typing all
this darn stuff again and again, because I want everything
to be the same here, except I'm going to change my title
to be Paragraphs for this demo. And inside of the body, I need a
whole bunch of paragraphs of text. And I don't really want
to come up with some text. So let me go to some random website
here and grab lorem ipsum text, which if you're involved in like
student newspaper or just design, this is placeholder text, kind of looks
like Latin, but technically isn't. Here, though, I have a handy way of
just getting three long paragraphs in something that looks like Latin. And I've put those,
notice, inside of the body. And they're indeed long. Look how long the
made-up words here are. So let me go now into
my browser tab here. Let me reload this page, and you'll
see two files have now appeared, Paragraphs.html, which is
my new one, and Hello.html. Let me click on Paragraphs.html,
and what clearly seems to be wrong? Yeah. AUDIENCE: One paragraph. SPEAKER 1: Yeah, it's obviously one
massive paragraph, instead of three. So that's interesting, but it's just a
little hint as to how pedantic HTML is. It will only do what you say. And each of these tags tells the
browser to start doing something, and then maybe stop doing something,
like, hey, browser, here comes my HTML. Hey, browser, here comes
the head of my page. Hey, browser, here comes the
title of my page, Hello, title. Hey, browser, that's it for the title. That's it for the head,
here comes the body tag. So it's kind of having this
conversation between the browser, between the HTML and the browser,
doing literally what it says. So if you want a
paragraph, you're probably going to want to use
the P tag for paragraph. And I'm going to go ahead
and add this to my code. I'm going to keep things neat,
even though the browser won't care, by indenting things here. Let me create another paragraph
tag here, and close it right after that one,
indenting again, and I'm keeping everything nice and orderly. Let me do one more here. Let me indent that, and then let me
add it to the end of my page here. So again, a little tedious, but now I
have three paragraphs of text that say, hey, browser, start a paragraph. Hey, browser, stop that paragraph. Start, stop, and so forth. Let me go back to the
browser window here. Let me hit Command R or
Control R to reload the page. And voila, now I have three
cleaner paragraphs, all right? So there's a P tag for paragraphs. So now we have that
particular building block. What if I want to add, for instance,
some headings to this page? Well, that's something
that's possible, too. Let me go ahead and create a
new file called Headings.html. Let me copy and paste
that same code as before. But now, let's preface each
paragraph with maybe H1. And I'm going to just
write the word one. And here I'm going to say H2, two. And down here I might say H3, three. So this is another tag,
another three tags, H1, H2, H3. As you might have inferred
by the file name I chose, this just gives you headings, like in
a book, different chapters or sections or subsections, or in
an academic paper, you have different hierarchies to
the text that you're writing. So now that I've added an H1 tag,
and the word one, H2 tag, the word two, H3 tag and the word three,
let's go back to the browser, reload the page again,
and voila, once the page reloads, I'll do it with the
manual button, reload the page. Oh, what am I doing wrong? Yeah. AUDIENCE: Not in headings file. SPEAKER 1: Right, I'm
not in the headings file. So let me go back a page. Now there's Headings.html. Let me click on that. OK, now we see some evidence of this. Again, it's nonsensical content. But you can kind of see that
H1 is apparently big and bold, H2 is slightly less big, but still bold. H3 is the same but a little smaller. And it goes all the way down to H6. After that, you should probably
reorganize your thoughts. But there are six
different hierarchies here, as you might use for chapters, sections,
subsections, and so forth, all right? So those are headings, as an
HTML tag, in our vocabulary. What's a common thing, too, well, let
me go to VS Code again, let me go ahead and get some boilerplate here,
create a file called List.html. Let's create a simple
list inside of my body, and I'll give this a title of List. And let me fix the title of this
one to be Headings, as well. So in List.html, suppose I want to have
a list of things, foo, bar, and baths, they're like a computer
scientist's go-to words, just like a mathematician might say xyz. Foo, bar, baths is in List.html. Let me go back to my
browser, hit the Back button. There's List.html, and, hopefully,
I'll see foo, bar, and baths, one on each line like a nice little
list, but, of course, I do not. And this is not English. Chrome thinks it might be Arabic. But that's curious, too,
because the Lang attribute should be overriding that. So Google is trying to override it. All right, what's the
obvious explanation why we're seeing foo, bar, and
baths on the same line, and not three separate ones? AUDIENCE: We didn't tell it. SPEAKER 1: We didn't tell it to do that. So we need paragraph tags,
or maybe something else. Turns out there is something else. There is a UL tag, for an
unordered list in HTML, inside of which you can
have LI tags, for list item, inside of which you can put your words. So there's my foo, there's
my bar, there's my baths. And, again, notice that VS Code
is finishing my thought for me. But notice the hierarchy, open
UL, open LI, close LI, open LI, close LI, open LI, close LI, close UL. So it's sort of done
in reverse order here. Let me go back to my browser, reload
the same page, List.html, and voila, a default bulleted list, that
still seems to be in Arabic. What if I want this list to be numbered? Well, you can probably guess. If you don't want an unordered list, but
an ordered list, what tag should I use? AUDIENCE: OL. SPEAKER 1: OL, sure, so let's try that. Not always that easy as just
guessing, but in this case, OL is going to do the trick. Let me go back to my other browser. Let me reload the page, and now it's
going to automatically number for me. It's a tiny thing, but
this is actually useful if you have a very long
list of data, and maybe you might add some things in the
middle, the beginning, or the end. It would just be annoying to
have to go and renumber it. The computer is doing it
for us by, instead, just numbering from top to bottom here. All right, what about
another type of layout, not just paragraphs, not just
lists, but what about tabular data? You've got some research
data you want to present, some financial data you want to present,
a phone book that you want to present. How might we go about laying
out data, a la a table? Well, let me create a
file called Table.html, and I'll just copy paste
where we started earlier. Let me start to close
some of these other files. And in Table.html, this is
going to be a bit more HTML, but I'm going to go ahead and do this. Table and close table, tables
can have table headings. So T head is the name of that tag, and
tables can have T bodies, table bodies. So I'm going to add that tag. And this is a common technique,
sort of start your thought, finish your thought, and then go
back and fill in what's in between. What do I want to put in this table? How about a bunch of names and numbers. So, for instance, like left
column name, right column number. So let's create a table row,
with what's called the TR tag. Let's create a table heading with
the TH tag, and let's say name here. Let's create another table
heading called number here. And all of that, to be
clear, is in one table row. Meanwhile, in the table body,
let me create another table row, but this time, it's not a heading. Now I'm in the guts of my table. Let's do table data, which is synonymous
with like the cell of the table, in like an Excel spreadsheet
or Google spreadsheet. In this TD, I'm going to
say like Carter's name, and then lets grab Carter's number
from our past demo, 617-495-1000. Then let's put me into the mix, and
I'll go ahead and copy paste here, which often is not good. But we'll see that there's a lot
of shared structure with HTML. Let me go ahead and do mine,
949-468-2750, and now save this page. So we're getting to be
a lot of indentation. I'm using four spaces by default.
Some people use two spaces by default. So long as you're consistent,
that's considered good style. But let me go back to my
browser here, and hit back. That then brings me to my
directory listing again. Here's Table.html, and this
is not that interesting yet. But you can see that there's
two columns, name and number. Because it's a table heading, TH,
the browser made it boldfaced for me. In there, in the table, are two
rows below that, Carter and David. It's a little, oh, I forgot my
number one, sorry about that. One and one, it's not the
prettiest table, right? I feel like I kind of
want to separate things a little more, maybe put
some borders or the like. But with HTML alone, I'm really
focusing on the structure alone. So we'll make this prettier soon. But for now, this is how you
might lay out tabular data. All right, let me pause here just
to see if there's any questions. But, again, the goal right now
is just to kind of throw at you some basic building blocks, that, again,
can be easily looked up in a reference. But we're going to start
stylizing these things soon, too. Yeah, in the middle. AUDIENCE: How to indent? SPEAKER 1: How do you indent paragraphs? Really good question. For that, we'll probably
going to want something called CSS, Cascading Style Sheets. So let me come back to
that, in just a little bit. For the stylization of these things,
beyond the basics, like big and bold, we're going to need a
different language altogether. All right, well, let's
now create what the web is full of, which is like
photographs and images and the like. Let me go ahead and create a new file
called Image.html, and let me go ahead and change the title
here to be, say, Image. And then, in the body of this page,
let's go ahead and put an image. The interesting thing about an
image is that it's actually not going to have a start tag and an end
tag, because that's kind of illogical. Like, how can you start an image
and then eventually finish it? It's either there or it isn't. So some tags do not have end tags. So let me do image, IMG,
source equals Harvard.jpeg. And let me go ahead, and,
in my terminal window, I actually came with a photo of Harvard. Let me grab this for just a second. Let me grab Harvard.jpeg and
put it into my directory, pretend that I downloaded
that in advance. And so I'm referring
to now a file called Harvard.jpeg, that apparently is in
the same folder as my Image.html file. If this image were on the
internet, like Harvard server, I could also say like
HTTPS://www.Harvard.edu/FolderName, whatever it is, /Harvard.jpeg, but
if you've in advance uploaded a file to your own, the Scode environment,
like I did before class, by dragging and dropping this
whole file, this photo of Harvard, you can just refer to it
relatively, so to speak. This would be the same thing
as saying ./Harvard.jpeg, go to the current directory and
get the file called Harvard.jpeg. But that's unnecessary to type. For accessibility purposes, though,
for someone who's vision-impaired, it's ideal if we also give this
an alternative text, something like Harvard University,
in the so-called Alt tag, and this is so that
screen readers will recite what it is the photo is,
for folks who can't see it. And if you're just on a slow
connection, sometimes you'll see the text of what
you're about to see, before the image itself downloads,
especially on a mobile device. So let's now go back to my open browser
tab, and let's look in the directory. I now have Harvard.jpeg, which I
downloaded in advance, and Image.html. Let me click on Image.html,
and here we have a really big picture of Memorial
Hall, the building we're currently in. Suffice it to say I should probably fix
this and maybe make it only so wide. But to do that, we're going to probably
want to use this other language, CSS. There are some historical
attributes that you can still use to control width and
height, and so forth. But we're going to do it
the better way, so to speak, with a language designed for just that. How about a video, though. I also came prepared with,
let me grab another file here, let me grab a file called
Halloween.mp4, which is an MPEG file. And let me go ahead and change this
now to be a file called Video.html. I'll change my title to be Video. And let's go ahead and now
introduce another tag, a video tag, open bracket video, and then let me go
ahead and close that tag proactively. And then inside of the video tag,
you can say the source of the video is going to be specifically
Halloween.mp4, the type of this file, I know, is Video/mp4, because I looked
up its content type or MIME type. And the video tag actually
has a few attributes. I can have this thing autoplay. I can have it loop forever. I can mute it, so that there's no
sound, which is necessary nowadays. Most browsers, to prevent ads, don't
autoplay videos, if they have sound. So if you mute your video, it
will autoplay, but presumably not annoy users. And let me set the width of this thing
to be like, oh, 1280 pixels wide. But I can make it any size I want. So I know this just from having
looked up the syntax for this tag. But notice one curiosity. Sometimes attributes don't have values. They're empty attributes. They're just single words,
autoplay, loop, muted, and that kind of makes
sense for any attribute that really does what it says. Like, it doesn't make sense
to say muted equals something. Like it's either muted or not. The attribute is there or not. Similarly, for these others, as well. So let me go back to my other browser
tab, reload the directory listing. There is both my mp4
and also Video.html, which is the web page that embeds it. And this is actually a video that was
just on Harvard's website yesterday, and it was amazing. So we included it in this demo here. This is the video that was on
Harvard.edu last night, same photo. But you can see here that
an image alone probably would not have the same effect. This is actually a movie, a small
video file that's now looping. Now there's some artifacts here, like
there's a white border around the top. I feel like it'd be
nice to fill the screen. But again, we'll come back to a language
that can allow us to do exactly that. Well, it's not just
videos like this, that you might want to put into a web page. Let me create another
file called iFrame.html. If you've ever poked around with, if
you have your own YouTube account, or if you had your own blog or
WordPress site, or Wix or Squarespace, you might have been in the
habit of embedding videos in websites, using like
embedded YouTube players. Well, this is possible, too, using
what's called an inline frame, an iFrame. And an iFrame is just a tag
that is literally iFrame. It has source equals,
and then a URL, and if it happens to be a YouTube video, there's
a certain URL format you need to follow, per YouTube's documentation. So you might do www.youtube.com, embed,
and then here's an ID of a video. So this is essentially what we do, if
we want to embed CS50's own lecture videos, in the course's website, or
the video player does literally this. If I want to allow full screen,
I can add this attribute, too, that I know exists, by just
having checked the documentation. And if I now go back to my browser
here, reload my directory listing, there's iFrame.html. It's not going to fill
the screen, because I haven't customized the aesthetics yet. But it does seem to embed a tiny little
video there for you to play with later, if you'd like. So we could change the width, change
the height, get rid of that margin, and so forth. But an iFrame is a way of embedding
someone else's web page in your web page, if they allow
it, so as to create all the more of an interactive experience
for them on, say, your site. All right, well, the web is, of
course, known for things like links. Let's go ahead and create
a file called Link.html. And if we want to create a web page that
actually links from itself somewhere else, let's go ahead and do this,
something very simple like visit Harvard.edu period. Now, in like Facebook, Instagram, a lot
of websites nowadays, if you just type in a domain name, or a
fully qualified domain name, it automatically becomes a link. That's because those websites have
code in them that automatically detects something that looks like a
URL, and turns it into a proper link. HTML itself does not do that for you. And so if I go back to my web
page here, click on Link.html, if you type visit
Harvard.edu period, that's all you're literally going to see. But instinctively, even if you've
never written HTML before, what should we probably do here
to solve this problem? What could we do to solve this problem. What do I probably want to add. Yeah. AUDIENCE: Surround your-- SPEAKER 1: Yeah, so I want to surround
the URL with some kind of link text. And you wouldn't necessarily
know this until someone told you, or you looked it up, but the tag for
creating a link is somewhat weirdly called the A tag for anchor. It has an attribute called
HREF for hyper-reference, which is like a link in
the virtual world to a URL. So let me type in Harvard's
full and proper URL here. Then I'm going to close the tag. And then I can still say Harvard.edu,
and make that what the human sees. But the place they're going to go
should be a full URL protocol and all, HTTP or HTTPS, and all. Now if I go back here
and reload the page, now it automatically gets underlined. It happens to be purple by default. Why? Because we visited
Harvard.edu a few minutes ago. So my browser, by default, is indicating
in purple that I've been there before. But now I have a link
that I can click on, and if I hover over it but don't click,
you'll see that, in most browsers, there's a little clue as to where
you will go if you click subsequently on this link. And without going too
far down a rabbit hole, but to tie together our discussion
of cybersecurity recently, what if I were to do
something like this. Right now you have the beginnings
of a phishing attack of sorts, P-H-I-S-H-I-N-G, whereby you can
create clearly a web page, or, heck, even an email using HTML, that tells
the user they're going to go one place, but they're really going to
go someplace else altogether. And that is the essence of
phishing attacks these days. If you've ever gotten a bogus
email pretending to be from PayPal or your bank or some
other website, odds are they've just written HTML
that says whatever they want, but the underlying tags might
do something very different. And so having the instinct to look
in the bottom left hand corner, or be a little suspicious when you're
just told blindly to click on a link, it's this easy to socially
engineer people, that is, deceive them, by just saying one
thing and linking to another. Well, what if I want to link my page
to another page I already created? Well, if I want to link
to that photo of Harvard, I can just do HREF = equals quote
unquote and the name of a file, in my same account, that
is itself a web page. So this is how you can create
relative links, multi-page web pages, multi-page websites, yourself. So if I now reload this
page, hover over Harvard.edu, you'll see in the bottom left
hand corner a very long URL. But that's because I'm in code
spaces right now, VS Code, and it's appending automatically
to the end of my current URL the file name, Image.html. But this should work. When I click on this, I
go immediately to that file we created earlier, with a
crazy, big version of the image. But that's just a way
that one page on a website can link to another page on a website. Let's do one other thing here,
making things more responsive, because, in fact, that wasn't a
particularly responsive website. Responsive means responding to the
size of the user's device, which is so important when someone
might be on a screen like this, or on a screen like this these days. There are special tags we can use to
tell the browser to modify its display, based on the hardware. So let me create a file
called Responsive.html. I'm going to copy/paste some starting
point here, call this title Responsive. And let me go ahead and just grab, let
me grab some of that lorem ipsum text from before, just so that we have a
sizable paragraph to play with here. And let me go ahead and
grab this text here. And I'm just going to paste
this into the body of this page. And that's it. So I just have a big paragraph,
at the moment, inside of my body. Let me go back to my browser. Let me open up this file,
called Responsive.html, to make the point that
it is not yet responsive. Let me go ahead and
click on Responsive.html. That looks fine. But here's another trick you can do,
using Chrome or Edge or other browsers these days. You can pretend to be another device. Let me go to View, developer,
developer tools again. Last time we used this to
use the Network tab, which was kind of interesting, because we
could see what the underlying network traffic is. But notice, we can also click on
this icon, in Chrome, at least, that looks like a mobile phone. I can turn my laptop into what looks
like a mobile device by clicking this. I'm going to click the dot dot dot
menu over here, and just move the dock. Instead of on the bottom,
where it might be by default, I'm going to move it
to the right hand side. So that now on the left,
you see what looks more like the shape of a vertical phone. And, in fact, if I go
to my dimensions here, I'll choose something like
iPhone X, so a few years back. Here's what that same website might
look like on an iPhone X. You know, that looks pretty damn
small, to be able to read it. And that's because the
website has not automatically responded to the fairly narrow
dimensions of the iPhone in question, or Android
device, or whatnot. So let me go ahead and do this. Let me go back into my code. And let me go into the head of
the page, and for the first time, add another tag up here. This word is now all over
the internet, but there is a metatag that is
called, that allows you to specify the name of some kind
of configuration detail here, or property, if you will. Viewport is the technical term
for the rectangular region that the human sees in a browser. It's essentially the body of the
page. but only the part the human is currently seeing. And you can specify the
content of the viewport should have an initial scale of 1. So it shouldn't be zoomed in or out. And the width that the
browser should assume should be equal to the device's width. These are sort of magical statements
that you just have to know or copy/paste or transcribe, that
just express, to the browser, assume that the width of the page is the
same thing as the width of the device. Don't assume the luxury of a
big laptop or desktop computer. Now, making only that change, let
me go back to my pretend iPhone here, using Chrome's developer tools. Let me reload the page. And now, it's not very effective on this
screen, if I were showing you this on, is there-- well, there we go. Let's do this. There we go. So if I zoom in to 100%, this would be
on an actual physical device, much more readable than it would
have been a moment ago, even though I realized that demo
was not necessarily persuasive. But it's as simple as
telling the browser to resize the thing to
the width of the page. All right, let me pause here to see
if there's any questions, because that feels like enough HTML tags. We'll add just a couple of more in. But for the most part,
like HTML tags are things you Google and figure out over
time, just to build up your vocabulary. The basic building blocks
are tags, attributes. Some attributes have values. Some do not. And that's sort of the
structure of HTML in essence. Questions on any of these, though. Yeah. AUDIENCE: Do attributes have an order? SPEAKER 1: Do attributes have an order? No, attributes can be in any
order, from left to right. I tend to be a little nit-picky,
and so I alphabetize them, if only because then I can easily
spot if something's missing, if it's not there alphabetically. Most people on the internet
don't seem to do that. Yeah, in the middle. Version. Yeah, good question. I mentioned that HTML
is starting to replace other languages for user interfaces. And it's not just HTML alone. It's HTML with CSS, with JavaScript,
both of which we'll get a taste of here today. That rather has been the
trend for portability, and the ability for companies,
for individual programmers, to write one version
of an app and have it work on Android devices and iPhones
and Macs and PCs, and the like. It is very expensive. It is very time-consuming to
learn a language like Java and write an Android app, learn
another language called Swift and make an iOS app, not to
mention make them look and behave the same, not to
mention fix a bug in one and then remember to
fix it in the other. I mean, this is just very painful
and time-consuming and costly. So this standardization on
HTML, CSS, and JavaScript, even for mobile apps and web apps,
has been increasingly compelling, because it solves problems like that. All right, so let's go ahead and now do
something that's finally interactive. All of these pages thus
far are really just tastes of static content, content
that does not change. Well, let's go ahead and do this. Let me introduce one other
format of URLs, which looks a little something like it did before. So slash path, but it could
actually be something like this, slash path question
mark, key equals value. You might not have noticed,
or cared to notice, the URLs in your URL bar every day. But these things are everywhere. Often when you type into a
search engine like Google a search query, whatever you
just typed ends up in the URL. When you click on a link that
contains some information, there might be a question mark,
and then some keys and values. There might be an ampersand
and more keys and values. Here, again, is that very
common programming paradigm of just associating keys with values. We can see this as follows. Let me actually go to
google.com, in a browser here, and let me search for something
the internet is filled with, cats. Enter, notice now that my
URL changed from google.com to google.com slash
search question mark, Q equals cats, ampersand
and then a bunch of stuff that I don't understand or know. So let's just delete it for now, and
leave it with the essence of that URL. And that still works. If I zoom out here, years ago
you would get pictures of cats. Now you get videos of the movie. And then that top query
there, is Cats a bad movie. But we can also, of
course, click on Images. And there are the
adorable cat, creepy cats. All right, this didn't used to
happen when we searched for cats. But anyhow, the point is that the URL
changed to include the user's input. And this is such a simple,
but such a powerful thing. This is how humans
provide input to servers. They don't manually create the
URLs, like I sort of just did. But when you fill out a form
on the web and you hit Enter, typically the URL suddenly
changes to include whatever you typed in,
in the URL, assuming the form is using the verb GET. That's not ideal. If you're typing in a
username, a password, a credit card information, because you
don't want the next person to sit down at your laptop to see literally
everything you typed in, saved in your history. So there's another verb, POST,
that can hide all of that. And it's just sent a little differently. But things like this are
typically sent via GET, and what that means underneath the
hood is that your browser is just making a request like this, Get/search? Q equals, whatever you typed in, the
host that you visited, and so forth. And hopefully what comes back is a page
full of search results, including cats. And what's interesting here now is, if
I go back to VS Code on my own computer, and let me go ahead and create a
file called, how about Search.html. In Search.html, I'm going to start
with some copy/paste from before, change my title to search. And in the body of this page, I'm
going to introduce a form tag. And in this form tag, I'm going
to have a couple of inputs. And the types of inputs are going to
be text, and the type of the input is going to be submit. And this isn't that
interesting yet, but let's see what is happening in the page itself. Let me go back to my directory listing. Let me click on Search.html. I seem to have the beginning
of my own search engine. It's not very interesting. It's just a text box
and a submit button. But let's finish my thoughts here. So let's specifically give
this text box a name of Q, which, if you roll back to the late '90s
when Larry and Sergey of Google fame created Google.com, Q represented query,
the query that the human's typing in. So the name of this
text box shall be text, shall be Q. The form is
going to use what method? Technically it uses GET
by default, but I'll be explicit and say method
equals quote unquote "get." Stupidly, it's lowercase in HTML, even
though what's in the envelope is indeed uppercase, by convention. The action of this form, specifically,
would ideally go to my own server. But we don't really have time
today to implement Google itself. So we're just going to send the
user's request to google.com/search. So I'm creating a form,
the action of which is to send the data to Google's slash
search path, using the GET method. It's going to send an input called Q,
whenever I click this Submit button. Let me go back to the
browser, reload the page. Nothing seems to have changed yet,
but, if I search for, let me zoom out, so we can see the URL bar. Right now I'm in Search.html. If I zoom out and search for
cats now and click Submit, I'm whisked away to google.com. But notice that the URL is
parameterized, with those key value pairs, that key value pair. And I get back a whole
bunch of cat results. And I can very easily now
make this a little prettier. Right now, it's not ideal that like
the human has to move their cursor and click in the box. And it's a little obnoxious
that autocomplete is enabled. If I don't want to
search for cats anymore, well, according to HTML's documentation,
I can say something like this. Autocomplete equals off, to turn
off autocomplete, auto focus to automatically put the
cursor inside of that text box. If I want some explanatory text, I can
put placeholder text like quote unquote "query." And now if I go back to
this page and reload, now it's a little more user-friendly. You see query in kind of gray text. The cursor is already
there and blinking. I don't have to even move my cursor. I can search for dogs now, and you
didn't see any autocomplete at all. Hit enter to submit, and
now I'm searching for, there we go, adorable dogs, instead. So what have I done? I've implemented the front end of
Google.com, just not the back end. To implement the back
end, we're obviously going to need like a really big
database, maybe something like SQL. We're going to need some code that like
searches the database for dogs or cats, or anything else. We're going to need Python
for something like that. And in fact, that's the direction
we're steering next week, when we implement that back end. But today it's all about this front end. Or any question, then, about forms,
these URL parameters, before we now transition to making things look
a little prettier, with CSS? And then we'll end by making
things a little more functional, with JavaScript. Anything at all? No? All right, so let's start to answer
a couple of the questions that came up, by making these pages a
little more aesthetically interesting. Let's go ahead now and introduce to
the mix one other language, as follows. Let me go ahead and create
a file called Home.html, as though I'm making a home
page for the very first time. And in this page, I'm going
to give a title of Home. And I'm just going to
have like three things. First I'm going to have maybe
a paragraph of text up here at the top, that says something
welcoming for my home page, like my name, John Harvard, for
instance, or John Harvard's home page. Then in the middle of the page,
I'm going to have some text like, welcome to my home
page exclamation point! And at the bottom of the page, I'm
going to have a final paragraph that says something like copyright,
the copyright symbol, John Harvard, or something like that. All right, so it's like a web page
with three different structural areas, made with text. This isn't that interesting. If I open this page called
Home.html, let me go ahead and create three quick paragraphs,
a first paragraph for John Harvard. Inside the middle, I'm going to say
something like welcome to my home page exclamation point! And at the bottom,
whoops, at the bottom, a little footer that says
something like copyright, a little simple copyright
symbol, and John Harvard's name. All right, now let me reload the page. And there we go. It's a very simple, very underwhelming
web page that has three main sections. Let's start to now stylize
this in an interesting way, so that it's a little more
aesthetically pleasing. First, these aren't really paragraphs. They're sort of like areas of the page,
divisions, like the header is up here. There's like the main part of my screen. And then there's the
footer of my screen. So paragraphs isn't quite
right, if these aren't really paragraphs of texts. I might more properly call
them divs or divisions of the page, which is a very commonly
used tag in HTML, which just has this generic rectangular region to it. It does not do anything aesthetically,
no bold facing, no size changes. It just creates an invisible
rectangular region, inside of which you can start to style the text. Or I can take this one step further. There's some other tags in HTML,
known as semantic tags, that literally have names that describe the
types of your page, which is all the more compelling these
days for accessibility, too, for screen readers, for search engines,
because now, a screen reader, a search engine can realize that footer
is probably a little fluffy. The header might be
a little interesting. The main part of the page
is probably the juicy part, that I want users to be able to search
for or read aloud, substantively. So let's start to stylize
this page somehow. Let's introduce a style
attribute in HTML, inside of which is going to be
text like this, font size colon large, text align colon center. On Main, I'm going to add a
style attribute and say font size medium, text align center. And then on the footer, I'm going
to say style equals font size small, text align center. What's going on here? Well, in blue is the
language we promised, called CSS, for Cascading Style Sheets. We're not really seeing the
Cascading Style Sheet of it yet. But in blue here, notice is
another very common paradigm. It's different syntax
now, but how would you describe what you're
looking at here in blue? This is another example of what
kind of programming convention? AUDIENCE: Key value. SPEAKER 1: Yeah, it's just
more key value pairs, right? It'd be nice if the world standardized
how you write key value pairs, because we've now seen equal signs
and arrows and colons and semicolons, and all this. But it's just different
languages, different choices. The key here is font-size,
the value is large. The other key is text-align,
the colon, the value is center. The semicolon just separates
one key value pair from another. Just like in the URL, the ampersand
did, in the context of HTTP. The designers of CSS
used semicolons instead. Strictly speaking, this
semicolon isn't necessary. I tend to include it just for
symmetry, but it doesn't matter, because there's nothing after that. This is a bit of a weird example. This is the co-mingling of
CSS inside of JavaScript. So as of now, you can use the CSS
language inside of the quote marks in the value of a style attribute. We did something a little
similarly last two weeks, a week plus ago, when we included
some SQL inside of Python. So again, languages can kind
of cross barriers together. But we're going to clean
this up, because this is going to get messy quickly,
certainly for large web pages, the size of Harvard's or Yale's, or the like. So let's see what this looks like. Let me go back to my browser
window here, reload the page. And it's not that different. But it's indeed centered, and it's
indeed large, medium, and small text. And let me make one refinement. The copyright symbol
actually can be expressed, but there's no key on
my US keyboard here. I can actually magically say
ampersand hash 169 semicolon, using what's called an HTML entity. It turns out there are numeric
codes, with this weird syntax, that allow you to specify symbols that
exist in Macs and PCs and phones, but that don't exist on most keyboards. If I reload the page now, now
it's a proper copyright symbol. So minor aesthetic, but it
introduces us to these HTML entities. So even if you've never
seen CSS before, you can probably find something
kind of dumb about what I did here, like poor design. It is correct, if my goal was small,
medium, and large, bottom up, what looks like a bad design,
perhaps, even if you've never seen this language before. Yeah. AUDIENCE: Same SPEAKER 1: Yeah, I've used
the same style three times, like copy/paste, or typing the
exact same thing again and again. It has rarely been a good thing. Well, here's where we can take
advantage of the design of CSS, because it supports what
we might call inheritance, whereby children inherit the properties,
the key value pairs of their parents or ancestors. And what that means is, I can do this. Let me get rid of this text align. Let me get rid of this text align. Let me get rid of this one. I could get rid of the semicolon,
too, but I'll leave it for now. And let me add all of that style
to the parent element, the body, so that it sort of cascades down to the
header, the main, and the footer tags as well. And let me close my quotes there, too. Now, if I go back to my browser
and hit reload, nothing changes. But it's a little
better designed, right? Because if I want to change the text
alignment to maybe be right aligned, I can now reload the page, and
voila, now it's over there. I change it in one place, not
in three different places. So that would seem to be
marginally better design. And could we do this
any more differently? Well, it's not that elegant that
it's all just in line with my HTML. This generally tends to
be bad practice, where you co-mingle your HTML and your
CSS, especially since some of you might be really good at laying
out the structure of web pages and the content and the data, and you
might have a horrible sense of design or just not care about the aesthetics. You might work with a
designer, an artist, who's much better at all of
these fine tunings aesthetically. Wouldn't it be nice if you could work
on the HTML, they could work on the CSS. And you don't have to
somehow like literally edit the same lines
of code as each other. Well, just like we can move
stuff into header files in C, or packages in Python, we
can do the same in CSS. So I'm actually going
to go ahead and do this. Let me get rid of all of
these style attributes, and let me now start to practice a
convention of not co-mingling CSS with my HTML. Let me instead move it into the
head of the page, in a style tag, instead of an attribute. This is one of the rare
examples where there are attributes that have the
same names of tags as vice versa. It's not very common,
but this one does exist. Here's a slightly different syntax for
expressing the same key value pairs. If I want to apply CSS properties,
that is, key value pairs, to the header of the page, I say
header, and then I use curly braces, and inside of those I say
font-size large, text-align center. Then, if I want to apply some properties
to the main section of the page, I again do font-size, say, medium,
and then I can do text-align center. Then, lastly, on the
footer of the page, I can assign some properties like
font-size small, and then text-align center semicolon. And I don't have to do
anything more in my HTML. It all just represents
the structure of my page. But, because of this style
tag in the head of the page, the browser knows in
advance that the moment it encounters a header tag, a
main tag, or a footer tag, it should apply those
properties, those styles. If I reload the page, other
than it being recentered now, there's no other changes. All we're doing is sort of
iteratively improving the design here. But now everything's
in the top of the file. But there's still a bad design here. What could I now do
that would be smarter? Similar problem to before. Yeah. AUDIENCE: Create it. SPEAKER 1: OK, create a
new file with just the CSS. I like that. Let's go there in just one second. But even as we're here,
there's still a redundancy we can probably chip away at. Yeah, get rid of the text-align center
in three different places, which doesn't seem necessary,
and perhaps someone else, if I get rid of text-align center,
what should I add to my style tag in order to bring it back, but
apply it to everything in the page? And the page, if I scroll
down, looks like this, in HTML. Yeah. AUDIENCE: The body. SPEAKER 1: Yeah, so the body tag. So let me go ahead and say body. And then in here, put text-align center. And that, now, if I reload the
page, has no visual effect, but it's just better
design, because now I factored out that kind of commonality. And so, just to make clear
what we've been doing here, these are all, again, CSS
properties, these key value pairs. And there's different types
of ways of using them. And there's this whole taxonomy. What we've been doing thus far are what
we're going to call type selectors, where the type is the name of a tag. And so it turns out there's
other ways, though, to do this. And let's head in this direction. Let's go ahead and maybe write
our CSS slightly differently, because you know what would be nice. I bet, after today, once I start
creating other files for my home page, or John Harvard's home
page, I might want to have centered text on other pages. And I might want to have large
text or medium text or small text. It'd be nice if I could reuse
these properties again and again, and kind of create my
own library, maybe even ultimately putting it
in a separate file. So let me do this. Instead of explicitly applying
text-align center to the body, let me create a new
noun, or an adjective, rather, for myself, called centered. It has to start with a
dot, because what I'm doing is inventing my own class, so to speak. This has nothing to do with
classes in Java or Python. Class here is this aesthetic feature. And, actually, let me rename
these, to be dot large, dot medium, and dot small. What this is doing for me
is it's inventing new words, well-named words, that I
can now use in this file, or potentially in other web
pages I make, as follows. I can now say, if I want
to center the whole body, I can say class equals centered. On the header tag, I can
say class equals large. On the main tag I can
say class equals medium. On the footer tag, I can
say class equals small. But let me take this one step further. As you suggested, why
don't I go ahead now and let me actually get rid
of-- let me grab all of the CSS, copy it to my clipboard. Let me get rid of the style tag here,
and create a new file called Home.css, and let me just save all of that same
text in a separate file ending in .css, nothing else, no HTML whatsoever. But let me go back to my
Home.html page, and this is one of the most annoyingly named
tags, because it doesn't really mean what it does, Link HREF
Home.css rel equals stylesheet. So ideally we would have used the
link tag for links in web pages, but this is link in the
sort of conceptual sense. We're linking this file to this other
one, so that they work together, using this hyper-reference,
Home.css, the relationship of that file to this one
is that of stylesheet. A stylesheet is a file
containing a whole bunch of stylizations, a whole bunch
of properties, as we just did. So here, too, it's
underwhelming the effect. If I reload the page, nothing changed. But now, I not only have
a better design here, because I can now use those same classes
in my second page that I might make, my third page, my fourth page, my bio,
my resume page, whatever it is I'm making on my website here, I
can reuse those styles by just including one line of code, instead of
copying and pasting all of that style stuff into file after file after file. And heck, if the rest
of the world is really impressed by my centered class, and
my large and medium and small classes, I could bundle this up, let other
people on the internet download it, and I have my own library, my own CSS
library, that other people can use. Why should you ever invent
a centered class again, if I already did it for you,
stupid and small as this one is. But it would be nice
now to package this up in a way that's usable
by other people as well. So this is perhaps the best
design, when it comes to CSS. Use classes where you can, use
external stylesheets where you can, but don't use the style attribute
where we began, which while explicit, starts to get messy quickly,
especially for large files. All right, any questions, then, on this. No, all right, so
that's class selectors. When you specify dot
something, that means you're selecting all of the tags in the
page, that have that particular class, and applying those properties. So there's a couple of
others here, just to give you a taste now of what's possible. There's so much more that you can
actually do with HTML and CSS together. Let me go ahead and open up a few
examples that I did here in advance. Let me go ahead and open up VS Code. And let me go ahead and copy
my source eight directory. Give me one second to grab the source
eight directory for today's lectures, so that I can now go into
my browser, go into some of the pre-made examples
in source eight, and let me open up paragraphs one here. So here's something,
it's a little subtle. But does anyone notice
how this is stylized? This is just some generic
lorem ipsum text again. But what's noteworthy
stylistically, a book might do this. Yeah? AUDIENCE: They're bigger. SPEAKER 1: Yeah, the first
paragraph's a little bigger. Why? Who knows, it's just a stylistic
thing at the beginning of the chapter. The first paragraph is bigger. How did we do that? Well, we can actually explore
this in a couple of ways. One, I can obviously go into
VS Code and show you the code. But, now, that we're using Chrome and
we're using these developer tools, let's again go into them. View developer, developer
tools, and now notice, let me turn off the mobile feature,
and let me move the dock back to the bottom, just so
that it's fully wide. We looked at the Network tab before. We looked at the mobile button before. Now let me click on Elements. What's nice about the Elements tab
is you can see a pretty printed version of the web page's HTML,
nicely color-coded, syntax highlighted for you, so that you can now henceforth
learn from, look at, the source code, the HTML source code, of
any web page on the internet. Notice that my own web page
here, it's not that interesting. There's a bunch of paragraph
tags of lorem ipsum text. But notice what I did. The very first one, I gave an ID to. This is something that you,
as a web designer, can do. You can give an ID attribute
to any tag in a page, to give it a unique identifier. The onus is on you, not to
reuse the word, anywhere else. If you reuse it, you've screwed up. It's incorrect behavior. But I chose an ID of
first, just so that I have some way of referring to the
very first paragraph in this file. If I look in the head of the
page, and the style tag here, notice that I have hash first. So just as I use dot for
classes, the world of CSS uses a hash symbol to
represent IDs, unique IDs. And what this is telling the browser,
whatever element has the first ID, F-I-R-S-T, without the hash,
apply font-size larger to it. And that's why the first paragraph,
and only the first paragraph, is actually stylized. If I actually go into
VS Code now, and let me go into my source eight directory. Let me open up Paragraphs1.html. Here is the actual file. If I want to change the color of that
first paragraph to green, for instance, I can do color colon: green. Let me close the developer
tools, reload the page. And now that page is green as well. You don't have to just use words. You can use hexadecimal. What was the hex code for green in RGB? Like no red, lots of green, no blue. So you could do 00 FF 00, using
a hash, which, coincidentally, is the same symbol, but it
has nothing to do with IDs. This is just how Photoshop and
web pages represent colors. Let's go back here and reload. It's the same, although it's a
slightly different version of green. This is pure green here. If I want to change it to red, that
would be, let's see, RGB FF 00 00, and here I can go and reload. Now it's first paragraph red. This actually gets
pretty tedious quickly. Like, if you're a web designer trying
to make a website for the first time, it actually might be fun
to tinker with the website, before you open up your editor
and you start making changes and save and reload. That's just more steps. So notice what you can
do with developer tools, too, in Chrome and other browsers. When I highlight over this
paragraph, under the Elements tab, notice that, one, it
gets highlighted in blue. If I move my cursor, it
doesn't get highlighted. If I move it, it gets highlighted. So it's showing me what
that tag represents. But notice over here on
the right, you can also see all of the stylizations
of that particular element. Some of them are built-in. The italicized ones here at the
bottom means user agent stylesheet. That means this is what Google makes
all paragraphs look like by default. But in non-italicized
here, you see hash first, which is my code, that I just changed. And if I want to start tinkering with
colors, I can do like 00 00 FF Enter. I changed it to blue. But notice, if I go back to VS Code, I
didn't change my original VS Code code. This is now purely client side. And this is a key detail. When I drew that picture
earlier of the browser going, making a request to the cloud, the
server in the cloud and the response coming back, the browser,
your Mac, your PC, your phone, has a copy of all the HTML and
CSS, so you can change it here, however you actually want. And, for instance, you can
do this with any website. Let's go, say, on a field trip
here, to how about Stanford.edu. So here's Stanford's
website as of today. Let's go ahead here
and let's see, there's their admissions page,
campus life, and so forth. Let me go ahead and view developer
tools on Stanford's page, developer tools, elements,
you can see all of their HTML. And notice it's collapsed,
so here is their header. Here's their main part, and
I'm using my keyboard shortcuts to just open and close the tags,
to dive in deeper and deeper. Suppose you want to kind
of mess with Stanford, you can actually like right
click on any element of a page, or control click, Inspect, and that's
going to jump you automatically to the tag in the Elements
tab that shows you that link. And notice, if I hover over this
LI, notice Stanford's using a list, as an unordered list from left to right. But it doesn't have to be a
bulleted list top to bottom. They've used CSS to change it to be
a list, from news, events, academics, research, health care,
campus admission, about. Well, so much for
admission, that's gone. So now, if I close developer tools,
now it's gone from Stanford's website. But, of course, what have I really done. I've just like mutated
my own local copy. So this is not hacking,
even though this might be how they do it in TV and the movies. It's still there if I reload the page. But it's a wonderfully powerful way
to, one, just iterate quickly, and try different things
stylistically, figure out how you want to design
something, and two, just learn how Stanford did something. So, for instance, if I right click
or control click on admission again, go to inspect, and let
me go to the LI tag. Let me keep going up, up,
up, up, up to the UL tag. There's going to be a lot going on here. But notice, they have applied
all of these CSS properties to that particular UL tag. But notice, here, this is
how, it's something like this. And we'd have to read more to learn
how this works, list style type none, this is how they probably
got rid of the bullets. And what you can do is just tinker. Like, all right, well,
what does this do? Well, let me uncheck it. All right, didn't really change
anything, font weights, uncheck this, there we go. So now the margin is changed, the
padding around it has changed. Let's get rid of this. We can just start turning
things on and off, just to get a sense of how
the web page works. I'm not really learning
anything here so far. Let me go to the LI here for, let's
go to the admissions one here. Margin, there we go, OK. So when there's a
display property in CSS, that's apparently effectively changing
things from vertical to horizontal, if I turn that off, now Stanford's
links all look like this. And there are those bullets. So again, just default styles,
that they've somehow overridden, and a good web designer
just knows ultimately how to do these kinds of things. All right, how about a couple
of final building blocks, before we'll take one more break. And then we'll dive in with
JavaScript to manipulate this stuff programmatically. Let me go ahead and open up,
how about Paragraphs2 here. Let me close this tab, let me go
into Paragraphs2, which is premade. And this one looks
the same, except, when I go ahead and inspect
this first paragraph, notice that I was able
to get rid of the ID somehow, which is just
to say, there's many, many ways to solve
problems in HTML and CSS, just like there is in C and Python. Let me look in the head and
the style of the page now. This is what we might call
another type of selector, that allows us to specify
the paragraph tag, that itself happens to
be the first child only. So you can apply CSS to a very
specific child, namely first child. There's also syntax for last
child, if just the first one is supposed to look a little different. So, here, I've just
gotten out of the business of creating my own unique
identifier and, instead, I'm using this type of selector as well. Well, what more can we do? Let me go into another example
here, called Link1.html, and here we have a very simple
page that just says visit Harvard. But notice it's purple
by default, because we've been to Harvard.edu before. Let's see if we can't maybe
stylize Harvard's links to be a little different. Let me go into Link version
2, now, which looks like this. And now Harvard is very red. How did I do that? Well, let me right click
on it, click Inspect, and I can start to poke around. It looks like my HTML is
not at all noteworthy. It's just very simple HTML,
anchor tag with an HREF. So let's look at the style. Let me zoom out. And we can look at it
in two different ways. We can literally look at
the style, contents here, or we can look at Chrome's
pretty version of it, over here. It looks like my style
sheet, in the style tag, has changed the color to be red, and the
text decoration, which is a new thing, but it's another CSS property, to none. Notice, if I turn that
off, links on the internet are underlined by
default, which tends to be good for familiarity, for
visibility, for accessibility. But, if it's very obvious what
is text and what is a link, maybe you change text
decoration to none. But maybe, watch this, maybe the
link comes, the line comes back when you hover over it. Well, let's look at how
I did this in style. Notice that I have stylization, and I
put my curly braces on the same line here, as tends to be convention in CSS. Color is red, text decoration is none. But, whenever an anchor
tag is hovered over, you can change the text decoration
to be back to the default, underline. So, again, just little ways of playing
around with the aesthetics of the page, once you understand
that, really, there's just different types of selectors. And you might have to remind
yourself, look them up occasionally, as to what the syntax is. But it's just another way of scoping
your properties to specific tags. Let's look at version 3 of this
here, which adds Yale to the mix. If I go to Link3.html, maybe I
want to have Harvard links red, Yale links blue. How might I have done this? Well, let's right click,
and click Inspect. And here we might have two links,
with a couple of techniques, just to, again, emphasize, you can
do this so many different ways. I gave my Harvard link an ID of
Harvard, my Yale link an ID of Yale. In my CSS, if we go to the head
of the page, I then did this. The tag with the Harvard ID, a.k.a. #Harvard, should be red,
#Yale should be blue, and then any anchor tag should
have no text decoration, unless you hover over it, at which
point it should be underlined. And so, if I hover over Harvard,
it's red underlined, Yale, it's blue underlined. If I want to get rid of the IDs, I
can do this a slightly different way. Let me go into Link4. Same effect, but notice,
I got rid of the IDs now. How else can I express myself? Well, let's look at the CSS here. The anchor tag has no text
decoration by default, unless you're hovering over it. And this is kind of cool. This is what we would
call, on our list here, an attribute selector, where you
select tags using CSS notation, based on an attribute. So this is saying, go ahead
and find any anchor tag who's HREF value happens to
equal this URL, and make it red. Do the same for Yale, and make it blue. Now, this might not be ideal, because
if there's something after the slash, these equal signs don't
work, because if it's a different Harvard or different Yale
link, this is a little too precise. So let me look at version
5 here, of Link.html. Look at this style, and I
did this a little smarter. This is new syntax. And, again, just the kind
of thing you look up. Star equals means, change any anchor
tag who's HREF contains anywhere in it Harvard.edu to red, and do the same
thing for Yale, based on star equals. So star here connotes wildcard. So search for Harvard.edu or
Yale.edu anywhere in the HREF, and if it's there, colorize the link. And, again, we could do this all
day long, with diminishing returns, to actually achieve the same kind
of stylizations in different ways. And as projects just
get larger and larger, you just have more and
more decisions to make. And so you have certain
conventions you start to adopt. And, indeed, if I may,
you have the introduction of what are called
frameworks, ultimately. If you're a full-time
web developer, or you're working for a company doing the same,
you might have internal conventions that you adhere to. For instance, the company might say,
always use classes, don't use IDs. Or always use attribute
selectors, or don't use this. And it wouldn't be necessarily
as draconian as that. But they might have a
style guide of sorts. But, what many people, and
many companies, do nowadays, is they do not come up with all
of their own CSS properties. They start with something off the shelf,
a framework, typically a free and open source framework, that just gives
them a lot of pretty stylizations for free, just by using
a third party library. And one of the most
popular ones nowadays is something called
Bootstrap, that CS50 uses on all of its websites,
super-popular in industry as well. It's at getbootstrap.com, and this
is just to give you a taste of it, a website that documents
the library that they offer. And there's so much documentation here,
but let me just go to things like, how about components. It just gives you, out of the
box, the CSS with which you can create little alerts. If you've ever noticed
on CS50's website, little colorful warnings at
the top of the page, or call outs, to draw your attention to things. How did we do that? It's probably a paragraph
tag or a div tag, and maybe we changed the font color. We changed the background color. Or it's a lot of stuff we could
absolutely do from scratch, but, you know what,
why would we reinvent the wheel if we can just use Bootstrap. So, for instance, let
me just scroll down. If you've ever seen on CS50's website
a yellow warning alert like this, let me just zoom in on this. We are just using HTML like this. We're using a div tag, which,
again, is an invisible division, a rectangular region of the page. But we're using classes called alert
and another class called alert warning. Those are classes that the
folks at Bootstrap invented. They associated certain
text colors and background colors and padding and margin
and like other aesthetics with, so all we have to do
is use those classes. Role equals alert, just makes clear
to like a screen reader that this is an alert, that should
probably be recited, and whatever's in between
the open tag and close tag, is what the human would see. How do you use something like Bootstrap? Well, you just read the documentation. Under Getting Started, there is a
link tag you copy/paste into your own. So let me do this. So in Table.html, we had code like this. Let me actually read Bootstrap's
documentation really fast. And they tell me... copy/paste this code. I'm going to put this
into the head of my page. And it's quite long, but
notice, it's a link tag, which I used earlier for my
own CSS file, the HREF of which is this CDN link, content
delivery network, that's referring to a specific version of
Bootstrap that's available on this day. And the file that I'm including
is called Bootstrap.min.css. This is an actual file I
can visit with my browser. If I open this in a separate
tab, this is the CSS that Bootstrap has made
freely available to us. Crazy long, no white space. That's because it's been
minimized, just to not waste space by adding lots
of white space and comments. But this contains a whole lot,
hundreds, of CSS properties that we can reuse, thanks to
classes that they invented. If I want to use some JavaScript
code, I can also copy this script tag. But we'll come back to that before long. Let me now just make a couple
of tweaks to this table. If I go into my browser
from before, this is what it looked like previously,
where name and number were bold, but centered, and then
Carter and David were on the left, and the numbers were to the right. It's fine. It's not that pretty, but it'd be nice
if it were a little prettier than that. So if we add Bootstrap into it,
notice one thing happens first, when I reload the page. No longer are Chrome's
default styles used. Now Bootstrap's default
styles are used, which is a way of enforcing similarity
across Chrome, Edge, Firefox, Safari, and others. Notice it went from a
serif font to a sans serif font, and something cleaner like this. It still looks pretty ugly, but let
me go into Bootstrap's documentation. Let me go under their
content tab, for tables. And if I just kind of
start skimming this, these are some good
looking tables, right? Like, there's some underlining
here, some bolder font. There's a dark line. If I keep going, ooh,
that's getting pretty, too, if I want to have a colorful table,
like I could figure all of this stuff out myself if I want
some dark mode here, if I want to have alternating
highlights, and so forth. There's so many different stylizations
of tables that I could do myself. But I care about making a phone book,
not about reinventing these wheels. So if I read the documentation closely,
it turns out that all I need to do is add Bootstrap's table
class to my table tag, and watch with a simple reload, what
my now Table.html file looks like. Much nicer, right? Might not be what you want, but, my
God, with like two lines of code, I just really prettied things up. And so here, then, is the value of
using something like a framework. It allows you to actually
create much prettier, much more user-friendly websites than you might
otherwise be able to make on your own, certainly quickly. In fact, let's iterate one
more time on one other example, before we introduce a bit of that code. Let me go ahead and open
up Search.html from before, which, recall, looks like this,
and Search.html on my browser was this very simple Google search. And suppose I want to reinvent
Google.com's UI a bit more. Here's a screenshot of
Google.com on a typical day. It's got an about link, a store
link, Gmail images, these weird dots, sign in, their logo. It's not appearing well
on the screen here, but there's a big text box in
the middle, and then two buttons, Google search, and I'm feeling lucky. Well, could I maybe go about
implementing this UI myself, using some HTML, some CSS,
and maybe Bootstrap's help, just so I don't have to figure out
all of these various stylizations? Well, here's my starting point. In Search.html, let's go and add
in Bootstrap, first and foremost, so that we have access to all of
their classes that are reusable now. And let me go ahead and
figure out how to do this. Well, just like Stanford's site had
like its NAV navigation bar, using a UL, but they changed it from being a
bulleted list to being left to right, I bet I can do something
like this myself. So let me go into the body
of my page and, first, based on Bootstrap's documentation,
let me add a div called a div with a class of container fluid. Container fluid is
just a class that comes with Bootstrap that says, make
your web page fluid, that is, grow to fill the window. So that way it's going to resize nicely. I'm going to go ahead and
fix my indentation here. If you haven't discovered
this yet, if you highlight multiple lines
in VS Code, you can hit Tab and indent them all at once. So now, I have all of
that inside of this div. Now, just like in Stanford's site, let's
create an unordered list that has maybe an LI, called with a class of NAV item,
and then in here, whoops, in here, let me go ahead and say, A
HREF=https://about.google, which is the real URL
of Google's about page. And I'll put the about text in there. Then I'm going to close my LI tag
here, and I want to do one other thing, because I'm using Bootstrap. Bootstrap's documentation,
if I read it closely, says to add a class to your links,
called like NAV link, and text dark, to make it dark, like black or dark
gray, instead of the default blue. All right, so I think I
have now an about link in a navigation part of my screen. Let me go ahead and
save this and reload. All right, so not exactly what I wanted. It's a bulleted list, still, so
I need to override this somehow. Let me read Bootstrap's
documentation a little more clearly. And let me pretend to do
that, for time's sake. If I go under content, oops,
if I go under components, and I go to Navs and
Tabs, long story short, if you want to create a pretty menu
like this, where your links are from the left to the
right, just like Stanford, I essentially need HTML like this. And this is subtle, but
I left off this class. I should have added a
class called NAV on my UL. So that was my bad. Let me go in here and
say add class equals NAV, and then again, this class
NAV item, Bootstrap told me to, NAV link text dark,
Bootstrap told me to. Let me go back to my page here,
reload, and OK, still kind of ugly. But at least the About link is
in the top left hand corner, just like it should be
in the real google.com. Now let me whip up a couple
of more links real fast. Let me go and do a little
copy/paste, though I bet next week we can avoid this kind of copy/paste. Let me change this link
to be Store.google.com. The text will be store. Let me go ahead and create
another one here for Gmail. So this one's going to go
to, officially, how about, technically it's www.google.com/gmail. Normally it just redirects. And let me grab one more of these. And for Google Images, and I'm
going to paste this, whoops, I'm going to, come on. I'm going to put this here, too. This is going to be images, and
that URL is IMG.hp, is the URL. All right, let me go ahead
and reload the browser page. Now it's coming along, right? About, store, Gmail, images. It's not quite what I want. So I'd have to read the
documentation to figure out how to maybe nudge one of these
over, to start right aligning it. And there's a couple of ways to do this. But one way is if I want Gmail to move
all the way over and push everything else, I can say that add some margin to
the Gmail list item, margin start auto. This is in Bootstrap's documentation, a
way of saying whatever space you have, just automatically
shove everything apart. And now, if I reload the page
again, now, voila, Gmail and images is over to the right. All right, so now we're
kind of moving along. Let me go ahead and add the
big blue button to sign in. So here with sign in, let me go
ahead and, over in my same NAV, yeah, so let's go ahead and do one
more LI, class equals NAV item. And then, inside of this LI
tag, what am I going to do? Turns out there is a class that can turn
a link into a button, if you say BTN, for button, and then button
primary, makes it blue, the HREF for this one is going
to be https://accounts.goo gle.com/service/login, which is
literally where you go if you click on that big blue button. The role of this link is that of button. And then sign in, is going
to be the text on it. If I now reload the page, now
we're getting even closer, although it looks a little stupid. Notice that sign in is way
in the top right hand corner, whereas the real google.com has
a little bit of margin around it? OK, that's an easy fix, too. Let me go back into my HTML here. Let me add margin-3. This, too, is a Bootstrap thing. They have a class called m-something. The something is a
number from like 1 to 5, I believe, that adds just
some amount of white space. So if I reload now, OK,
it's just a little prettier. And now let me accelerate. Just to demonstrate how I can
take this home, let me go ahead and open up my premade
version of this, whereby I added to this some final flourishes. If I go to Search2.html, I
decided to replace their logo with just this out of a
cat, and notice that I re-implemented essentially google.com. Here's a text box, here's two
buttons, even though they're a little washed out on the screen. I even figured out how to get dots
that look pretty similar to Google's. And if we view source, you can see
how I kind of finished this code. If I go to view developer tools, and I
go to elements, and I go into this div, and I go into this div, you'll see
that here's an image tag for happy cat. And I added some classes there to make
it fluid, and width 25% of the screen. If I go into the form tag, this
is the same form tag as before. But, notice, I used
button tags this time, with button and button light classes. And then I stylized
them in a certain way. And so in the end result, if I want
to go ahead and search now for birds, and click Google search,
voila, I've implemented something that's pretty
darn close to Google.com, without even touching raw CSS myself. And now here's the value,
then, of a framework. You can just start to use
off the shelf functionality that someone else created for you. But if you want to make
refinements, you don't really like the shade of blue that
Bootstrap chose, or the gray button, or you want to curve
things a bit more, that's where you can create
your own CSS file, and do the last mile, sort
of fine tuning things. And that tends to be best practice. Stand on the shoulders of others as
much as you can, using libraries. And then if you really don't
like what the library is doing, then use your own skills and
understanding of HTML and CSS to refine things a bit further. But still, after all of that, all of
these examples we've done thus far are still static, other
than the Google one, which searches on the real Google.com. Let's take a final 5
minute break and we'll give you a sense of what we can next
do, next week onward, with JavaScript. See you in five. All right, so I think
it's fair to say, we're about to see our very last language. Next week and final
projects are ultimately going to be about
synthesizing so many of these. Thankfully, this language called
JavaScript is quite similar syntactically to both C and Python. And, indeed, if you can imagine
doing something in either of those, you can probably do it in
some form in JavaScript. The most fundamental
difference today, though, is that when you have written C
code and Python code thus far, you've done it on the server. You've done it in the
terminal window environment. And when you run the code, it's
running in the cloud on the server. The difference now today
with JavaScript is, even though you're going to write
it in the cloud using VS Code, recall that, when a browser gets
the page containing this code, it's going to get a copy of the HTML,
the CSS, and the JavaScript code. So JavaScript, that we see today, is
all going to be executed in the browser, on users' own Macs, PCs, and
phones, not in the server. JavaScript can be used on the server,
using an environment called Node.js. It's an alternative to Python or
Ruby or Java or other languages. We are using it today client
side, which is a key difference. So in Scratch, let's
do this one last time. If you wanted to create a variable
in Scratch, set encounter equal to 0. In JavaScript, it's
going to look like this. You don't specify the type,
but you do use the keyword let, and there's a few others as well, that
say let counter equal 0 semicolon. If you want to increment that
variable by one, you in JavaScript could say something like,
counter equals counter plus 1, or you can do it more
succinctly, with plus equals, or the plus plus is back in JavaScript. You can now say counter
plus plus semicolon again. In Scratch, if you wanted to
do a conditional like this, asking if x less than y, it looks
pretty much like C. The parentheses are, unfortunately, back. The curly braces here are back, if you
have multiple statements in particular. But, syntactically, it's pretty much
the same as it was for if, for if else, and even for it's else if else. Unlike Python, it's two
words again, else if. So quite, quite like C,
nothing new beyond that. If you want to do something forever
in Scratch, you'd use this block. In JavaScript, you can do it a few
ways, similar to Python, similar to C, you just say while true. In JavaScript, Booleans are
lowercase again, just like in C. So it's lowercase true. If you want to do something
a finite number of times, like repeat three times,
looks almost like C as well. The only difference, really, is using
the word let here, instead of INT. And, again, you'll use let to
create a string, or an INT, or any other type of
variable in JavaScript. The browser will figure out
what type you mean from context. In C we would have said INT instead. Ultimately, this language, and that's
it for our tour of JavaScript syntax. There's bunches of other
features, but syntactically it's going to be that accessible,
relatively speaking. The power of JavaScript
running in the user's browser is going to be that you can
change this thing in memory. Think about most any website, that's
at all interesting today, that you use. It's typically very
interactive and dynamic. If you're sitting in front of Gmail on
a laptop or desktop with the browser tab open, and someone sends you
an email, all of a sudden, another row appears in your
inbox, another row, another row. How is that implemented? Honestly, it could be an HTML table. Maybe it's a bunch of
divs top to bottom. The point, though, is, you don't
have to hit Command R or Control R to reload the page to see more email. It automatically appears
every few seconds or minutes. How is that working? When you visit Gmail.com,
you are downloading not just HTML and CSS with your
initial inbox, presumably. You're downloading some
JavaScript code, that is designed to keep talking every
second, every 10 seconds or something, to Gmail servers, and they,
then, are using their code to add another element, another
element, another element, to the existing DOM, document object
model, which is the fancy term for tree in memory that represents HTML,
so that the web page can continue to update in real time. Google Maps, same thing. If you click and drag and drag
and drag, your browser did not download the entire world to
your Mac or PC by default. It only downloaded what's in your
viewport, the rectangular region. But when you click and drag, it's
going to get some more tiles up there, some more images, some more
images, as you keep dragging, using JavaScript, again, behind the scenes. So let's actually use JavaScript
to start interacting with pages. How can we do this? We can put the JavaScript
code in the head of the page, in the body of the page, or even
factor it out to a separate file. So let's take a look. Here is a new version of
Hello.html, that, during the break, I just added a form to, because it'd
be nice if this page didn't just say Hello, title, Hello, body, it said,
Hello, David, Hello, Carter, Hello, whoever uses it. I've got a form that I borrowed
from some of our earlier code, and that form has an input whose ID is
name, that also has a submit button. But there's no code in this yet. So let's add a little bit of
JavaScript code as follows. Suppose that, when this form is
submitted, I want to greet the user. How can I do that? Well, let's do it the
somewhat messy way first. I can add an attribute called
on submit to the form element, and I can say on submit, call the
function called greet, close quotes. Unfortunately, this
function doesn't yet exist. But I can make it exist. But there's another detail here. When the user clicks submit, normally
forms get submitted to the server. I don't want to do that today. I want to just submit the form to
the browsers, keep on the same page, and just print to the screen,
Hello, David, or so forth. So I'm also going to go
ahead and say, return false. And this is a JavaScript way of telling
the browser, even when the user tries to submit the form, return false. Like, no, don't let them
actually submit the form. But do call this function called greet. In the head of my page, I'm going to add
a script tag, wherein the language is implicitly JavaScript,
and has no relationship, for those of you who took APCS with
Java, just a similarly named language, but no relation, I'm going to
name a function called Greet. Apparently in JavaScript, the
way you create a function is you literally say the word
function instead of Def. You don't specify a return type. And in this function, I could do
something like this, alert quote unquote, how about, Hello, there. Initially I'm going to keep it simple,
using a built-in function called alert, which is not a good user interface. There are better ways to do this. But we're doing something simple first. Let me now go ahead and
load this page again. It still looks as simple as before,
with just a simple text box. I'll zoom in to make it bigger. I'm going to type my name,
but I think it's going to be ignored when I click Submit. It just says, Hello, there. And this is, again, this
is an ugly user interface. It literally says the whole
code space URL of the web page is saying this to you. It's really just meant for simple
interactions like this, for now. All right, let's have it
say Hello, David, somehow. Well, how can I do this? Well, if this element on the
page was given by me a unique ID, it'd be nice if, just like in CSS, I
can go grab the value of that text box, using code. And I actually can. Let me go ahead and do this. Let me store, in a variable
called name, the result of calling a special function
called document.queryselector. This query selector function
is JavaScript's version of what we were doing
in CSS, to select nodes, using hashes or dots or other syntax. It's the same syntax. So if I want to select the
element whose unique ID is name, I can literally just pass, in
single or double quotes, hash name, just like in CSS. That gives me the actual
node from the tree. It gives me one of these rectangles
from the DOM, the document object model. If I actually want to get at
the specific value therein, I need to go one step
further and say .value. So, similar in spirit
to Python, where we saw a lot of dot notation, where
you can go inside an object, inside of an object,
that's what's going on. Long story short, in JavaScript,
there is a special global variable called document, that lets you just do
stuff with the document, the web page itself. One of those functions
is called query selector. That function returns to you
whatever it is you're selecting. And dot value means go
inside of that rectangle, and grab the actual text
that the human typed in. So if I want to now say,
Hello, to that person, the syntax is a little
different from C and Python. I can use concatenation, which
actually does exist in Python, but we didn't use it much. I can go ahead and say hello,
quote unquote "Hello," plus name. All right, now, if I go
back to the browser window, reload the page, to get the latest
version of the code, type in David, and click Submit, now
I see, Hello, David. Not the best website, but
it does demonstrate how I can start to interact with the page. But let me stipulate that
this co-mingling of languages is never a good thing. It's fine to use
classes, but using style equals quote unquote and
a whole bunch of CSS, that was not going to scale well, once
you have lots and lots of properties. Same here, once you
have more and more code, you don't want to just put your code
inside of this on submit handler. So there's a better way. Let's get rid of that
on summit attribute, and literally never use it again. That was for demonstration's sake only. And let's do this. Let me move the script tag,
actually, just below the form, but still inside the body, so
that the script tag exists only after the form tag exists, logically. Just like in Python, your code is
read top to bottom, left to right. And let me now do this. Let me define this function
called Greet, and then let me do this, document.queryselector,
let me select the form on the page. It doesn't have a unique ID. It doesn't need to. I can just reference it by name, form,
because there's only one of them. And let me call this special
function, add event listener. This is a function that
listens for events. Now this is actually a term
of art within programming. Many different languages
are governed by events. And pretty much any user interface is
governed by events, especially phones. On phones, you have touches, and you
have drags, and you have long press, and you have pinch, and all
of these other gestures. On your Mac or PC you
have click, you have drag, you have key down, key up, as
you're moving your hands up and down on the keyboard. This is a non-exhaustive
list of all of the events that you can listen for in the
context of web programming. And this might be a throwback
to Scratch, where, recall, Scratch let you broadcast events. And we had the two puppets sort of
talking to one another via Events. In the world of web programming, game
programming, any human physical device these days, they're
just governed by events. And you write code that listens
for these events happening. So what do I want to listen for? Well, I want to add an event
listener for the Submit event. And when that happens, I want to
call the Greet function, like this. So this is kind of interesting. Thank you, I have my Greet
function as before, no changes. But I'm adding one
line of code down here. I'm telling the browser to
use document.queryselector to select the form. Then I'm adding an event listener,
specifically for the Submit event. And when that happens, I call Greet. Notice I am not using
parentheses after Greet. I don't want to call Greet right away. I want to tell the browser to call
Greet, when it hears this Submit event. Now let me go ahead and deliberately,
I think, trip over something here, let me type in my name,
David, submit, and there we go. All right, Hello, David. All right, but let's now make
this slightly better designed. Right now, I'm defining a
function Greet, which is fine. But I'm only using it in one place. And you might recall, we
stumbled on this in Python, where I was like, why are we creating
a special function called get value when we're only using
it like one line later? And we introduced what type of
function in Python the other day? AUDIENCE: Lambda. SPEAKER 1: Yeah, so lambda
functions, anonymous functions. You can actually do this
in JavaScript as well. If I want to define a function all
at once, I can actually do this. Let me cut this onto my
clipboard, paste it over here. Let me fix all of the alignment. Let me get rid of the name. And I can actually, now, do this. The syntax is a little weird. But using now just these four
lines of code, I can do this. I can tell the browser to add an
event listener for the Submit event. And then when it hears that, call
this function that has no name. And unlike Python, this function
can have multiple lines, which is actually a nice thing. It looks a little weird. There's a lot of indentation
in curly braces going on now. But you can think of this as just
being, run these two lines of code, when the form is submitted. But if I want to block the form
from actually being submitted, I've got to do one other thing. And you would only know this from being
told it or reading the documentation. I need to do this
function, prevent default, passing in this E argument, which
is a variable that represents the event, more on
that another time, that just allows us to prevent
whatever the default handling of that particular event is. So long story short, this is
representative of the type of code you might write in JavaScript,
whereby you can actually interact with your code, the user's actual form. And we can do interesting things, too. Built into browsers nowadays
is functionality like this. So here's a very simple example, that
has just three buttons in it, one red, one green, one blue. Well, it turns out using
JavaScript, you can control the CSS of a page programmatically. I can change the background
of the body of the page to red, to green, to blue, just by
listening for clicks on these buttons, and then changing CSS properties. Just to give you a taste of this,
if I view the page's source, similar code here, I can
select the red button by an ID that I apparently defined
on it, right up here. I can add an event listener, this
time not for submit, but for click. And when it's clicked, I
execute this one line of code. And this one line of code
we haven't seen before, but you can go into the body of
the page, its style property, and you can change its
background color to red. This is one example of
two different groups not talking to one another in advance. In CSS, properties that have two
words are usually hyphenated, like background-color. Unfortunately, in JavaScript, if
you do something dash something, that's subtraction, which is
logically nonsensical here. So in CSS, you can convert
background-color to, in JavaScript, background Color, where
you capitalize the C, and you get rid of the minus sign. What else can we do here? Well, back in the day, there
used to be a blink tag. And it's one of the
few historical examples of a tag that was removed from HTML,
because in the late '90s, early 2000s, this is what the web looked like. There was a lot of this kind of stuff. There was even a marquee
that would move text from left to right over the screen. And the web was a very ugly place. I will admit, my very first web page
probably used both of these tags. But how can we bring it back? Well, this is a version of the
blink tag implemented in JavaScript. How? I wrote some code in this example, that
waits every 500 milliseconds to change the CSS of the page to be
visible, invisible, visible, invisible, because built into
JavaScript is support for a clock. So you can just do something
on some sort of schedule. Let me go ahead and open up
this example, autocomplete. So let me zoom back out. In Autocomplete.html, I whipped up as
an example, that has just a text box, but I also grabbed the
dictionary from problem set 5 speller, so that if I want
to search for something like Apple, this searches that 140,000
words, using JavaScript, to create what we know in the
world of the web as autocomplete. When you start searching
for something, you should start to see words
that start with that phrase. And sure enough, if I search
for something like banana, here's the three variants of bananas
that appear in that file, and so forth. How is that working? Just JavaScript, when
it finds matching words, it's just updating the DOM, the
tree in the computer's memory, to show more and more text, or less. And for one final example, this is how
programs like DoorDash and Google Maps and Uber Eats and so work. You have built into browsers today some
fancy APIs, application programming interfaces, whereby you can ask for
information about the user's device. For instance, here, I wrote a
program, in Geolocation.html, that's apparently asking to know my location. All right, let me go ahead
and allow it this time, if that's something you're
comfortable with on your own device. It's taking a moment, because sometimes
these things take a little while to analyze. But, hopefully, in just a moment, there
are apparently my GPS coordinates, and as a final flourish today, for what
you can do with a little bit of HTML for your structure, CSS
for your style, and now JavaScript for your logic, which we'll
tie in again next week, let me go ahead and search Google for
those GPS coordinates. Zoom in here on Google Maps,
and if we zoom in, in, in, OK, we're pretty close. We're not on that street, but
there, oh, there it is, actually. There is the marker it had put for us. We're indeed here in Memorial Hall. So all that with JavaScript,
but the basic understanding of the DOM and the
document object model, we'll pick up where
we left off next week. And now add a back-end. See you next time. [MUSIC PLAYING] DAVID: All right. So this is CS50 and this is
week nine, and this is it in terms of programming fundamentals. Today, we come rather full circle
with so many of the languages that we've been looking at
over the past several weeks. And with HTML and CSS
and JavaScript last week, we're going to add back into
the mix, Python and SQL. And with that, do we have the
ability to program for the web. And even though this isn't
the only user interface out there, increasingly-- or people
certainly using laptops and desktops and a browser to access applications
that people have written, but it's also, increasingly, the way
that mobile apps are written as well. There are languages
called Swift for iOS, there are languages
called Java for Android, but coding applications
in both of those language means knowing twice as many language,
building twice as many applications, potentially. So we're increasingly seeing,
for better or for worse, that the world is starting
to really standardize, at least for the next some number of
years, on HTML, CSS, and JavaScript coupled with other languages like
Python and SQL on the so-called backend. And so today, we'll tie
all of those together and give you the last of the
tools in your toolkit with which to tackle final projects to
go off into the real world, ultimately, and somehow solve
problems with programming. But we need an additional tool today,
and we've sort of outgrown HTTP server. This is just a program that
comes on certain computers that you can install
for free, happens to be written in a language called
JavaScript, but it's a program that we've been using to
run a web server in VSCO. But you can run it on your own
Mac or PC or anywhere else. But all this particular
HTTP server does is serve up static content
like HTML files, CSS files, JavaScript files, maybe images, maybe
video files, but just static content. It has no ability to really interact
with the user beyond simple clicks. You can create a web form and serve
it visually using HTTP server, but if the human types in input into
a form and click Submit, unless you submit it elsewhere to something like
google.com like we did last time, it's not actually going to go anywhere
because this server can't actually process the requests that are coming in. So today, we're going to introduce
another type of server that comes with Python that allows
us to not only serve web pages but also process user input. And recall that all that input is
going to come ultimately from the URL, or more deeply inside of
those virtual envelopes. So here's the canonical URL we talked
about last week for random website like www.example.com. And I've highlighted the slash to
connote the root of the web server, like the default folder
where, presumably, there's a file called index.html
or something else in there. Otherwise, you might have
a more explicit mention of the actual file named file.html. You can have folders, as you probably
gleaned from the most recent problem set. You can have files in
folders like this, and these are all examples of what a programmer
would typically call a path. So it might not just
be a single word, it might have multiple slashes and multiple
folders and some folders and files. But this is just more
generally known as a path. But there's another term of our,
that's essentially equivalent, that we'll introduce today. This is also synonymously
called a route, which is maybe a better generic description of what
these things are because it turns out they don't have to
map to, that is, refer to a specific folder
or a specific file, you can come up with your
own routes in a website. And just make sure that when the
user visits that, you give them a certain website page. If they visit something else, you
give them a different web page. It doesn't have to map to a very
specific file, as we'll soon see. And if you want to get input from
the user, just like Google does, like q=cats, you can add a question
mark at the end of this route. The key, or the HTTP parameter name
that you want to define for yourself, and then equal sum value that,
presumably, the human typed in. If you have more of these,
you can put an ampersand, and then more key equals value pairs
ampersand, repeat, repeat, repeat. The catch, though, is that using the
tools that we had last week alone, we don't really have the ability to
parse, that is, to analyze and extract things like q equals cats. You could have appended question
mark q equals cats or anything else to any of URLs in your home
page for problem set eight, but it doesn't actually do
anything useful, necessarily, unless you use some fancy JavaScript. The server is not going to bother
even looking in that for you. But today, we're going to
introduce using a bit of Python. And in fact, we're going to use a web
server implemented in Python, instead of using HTTP server alone,
to automatically, for you, look for any key value pairs
after the question mark and then hand them to you in
the form of a Python dictionary. Recall that a dictionary in Python, a
dict object, is just key value pairs. That seems like a perfect fit
for these kinds of parameters. And you're not going to have
to write that code yourself. It's going to be handed to you by
way of what's called a framework. So this will be the
second of two frameworks, really, that we look at in the class. And a framework is essentially
a bunch of libraries that someone else wrote
and a set of conventions, therefore, for doing things. So those of you who really
started dabbling with Bootstrap this past week to make your home
pages prettier and nicely laid out, you are using a framework. Why? Well, you're using libraries, code that
someone else wrote, like all the CSS, maybe some of the JavaScript that
the Bootstrap people wrote for you. But it's also a framework in the
sense that you have to go all in. You have to use Bootstraps
classes, and you have to lay out your divs or
your spans or your table tags in a sort of Bootstrap-friendly way. And it's not too onerous, but
you're following conventions that a bunch of humans standardized on. So similarly, in the world of
Python, is there another framework we're going to start using today. And whereas Bootstrap is
used for CSS and JavaScript, Flask is going to be used for Python. And it just solves a lot
of common problems for us. It's going to make it easier
for us to analyze the URLs and get key value pairs,
it's going to make it easier for us to find files or
images that the human wants to see when visiting our website. It's even going to make it easier
to send emails automatically, like when someone fills out a form. You can dynamically, using code,
send them an email as well. So Flask, and with it
some related libraries, it's just going to make stuff
like that easier for us. And to do this, all we have to do
is adhere to some pretty minimalist requirements of this framework. We're going to have to create a
file for ourselves called app.py, this is where our web app or
application is going to live. If we have any libraries that we want to
use, the convention in the Python world is to have a very simple text
file called requirements.txt where you list the names
of those libraries, top to bottom, in that text file,
similar in spirit to the include or the import statements that we
saw in C and Python, respectively. We're going to have a static
folder or static directory, which means any files you create that
are not ever going to change, like images, CSS files,
JavaScript files, they're going to go in this folder. And then lastly, any
HTML that you write, web pages you want the
human to see, are going to go in a folder called templates. So this is, again, evidence of
what we mean by a framework. Do you have to make a web app like this? No, but if you're using
this particular framework, this is what people decided
would be the human conventions. If you've heard of other frameworks like
Django or asp.net or bunches of others, there are just different conventions
out there for creating applications. Flask is a very nice
microframework in that that's it. All you have to do is adhere to
these pretty minimalist requirements to get some code up and running. All right, so let's go
ahead and make a web app. Let me go ahead and
switch over to VS Code here, and let me practice
what I'm preaching here by first creating app.py. And let's go ahead and create
an application that very simply, maybe, says hello to the user. So something that, initially, is not all
that dynamic, pretty static, in fact. But we'll build on that
as we've always done. So in app.py, what I'm going to do
first is exactly the line of code I had on the screen earlier. From Flask, import Flask, with a capital
F second and a lowercase f first. And I'm also going to
preemptively import a couple of functions,
render template, and request. More on those in just a bit. And then below that, I'm going
to say, go ahead and do this. Give me a web-- a
variable called app that's going to be the result of calling
the Flask function and passing in it this weird incantation here, name. So we've seen this a few weeks back
when we played around with Python and we had that if main thing
at the bottom of the screen. For now, just know that __name__
refers to the name of the current file. And so this line here, simple as
it is, tells Python, hey, Python, turn this file into a Flask application. Flask is a function that just figures
out, then, how to do the rest. The last thing I'm going to do for this
very simple web application is this. I'm going to say that I'm
going to have a function called index that takes no arguments. And whenever this
function is called, I want to return the results of rendering
a template called index.html. And that's it. So let's assume there's a file
somewhere, haven't created it yet, called index.html. But render template
means render this file that is printed to the
user's screen, so to speak. The last thing I'm going to
do is I have to tell Flask when to call this index function. And so I'm going to tell it to define
a route for, quote unquote, "slash." And that's it. So let's take a look at
what I just created here. This is slightly new syntax, and
it's really the only weirdness that we'll have today in Python. This is what's known in Python
is what's called a decorator. A decorator is a
special type of function that modifies, essentially,
another function. For our purposes, just know
that on line six this says, hey Python, define a route
for slash, the default page on my website application. The next two lines, seven
and eight, say, hey Python, define a function called
index, takes no arguments. And the only thing you should ever do is
return render template of quote unquote "index.html." All right, so that's it. So really, the next question,
naturally, should be all right, well, what is in index.html? Well, let me go ahead and do that next. Let me create a directory called
templates, practicing, again, what I preached earlier. So I'm going to create a new
empty directory called templates, I'm going to go and
CD into that directory and then do code of index.html. So here is going to be my index page. And I'm going to do a very
simple web page, doc type HTML. I'm just going to borrow
some stuff from last week. HTML language equals English. I'll close that tag. I'll then do a head tag, I'll do a meta
tag, the name of which is viewport. This makes my site recall responsive. That is, it just grows and shrink
to fit the size of the device. The initial scale for which is going
to be one, and the width of which is going to be device width. So I'm typing this out,
I have it printed here. This is stuff I typically copy paste. But then lastly, I'm going to
add in my title, which will just be hello for the name of this app. And then the body-- whoops, Bobby. The body of this tag will be-- there we go. The body of this page, rather,
will just be hello comma world. So very uninteresting and really a
regression to where we began last week. But let's go now and experiment
with these two files. I'm not going to bother
with a static folder right now, because I don't have any
other files that I want to serve up. No images, no CSS, nothing like that. And honestly, requirements.txt
is going to be pretty simple. I'm going to go requirements.txt and
just say make sure the system has access to the Flask library itself. All right, but that's the only
thing we can add in there for now. All right, so now I have two files,
app.py, and I have index.html. But index.html thank you is
inside of my templates directory so how do I actually start
a web server last week, I would have said HTTP server. But HTTP server is not a Python thing. It has no idea about Flask or
Python or anything I just wrote. HTTP server will just
spit out static files. So if I ran HTTP server, and
then I clicked on app.py, I would literally see my Python code. It would not get executed because HTTP
server is just for static content. But today, I'm going to run a
different command called Flask run. So this framework Flask that I
actually preinstalled in advance, so it wasn't strictly necessary that I
create that requirements.txt file just yet, comes with a program called Flask,
takes command line arguments like the word run, and when I do that, you'll
see somewhat similar output to last week whereby you'll see the name-- your URL for your
unique preview of that. You might see a pop up saying
that your application is running on TCP port, something or other. By default, last week,
we used port 8080. Flask, just because, prefers port 5,000. So that's fine too. I'm going to go ahead
and open up this URL now. And once it authenticates
and redirects me, just to make sure I'm allowed to access
that particular port, let me zoom in. Voila, there's the extent
of this application. If I view source by right-clicking
or control clicking, there's my HTML that's been spit out. So really, I've just reinvented
the wheel from last week because there's no dynamism
now, nothing at all. But what if I do this? Let me close the source
and let me zoom out. So you can see my URL bar. Let me zoom in now, and I have
a very unique cryptic URL. But the point is that
it ends with nothing. Or implicitly, it ends with slash. This is just Chrome
being a little helpful. It doesn't bother showing you a slash,
even though it's implicitly there. But let me do something explicit like
my name equals, quote unquote, "David." So there's a key value
pair that I've manually typed into my URL bar and hit Enter. Nothing happens, nothing changes. It still says hello, world. But the opportunity today is to
now, dynamically, get at the input from that URL and start
displaying it to the user. So let me go back over here to
my terminal window and code. Let me move that down
to the bottom there. And what if I want to
say, huh, hello, name. I ideally want to say something like-- I don't want to hard code
David because then it's never going to say hello to anyone else. I want to put like a variable name
here, like name should go here. But it's not an HTML tag, so I
need some kind of placeholder. Well, here's what I can do. If I go back to my Python code, I can
now define a variable called name. And I can ask Flask to go
into the current request, into its arguments, that is
in the URL, as they're called, and get whatever the value of
the parameter called name is. That puts that into a variable for me. And then, in render template--
this is one of those functions that can take more than one argument. If it takes another
argument, you can pass in the name of any variable you want. So if I want to pass in my name, I
can literally say name equals name. So this is the name of a variable
I want to give to the template. This is the actual variable that
I want to get the value from. And now lastly, in my index.html,
the syntax as of today in Flask, is to do two curly braces and
then put the name of the variable that you want to plug in. So here's what we mean by a template. A template is like a blueprint
in the real world, where it's plans to make something. This is the plan to make a web page
that has all of this code literally, but there's this placeholder with
two curly braces here and here that says go ahead and plug in the
value of the name variable right there. So in this sense, it's similar
in spirit to our f strings or format strings in Python. The syntax is a little different
just because reasonable people disagree, different people,
different frameworks come up with different conventions. The convention in Flask,
in their templates, is to use two curly braces here. The hope is that you, the
programmer, will never want to display two curly
braces in your actual web page. But even if you do,
there's a workaround. We can escape that. So now let me go ahead and go
back to my browser tab here. Previously, even though
I added name equals David to the end of the URL with a question
mark, it still said hello, world. But now, hopefully, if
I made these changes, let me go ahead and open
up my terminal window. Let me restart Flask so it
loads my changes by default. Let me go back to my hello tab and
click reload so it grabs the page anew from the server. And there we go, hello, David. I can play around now and I can change
the URL appear to, for instance, Carter. Zoom out, hit Enter. And now we have something more dynamic. So the new pieces here are, in
Python, we have some code here that allows us to access,
programmatically, everything that's after the
question mark in the URL. And the only thing we have to do that
is call this function request.args.get. You and I don't have
to bother figuring out where is the question mark,
where is the equal sign, where are the ampersands, potentially. The framework, Flask,
does all of that for us. OK, any questions then on
these principles thus far? Yeah, in back. AUDIENCE: Why do you say the
question mark in the URL? DAVID: Why do you need a
question mark in the URL? The short answer is just because that
is where key value pairs must go. If you're making a GET request
from a browser to a server, the convention, standardized by the
HTTP protocol, is to put them in the URL after the so-called route or
path, then a question mark. And it delineates what's
part of the root or the path, and what's part of the
human input to the right. Other questions? Yeah. AUDIENCE: Can you go over again why
the left and right in the [INAUDIBLE]?? DAVID: Sure. This is this annoying
thing about Python. When you pass in parameters,
two functions that have names, you typically say something
equals something else. So let me make a slight tweak here. How about I say name of person here. This allows me to invent my
own variable for my template and assign it the value of name. I now, though, have to go into my
index file and say name of person-- did I get that right? Name of person, yeah. So these two have to match. And so this is just stupid because
it's unnecessarily verbose. So what typically people do is they
just use the same name as the variable itself, even though it looks admittedly
stupid, but it has two different roles. The thing to the left
of the equal sign is the name of the variable you plan to use
in the template, the thing on the right is the actual value you're assigning it. And this is because its general purpose. I could override this and I could
say something like name always equals Emma, no matter
what that variable is. And now if I go back to
my browser and reload, no matter what's in the URL,
David or Carter, It's always-- OK, Emma broke the server. What did I do? Oh, I didn't change my template back. There we go. Let me change that back to be
name, so that it's name there and it's name here. But I've hardcoded
Emma's name, so now we're only ever going to see Emma no
matter whose name is in the URL. That's all. All right, so this is
bad user interface. If, in order to get a greeting
for the day, you, the user, have to manually change the
URL, which none of us ever do. This is not how web pages work. What is the more normal mechanism
for getting input from the user and putting it in that
URL automatically? How did we do that last week? With Google, if you recall. AUDIENCE: We have the search
bar and we [INAUDIBLE] you have to make something in there [INAUDIBLE]. DAVID: OK, so we did make something in
order to get the input from the user. And specifically, what was the tag
or the terminology we used last week? AUDIENCE: [INAUDIBLE]. DAVID: Sorry, a little louder? Oh, no. But yeah. AUDIENCE: Is it input? DAVID: So the input tag,
inside of the form tag. So in short, forms, or of
course, how the web works and how we typically
get input from the user, whether it's a button or a text box
or a dropdown menu or something else. So let's go ahead and add
that into the mix here. So let's enhance this hello app
to do a little something more by, this time, just doing this. Let me get rid of this
name stuff and let me just have a very simple index.html file
that, by default, is going to simply ask the user for some input as follows. I'm going to go back into my
index.html, and instead of printing out the user's name, this is the page I'm
going to use to actually get input from the user. So I'm going to create a form tag. The method I'm going to use for now
is going to be, quote unquote, "get." Then, inside of that form, I'm
going to have an input tag. And I'm going to turn off
autocomplete like we did last week. I'm going to turn on auto focus, so it
puts the cursor in the text box for me. I'm going to give the name
of this input the name, name. Not to be too confusing, but I'm
asking the human for their name. So it makes sense that the name of the
input should be, quote unquote, "name." The placeholder I want the
human to see in light gray text will be Name with a capital N,
just so it's a little grammatical. And then type of this text fiel-- type of this input is going to be text. Then I'm just going to give myself,
like last week, a submit button. And I don't care what
it says, it's just going to say the default submit terminology. Let me go ahead, now, and open
up my terminal window again. Let me go to that same URL
so that I can see-- whoops. There we go. So that was just cached from earlier. Let me go back to that same
URL, my GitHub preview.dev URL, and here I have the form. And now, I can type in anything I want. The catch, though, is when I click
Submit, where is it going to go? Well, let's be explicit. It does have a default value,
but let me go into my index.html and let me add, just like we
did last week for it, Google. Whereas previously, I said something
like www.google.com/search, but today, we're not going to rely
on some third party. I'm going to implement
the so-called backend, and I'm going to have the user
submit this form to a second route, not just slash, how about /greet. I can make it up, whatever I want. Greet feels like a nice operative word,
so /greet is where the user will be sent when they click
Submit on this form. All right, so let's go ahead now
and go back to my browser tab. Let me go ahead, actually,
and let me reload Flask here so that it reloads
all of my changes. Let me reload this tab so that I get
the very latest HTML and, indeed, quick safety check. If I view page source, we
indeed see that my browser has downloaded the latest HTML. So it definitely has changed. Let's go ahead and type in David. And when I click Submit
here, what's going to happen? Hypotheses. What's going to happen visually,
functionally, however you want to interpret when I click Submit. Yeah? AUDIENCE: [INAUDIBLE] an empty page. DAVID: OK, the user's going
to go to an empty page. Pretty good instinct, because-- no where else, if I mentioned
/greet, it doesn't seem to exist. How's the URL going to
change, just to be clear? What's going to appear,
suddenly, in the URL? Yeah? AUDIENCE: 404? DAVID: 404? No, not in the URL. Specifically in the URL, something's
going to get added automatically when I click. AUDIENCE: The key value pair? DAVID: The key value pair, right. That's how forms work. That's why our Google
trick last week worked. I sort of recreated a
form on my own website. And even though I didn't get around
to implementing google.com itself, I can still send the information
to Google just relying on browsers, standardizing-- to your question earlier, that
whenever you submit a form, it automatically ends up after
a question mark in the URL if you're using GET. So this both of you are
right, this is going to break. And all three of you are right,
in effect, 404 not found. You can see it in the tab here. That's the error that has come back. But what's interesting, and most
important, the URL did change. And it went to /greet?name=david. So I just, now, need to add
some logic that actually looks for that so-called route. So let me go back to my app.py. Let me define another route for,
quote unquote, "slash greet." And then, inside of-- under this,
let me define another function. I'll call it greet, but I
could call it anything I want. No arguments, for now,
for this, and then let me go ahead and
do this in my app.py. This time around, I do want
to get the human's name. So let me say requeste.args
get quote unquote "name", and let me store that in
a variable called name. Then let me return a
template, and you know what, I'm going to give myself
a new template, greet.html. Because this has a different
purpose, it's not a form. I want to say hello to the
user in this HTML file, and I want to pass, into it, the
name that the human just typed in. All right, so now if I go up and
reload the page, what might happen now? Other logical check here. If I go ahead and hit reload or resubmit
the form, what might happen now? Any instincts? Let me try, so let's try this. Let's go ahead and reload the page. Previously, it was not found. Now it's worse, and this is
the 500 error, internal server error that I promised next week we will
all encounter accidentally, ultimately. But here we have an
internal server error. Because it's an internal error, this
means something's wrong with your code. So the route was actually found
because it's not a 404 this time. But if we go into VS Code here and
we look at the console, the terminal window, you'll see that-- this is actually a bit misleading. Do I want to do this? Let me reload this. Let me reload here. Oh, standby. Come on. There we go. Come on. OK, here we have this
error here, and this is where your terminal window
is going to be helpful. In your terminal window,
by default, is typically going to go helpful
stuff like a log, L-O-G, of what it is the server
is seeing from the browser. For instance, here's what the
server just saw in purple. Get /greet?name=david
using HTTP version 1.0. Here, though, is the status code
that the server returned, 500. Why, what's the error? Well, here's where we get these
annoying pretty cryptic Python messages that help50 might
ultimately help you with, or here, we might just
have a clue at the bottom. And this is actually pretty
clear, even though we've never seen this error before. What did I screw up here? I just didn't create greet.html, right? Template not found. All right, so that must be
the last piece of the puzzle. And again, representative of how you
might diagnose problems like these, let me go into my terminal window. After hitting Control C, which
cancels or interrupts a process, let me go into my templates directory. If I type ls, I only have index.html. So let's code up greet.html. And in this file let's
quickly do doc type. Doc type HTML, open bracket
HTML, language equals English. Inside of this, I'll have the head tag,
inside of here, I'll have the meta. The name is viewport,
the content of which is-- I always forget this to. The content of which is initial scale
equals one, width equals device width. Quote unquote, title
is still going to be, I'll call this greet
because this is my template. And then here, in the body, I'm
going to have hello comma name. So I could have kept around the old
version of this, but I just recreated, essentially, my second template. So index.html now is almost the
same, but the title is different and it has a form. greet.html is almost the same,
but it does not have a form. It just has the hello comma name. So let me now go ahead and
rerun in the correct directory. You have to run Flask wherever app.py
is, not in your templates directory. So let me do Flask run to
get back to where I was. Let me go into my other tab. Cross my fingers this time
that, when I go back to slash and I get index.html's form, now
I type in David and click Submit, now we get hello, David. And now we have a full-fledged web
app that has two different routes, slash and /greet, the latter of
which takes input like this and then, using a template, spits it out. But something could go wrong,
and let's see what happens here. Suppose I don't type anything in. Let me go here and just click Submit. Now, I mean, it looks stupid. So there's bunches of
ways we could solve this. I could require that the user
have input on the previous page, I could have some kind
of error check for this. But there's another mechanism I
can use that I'll just show you. It turns out this GET function,
in the context of HTTP and also in general with
Python dictionaries, you can actually supply a default value. So if there is no name parameter
or no value for a name parameter, you can actually give it
a default value like this. So I'll say world, for instance. Now, let me go back here. Let me type in nothing
again and click Submit. And hopefully this time,
I'll do-- oops, sorry. Let me restart Flask
to reload the template. Let me go ahead and type nothing
this time, clicking Submit. And hopefully, we now-- Oh, interesting. I should have faked this. Suppose that the reason this-- Oh. Suppose I just get rid of name
altogether like this and hit Enter. Now I see hello, world,
and this is a subtlety that I didn't intend to get into here. When you have question
mark name equals nothing, you're passing in
what's called-- whoops. When you have greet question
mark name equals something, you actually are giving a value to name. It is quote unquote
with nothing in between. That is different from
having no value at all. So allow me to just propose
that the error here, we would want to require
this in a different way. And probably the most
robust way to do this would be to go in here, in my HTML, and
say that the name field is required. Now, if I go back to my form
after restarting Flask here, and I go ahead and click reload
on my form and type in nothing and click Submit, now the
browser is going to yell at me. But just as a teaser
for something we'll be doing in the next problem set
in terms of error checking, you should never, ever, ever rely on
client side safety checks like this. Because we know, from last week, that
a curious programmer can go to inspect, and let me poke around the HTML here. Let me go into the body, the form. OK, you say required,
I say not required. You can just delete what's
in the dom, in the browser, and now I can go ahead
and submit this form. And it appears to be broken. Not a big deal with a silly little
greeting application like this. But if you're trying to
require that humans actually provide input that is necessary for
the correct operation of the site, you don't want to trust that the HTML
is not altered by some adversary. All right, any questions,
then, on this particular app before we add another feature here? Any questions here? Yeah. AUDIENCE: Do you guys [INAUDIBLE]. DAVID: Sorry, little louder. In the index function-- AUDIENCE: Oh, sorry. [INAUDIBLE] DAVID: Sorry? AUDIENCE: [INAUDIBLE] DAVID: Would it be a problem if what? AUDIENCE: You have to [INAUDIBLE]. DAVID: No. I mean no, this is OK. What you should really do is something
we're going to do with another example where I'm going to start
error checking things. So let me wave my hands at
that and propose that we'll solve this better in just a bit. But it's not bad to do
what I just did here, it's only going to handle one of the
scenarios that I was worried about. Not all of them. All right, so even though
this is new to most of us here, consider index.html, my first
template, and consider greet.html, my second template. What might be arguably badly designed? Even though this might
be the first time you've ever touched web programming like this. What's bad or dumb about this
design of these two templates alone? And there's a reason, too, that I bored
us by typing it out that second time. Yeah? AUDIENCE: [INAUDIBLE] you said,
stuff like Notepad and [INAUDIBLE].. DAVID: Yeah, there's so much repetition. I mean, it was deliberately tedious
that I was retyping everything. The doc type, the HTML tag,
the head tag, the title tag. And little things did change
along the way, like the title and certainly, the content of the body. But so much of this, I
mean, almost all of the page is a copy of itself in multiple files. And God forbid we have a third template,
a fourth template, a hundredth template for a really big website. This is going to get very
tedious very quickly. And suppose you want to
change something in one place, you're going to have to change it now in
two, three, a hundred different places instead. So just like in
programming more generally, we have this ability to
factor out commonalities. So do you in the context
of web programming, and specifically
templating, have the ability to factor out all of
those commonalities. The syntax is going to
be a little curious, but it functionally is
pretty straightforward. Let me go ahead and do this. Let me go ahead and copy
the contents of index.html. Let me go into my templates
directory and code a file that, by default, is called layout.html. And let me go ahead, and per
your answer, copy all of those commonalities into
this file now instead. So here I have a file
called layout.html. I don't want to give every page the same
title, maybe, but for now that's OK. I'm going to call everything hello. But in the body of the page,
what I'm going to do here is just have a placeholder for actual
contents that do change. So in this layout, I'm
going to go ahead in here and just put in the body of my
page, how about this syntax? And this is admittedly new. Block body, and then percent
sign close curly brace. And then I'm going to do end block. So a curious syntax here, but
this is more template syntax. The other template syntax we saw
before was the two curly braces. That's for just plugging in values. There's this other syntax with Flask
that allows you to, say, a single curly brace, a percent sign, and then some
functionality like this defining a block. And this one's a little weird
because there's literally nothing between the close curly
and the open curly brace here. But let's see what this can do for us. Let me now go into my index.html, which
is where I borrowed most of that code from, and let me focus on
what is minimally different. The only thing that's really different
in this page, title aside, is the form. So let me go ahead and just cut
that form out to my clipboard. Let me change the first
line of index.html to say this file is going
to extend layout.html, and notice I'm using
the curly braces again. And this file is going to
have its own body block inside of which is just
the HTML that I actually want to make specific to this page. And I'll keep my indentation
nice and neat here. And let's consider what I've done. This is starting to look
weird fast, and this is now a mix of HTML with templating code. Index.html, first line now says, hey,
Flask, this file extends layout.html, whatever that is. This next line, three through
10, says, hey, Flask, here is what I consider my body block to be. Plug this into the layout placeholder. Therefore, so if I now go back
to layout.html, and layout.html, it's almost all HTML by contrast. But there is this placeholder, and
if I want to put a default value, I could say-- whoops. If I want to put a
default value, I could put a default value there just in case
some page does not have a body block. But in general, that's
not going to be relevant. So this is just a placeholder,
albeit a little verbose, that says plug in the page-specific
content right here. So if I go now into greet.html,
this one's even easier. I'm going to cut this content
and get rid of everything else. Greet.html 2 is going to extend
layouts, dot HTML extends plural, and then I'm going to have my body block
here simply be this one line of code. And then I'm going to go
ahead and end that block here. These are not HTML tags,
this is not HTML syntax. Technically, the syntax we keep
seeing with the curly braces, and these now curly braces with percent
signs, is an example of Jinja syntax, J-I-N-J-A, which is a language,
that some humans invented, for this purpose of templating. And the people who
invented Flask decided, we're not going to come
up with our own syntax, we're going to use these other
people's syntax called Jinja syntax. So again, there starts to be
at this point in the course, and really in computing, a lot of
sharing, now, of ideas and sharing of code. So Flask is using this syntax, but
other libraries and other languages might also too. All right, so now
index.html is half HTML, half templating code, Jinja syntax. Greet.html is almost all
Jinja syntax, no tags even, but because they both
extend layout.html, now I think I've improved
the design of this thing. If I go back to app.py, none
of this really needs to change. I don't change my templates
to mention layout.html, that's already implicit in the fact
that we have the extends keyword. So now if I go ahead and
open my terminal window, go back to the same folder
as app.py and do Flask run, all right, my application
is running on port 5000. Let me now go back to the /route
in my browser and hit Enter, I have this form again. And just as a little check, let
me view the source of the page that my browser is seeing. And there's all of the code. No mention of Jinja, no curly
braces, no percent signs. It's just HTML. It's not quite pretty printed in
the same way, but that's fine. Because now, we're starting to
dynamically generate websites. And by that, I mean this isn't
quite indented nicely or perfectly. That's fine. If it's indented in the
source code version, doesn't matter what the
browser really sees. Let me now go ahead and type
in my name, click Submit. I should see, yep, hello, David. Let me go ahead and view
the source of this page. And we'll see almost the same
thing with what's plugged in there. So this is, now, web programming
in the literal sense. I did not hard code a page that says
hello comma David, hello comma Carter, hello comma Emma. I hardcoded a page that has a
template with a placeholder, and now I'm using actual
logic, some code in app.py, to actually tell the server
what to send to the browser. All right, any questions,
then, on where we're at here? This is now a web application. Simple though it is, it's
no longer just a web site. Yeah? AUDIENCE: Is what we did just better
for design or for memory [INAUDIBLE]?? DAVID: It better for
design or for memory? Both. It's definitely better
for design because, truly, if we had a third page,
fourth page, I would really start just resorting to copy paste. And as you saw with home page,
often, in the head of your page, you might want to include some CSS
files like Bootstrap or something else. You might want to have
other information up there. If you had to upgrade the version of
Bootstrap or you change libraries, so you want to change
one of those lines, you would literally have to go into
three, four, a hundred different files to make one simple change. So that's bad design. And in terms of memory, yes. Theoretically, the server, because
it knows there's this common layout, it can theoretically do some
optimizations underneath the hood. Flask is probably doing that, but
not in the mode we're using it. We're using it in
development mode, which means it's typically
reloading things each time. Other questions on this application? Anything at all? All right, so let me ask a question,
not just in terms of the code design. What about the implications for privacy? Why is this maybe not the best design
for users, how I've implemented this? I've used a web form, but-- Yeah? AUDIENCE: For some reason,
you wanted your name. So these private people
could just look at the URL. DAVID: Yeah. I mean, if you have a
nosy sibling or roommate and they have access to
your laptop and they just go trolling through your
autocomplete or your history, like, literally what you typed into
a website is going to be visible. Not a big deal if it's your name, but
if it's your password, your credit card or anything else
that's mildly sensitive, you probably don't want it
ending up in the URL at all even if you're in
incognito mode or whatnot. You just don't want to expose yourself
or your users to that kind of risk. So perhaps, we can do better than that. And fortunately, this one
is actually an easy change. Let me go into my
index.html where my form is. And in my form, I can just change
the method from GET to POST. It's still going to send key
value pairs to the server, but it's not going to
put them in the URL. The upside of which is that we
can assuage this privacy concern, but I'm going to have to
make one other change too. Because now, if I go ahead and run
Flask again after making that change, and I now reload the form to make
sure I have the latest version. You should be in the habit
of going to View, Developer, View Source, or Developer
Tools just to make sure that what you're seeing in your
browser is what you intend. And yes, I do see what I wanted. Method equals POST now. Let me go ahead and type
in David and click Submit. Now I get a different error. This one is HTTP 405,
method not allowed. Why is that? Well, in my Flask application, I've
only defined a couple of routes so far. One of which is for slash,
then that worked fine. One of which is for /greet,
and that used to work fine. But apparently, what Flask is doing
is it only supports GET by default. So if I want to change this route to
support different methods, I can say, quote unquote "POST" inside
of this parameter here. So that now, I can actually
support POST, not just GET. And if I now restart Flask, so Flask
run, Enter, and I go back to this URL. Let me go back one screen
to the form, reload the page just to make
sure I have the latest even though nothing there has changed. Type David and click Submit now,
now I should see hello, world. Notice that I'm at the greet route,
but there's no mention of name equals anything in the URL. All right, so that's an
interesting takeaway. It's a simple change, but whereas GET
puts things in the URL, POST does not. But it still works so long
as you tweak the backend to look as a POST request, which
means look deeper in the envelope. It's not going to be as simple
as looking at the URL itself. Why shouldn't we just always use POST? Why not use POST everywhere? Any thoughts? Right, because it's obnoxious to
be putting any information in URLs if you're leaving these little
breadcrumbs in your history and people can poke around and see
what you've been doing. Yeah, what do you think? AUDIENCE: You're supposed
to duplicate [INAUDIBLE].. DAVID: Yeah. I mean, if you get rid of GET
requests and put nothing in the URL, your history, your autocomplete,
gets pretty less useful. Because none of the information is
there for storage, so you can't just go through the menu and hit Enter. You'd have to re-fill out the form. And there's this other
symptom that you can see here. Let me zoom out and let
me just reload this page. Notice that you'll get
this warning, and it'll look different in Safari and Firefox
and Edge and Chrome here, confirm form. args So your browser might remember what
your inputs were and that's great, but just while you're on the page. And this is in contrast to GET,
where the state is information. Like, key value pairs is
embedded in the URL itself. And if you looked at an
email I sent earlier today, I deliberately linked
to https://www.google.c om/search?q=what+time+is+it. This is, by definition, a GET
request when you click on it. Because it's going to grab the
information, the key value pair, from the URL, send it to Google
server, and it's just going to work. And the reason I sent this
via email earlier was I wanted people to very quickly be able
to check what is the current time. And so I can sort automate the process
of creating a Google search for you, but that you induce when
you click that link. If Google did not support GET, they
only supported this, the best I could do is send you all to this
URL which, unfortunately, has no useful information. I would have had to add to my
email, by the way, type in the words what time is it. So it's just bad for usability. So there, too, we might have design
when it comes to the low level code, but also the design when it comes
to the user experience, or UX, as a computer scientist would call it. Just in terms of what you want
to optimize for, ultimately. So GET and POST both have their roles. It depends on what kind of
functionality you want to provide and what kind of sensitivity
there might be around it. All right, any questions, then, on
this, our first web application? Super simple, just gets someone's
name and prints it back out. But we now have all
the plumbing with which to create really most anything we want. All right, let's go ahead
and take a five minute break. And when we come back, we'll add to
this some first year intramural sports. All right, so we are back. And recall that the last
thing we just changed was the route to use
POST instead of GET. So gone is my name and
any value in the URL. But there was a subtle bug or change
here that we didn't call out earlier. I did type David into the
form and I did click Submit, and yet here it is
saying hello comma world. So that seems to be
broken all of a sudden, even though we added support for POST. But something must be wrong. Logically, it must be the case here. Intuitively, that if I'm seeing
hello, world, that's the default value I gave the name variable. It must be that it's
not seeing a key called name in request.args, which is this. Gives you access to
everything after the URL. That's because there's this
other thing we should know about, which is not just
request.args but request.form. These are horribly named, but
request.args is for GET requests, request.form is for POST requests. Otherwise, they're pretty
much functionally the same. But the onus is on you,
the user or the programmer, to make sure you're using the right one. So I think if we want
to get rid of the world and actually see what
I, the human, typed in, I think I can just change
request.args to request.form. Still dot get, still
quote unquote "name," and now, if I go ahead and rerun
Flask in my terminal window, go back to my browser, go
back to-- and actually, I won't even go back to the form. I will literally just reload,
Command R or Control R, and what this warning is
saying is it's going to submit the same information to the website. When I click Continue, now I
should see hello comma David. So again, you, too, are
going to encounter, probably, all these little subtleties. But if you focus on, really, the
first principles of last week, like what it HTTP, how
does it get request work, how does a POST request
work now, you should have a lot of the mental
building blocks with which to solve problems like these. And let me give you one other mental
model, now, for what it is we're doing. This framework called Flask is just an
example of many different frameworks that all implement the same
paradigm, the same way of thinking and the same way of
programming applications. And that's known as MVC,
model view controller. And here's a very simple diagram
that represents the process that you and I have been implementing thus far. And actually, this is more than
we've been implementing thus far. In app.py is what a programmer
would typically call the controller. That's the code you're
writing, this are called business logic that makes all of the
decisions, decides what to render, what values to show, and so forth. In layout.html, index.html, greet.html
is the so-called view templates that is the visualizations
that the human actually sees, the user interface. Those things are dumb, they pretty
much just say plop some values here. All of the hard work is done in app.py. So controller, AKA app.py, is where
your Python code generally is. And in your view is where your HTML and
your Jinja code, your Jinja templating, the curly braces, the curly braces
with percent signs, usually is. We haven't added an M
to MVC yet model, that's going to refer to things
like CSV files or databases. The model, where do you keep
actual data, typically long term. So we'll come back to
that, but this picture, where you have one of these-- each of
these components communicating with one another is representative of
how a lot of frameworks work. What we're teaching today, this week,
is not really specific to Python. It's not really specific to Flask,
even though we're using Flask. It really is a very
common paradigm that you could implement in Java, C sharp, or
bunches of other languages as well. All right, so let's now
pivot back to VS Code here. Let me stop running
Flask, and let me go ahead and create a new folder altogether
after closing these files here. And let me go ahead and create
a folder called FroshIMS, representing freshman intramural
sports or first year intramural sports that I can now CD into. And now I'm going to code an app.py. And in anticipation, I'm going to
create another templates directory. This one in the FroshIMS folder. And then in my templates directory,
I'm going to create a layout.html. and I'm just going to
get myself started here. FroshIMS will go here. I'm just copying my layout
from earlier because most of my interesting work, this time, is
now going to be, initially, in app.py. So what is it we're creating? So literally, the very first
thing I wrote as a web application 20 years ago, was a site that
literally looked like this. So I was like a sophomore
or junior at the time. I'd taken CS50 and a
follow-on class only. I had no idea how to do web programming. Neither of those two courses taught
web programming back in the day. So I taught myself, at the
time, a language called Perl. And I learned a little something about
CSV files, and I sort of read enough-- can't even say googled enough,
because Google didn't come out for a couple of years later. Read enough online to figure out how to
make a web application so that students on campus, first years,
could actually register via a website for intramural sports. Back in my day, you would
literally fill out a piece of paper and then walk it across the yard to
Wigglesworth Hall, one of the dorms, slide it under the dorm
of the Proctor or RA, and thus you were
registered for sports so. 1996, 1997. We could do better by then. There was an internet,
just wasn't really being used much on campus or more generally. So background images
that repeat infinitely was in vogue, apparently, at the time. All of this was like images
that I had to hand make because we did not have the features
that JavaScript and CSS nowadays have. So it was really just HTML, and it was
really just controller code written, not in Python, but in Perl. And it was really just
the same building blocks that we hear already today now have. So we'll get rid of all of
the imagery and focus more on the functionality and the
aesthetics, but let's see if we can whip up a web
application via which someone could register for one such intramural sport. So in app.py, me go ahead and
import some familiar things now. From Flask, let's import
capital Flask, which is that function we need to kick
everything kick start everything. Render templates, so we have the
ability to render, that is print out, those templates, and request
so that we have the ability to get at input from the human. Let me go ahead and create
the application itself using this magical incantation here. And then let's go ahead and define a
route for slash for instance first. I'm going to define a
function called index. But just to be clear, this
function could be anything. Foo, bar, baz, anything else. But I tend to name
them in a manner that's consistent with what
the route is called. But you could call it
anything you want, it's just the function that will get
called for this particular route. Now, let me go ahead here
and just get things started. Return, render template of index.html. Just keep it simple, nothing more. So there's nothing really
FroshIM specific about this here, I just want to make sure I'm
doing everything correctly. Meanwhile, I've got my layout. OK, let me go ahead, and in my
templates directory, code a file called index.html. And let's just do extends
layout.html at the top just so that we get
benefit from that template. And down here, I'm just
going to say to do. Just so that I have something
going on visually to make sure I've not screwed up yet. In my FroshIMS directory,
let me do Flask run. Let me now go back to my previous URL,
which used to be my hello example. But now, I'm serving
up the FroshIM site. Oh, and I'm seeing nothing. That's because I
screwed up accidentally. What did I do wrong in index.html? What am I doing wrong? This file extends layout.html, but-- AUDIENCE: You left out the block tag? DAVID: Yeah. I forgot to tell Flask what
to plug into that layout. So I just need to say block body, and
then in here, I can just say to do or whatever I want to
eventually get around to. Then end the block. Let me end this tag here. OK, so now it looks ugly, more cryptic. But this is, again, the
essence of doing templating. Let me now restart Flask up
here, let me go back to the page. Let me reload. Crossing my fingers this
time, and there we go. To do. So it's not the application
I want, but at least I know I have some of the
plumbing there by default. All right, so if I want the
user to be able to register for one of these sports,
let's enhance, now, index.html to actually
have a form that's maybe got a dropdown menu for all of
the sports for which you can register. So let me go into this template here. And instead of to do, let's
go ahead and give myself, how about an H1 tag that just says
register so the user knows what it is they're looking at. How about a form tag
that's going to use POST, just because it's not really necessary
to put this kind of information in the URL. The action for that, how
about we plan to create a register route so that we're sending
information from to a register route. So we'll have to come back to that. In here, let me go ahead and create,
how about an input with autocomplete equals off, auto focus on. How about a name equals
name, because I'm going to ask the student for their
name using placeholder text of quote unquote "name." And the type of this box will be text. So this is pretty much
identical to before. But if you've not seen this
yet, let's create a select menu, a so-called dropdown menu in HTML. And maybe the first option
I want to be in there is going to be, oh, how
about the current three sports for the fall, which are
basketball, and another option is going to be soccer,
and a third option is going to be ultimate frisbee for
first year intramurals right now. So I've got those three options. I've got my form. I haven't implemented my route yet,
but this feels like a good time to go back now and check
if my form has reloaded. So let me go ahead and
stop and start Flask. You'll see there's ways to
automate the process of restarting the server that we'll do for
you for problem set nine, so you don't have to
keep stopping Flask. Let me reload my index route
and OK, it's not that pretty. It's not though, maybe-- nor was this. But it now has at least
some functionality where I can type in my name
and then type in the sport. Now, I might be biasing
people toward basketball. Like UX wise, user experience
wise, it's obnoxious to precheck basketball but not the others. So there's some little
tweaks we can make there. Let me go back into index.html. Let me create an empty option up here
that, technically, this option is not going to have the name of any sports. But it's just going to have a
word I want the human to see, so I'm actually going to disable this
option and make it selected by default. But I'm going to say sport up here. And there's different ways to do this,
this is just one way of creating, essentially, a-- whoops, option. Yep, that looks right. Creating a placeholder
sports so that the user sees something in the dropdown. Let me go ahead and restart
Flask, reload the page, and now it's just going
to be marginally better. Now you see sport that's
checked by default, but you have to check one of
these other ones ultimately. All right, so that's pretty good. So let me now type in David. I'll register for ultimate frisbee. OK, I definitely forgot something. Submit button. So let's add that. All right, so input type equals submit. All right, let's put that in. Restart Flask, reload. Getting better. Submit could be a little prettier. Recall that we can change some of
these HTTP-- these HTML attributes. The value of this button
should be register, maybe, just to make things a little prettier. Let me now reload the page and register. All right, so now we really have
the beginnings of the user interface that I created some years ago to let
people actually register for the sport. So let's go, now, and create maybe
the other route that we might need. Let me go into app.py. And in here, if we want to
allow the user to register, let's do a little bit of error checking
which I promised we'd come back to. What could the user do wrong? Because assume that they will. One, they might not type their name. Two, they might not choose a sport. So they might just submit an empty form. So that's two things we
could check for, just so that we're not scoring bogus
entries in our database, ultimately. So let's create another
route called greet, /greet. And then in this route, let's
create a function called greet but can be called anything we want. And then let's go ahead, and in
the greet function, let's go ahead and validate the submission. So a little comment to myself here. How about if there is not a
request.form GET name value, so that is if that
function returns nothing, like quote unquote, or the
special word none in Python. Or request.form.get"sport" not
in quote unquote, what were they? Basketball, the other one was soccer,
and the last was ultimate frisbee. Getting a little long, but notice
what I'm-- the question I'm asking. If the user did not
give us a name, that is, if this function returns
the equivalent of false, which is, quote unquote, or literally
none if there's no such parameter. Or if the sport the user provided is
not some value in basketball, soccer, or ultimate frisbee, which I've defined
as a Python list, then let's go ahead and just yell at the user in some way. Let's return render
template of failure.html. And that's just going to be some
error message inside of that file. Otherwise, if they get
this far, let's go ahead and confirm registration
by just returning-- whoops, returning render template quote
unquote "success" dot HTML. All right, so a couple
quick things to do. Let me first go in and in
my templates directory, let's create this failure.html file. And this is just meant to
be a message to the user that they fail to provide
the information correctly. So let me go ahead and in failure.html. not repeat my past mistake. So let me extend layout.html and in
the block body, you are not registered. I'll just yell at them like that so
that they know something went wrong. And then let me create one other
file called success.html, that similarly is mostly just Jinja syntax. And I'm just going to say for
now, even though they're not technically registered in any
database, you are registered. That's what we mean by success. All right, so let me go ahead,
and back in my FroshIMS, directory run Flask run. Let me go back to the form and reload. Should look the same. All right, so now let me
not cooperate and just immediately click Register impatiently. OK, what did I do wrong. Register-- oh, I'm
confusing our two examples. All right, I spotted the error. What did I do wrong? Unintentional. There's where I am, what did
I actually invent over here? Where did I screw up? Anyone? AUDIENCE: Register, not greet. DAVID: Thank you. So register, not greet. I had last example on my mind,
so the route should be register. Ironically, the function could be greet,
because that actually doesn't matter. But to keep ourselves sane, let's
use the one and the same words there. Let me go ahead now and
start Flask as intended. Let me reload the form just
to make sure all is working. Now, let me not cooperate and be
a bad user, clicking register-- oh my God. OK, other unintended mistake. But this one we've seen before. Notice that by default,
route only support GET. So if I want to
specifically support POST, I have to pass in, by a methods
parameter, a list of allowed route methods that could be
GET comma POST, but if I don't have no need for a GET in
this context, I can just do POST. All right, now let's
do this one last time. Reload the form to make sure
everything's OK, click Register, and you are not registered. So it's catching that. All right, let me go ahead and
at least give them my name. Register. You are not registered. Fine, I'm going to go ahead and be
David with ultimate frisbee register. Huh. OK. What should I-- what
did I mean to do here? All right, so let's figure this out. How to debug something like this,
which is my third and final unintended, unforced error? How can we go about
troubleshooting this? Turn this into the teachable moment. All right, well first,
some safety checks. What did I actually submit? Let me go ahead and view page
source, a good rule of thumb. Look at the HTML that you
actually sent to the user. So here, I have an
input with a name name. So that's what I
intended, that looks OK. Ah, I see it already, even
though you, if you've never used a select menu, you might
not know what, apparently, is missing from here that I
did have for my text input. Just intuitively, logically. What's going through my
head, embarrassingly, is, all right, if my form thinks
that it's missing a name or a sport, how did I create a situation in which
name is blank or sport is blank? Well, name, I don't think
it's going to be blank because I explicitly gave
this text field a name name and that did work last time. I've now given a second input
in the form of the select menu. But what seems to be missing here
that I'm assuming exists here? It's just a dumb mistake I made. What might be missing here? If request.form gives you all of
the inputs that the user might have typed in, let me
go into my actual code here in my form and name equals sport. I just didn't give a name to that input. So it exists, and the
browser doesn't care. It's still going to
display the form to you, it just hasn't given it a unique name
to actually transmit to the server. So now, if I'm not going
to put my foot in my mouth, I think that's what I did wrong. And again, my process
for figuring that out was looking at my code,
thinking through logically, is this right, is this right? No, I was missing the name there. So let's run Flask,
let's reload the form just to make sure it's all defaults
again, type in my name and type in ultimate frisbee, crossing
my fingers extra hard this time. And there. You are registered. So I can emphasize-- I did not intend to
screw up in that way, but that's exactly the right
kind of thought process to diagnose issues like this. Go back to the basics, go back to what
HTTP and what HTML forms are all about, and just rule things in and out. There's only a finite number of
ways I could have screwed that up. Yeah? AUDIENCE: Are you [INAUDIBLE]. DAVID: Excuse-- say a little louder? AUDIENCE: I don't understand why
name equals sport [INAUDIBLE].. DAVID: Why did name equal
sport address the problem? Well, let's first go back to the HTML. Previously, it was just the reality that
I had this user input dropdown menu, but I never gave it a name. But names, or more
generally, key value pairs, is how information is sent
from a form to the server. So if there's no name, there's no key to
send, even if the human types a value. It would be like nothing equals ultimate
frisbee, and that just doesn't work. The browser is just
not going to send it. However, in app.py, I was naively
assuming that in my requests form, there would be a name called
quote unquote "sport." It could have been anything,
but I was assuming it was sport. But I never told the form that. And if I really wanted to dig in,
we could do a little something more. Let me go back to the
way it was a moment ago. Let me get rid of the name
of the sport dropdown menu. Let me rerun Flask down here
and reload the form itself after it finishes being served. And now, let me do this. View Developer Tools, and then let me
watch the Network tab, which recall, we played around with
a little bit last week. And we also played around with Curl,
which let us see the HTTP requests. Here's another-- here's
what I would have done if I still wasn't seeing the error
and was really embarrassed on stage. I would have typed in my name as before,
I would have chosen ultimate frisbee. I would have clicked register. And now, I would have
looked at the HTTP request. And I would click on Register here. And just like we did last week, I
would go down to the request down here. And there's a whole lot of stuff
that we can typically ignore. But here, let me zoom
in, way at the bottom, what Chrome's developer
tools are doing for me, it's showing me all of the
form data that was submitted. So this really would have
been my telltale clue. I'm just not sending the sport,
even if the human typed it in. And logically, because
I've done this before, that must mean I didn't
give the thing a name. But another good tool. Like good programmers, web developers
are using these kinds of tools all the time. They're not writing bug-free code. That's not the point to get to. The point to get to is
being a good diagnostician, I would say, in these cases. OK, other questions on this? Yeah. AUDIENCE: What if you want to
edit one HTML in CSS, [INAUDIBLE].. DAVID: I'm sorry, a little bit louder? AUDIENCE: If you want to
edit in CSS or anything, in HTML, once you have to fix
the template, how do you that? DAVID: So how would you edit
CSS if you have these templates? That process we'll
actually see before long. It's almost going to be the exact same. Just to give you a teaser for this,
and you'll do this in the problem set, but we'll give you some distribution
code to automate this process. You can absolutely still
do something like this. Link href equals quote
unquote "styles" dot CSS rel equals style sheet, that's one
of the techniques we showed last week. The only difference today, using Flask,
is that all of your static files, by convention, should go
in your static folder. So the change you would
make in your layout would be to say that styles dot
CSS is in your static folder. And then, if I go into
my FroshIMS directory, I can create a static folder. I can CD into it,
nothing's there by default. But if I now code a
file called styles.css, I could now do something like this body. And in here, I could say background
color, say FF0000 to make it red. Let me go ahead now and restart
Flask in the FroshIMS directory. Cross my fingers because
I'm doing this on the fly. Go back to my form and reload. Voila, now we've tied together
last week's stuff as well. If I answered the right question? AUDIENCE: [INAUDIBLE] change
one page and not the other. DAVID: If you want to change one page
and not the other in terms of CSS? AUDIENCE: Yes. DAVID: That depends. In that case, you might want to have
different CSS files for each page if they're different. You could use different classes in one
template than you did in the other. There's different ways to do that. You could even have a
placeholder in your layout that allows you to plug in
the URL of a specific style sheet in your individual files. But that starts to get
more complicated quickly. So in short, you can absolutely do it. But typically, I would
say most websites try not to use different style Sheets per page. They reuse the styles
as much as they can. All right, let me go ahead
and revert this real quick. And let's start to add a little
bit more functionality here. I'm going to go ahead and just
remove the static folder just so as to not complicate things just yet. And let's go ahead and just play
around with a different user interface mechanism. In my form here, the dropdown
menu is perfectly fine. Nothing wrong with it. But suppose that I wanted to
change it to checkboxes instead. Maybe I want students to be able to
register for multiple sports instead. Well, it might make sense to
clean this up in a couple of ways. And let's do this. Before we even get into the checkboxes,
there's one subtle bad design here. Notice that I've hardcoded basketball,
soccer, and ultimate frisbee here. And if you recall, in app.py, I also
enumerated all three of those here. And any time you see copy paste
or the equivalent thereof, feels like we could do better. So what if I instead do this. What if I instead give myself
a global variable of Sports, I'll capitalize the word
just to connote that it's meant to be constant even though
Python does not have constants, per se. The first sport will be basketball. The second will be soccer. The third will be ultimate frisbee. Now I have one convenient
place to store all of my sports if it changes next semester
or next year or whatnot. But notice what I could do to. I could now do something like this. Let me pass into my
index template a variable called sports that's equal to
that global variable sports. Let me go into my index now,
and this is really, now, going to hint at the power of
templating and Jinja, in this case here. Let me go ahead and get rid of all
three of these hard coded options and let me show you some slightly
different syntax for sport, in sports. Then end for. We've not seen this end for syntax. There's like end block syntax,
but it's as simple as that. So you have a start and an end to your
block without indentation mattering. Watch what I can do here. Option curly brace
sport close curly brace. Let me save that. Let me go back into my
terminal window, do Flask run. And if I didn't mess up
here, let me go back to this. The red's going to go away
because I deleted my CSS. And now I still have a sport
dropdown and all of those sports are still there. I can make one more improvement now. I don't need to mention these
same sports manually in app.py. I can now just say if the
user's inputed sport is not in my global variable, sports,
and ask the same question. And this is really
handy because if there's another sport, for instance, that
gets added, like say football, all I have to do is
change my global variable. And if I reload the form now
and look in the dropdown, boom, now I have support for a fourth sport. And I can keep adding and adding there. So here's where templating starts
to get really powerful in that now, in this template, I'm using
Jinja's for loop syntax, which is almost identical to
Python here, except you need the curly brace and the percent
sign and you need the weird ending and for. But it's the same idea as in Python. Iterating over something with a for loop
lets you generate more and more HTML. And this is like every
website out there. For instance, Gmail. When you visit your inbox and you
see all of this big table of emails, Google has not hardcoded
your emails manually. They have grabbed them from a database. They have some kind
of for loop like this, and are just outputting table row after
table row or div after div dynamically. All right, so now, let's go
ahead and change this, maybe, to, oh, how about little
checkboxes or radio buttons. So let me go ahead and do this. Instead of a select menu, I'm going to
go ahead and do something like this. For each of these sports let me go
ahead and output, not an option, but let me go ahead and
output an input tag, the name for which is quote
unquote "sport," the type of which is checkbox, the value of which is
going to be the current "sport," quote unquote, and then afterward
I need to redundantly, seemingly, output the sport. So you see a word next to the checkbox. And we'll look at the result
of this in just a moment. So it's actually a little simpler
than a select menu, a dropdown menu, because now watch what
happens if I reload my form. Different user interface,
and it's not as pretty, but it's going to allow users to sign
up for multiple sports at once now, it would seem. Now I can click on basketball
and football and soccer or some other combination thereof. If I view the page's source, this
is, again, the power of templating. I didn't have to type out four
inputs, I got them now automatically. And these things all have
the same name, but that's OK. It turns out with Flask, if it sees
multiple values for the same name, it's going to hand them back to you as
a list if you use the right function. All right, but suppose we don't want
users registering for multiple sports. Maybe capacity is an issue. Let me go ahead and change this
checkbox to radio button, which a radio button is mutually exclusive. So you can only sign up for one. So now, once I reload
the page, there we go. It now looks like this. And because I've given each of these
inputs the same name, quote unquote, "sport," that's what makes
them mutually exclusive. The browser knows all four of
these things are types of sports, therefore I'm only going to let
you select one of these things. And that's simply because
they all have the same name. Again, if I view page source, notice
all of them, name equal sport, name equals sport, name equals
sport, but what differs is the value that each one is going to have. All right, any questions,
then, on this approach? All right. Well, let me go ahead and
open a version of this that I made in advance that's going
to now start saving the information. So thus far, we're
not quite at the point of where this website was, which
actually allowed the proctors to see, like in a database, everyone
who had registered for sports. Now, we're literally telling
students you are registered or you are not registered,
but we're literally doing nothing with this information. So how might we go
about implementing this? Well, let me go ahead
and close these tabs, and let me go into what I call version
three of this in the code for today. And let me go into my source
nine directory, FroshIMS3, and let me go ahead and open up app.py. So this is a premade version. I've gotten rid of
football, in this case. But I've added one
thing at the very top. What's, in English, does
this represent on line seven? What would you describe
what that thing is? What are we looking at? What do you think? AUDIENCE: It's an empty dictionary. DAVID: Yeah, it's an
empty dictionary, right? Registrants is apparently
a variable on the left. It's being assigned an empty
dictionary on the right. And a dictionary, again,
is just key value pairs. Here, again, is where dictionaries
are just such a useful data structure. Why? Because this is going to allow me
to remember that David registered for ultimate frisbee, Carter
registered for soccer, Emma registered for something else. You can associate keys with
values, names with sports, assuming a model where you can only
register for one sport for now. And so let's see what the
logic is that handles this. Here in my register route
in the code I've premade, notice that I'm validating
the user's name. Slightly differently from
before but same idea. I'm using request.form.get
to get the human's name. If not name, so if the
human did not type a name, I'm going to output error.html. But notice I've started to make
the user interface more expressive. I'm telling the user, apparently,
with a message what they did wrong. Well how? I'm apparently passing
to my error template, instead of just failure.html,
a specific message. So let's go down this rabbit hole. Let me actually go into
templates/error.hml, and sure enough, here's a new file I created here, that
adorably is apparently going to have a grumpy cat as part of the error
message, but notice what I've done. In my block body I've got an H1 tag
that just says error, big and bold. I then have a paragraph
tag that plugs in whatever the error message is that the
controller, app.py, is passing in. And then just for fun, I have a
picture of a grumpy cat connoting that there was, in fact, an error. Let's keep looking. How do I validate sport? I do similarly
request.form.get of sport, and I store it in a
variable called sport. If there's no such sport, that is the
human did not check any of the boxes, then I'm going to render
error.html two, but I'm going to give a different
message, missing sport. Else, if the sport they did type in
is not in my sports global variable, I'm going to render error.html,
but complain differently, you gave me an invalid sport somehow. As if a hacker went into
the HTML of the page, changed it to add their
own sport like volleyball. Even though it's not offered,
they submitted volleyball. But that's OK, I'm rejecting it, even
though they might have maliciously tried to send it to me by
changing the dom locally. And then really, the magic is just this. I remember that this
person has registered by indexing into the
registrant dictionary using the name the human typed in as the
key and assigning it a value of sport. Why is this useful? Well, I added one final route here. I have a /registrants route with a
registrants function that renders a template called registrants.html. But it takes as input that
global variable just like before. So let's go down this rabbit hole let me
go into templates registrants dot HTML. Here's this template. It looks a little crazy big,
but it extends the layout. Here comes the body. I've got an H1 tag that says
registrants, big and bold. Then I've got a table
that we saw last week. This has a table head that just
says name sport for two columns. Then it has a table body where in,
using this for loop in Jinja syntax, I'm saying, for each name
in the registrants variable, output a table row, start tag,
and end tag, inside of which, two table datas, two
cells, table data for name, table data for registrants bracket name. So it's very similar to Python syntax. It essentially is Python syntax, albeit
with these curly braces and the percent sign. So the net effect here is what? Let me open up my terminal
window, run Flask run. Let me now go into the
form that I premade here. So gone is football. Let me go ahead and type in David. Let me choose, oh, no sport. Register. Error, missing sport. And there is the grumpy cat. So missing sport, though,
specifically was outputed. All right, fine. Let me go ahead and say no name. But I'll choose basketball. Register. Missing name. All right, and let me
maliciously, now, do this. Now I'm hacking. Let me go into this. I'll type my name, sure, but let
me go into the body tag down here. Let me maliciously go down in ultimate
frisbee, heck with that, let's volleyball. Change that and change
this to volleyball. Enter. So now, I can register for
any sport I want to create. Let me click register,
but invalid sports. So again, that speaks to
the power and the need for checking things on backend
and not trusting users. It is that easy to hack websites
otherwise if you're not validating data server side. All right, finally, let's
just do this for real. David is going to register
for ultimate frisbee. Clicking register. And now, the output is not
very pretty, but notice I'm at the registrants route. And if I zoom out, I have an HTML table. Two columns, name and sport,
David and ultimate frisbee. Let me go back to the form, letting me
pretend Carter walked up to my laptop and registered for basketball. Register. Now we see two rows in this table,
David, ultimate frisbee, Carter, basketball. And if we do this one
more time, maybe Emma comes along and registers
for soccer register. All of this information is being
stored in this dictionary, now. All right, so that's great. Now we have a database, albeit in
the form of a Python dictionary. But why is this, maybe, not
the best implementation? Why is it not great? Yeah. AUDIENCE: You are storing [INAUDIBLE]. DAVID: Yeah. So we're only storing this
dictionary in the computer's memory, and that's great until I hit
Control C and kill Flask, stopping the web server. Or the server reboots, or maybe
I close my laptop or whatever. If the server stops running,
memory is going to be lost. RAM is volatile. It's thrown away when you lose
power or stop the program. So maybe this isn't the best approach. Maybe it would be better
to use a CSV file. And in fact, some 20 years ago,
that's literally what I did. I stored everything in a CSV file. But let's skip that step,
because we already saw last week, or a couple of weeks ago
now, how we can use SQLite. Let's see if we can't
marry in some SQL here to store an actual
database for the program. Let me go back here and
let me open up, say, version four of this, which
is almost the same but it adds a bit more functionality. Let me close these tabs and let me
open up app.py now in version four. So notice it's almost
the same, but at the top, I'm creating a database connection
to a database called FroshIMS.db. So that's a database
I created in advance. So let's go down that rabbit hole. What does it look like? Let me make my terminal window bigger. Let me run SQLite 3 of FroshIMS.db. OK, I'm in. Let's do .schema. and let's just infer what
I designed this to be. I have a table called registrants,
which has one, two, three columns. An ID column that's an integer, a name
column that's text but cannot be null, and a sport column that's
also text, cannot be null, and the primary key is just ID. So that I have a unique
ID for every registration. Let's see if there's
anyone in there yet. Select star from registrants. OK, there's no one in there. No one is yet registered for sports. So let's go back to the
code and continue on. In my code now, I've got
the same global variable for validation and
generation of my HTML. Looks like my index route is the same. It's dynamically generating
the menu of sports. Interestingly, we'll come back to this. There's a deregister route
that's going to allow someone to deregister themselves if
they want to exit the sport or undo their registration. But this is the juicy part. Here's my new and
improved register route. Still works on POST, so
some mild privacy there. I'm validating the
submission as follows. I'm getting the user's inputted
name, the user's inputted sport, and if it is not a name or
the sport is not in sports, I'm going to render failure.html. So I kept it simple. There's no cat in this version. It just says failure. Otherwise, recall how we
co-mingled SQL and Python before. We're using CS50's SQL
library, but that just makes it a little easier to execute
SQL queries and we're executing this. Insert into registrants
name comma sport. What two values, the name and the
sport, that came from that HTML form. And then lastly, and this is a new
function that we're calling out explicitly now, Flask
also gives you access to a redirect function, which is how
safetyschool.org, Harvardsucks.org, and all these other sites we
played around with last week we're all implemented redirecting
the user from one place to another. This Flask function
redirect comes from my just having imported it at the
very top of this file. It handles the HTTP 301 or 302 or 307
code, whatever the appropriate one is. It does that for me. All right, so that's it for
registering via this route. Let's look at what the
registrant's route is. Here, we have a new
route for /registrants. And instead of just iterating
over a dictionary like before, we're getting back, let's
see, db.execute of select star from registrants. So that's literally the programmatic
version of what I just did manually. That gives me back a
list of dictionaries, each of which represents
one row in the table. Then, I'm going to render
register and start HTML, passing in literally
that list of dictionaries just like using CS50's
library in the past. So let's go and look
at these-- that form. If I go into templates and
open up registrants.html, oh, OK, it's just a table like before. And actually, let me change this
syntactically for consistency. We have a Jinja for loop that
iterates over each registrant and for each of them,
outputs a table row. Oh, but this is interesting. Instead of just having two columns
with the person's name and sport, notice that I'm also
outputting a full-fledged form. All right, this is
starting to get juicy. So let's actually go back
to my terminal window, run Flask, and actually see what
this example looks like now. Let me reload the page. All right. In the home page, it
looks exactly the same. But let me now register for something. David for ultimate frisbee, register. Oh, damn it. Let's try this again. David registering for
ultimate frisbee, register. OK. So good thing I have deregister. So this is what it should now look like. I have a page at the route called
/registrants that has a table with two columns, name and sport,
David, ultimate frisbee. But oh, wait, a third column. Why? Because if I view the page source,
notice that it's not the prettiest UI. For every row in this table, I'm also
going to be outputting a form just to deregister that user. But before we see how that works,
let me go ahead and register Carter, for instance. So Carter will give you basketball. Again, register. The table grows. Now, let me go back and let's
register Emma for soccer. And the table should grow. Before we look at that HTML, let's
go back to my terminal window. Let's go into SQLite FroshIMS. Let me go into FroshIMS, and let me
open up with SQLite 3 FroshIMS.db. And now do select star from registrants. And whereas, previously, when I executed
this there were zero people, now there's indeed three. So now we see exactly what's
going on underneath the hood. So let's look at this
form now-- this page now. If I want to unregister, deregister
one of these people specifically, how do we do this? Clicking one of those
buttons will indeed delete the row from the database. But how do we go about linking a web
page with Python code with a database? This is the last piece of the puzzle. Up until now, everything's been
with forms and also with URLs. But what if the user is
not typing anything in, they're just clicking a button? Well, watch this. Let me go ahead and
sniff the traffic, which you could be in the habit of doing now. Any time you're curious how a website
works, let me go to the Network tab. And Carter, shall we
deregister you from basketball? Let's deregister Carter and
let's see what just happened. If I look at the deregister
request, notice that it's a POST. The status code that
eventually came back as 302, but let's look at the request itself. All the headers there we'll ignore. The only thing that
button submits, cleverly, is an ID parameter, a key equaling two. What does two presumably
represent or map to? Where did this two come from? It doesn't say Carter, it
doesn't say basketball? What is it? AUDIENCE: The second
person who registered. DAVID: The second
person that registered. So those primary keys that we started
talking about a couple of weeks ago, why it's useful to be able to
uniquely identify a row in a table, here is just one of the reasons why. If it suffices for me just to
send the ID number of the person I want to delete from the database,
because I can then have code like this. If I go into app.py and I look at my
deregister route now, the last of them, notice that I got this. I first go into the form, and I get
the ID that was submitted, hopefully. If there was, in fact, an ID, and
the form wasn't somehow empty, I execute this line of code. Delete from registrants where
ID equals question mark, and then I plug-in that number,
deleting Carter and only Carter. And I'm not using his name, because
what if we have two people named Carter, two people named Emma or David? You don't want to delete both of them. That's why these unique
IDs are so, so important. And here's another reason why. You don't want to store
some things in URLs. Suppose we went to this
URL, deregister?ID=3. Suppose I, maliciously,
emailed this URL to Emma. It doesn't matter so much
what the beginning is, but supposed I emailed her this URL,
/deregister?ID=3, and I said, hey, Emma, click this. And it uses GET instead of POST. What did I just trick her into doing? What's going to happen
if Emma clicks this? Yeah? AUDIENCE: Deregistering? DAVID: You would trick her
into deregistering herself. Why? Because if she's logged
into this FroshIMS website, and the URL contains her ID just
because I'm being malicious, and she clicked on it and
the website is using GET, unfortunately, GET URLs
are, again, stateful. They have state information in the URLs. And in this case, it's enough
to delete the user and boom, she would have accidentally
deregistered herself. And this is pretty innocuous. Suppose that this was
her bank account trying to make a withdrawal or a deposit. Suppose that this were some
other website, a Facebook URL, trying to trick her into
posting something automatically. Here, too, is another
consideration when you should use POST versus GET,
because GET requests can be plugged into emails sent via Slack
messages, text messages, or the like. And unless there's a
prompt saying, are you sure you want to deregister
yourself, you might blindly trick the user into being
vulnerable to what's called a cross-site request forgery. A fancy way of saying you
trick them into clicking a link that they shouldn't have, because
the website was using GET alone. All right, any question, then,
on these building blocks? Yeah. AUDIENCE: What do the first
thing in the instance of the SQL [INAUDIBLE] where they
have three slashes? What does that mean? DAVID: When three columns, you mean? AUDIENCE: No, three forward slashes. DAVID: The three forward slashes. I'm not sure I follow. AUDIENCE: Yeah, so I
think it's in [INAUDIBLE].. DAVID: Sorry, it's in where? Which file? AUDIENCE: It's in [INAUDIBLE] scroll up. [INAUDIBLE] DAVID: Sorry, the other direction? AUDIENCE: Yeah. DAVID: OK. AUDIENCE: [INAUDIBLE]. So please scroll a little bit more. DAVID: Keep scrolling more? Oh, this thing. OK, sorry. This is a URI, it's typical syntax
that's referring to the SQLite protocol, so to speak, which means
use SQLite to talk to a file locally. :// is just like you and I see in URLs. The third slash, essentially,
means current folder. That's all. So it's a weird curiosity,
but it's typical whenever you're referring to a
local file and not one that's elsewhere on the internet. That's a bit of an oversimplification,
but that's indeed a convention. Sorry for not clicking earlier. All right, let's do one
other iteration of FroshIMS here just to show what I was
actually doing too, back in the day, was not only storing these
things in CSV files, as I recall. I was also automatically
generating an email to the proctor in charge of
the intramural sports program so that they would have sort of a
running history of people registering and they could easily
reply to them as well. Let me go into FroshIMS version
five, which I precreated here, and let me go ahead and open
up, say, app.py this time. And this is some code
that I wrote in advance. And it looks a little scary at first
glance, but I've done the following. I have now added the Flask
mail library to the picture by adding Flask mail to
requirements.txt and running a command to automatically install email
support for Flask as well. And this is a little bit
cryptic, but it's honestly mostly copy paste from the documentation. What I'm doing here is
I'm configuring my Flask application with a few configuration
variables, if you will. This is the syntax for that.
app.config is a special dictionary that comes with Flask that is automatically
created when you create the app appear on line nine, and I just had to fill
in a whole bunch of configuration values for the default
sender address that I want to send email as, the default
password I want to use to send email, the port number, the TCP port,
that we talked about last week. The mail server, I'm going to use
Gmail's smtp.gmail.com server. Use TLS, this means use encryption. So I set that to true. Mail username, this is going
to grab it from my environment. So for security purposes, I didn't
want to hard code my own Gmail username and password into the code. So I'm actually storing those in what
are called environment variables. You'll see more of these
in problem set nine, and it's a very common
convention on a server in the real world to store sensitive
information in the computer's memory so that it can be accessed
when your website is running, but not in your source code. It's way too easy if
you put credentials, sensitive stuff in your source
code, to post it to GitHub or to screenshot it accidentally,
or for information to leak out. So for today's purposes, know that the
OS.environ dictionary refers to what are called environment variables. And this is like an out-of-band, a
special way of defining key value pairs in the computer's memory
by running a certain command but that never show up
in your actual code. Otherwise, there would be so
many usernames and passwords accidentally visible on the internet. So I've installed this in advance. Let me see if I can do this correctly. Let me go over to another
tab in just a moment. And here, I have on my second
screen here, John Harvards inbox. It's currently empty, and I'm
going to go ahead and register for some sport as John
Harvard here, hopefully. So let me go ahead and run
Flask run on this version five. Let me go ahead and
reload the main screen. Not that one. Let me reload the main screen here. This time, clearly, I'm
asking for name and email. So name will be John Harvard.
[email protected]. He'll register for, how about soccer. Register. And if I did this correctly, not
only is John Harvard, on his screen, seeing you are registered, but when he
checks his email on this other screen, crossing his fingers that this
actually works as a demonstration, and I promise it did right before class. Horrifying. I don't think there's
a mistake this time. Let me try something
over here real quick, but I don't think this is broken. It wouldn't have said
success if it were. I just tried submitting again, so I
just did another you are registered. Oh, I'm really sad right now. AUDIENCE: [INAUDIBLE] DAVID: What's that? AUDIENCE: Check spam. DAVID: I could check
spam, but then it's-- not sure we want to show spam here on
the internet that every one of us gets. Oh, maybe. Oh! [LAUGHTER AND APPLAUDING] Thank you. OK. Wow, that was a risky click I worried. All right, so you are registered
is the email that I sent out, and it doesn't have any
actual information in it. But back in the day it
would have, because I included the student's
name and their dorm and all of the other fields of
information that we asked for. So let's just take a quick look
at how that code might work. I did have to configure Gmail
in a certain way to allow, what they call, less
secure apps using SMTP, which is the protocol
used for outbound email. But besides setting these things, let's
look at the register route down here. It's actually pretty straightforward. In my register route, I validated
the submission just like before. Nothing new there. I then confirmed the registration
down here, nothing new there. All I did was use two new lines of code. And it's this easy to automate
the sending of emails. I apparently have done
it too many times, which is why it ended up in spam. I created a variable called message. I used a message function that
I must have imported higher up, so we'll go back to that. Here's, apparently, the subject
line as the first argument. And the second argument is the
named parameter recipients, which takes a list of emails that
should get the confirmation email. So in brackets, I just
put the one user's email and then mail.send that message. So let's scroll back up to see what
message and what mail actually is. Mail, I think, we saw. Yep, mail is this, which
I have as a variable because I followed the
documentation for this library. You simply configure your current app
with Mail support, capital M here. And if you look up here
now, on line seven, here's the new library
from Flask mail I imported. Capital Mail, capital Message, so that
I had the ability to create a message and send a mail. So such a simple thing whether you
want to confirm things for users, you want to do password resets. It can be this easy to
actually generate emails provided you have the requisite
access and software installed. And just to make clear that
I did add something here, let me open up my requirements.txt
file, and indeed, I have both Flask and
Flask-mail ready to go. But I ran the command in
advance to actually do that. All right, any questions,
then, on these examples here? No? All right. So what other pieces might actually
remain for us let me flip over here. It turns out that a key
component of most any web application nowadays that
we haven't touched on yet, but it'll be one of our final flourishes
today, is the notion of a session. And a session is actually
a feature that derives from all of the basics we talked
about today and last week, and a session is the technical term for
what you and I know as a shopping cart. When you go to amazon.com and you start
adding things to your shopping cart, they follow you from
page to page to page. Heck if you close your browser,
come back to the next day, they're typically still your shopping
cart, which is great for Amazon because they want your business. They don't want you to have to
start from scratch the next day. Similarly, when you log
into any website these days, even if it's not an e-commerce thing
but it has usernames and passwords, you and I are not in
the habit of logging into every darn page
we visit on a website. Typically, you log in once, and then
for the next hour, day, week, year, you stay logged into that website. So somehow, the website is
remembering that you have logged in. And that is being implemented
by way of this thing called a session, and perhaps
a more familiar term that you might know as, and
worry about, called cookies. Let's go ahead and take one
more five minute break here. And when we come back, we'll
look at cookies, sessions, and these final features. All right. So the promise now is that
we're going to implement this notion of a session, which is going
to allow us to log users in and keep them logged in and even implement
things like a shopping cart. And the overarching goal here
is to build an application that is, quote unquote, "stateful." Again, state refers to information,
and something that's stateful remembers information. And in this context, the curiosity
is that HTTP is technically a stateless protocol. Once you visit a URL,
http://something, hit Enter, web page is downloaded to
your browser, like that's it. You can unplug from the internet,
you can turn off your Wi-Fi, but you still have the web page locally. And yet we somehow want to make
sure that the next time you click on a link on that website,
it doesn't forget who you are. Or the next thing you add
to your shopping cart, it doesn't forget what
was already there. So we somehow want to
make HTTP stateful, and we can actually do this using the
building blocks we've seen thus far. So concretely, here's a form you might
see occasionally, but pretty rarely, when you log into Gmail. And I say rarely because most of
you don't log into Gmail frequently, you just stay logged in, pretty
much endlessly, in your browser. And that's because Google
has made the conscious choice to give you a very long session
time, maybe a day, a week, a month, a year, because
they don't really want to add friction to using their tool
and making you log in every darn day. By contrast, there's other
applications on campus, including some of the CS50 zone,
that makes you log in every time. Because we want to make
sure that it's indeed you accessing the site, and not a roommate
or friend or someone maliciously. So once you do fill out this
form, how does Google subsequently know that you are you, and
when you reload the page even or open a second tab for
your same Gmail account, how do they know that you're still
David or Carter or Emma or someone else? Well, let's look underneath
the hood of what's going on. When you log into Gmail,
essentially, you initially see a form like this
using a GET request. And the website responds
like we saw last week with some kind of HTTP response. Hopefully 200 OK with the form. Meanwhile, the website might
also respond with an HTTP header that, last week we didn't care
about, this week, we now do. Whenever you visit a website,
it is very commonly the case that the website is putting
a cookie on your computer. And you may generally know
that cookies can be bad and they track you in some way, and
that's both a blessing and a curse. Without cookies, you could not implement
things like shopping carts and log-ins as we know them today. Unfortunately, they can also
be used for ill purposes like tracking you on every website and
serving you ads more effectively and so forth. So with good comes some bad. But the basic primitive for
us, the computer scientist, boils down to just HTTP headers. A cookie is typically a big number,
a big, seemingly random value, that a server tells your browser
to store in memory, or even longer term, store on disk. So you can think of it like a file that
a server is planting on your computer. And the promise that HTTP
makes is that if a server sets a cookie on your computer,
you will represent that same cookie or that same
value on every subsequent request. So when you visit the
website like Gmail, they plop a cookie on your computer
like this with some session equals value, some long random value. One, two, three, A, B,
C, something like that. And when you then visit another page
on gmail.com or any other website, you send the opposite header, not
set cookie, but just cookie colon, and you send the exact same value. It's similar to going to a
club or an amusement park where you pay once, you
go through the gates once, you get checked by
security once, and then they very often take like a little stamp
and say, OK, now you can come and go. And then for you, efficiency-wise,
if you come back later in the day or later in the evening, you
can just present your hand. You've been stamped, presumably. They've already-- you've
already paid, you've already been searched or whatnot. And so it's this sort
of fast track ticket back into the club, back into the park. That's essentially what a
cookie is doing for you, whereby it's a way of reminding the
website we've already done this, you already asked me for
my username and password. This is my path to now come and go. Now, unlike this hand stamp, which
can be easily copied or transferred or duplicated or kept
on over multiple days, these cookies are really big, seemingly
random values, letters and numbers. So statistically, there's
no way someone else is just going to guess your cookie
value and pretend to be you It's just very low probability, statistically. But this is all it boils down to is this
agreement between browser and server to send these values back
and forth in this way. So when we actually
translate this, now, to code, let's do something like
a simple login app. Let me go into a folder I made
in advance today called login. And let me code up app.py and
let's take a look in here. So what's going on? A couple of new things up top. If I want to have the ability to
stamp my users hands, virtually, and implement sessions, I'm going
to have to import from Flask support for sessions. So this is another feature you
get for free by using a framework and not having to implement
all this yourself. And from the Flask
session library, I'm going to import Session, capital S. Why? I'm going to configure
the session as follows. Long story short, there's different
ways to implement sessions. The server can store these
cookies in a database, in a file, in memory, in RAM, in other places too. We are telling it to store these
cookies on the server's hard drive. So in fact, whenever you use sessions
as you will for problem set nine, you'll actually see a folder
suddenly appear called Flask_session, inside of which are the
cookies, essentially, for any users or friends
or yourself who've been visiting your particular application. So I'm setting it to
use the file system, and I don't want them to be
permanent because I want, when you close your browser,
the session to go away. They could be made to be
permanent and last much longer. Then I tell my app to support sessions. And that's it for now. Let's see what this application actually
does before we dissect the code. Let me go over to my terminal window,
run Flask run, and then let me go ahead and reload my preview URL. Give it a second to kick back in. Let me go ahead and open my URL. Come on. Oops, let me go ahead. Too long of a break. There we go. So this website simply has a login form. There's no password,
though I could certainly add that and check for that too. It just asks for your name. So I'm going to log in as
myself, David, and click Login. And now notice I'm currently
at the /login route. But notice this. If I try to go to the
default route, just, slash, which is where most
websites live by default, notice that I magically
get redirected to log in. So somehow, my code knows, hey, if
you're not logged in, you're going to /login instead. Let me type in my name,
David, and click Login. And now notice I am back at slash. Chrome is sort of annoyingly hiding
it, but this is the same thing as just a single slash. And now notice it says you
are logged in as David. Log out. What's cool is notice if I reload
the page, it still knows that. If I create a second tab and go to
the same URL, it still knows that. I could even-- I could keep doing
this in multiple tabs, it's still going to remember me on both
of them as being logged in as David. So how does that work? Especially when I click Log Out,
then I get forgotten altogether. All right, so let's see how this works. And it's some basic building blocks. Under my /route, notice I have this. If there is no name in the session,
redirect the user to /login. So these two lines
together are what implement that automatic redirection using
HTTP 301 or 302 automatically. It's handled for me
with these two lines. Otherwise, show index.html. All right, let's go
down that rabbit hole. What's in index.html? Well, if I look in my-- let me look in my templates
folder for my login demo and look at templates/index.html. All right, so what's going on here? I extend layout.html,
I have a block body, and then I've got some other syntax. So we haven't seen this yet,
but it's more Jinja stuff, which again, is almost identical to Python. If there's a name in
the session variable, then literally say you are logged in
as curly braces session bracket name. And then notice this, I've got a simple
HTML link to log out via /logout. Else, if there is no
name in the session, then it apparently says you are not
logged in and it leads me to an HTML link to /login and then end diff. So again, Jinja does
not rely on indentation. Recall the HTML and CSS don't
really care about indentation, only the human does. But in code with Jinja,
you need these end tags, end block, end for, end
if, to make super obvious that you're done with that thought. So session is just this
magic variable that we now have access to because we've
included these two lines of code and these that handle that whole
process of stamping every user's hand with a different, unique identifier. If I made my code space
public and I let all of you visit the exact same URL, all of
you would be logged out by default. You could all type your
own names individually, all log in at the same URL
using different sessions. And in fact, I would then see,
if I go into my terminal window here and my login directory, notice the
Flask session directory I mentioned. And if I CD into that and type ls,
notice that I had two tabs open, or actually, I think I
started the server twice. I have two files in there. I would ultimately have one
file for every one of you. And that's what's
beautiful about sessions is it creates the illusion
of per user storage. Inside of my session is my name,
inside of your session, so to speak, is your name. And the same is going to apply to
shopping carts, ultimately, as well. Let's see how login works here. My login route supports both GET and
POST, so I could play around if I want. And notice this, this login route
is kind of interesting as follows. If the user got to this
route via POST, my inference is that they must have submitted a form. Why? Because that's how I'm going to
design the HTML form in a second. And if they did submit
the form via POST, I'm going to store, in
the session, at the name key, whatever the human's name is. And then, I'm going to
redirect them back to slash. Otherwise, I'm going to
show them the login form. So this is what's cool. If I go to this login
form, which lives at, literally, slash login, by default,
when you visit a URL like that, you're visiting via GET. And so that's why I see the form. However, notice this. The form, very cleverly,
submits to itself, like the one route/login submits
to its same self, /login, but it uses POST when
you submit the form. And this is a nice way of having one
route but for two different types of operations or views. When I'm just there visiting /login
via URL, it shows me the form. But if I submit the form, then this
logic, these three lines, kick in, and this just avoids my having to have
both an index route and a greet route, for instance. I can just have one route that
handles both GET and POST. How about logout? What does this do? Well, it's as simple as this. Change whatever name
is in the session to be none, which is Python's version of null,
essentially, and then redirect the user back to slash. Because now, in index.html, I will
not notice a name there anymore. This will be false. And so I'll tell the user
instead, you are not logged in. So like it's-- I want to say as simple as
this is, though I realize this is a bunch of steps involved. This is the essence of every
website on the internet that has usernames and passwords. And we skip the password name step for
that, more on that in problem set nine, but this is how every website out
there remembers that you're logged in. And how this works,
ultimately, is that as soon as you use in Python lines
like this and lines like this, Flask takes care of stamping the
virtual hand of all of your users and whenever Flask sees the same
cookie coming back from a user, it grabs the appropriate
file from that folder, loads it into the
session global variable so that your code is now unique
to that user and their name. Let's do one other
example with sessions here that'll show how we might use
these, now, for shopping carts. Let me go into the store example here. Let me go ahead and
run this thing first. If I run store in my same
tab and go back over here, we'll see a very ugly
e-commerce site that just sells seven different books here. But each of these books has a button
via which I can add it to my cart. All right, well where are
these books coming from? Well, let's poke around. Let me go into my terminal window again. Let me go into this example,
which is called store, and let me open up about
index dot ht-- whoops. Let's open up index,
how about, books.html is the default one, not index this time. So if I look here, notice that
that route that we just saw uses a for loop in Jinja to iterate
over a whole bunch of books, apparently, and it outputs, in an H2
tag, the title of the book, and then another one of these forms. So that's interesting. Let's go back one step. Let's go ahead and open up app.py,
because that must be-- excuse me, what's ticking all of this off. Notice that this file is
importing session support. It's configuring sessions
down here, but it's also connecting to a store.db file. So it's adding some SQLite. And notice this, in my /route,
I'm selecting star from books, which is going to give me
a list of dictionaries, each of which represents a row of books. And I'm going to pass that list of
books into my books.html template, which is why this for loop
works the way it does. Let's look at this actual database. Let me increase my terminal window
and do SQLite of store.db.schema will show me everything. There's not much there. It's a book-- it's a table called
books with two columns, ID and title. Let's do select star
from books semicolon. There are the seven books,
each of which has a unique ID. And you might see where this is going. If I go to the UI and I look
at each of these buttons for add to cart, just like Amazon might
have, notice that each of these buttons is just a form. And what's magical here,
just like deregister, even though I didn't
highlight it at the time, there's another type
of input that allows you to specify a value without the
human being able easily to change it. Instead of type equals
text or type equals submit, type equals hidden will put the value in
the form but not reveal it to the user. So that's how I'm saying that
the idea of this book is one, the idea of this book is two, the idea
of this book is three, and so forth. And each of these forms,
then, will submit, apparently, to /cart using POST and that would
seem to be what adds things to cart. So let's try this. Let me click on one or two of these. Let's add the first book, add to cart. Here's my cart. Notice my route change to /cart. All right, let's go back and
let's add the book number two. There we have that one. And let's skip ahead to the
seventh book, Deathly Hallows, and now we have all three books here. So what does the cart route do at /cart? Well, let's look. If I go back to my terminal window,
look at app.py and look at /cart, OK, there's a lot going on
here, but let's see. So the /cart route
supports both GET or POST, which is a nice way to
consolidate things into one URL. All right, this is interesting. If there is not a, quote
unquote, "cart" key in session, we haven't technically seen this syntax. But long story short, these lines
here do ensure that the cart exists. What do I mean by that? It makes sure that there's a cart
key in the session, global variable, and it's by default going
to be an empty list. Why? That just means you have
an empty shopping cart. But if the user visits this route via
POST and the user did provide an ID, they didn't muck with the form in any
way and try to hack into the website, they gave me a valid ID, then
I'm going to use this syntax. If session bracket cart is a list-- recall from a couple of weeks
ago that dot append just adds something to the list. So I'm going to add the ID to the
list and return the user to cart. Otherwise, if the user is at /cart
via GET, implicitly, we just do this. Select star from books where ID is in. And this might be syntax
you recall from Pset six. It lets you look for
multiple IDs all at once, because if I have a list of session-- list of IDs in my cart, I can
get all of those books at once. So long story short,
what has happened here? I am storing, in the cart, the books
that I myself have added to my cart. My browser is sending the same
hand stamp again and again, which is how this website knows that
it's me adding these books to my cart and not you or not Carter or not Emma. Indeed, if all of us visited the
same long URL and I made it public and allowed that, then we would
all have our own illusions of our own separate carts. And each of those carts,
in practice, would just be stored in this Flask
session directory on the server so that the server
can keep track of each of us using, again, these
cookie values that are being sent back and forth via these headers. All right. I know that's a lot,
but again, it's just the new Python way of
just leveraging those HTTP headers from last week in a clever way. Any questions before we look
at one final set of examples? Yeah. AUDIENCE: [INAUDIBLE] understand how
a log in has to do with [INAUDIBLE]?? How does it use [INAUDIBLE],,
how do you change [INAUDIBLE]?? Because in order to use a GET
request dot [INAUDIBLE] equals, there has to be an
exchange in [INAUDIBLE].. DAVID: So I think you're
asking about using the GET and POST in the same function. So this is just a nice
aesthetic, if you will. If I had to have separate
routes for GET and POST, I mean, it literally might mean I need
twice as many routes in my file. And it just starts to
get a little annoying. And these days, too, in
terms of user experience, this is maybe only appeals to the
geek in us, but having clean URLs is actually a thing. You don't want to have
lots of words in the URL, it's nice if the URLs are nice and
succinct and canonical, if you will. So it's nice if I can centralize all of
my shopping cart functionality in /cart only, and not in multiple routes,
one for GET, one for POST. It's a little nitpicky of me,
but this is commonly done here. So what this code here means is
that this route, this function, henceforth will support both
GET requests and POST requests. But then I need to distinguish between
whether it's GET or POST coming in. Because if it's a GET request,
I want to show the cart. If it's a POST request, I
want to update the cart. And the simplest way to do that
is just to check this value here. In the request variable that we
imported from Flask up above, you can check what is the
current type of request. Is it a GET, is it a POST, or
is it something else altogether? There are other verbs. If it's a POST, that must mean, because
I created the web form that uses POST, that the user clicked
the Add to Cart button. Otherwise, if it's not POST, it's
implicitly going to be logically GET. Then, I just want to show the
user the contents of the cart and I use these lines instead. So it's just one way of avoiding having
two routes for two different HTTP verbs. You can combine them so long
as you have a check like this. If I really wanted to be pedantic, I
could do this elif request.method=get. This would be more symmetric,
but it's not really necessary, because I know there's
only two possibilities. Hope that helps. All right, let's do one
final set of examples here that's going to tie the
last of these features together to something
that you probably see quite often in real-world applications. And that, for better
or for worse, is now going to involve tying back in
some JavaScript from last week. The goal at hand of
these examples is not to necessarily master how you yourself
would write the Python code, the SQL code, the JavaScript code, but just
to give you a mental model for how these different languages work. So that for final
projects, especially if you do want to add JavaScript functionality,
much more interactive user interface, you at least have the bare
bones of a mental model for how you can tie these languages together. Even though our focus, generally,
has been more on Python and SQL than on JavaScript from last week. Let me go ahead and open up an example
called shows, version zero of this. And let me do Flask run. And let me go into my URL here and
see what this application looks like by default. This has just a simple
query text box with a search box. Let's take a look at the HTML
that just got sent to my browser. All right, there's not
much going on here at all. So there's a form whose
action is /search. It's going to submit via GET. It's going to use a q parameter, just
like Google it seems, and submit it. So this actually looks like the
Google form we did last week. So let's see what goes on here. Let me search for something like cat. Enter. OK, so it looks like-- all right, so this is actually
a somewhat familiar file. What I've gone ahead and done is I've
grabbed all of the titles of TV shows from a couple of weeks ago
when we first introduced SQL, and I loaded them into this
demo so that you can search by keyword for any word you want. I just searched for cat. If we were to do this again, we
would see all the title of TV shows that contain D-O-G, dog, as a
substring somewhere and so forth. So this is a traditional
way of doing this. Just like in Google, it uses
/search?q=cat, q=dog, and so forth. How does that work? Well, let's just take a
quick look at app.py here. Let me go into my zero example
here, show zero, and open up app.py and see what's going on. All right, very simple. Here's the form, that's
how we started today. And here is the /search route. Well, what's going on here? This gets a little interesting. So I first select a whole
bunch of shows by doing this. Select star from shows, where
title like question mark. And then I'm using some
percent signs from SQL on both the left and the
right, and I'm plugging in whatever the user's input was for q. If I didn't use like and
I used equal instead, I could get rid of these curly
brace, these percent signs, but then it would have to be a show
called cat or called dog as opposed to it being like cat or like dog. This whole line returns to me a
list of dictionaries, each of which represents a show in the database. And then, I'm passing all of those
shows to a template called search.html. So let's just follow that
breadcrumb, let's open up shows dot-- sorry, search.html. All right, so this is
where templating gets cool. So I just passed back hundreds
of results, potentially, but the only thing I'm
outputting is an unordered list and using a Jinja for
loop and li tag containing the titles of each of those shows. And just to prove that this
is indeed a familiar data set and I actually simplified it a bit,
if I look at shows.db with SQLite, I threw away all the other stuff like
ratings and actors and everyone else and I just have, for instance,
select star from shows limit 10, just so we can see 10 of them. There's 10 of the shows
from that database. So that's all that's
in the database itself. So it would look like this is a
pretty vanilla web application. It uses GET, it submits
it to the server, the server spits out a response,
and that response, then, looks like this, which is a huge
number of li tags, one for each cat or one for each dog match. But everything else
comes from a layout.html. All the stuff at the
top and at the bottom. All right, so these days, though, we're
in the habit of seeing autocomplete. And you start typing something
and you don't have to hit Submit, you don't have to click a button,
you don't have to go to a new page. Web applications, nowadays,
are much more dynamic. So let's take a look at this
version one of this thing. Let me go into shows one
and close my previous tabs and run Flask run in here. And it's almost the same thing, but
watch the behavior change a little bit. I'm reloading the form,
there's no button now. So gone is the need for a submit button. I want to implement autocomplete now. So let's go ahead and
type in C. OK, there's every show that starts
with C. A, there's every show that has C-A in it, rather. T, there's every show with C-A-T in it. I can start it again and do dog,
but notice how instantaneous it was. And notice my URL never changed,
there's no /search route, and it's just immediate. With every keystroke, it is
searching again and again and again. That's a nice UX, user experience,
because it's immediate. This is what users are
used to these days. But if I look at the source code
here, notice that in the source code, there's just an empty UL by default but
there is some fancy JavaScript code. So let's see what's going on here. This JavaScript code
is doing the following. Let me zoom in a little bit more. This JavaScript code is first
selecting, with query selector, which you used this past week,
quote unquote "input, " all right, so that's just getting the text box. Then it's adding an event listener
to that input for the input event. We didn't talk about this
last week, but literally, when you provide any
kind of input by typing, by pasting, by any other
user interface mechanism, it triggers an event called input. So similar to key press or key up. I then have a function, no worries
about this async function for now. Then what do I do inside of this? All right, so this is
new, and this is the part that let's just focus on the
ideas and not the syntax. JavaScript, nowadays,
comes with a function called fetch that allows you to
GET or POST information to a server without reloading the whole page. You can sort of secretly
do it inside of the page. What do I want to fetch? slash search question mark q equals
whatever the value of that input is. When I get back a response, I want
to get the text of that response and store it in a variable called shows. And I'm deliberately bouncing
around, ignoring special words like await and await here, but for
now, just focus on what came back. A response came back from the
server, I'm getting the text from it, storing it in a variable called shows. What am I then doing? I'm using query selector to select
my UL, which is empty by default, and I'm changing its inner HTML
to be equal to the shows that came back from the server. So let's poke around. Here's where, again, developer
tools are quite powerful. Let me go ahead and reload this
page to get rid of everything. And let me now open up inspect. Let me go to the Network tab and
let's just sniff the traffic going between my browser and server. I'm going to search for C. Notice that
immediately triggered an HTTP request to /search?q=c. So I didn't even finish my cat
thought, but notice what came back. A bunch of response headers, but let's
actually click on the raw response. This is literally the response from the
server, just a whole bunch of li tags. No UL, no HTML, no
title, no body, nothing. Just li tags. And we can actually simulate this. Let me manually go to
that same URL, q=c, Enter. We are just going to get back-- whoops, sorry. slash search q equals c, we are
just going to get back this stuff, which if I view source, it's
not even a complete web page. The browser is trying to show it to me
as a complete web page with bullets, but it's really just partial HTML. But that's perfect,
because this is literally what I essentially want my
Python code to copy paste into the otherwise empty UL tag. And that's what this JavaScript
code then, here, is doing. Once it gets back that response from the
server, it's using these lines of code to plug all of those li's
into the UL after the fact. Again, changing the so-called dom. But there's a slightly better way
to do this because, honestly, this is not the best design. Because if you've got a hundred shows or
more, you're sending all of these tags unnecessarily. Why do I need to send all
of these stupid HTML tags? Why don't I just create those
when I'm ready to create them? Well, here's the final flourish. Whenever making a web
application nowadays, where client and server keep talking
to one another, Google Maps does this, Gmail does this, literally
every cool application nowadays you load the
page once and then it keeps on interacting
with you without you reloading or having to change the URL. Let's actually use a format called
JSON, JavaScript Object Notation, which is to say there's just a
better, more efficient, better designed way to send that same data. I'm going to go into shows
two now and do Flask run. And I'm going to go
back to my page here. The user interface is exactly the same,
and it still works exactly the same. Here's C, C-A, C-A-T, and so forth. But let's see what's coming back now. If I go to /search?q=cat, Enter, notice
that I get this crazy-looking syntax. But the fact that it's so
compact is actually a good thing. This is actually going to-- let
me format it a little nicer, well, or a little worse. This is what's called
JavaScript Object Notation. In JavaScript, an angle-- a square
bracket means here comes an array. In JavaScript, a curly bracket says
here comes an object, AKA, a dictionary. And you might recall from-- did we do? Yes, sort of recall that you can now
have keys and values in JavaScript notation using colons like this. So long story short, cryptic
as this is to you and me and not very human friendly,
it's very machine friendly. Because for every
title in that database, I get back its ID and its title, its
ID and its title, its ID and its title. And this is a very generic format that
an API, an application programming interface, might return to you. And this is how APIs, nowadays, work. You get back very raw textual
data in this format, JSON format, and then you can write code that
actually programmatically turns that JSON data into any language
you want, for instance, HTML. So here's the third and final
version of this program. I, again, select my input. I, again, listen for input. I then, when I get input,
call this function. I fetch slash search q equals whatever
that input was, C or C-A or C-A-T. I then wait for the response,
but instead of getting text, I'm calling this other function that
comes with JavaScript these days, called JSON, that just parses that. It turns it into a dictionary for me,
or really a list of dictionaries for me, and stores it in a
variable called shows. And this is where you start to see the
convergence of HTML with JavaScript. Let me initialize a variable called
HTML to nothing, quote unquote, using single quotes, but I
could also use double quotes. This is JavaScript syntax for a loop. Let me iterate over every
ID in the show's list that I just got back in the server,
that big chunk of JSON data. Let me create a variable called
Title that's equal to the shows-- the title of the show at that ID. But for reasons we'll
come back to, let me replace a couple of scary characters. Then let me dynamically add to this
variable, an li tag, the actual title, and a close li tag. And then very lastly,
after this for loop, let me update the ULs in our HTML to
be the HTML I just created on the fly. So in short, don't worry
too much about the syntax because you won't need to use
this unless you start playing with more advanced features quite soon. But what we're doing is,
with JavaScript, we're creating a bigger and bigger
and bigger string of HTML containing all of the open brackets,
the li tags, the closed brackets, but we're just grabbing the
raw data from the server. And so in fact in
problem set nine, you're going to use a real world third
party API, application programming interface, for which you sign up. The data you're going to get
back from that API is not going to be show titles, but
actually stock quotes and stocks ticker symbols and the prices of last-- at which stocks were
last bought or sold, and you're going to get that
data back in JSON format. And you're going to write a bit of
code that's then going to convert that to the requisite HTML on the page. So the final result here is
literally the kind of autocomplete that you and I see and
take for granted every day, and that's ultimately how it works. HTML and CSS are used to present
the data, your so-called view. Python might be used to send or
get the data on the backend server. And then lastly, JavaScript
is going to be used to make things dynamic and interactive. So I know that's a whole
bunch of building blocks, but the whole point of problem set
nine to tie everything together, set the stage for hopefully a
very successful final project. Why don't we go ahead and
wrap up there, and we'll see you one last time next week for emoji.