Sie sind auf Seite 1von 28

[MUSIC PLAYING] DAVID J. MALAN: All right.

This is CS50 and this is the start of


week two. And you'll recall that over the past couple of weeks, we've been building
up. First initially from Scratch, the graphical programming language that we then,
just last week, translated to the equivalent program NC. And of course, there's a
lot more syntax now. It's entirely text but the ideas, recall, were fundamentally
the same. The catch is that computers don't understand this. They only understand
what language? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: zeros and ones or binary. And
so there's a requisite step in order for us to get from this code to binary. And
what was that step or that program or process called? AUDIENCE: [INAUDIBLE] DAVID
J. MALAN: Yeah, so compiling. And of course, recall as you've now experimented with
this past week that to compile a program, you can use clang for C, language. And
you can just say clang and then the name of the file that you want to compile. And
that outputs by default a pretty oddly named program. Just a dot out. Which stands
for assembler output. More on that in just a moment. But recall too that you can
override that default behavior. And you can actually say, Output instead a program
called, hello instead of just a dot out. But you can go one step further, and you
actually use Make. And Make it self is not a compiler, it's a build utility. But in
layman's terms, what does it do for us? AUDIENCE: [INAUDIBLE] DAVID J. MALAN:
compiles it. And it essentially figures out all of those otherwise cryptic looking
command line arguments. Like dash-o something, and so forth. So that the program is
built just the way we want it without our having to remember those seemingly
magical incantations. And though that only works for programs as simple as this. In
fact, some of you with the most recent problems that might have encountered
compilation errors that we actually did not encounter deliberately in class because
Make was helping us out. In fact, as soon as you enhance a program to actually take
user input using CS50's library by including CS50 dot H, some of you might have
realized that all of a sudden the sandbox, and more generally Clang, didn't know
what get_string was. And frankly, Clang might not even known what a string was. And
that's because those two are features of CS50's library that you have to teach
Clang about. But it's not enough to teach Clang what they look like, as by
including CS50.h. Turns out there's a missing step that Make helps us solve but
that you too can just solve manually if you want. And by that I mean this, instead
of compiling a program with just Clang, hello.c. When you want to use CS50's
library, you actually need to add this additional command line argument.
Specifically at the end, can't go in the beginning like dash-O. And dash-L stands
for link. And this is a way of telling Clang, by the way when compiling my program,
please link in CS50's zeros and ones that we the staff wrote some weeks ago and
installed in the sandbox for you. So you've got your zeros and ones and then you've
got our zeros and ones so to speak. And dash-LCS50 says to link them together. So
if you were getting some kind of undefined reference error to get_string or you
didn't-- you weren't able to compile a program that just used any of the get
functions from CS50's library. Odds are, this simple change dash-LCS50 would have
fixed. But of course, this isn't interesting stuff to remember, let alone
remembering how to use dash-0 as well, at which point the command gets really
tedious to type. So here comes, Make again. Make automates all of this for us. And
in fact, if you henceforth start running Make and then pay closer attention to the
fairly long line of output that it outputs, you'll actually see mention of dash-
LCS50, you'll see mention of even dash-LM, which stands for math. So if you're
using round, for instance, you might have discovered that round two also doesn't
work out of the box unless you use Make itself or this more nuanced approach. So
this is all to say that compiling is a bit of a white lie. Like, yes you've been
compiling and you've been going from source code to machine code. But it turns out
that there's been a number of other steps happening for you that we're going to
just slap some labels on today. At the end of the day, we're just breaking the
abstraction. So compiling is this abstraction from source code to machine code.
Let's just kind of zoom in briefly to appreciate what it is that's going on in
hopes that it makes the code we're compiling a little more understandable. So step
one of four, when it comes to actually compiling a program is called Pre-
processing. So recall that this program we just looked at had a couple of includes
at the top of the file. These are generally known as pre-processor directives. Not
a particularly interesting term but they're demarcated by the hash at the start of
these lines. That's a signal to Clang that these things should be handled first.
Preprocessed. Process before everything else. And in fact, the reason for this we
did discuss last week, inside of CS50.h is what, for instance? AUDIENCE:
[INAUDIBLE] DAVID J. MALAN: Specifically, the declaration of get strings. So
there's some lines of code, the prototype if you recall, that one line of code that
teaches Clang what the inputs to get_string are and what the outputs are. The
return type and the arguments, so to speak. And so when you have include CS50.h at
the top of the file, what is happening when you first run Clang during this so-
called pre-processing step, is Clang looks on the hard drive for the file literally
called CS50.h. It grabs its contents and essentially finds and replaces this line
here. So somewhere in CS50.h is a line like this yellow one here that says
get_string, is a function that returns a string. And it takes as input, the so-
called argument, a string that we'll call prompt. Meanwhile, with include standard
I/O. What's the point of including that? What is declared inside of that file
presumably? Yeah? AUDIENCE: It's the standard inputs and outputs. DAVID J. MALAN:
Standard inputs and outputs. And more specifically, what example there of? What
function? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: So printf. The other function we
keep using. So inside of standard io.h, somewhere on the sandbox's hard drive is
similarly a line of code that frankly looks a little more cryptic but we'll come
back to this sort of thing down the road, that says print if is a function. Happens
to return on int, but more on that another time. Happens to take a char* format.
But more on that another time. Indeed, this is one of the reasons we hide this
detail early on because there's some syntax that's just a distraction for now. But
that's all that's going on. The sharp include sign is just finding and replacing
the contents. Plus dot, dot, dot, a bunch of other things in those files as well.
So when we say pre-processing, we just mean that that's getting substituted in so
you don't have to copy and paste this sort of thing manually yourself. So
"compiling" is a word that actually has a well-defined meaning. Once you've
preprocessed your code, and your code looks essentially like this, unbeknownst to
you, then comes the actual compilation step. And this code here gets turned into
this code here. Now this is scary-looking, and this is the sort of thing that if
you take a class like CS61 at Harvard, or, more generally, systems programming, so
to speak, you might see something like this. This is x86 64-bit assembly
instructions. And the only thing interesting about that claim for the moment is
that assembly-- I kind of alluded to that earlier-- assembler output, a.out.
There's actually a relationship here, but long story short, these are the lower
level instructions that only the CPU, the brain inside your computer, actually
understands. Your CPU does not understand C. It doesn't understand Python or C++ or
Java or any language with which you might be familiar. It only understands this
cryptic-looking thing. But frankly, from the looks of it, you might glean that
probably not so much fun to program in this. I mean, arguably, it's not that much
fun to program yet in C, So this looks even more cryptic. But that's OK. C and lots
of languages are just these abstractions on top of the lower level stuff that the
CPUs do actually understand so that we don't have to worry about it as much. But if
we highlight a few terms, here you'll see some familiar things. So main is
mentioned in this so-called assembly code. You see mention of get string and
printf, so we're not losing information. It's just being presented in really a
different language, assembly language. Now you can glean, perhaps, from some of the
names of these instructions, this is what Intel Inside means. When Intel or any
brand of CPU understands instructions, it means things like pushing and moving and
subtracting and calling. These are all low level verbs, functions, if you will, but
at the level of the CPU. But for more on that, you can take entire courses. But
just to take the hood off of this for today, this is a step that's been happening
for us magically unbeknownst to us, thanks to Clang. So assembling-- now that
you've got this cryptic-looking code that we will never see again-- we'll never
need to output again-- what do you do with it? Well, you said earlier that
computers only understand zeros and ones, so the third step is actually to convert
this assembly language to actual zeros and ones that now look like this. So the
assembling step happening, unbeknownst to you, every time you run Clang or, in
turn, run make, we're getting zeros and ones out of the assembly code, and we're
getting the assembly code out of your C-code. But here's the fourth and final step.
Recall that we need to link in other people's zeros and ones.
If you're using printf you didn't write that. Someone else created those zeros and
ones, the patterns that the computer understands. You didn't create get string. We
did, so you need access to those zeros and ones so that your program can use them
as well. So linking, essentially, does this. If you've written a program-- for
instance, hello.c-- and it happens to use a couple of other libraries, files that
other people wrote of useful code for you, like cs50.c, which does exist somewhere,
and even stdio.c, which does exist somewhere, or technically, Standard IO is such a
big library, they actually put printf in a file specifically called printf.c. But
somewhere in the sandbox's hard drive, in all of our Macs and PCs, if they support
compiling, are, for instance, files like these. But we've got to convert this to
zeros and ones, this, and this, and then somehow combine them. So pictorially, this
just looks a bit like this. And this is all happening automatically by Clang.
Hello.c, the code you wrote, gets compiled to assembly, which then gets assembled
into zeros and ones, so-called machine code or object code. Cs50.c-- we did this
for you before the semester started. Printf was done way before any of us started
decades ago and looks like this. These are three separate files, though, so the
linking step literally means, link all of these things together, and combine the
zeros and ones from, like, three, at least, separate files, and just combine them
in such a way that now the CPU knows how to use not just your code but printf and
get string and so forth. So last week, we introduced compiling as an abstraction,
if you will, and this is all that we've really meant this whole time. But now that
we've seen what's going on underneath the hood, and we can stipulate that my CPU
that looks physically like this, albeit smaller in a laptop or desktop, knows how
to deal with all of that. So any questions on these four steps-- pre-processing,
compiling, assembling, linking? But generally, now, we can just call them
compiling, as most people do. Any questions? Yeah. AUDIENCE: How does the CPU know
that [INAUDIBLE] is there? Is that [INAUDIBLE]? DAVID J. MALAN: Not in the pre-
processing step, so the question is, how does the computer know that printf is the
only function that's there? Essentially, when you're linking in code, only the
requisite zeros and ones are typically linked in. Sometimes you get more than you
actually need, if it's a big library, but that's OK, too. Those zeros and ones are
just never used by the CPU. Good question. Other questions? OK, all right. So now
that we know this is possible, let's start to build our way back up, because
everyone here probably knows now that when writing in C, which is kind of up here
conceptually, like, it is not without its hurdles and problems and bugs and
mistakes. So let's introduce a few techniques and tools with which you can
henceforth, starting this week and beyond, trying to troubleshoot those problems
yourself rather than just trying to read through the cryptic-looking error messages
or reach out for help to another human. Let's see if software can actually answer
some of these questions for you. So let me go ahead and do this. Let me go ahead
and open up a sandbox here, and I'm going to go ahead and create a new file called
buggy0.c in which I will, this time, deliberately introduce a bug. I'm going to go
ahead and create my function called main, which, again, is the default, like when
green flag is clicked. And I'm going to go ahead and say, printf, quote, unquote,
"Hello world/m." All right. Looks pretty good. I'm going to go ahead and compile
buggy0, Enter, and of course, I get a bunch of error messages here. Let me zoom in
on them. Fortunately, I only have two, but remember, you have to, have to, have to
always scroll up to look at the first, because there might just be an annoying
cascading effect from one earlier bug to the later. So buggy0.c, line 5, is what
this means, character 5, so like 5 spaces in, implicitly declaring library function
printf with dot, dot, dot. So you're going to start to see this pretty often if you
make this particular mistake or oversight. Implicitly declaring something means you
forgot to teach Clang that something exists. And you probably know from experience,
perhaps now, what the solution is. What's the first mistake I made here? AUDIENCE:
[INAUDIBLE]. DAVID J. MALAN: Yeah, I didn't include the header file, so to speak,
for the library. I'm missing, at the top of the file, include stdio.h, in which
printf is defined. But let's propose that you're not quite sure how to get to that
point, and how can we get, actually, some help with this? Let me actually increase
the size of my terminal here, and recall that just a moment ago, I ran makebuggy0,
which yielded the errors that I saw. It turns out that installed in the sandbox is
a command that we, the staff, wrote called help50. And this is just a program we
wrote that takes as input any error messages that your code or some program has
outputted. We kind of look for familiar words and phrases, just like a TF would in
office hours, and if we recognize some error message, we're going to try to
provide, either rhetorically or explicitly, some advice on how to handle. So if I
go ahead and run this command now, notice there's a bit more output. I see exactly
the same output in white and green and red as before, but down below is some
yellow, which comes specifically from help50. And if I go ahead and zoom in on
this, you'll see that the line of output that we recognized is this one, that same
one I verbally drew attention to before-- buggy0.c, line 5, error, implicitly
declaring library function printf, and so forth. So here, without the background
highlighting, but still in yellow, is our advice or a question a TF or CA might ask
you in office hours. Well, did you forget to include stdio.h in which printf is
declared atop your file? And hopefully, our questions, rhetorical or otherwise, are
correct, and that will get you further along. So let's go ahead and try that
advice. So include stdio.h. Now let me go ahead and go back down here. And if you
don't like clutter, you can type "clear," or hit Control+L in the terminal window
to keep cleaning it like I do. If you want to go ahead now and run makebuggy0,
Enter, fewer errors, so that's progress, and not the same. So this one's, perhaps,
a little easier. Reading the line, what line of code is buggy here? AUDIENCE:
Forgot the semicolon. DAVID J. MALAN: Yeah, so this is now still on line 5, it
turns out, but for a different reason. I seem to be missing a semi-colon. But I
could similarly ask help50 for help with that and hope that it recognizes my error.
So this, too, should start being your first instinct. If on first glance, you don't
really understand what an error message is doing, even though you've scrolled to
the very first one, like literally ask this program for help by rerunning the exact
same command you just ran, but prefix it with help50 and a space, and that will run
help50 for you. Any questions on that process? All right, let's take a look at one
other program, for instance, that, this time, has a different error involved in it.
So how about-- let me go ahead and whip up a quick program here. I'll call this
buggy2.c for consistency with some of the samples we have online for you later. And
in this example, I'm going to go ahead and write the correct thing at first,
stdio.h, and then I'm going to have int main void, which just gets my whole program
started. And then I'm going to have a loop, and recall for-- [CLEARS THROAT] excuse
me-- Mario or some other program, you might have done something like int i get 0, i
is less than or equal to-- let's do this 10 times, and then i++. And all I want to
do in this program is print out that value of i, as I can do, with the %i
placeholder-- so a simple program. Just want it to count from 0 to 10. So let's go
ahead and run buggy2, or rather, I want to-- let's not print up-- rewind. Let's go
ahead and just print out a hash symbol and not spoil the solution this way. So
here, I go ahead and print out buggy2. My goal is now I will stipulate to print out
just 10 hash symbols, one per line, which is what I want to do here. And now I'm
going to go ahead and run ./buggy2, and I should see, hopefully, 10 hashes. And I
kind of spoiled this a little bit, but what do I instead see? Yeah, I think I see
more than I expect. And we can kind of zoom in here and double check, so 1, 2, 3,
4, 5, 6, 7, 8, 9, 10, ooh, 11. 11. Now some of your eyes might already be darting
to what the solution should be, but let's just propose that it's not obvious. And
if it is actually not obvious, all the better, so how might you go about diagnosing
this kind of problem, short of just reaching out and asking a human for help. This
is not a problem that help50 can help with, because it's not an error message. Your
program is working. It's just not outputting what you wanted it to work, but it's
not an error message from the compiler with which help50 can help. So you want to
kind of get eyes into what your program is doing, and you want to understand, why
are you printing 11 when you really are setting this up from 0 to 10? Well, one of
the most common techniques in C or any language, honestly, is to use printf for
just other purposes-- diagnostic purposes. For instance, there's not much going on
in this program, but I'd argue that it would be interesting for me to know, and
therefore understand my program, by just, let's print out this value of i on each
iteration, as by doing the line of code that I earlier did, and just say something
literally like, i is %i. I'm going to remove this ultimately, because it's going to
make my program look a little silly, but it's going to help me understand what's
going on. Let me go ahead
and recompile buggy2, ./bugg2, and this time, I see a lot more output. But if I
zoom in, now it's kind of-- now the computer is essentially helping me understand
what's going on. When i is 0, here's one of them. When i is 1, here's another. I is
2, 3, 4, 5, 6, 7, 8, 9, and that looks good. But if we scroll a little further, it
feels a little problematic that i can also be 10. So what's logically the bug in
this program? AUDIENCE: [INAUDIBLE]. DAVID J. MALAN: Yeah. I use less than or equal
to, because I kind of confuse the paradigm. Like programmers tend to start counting
at zero, apparently, but I want to do this 10 times, and in the human world, if I
want to do something 10 times, I might count up to and including 10. But you can't
have it both ways. You can't start at zero and end at 10 if you want to do
something exactly 10 times. So there's a couple of possibilities here. How might we
fix this? Yeah, so we could certainly change it to less than. What's another
correct approach? Yeah, so we could leave this alone and just start counting at
one, and if you're not actually printing the values in your actual program, that
might be perfectly reasonable, too. It's just not conventional. Get comfortable
with, quickly, just counting from zero, because that's just what most everyone does
these days. But the technique here is just use printf. Like, when in doubt,
literally use printf on this line, on this line, on this line. Anywhere something
is interesting maybe going on in your program, just use it to print out the strings
that are in your variables, print out the integers that are in your variables, or
anything else. And it allows you to kind of see, so to speak, what's going on
inside of your program, printf. One last tool-- so it's not uncommon, when writing
code, to maybe get a little sloppy early on, especially when you're not quite
familiar with the patterns. And for instance, if I go ahead and do this by deleting
a whole bunch of whitespace, even after fixing this mistake by going from zero to
10, is this program now correct, if the goal is to print 10 hashes? Yeah, I heard
yes. Why is it correct? In what sense? Yeah, exactly. It still works. It prints out
the 10 hashes, one per line, but it's poorly written in the sense of style. So
recall that we tend to evaluate, and the world tends to think about code in at
least three ways. One, the correctness-- does it do what it's supposed to do, like
print 10 hashes? And yes, it does, because all I did was delete whitespace. I
didn't actually change or break the code after making that fix. Two is design, like
how thoughtful, how well-written is the code? And frankly, it's kind of hard to
write this in too many ways, because it's so few lines. But you'll see over time,
as your programs grow, the teaching fellows and staff can provide you with feedback
on the design of your code. But style is relatively easy. And I've been teaching it
mostly by way of example, if you will, because I've been very methodically
indenting my code and making sure everything looks very pretty, or at least pretty
to a trained eye. But this, let's just stipulate, is not pretty. Like, left
aligning everything still works, not incorrect, but it's poorly styled. And what
would be an argument for not writing code like this and, instead, writing code the
way I did a moment ago, albeit after fixing the bug? Yeah. AUDIENCE: It'll help you
identify each little subroutine that goes through the thing, so you know this
section is here. DAVID J. MALAN: Yeah. AUDIENCE: [INAUDIBLE] next one, so you know
where everything is. DAVID J. MALAN: Exactly. Let me summarize this. It allows you
to see, more visually, what are the individual subroutines or blocks of code doing
that are associated with each other? Scratch is colorful, and it has shapes, like
the hugging shape that a lot of the control blocks make, to make clear visually to
the programmer that this block encompasses others, and, therefore, this repeats
block or this forever block is doing these things again and again and again. That's
the role that these curly braces serve, and indentation in this and in other
contexts just helps it become more obvious to the programmer what is inside of what
and what is happening where. So this is just better written, because you can see
that the code inside of main is everything that's indented here. The code that's
inside the for loop is everything that's indented here. So it's just for us human
readers, teaching fellows in the case of a course, or colleagues in the case of the
real world. But suppose that you don't quite see these patterns too readily
initially. That, too, is fine. CS50 has on its website what we call a style guide.
It's just a summary of what your code should look like when using certain features
of C-- loops, conditions, variables, functions, and so forth. And it's linked on
the course's website. But there's also a tool that you can use when writing your
code that'll help you clean it up and make it consistent, not just for the sake of
making it consistent with the style guide, but just making your own code more
readable. So for instance, if I go ahead and run a command called style50 on this
program, buggy2.c, and then hit Enter, I'm going to see some output that's
colorful. I see my own code in white, and then I see, anywhere I should have
indented, green spaces that are sort of encouraging me to put space, space, space,
space here. Put space, space, space, space here. Put eight spaces here, four spaces
here, and so forth, and then it's reminding me I should add comments as well. This
is a short program-- doesn't necessarily need a lot of commenting to explain what's
going on. But just one //, like we saw last week to explain, maybe at the top of
the file or top the block of the code, would make style50 happy as well. So let's
do that. Let me go ahead and take its advice and actually indent this with Tab,
this with Tab, this with Tab, this with Tab, and this once more. And you'll notice
that on your keyboard, even though you're hitting Tab, it's actually converting it
for you, which is very common to four spaces, so you don't have to hit the spacebar
four times. Just get into the habit of using Tab. And let me go ahead and write a
comment here. "Print 10 hashes." This way, my colleagues, my teaching fellow,
myself in a week don't have to read my own code again and figure out what it's
doing. I can read the comments alone per the //. If I run style50 again, now it
looks good. It's in accordance with the style guide, and it's just more prettily
written, so pretty printed would be a term of art in programming when your code
looks good and isn't just correct. Any questions then? Yeah. AUDIENCE: I tried
using [INAUDIBLE] this past week and it said I needed a new program. DAVID J.
MALAN: That's-- it wasn't enabled for the first week of the class. It's enabled as
of right now and henceforth. Other questions? No. All right, so just to recap then,
three tools to have in the proverbial toolbox now are help50 anytime you see an
error message that you don't understand, whether it's with make or Clang or,
perhaps, something else. Printf-- when you've got a logical program-- a bug in your
program, and it's just not working the way it's supposed to or the way the problem
set tells you it should, and then style50 when you want to make sure that, does my
code look right in terms of style, and is it as readable as possible? And honestly,
you'll find us at office hours and the like often encouraging you, hey, before we
answer this question, can you please run style50 on your code? Can you please clean
up your code, because it just makes our lives, too, as other humans so much easier
when we can understand what's going on without having to visually figure out what
parentheses and curly braces line up. And so do get into that habit, because it
will save you time from having to waste time parsing things visually yourself. All
right. So there's not just CPUs in computers. CPUs are the brains, central
processing unit, and that's why we keep emphasizing the instructions that computers
understand. There's also this, which we saw last time, too. This is an example of
what type of hardware? AUDIENCE: RAM. DAVID J. MALAN: RAM, or Random Access Memory.
This is the type of memory that laptops, desktops, servers have that is used
whenever you run a program or open a file. There's another type of memory called
hard drives or solid state drives, which you're probably familiar as a consumer,
and that's just where your files are stored permanently. Your battery can die. You
can pull the plug from your laptop or desktop, and any files saved on a hard drive
are persistent. They stay there because of the technology being used to implement
that. But RAM is more ephemeral. RAM is powered only by electricity. It's only used
when the power is on or the battery is charged, and it's where your files and
programs live effectively when you double click on them and open them. So when you
double click on something like Microsoft Word, it is copied from your hard drive
long term into this type of memory, because this type of memory, though smaller in
capacity-- you don't have as many bytes of it-- but it is much, much, much, much
faster. Similarly, when you open a document, or you go to a web page, the contents
of the file you're seeing are stored in this type of hardware, because even though
you don't have terribly many bytes of it, it's just much, much, much, much faster.
And so this will be thematic in computer science and in hardware. You sort of have
lots of cheap, slow stuff, like hard disk space, relatively speaking, and you have
a little less of the more expensive but faster stuff like RAM. And you have just
one, usually, CPU, which is the really fast thing that can do a billion things per
second. But it, too, is more expensive. So there's
four visible chips on this thing, if you will. And we won't get into the details
of how these things work, but let's just zoom in on this one black chip here and
focus on it as being representative as some amount of memory. Maybe it's one
megabyte, one million bytes. Maybe it's even one gigabyte these days, one billion
bytes. But this is to say that this chip can be thought of as just having a bunch
of bytes in it. This is not to scale. You have many more bytes than these, but let
me propose that you just think of each of these squares here as representing one
byte. So the very first byte of memory I have access to is here. Next one is here,
and so forth. And the fact that they wrap around is just an artist rendition. These
things you can think of just virtually as going left to right, not in any kind of
grid, but physically, they look like this. So when you actually create a variable
in a program like C, like you need a char. A char tends to be one byte or eight
bits, and so that means when you have a variable of type char in a C program, it
goes, literally, physically in one of these boxes, inside of your computer's RAM.
So for instance, it might take up this much space at top left. If you have a bigger
type of data, so you have an integer, which tends to be four bytes or 32 bits, you
might need more than one square, so the computer might give you access to four
squares instead. And you have 32 bits spanning that region of memory. But honestly,
I chose those boxes arbitrarily. They could be anywhere in that chip or in any of
the other chips. It's up to the computer to just remember where they are for you.
You don't need to remember that, per se. But if we think about this grid, it turns
out this is actually very valuable that we have chunks of memory-- bytes, if you
will-- that are back to back to back to back. And in fact, there's a word for this
technique. This is contiguous memory-- back to back to back to back to back. And in
general, in programming, this is referred to as an array. You might recall from
Scratch, if you use this feature, it actually has things called lists, which are
exactly that-- lists of values, lists of words, lists of strings. An array is just
a contiguous chunk of memory, such that you can store something here, something
here, something here, something here, and so forth. So it turns out an array, this
super simple primitive, is actually incredibly powerful. Just being able to store
things in my computer's memory back to back to back to back enables so many
possibilities, both design-wise, like how well I can write my code, and also how
fast I can make my code run. So let me go ahead and take out an example. Let me go
ahead and open up, for instance, a new file in a sandbox, and we'll call this
score0. So let me go ahead and close this one, create a new file called scores0.c.
And in this file, let's go ahead and write a relatively simple program. Let me go
ahead and, as usual, give myself access to some helpful functions-- cs50.h and
stdio.h. And no need to copy all this down verbatim, if you don't like. Everything
will have or is already on the course's website. Let me start my program as usual
with int main void. And then let me write a program, as this program's name
implies, that, like, asks the user for three scores on recent problem sets,
quizzes, whatever, and then kind of creates a very simple chart of them, like a bar
chart to kind of help me visualize how well or how poorly I did on something. So if
I want to get an integer, no surprise, we can use the get int function, and I can
just ask the user for their first score. But I should probably do something with
this score, and on the left hand side of this, what do I typically put? Yeah. So
int-- sure, score 1 equals this, and then my semi-colon. So you might not have had
many occasions to use ints just yet, but get int is in the cs50 library. This is
the so-called prompt that the human sees, and let me actually fix my space, because
I want the human to see the space after the colon. But that's just an aesthetic
detail. And then when I get back this value, its return value-- just like Aaron,
last week, handed me a piece of paper, so does get int hand me a virtual piece of
paper with a number that I'm going to store in a variable called Score 1. And now
just to be clear, what has just happened effectively is this. The moment you create
a variable of type int, which is four bytes, literally, this is what Clang or, more
generally, the computer has done for you. That int that the human typed in is
stored literally in four contiguous bytes back to back to back, maybe here, maybe
here, but together. So that's all that's going on when you're actually using C. So
let me go back into my code here, and now I want to-- it's not interesting to plot
one score. So let's go ahead and do another. So int Score 2, get int, get int, and
I'll ask the user for score 2, semi-colon, and then let's get one more, Score 3,
get int, call it Score 3, semi-colon. All right, so now let me go ahead and
generate a bar, like a bar chart of this. I'm going to use what we'll call ASCII
art. ASCII, of course, is just text, recall-- very simple text in a computer. And I
can kind of make a bar chart pretty simply by just printing out like a bunch of
hashes horizontally, so a short bar will represent a small number, and a long bar
will represent a big number. So let me go ahead and say to the user, all right,
here's your Score 1. I'm going to go ahead, then, and say, for int i get 0. I is
less than Score 1, i++. And now if I scroll down and give myself a bit of room
here, let me go ahead and implement just a simple print. So go ahead and print out
a hash, and then when you're all done with that, print out a new line at the end of
that loop. And let's just pause there. Just to recap, I've asked the human for
three scores. I'm only doing something with one of them at the moment, so in fact,
just as a quick check, let me delete those so as to not get ahead of myself. Let me
do make score 0. Cross my fingers. OK, no errors. Now let me go ahead and do
./score0, and your first score on a pset this year out of 100 has been? OK, 100.
And good job. So it's a really long bar, and if we count those up, hopefully,
there's actually 100 bars. And if we run it again and say, eh, it didn't go so
well. I got a 50. That's half as big a bar. So it seems like we're on our way
correctness-wise. So now let me go ahead and get the other scores. Well, I had them
here a moment ago. So let me go ahead and just, well, copy, paste, and change this
to two, change this to three, change this to three, this to three. All right, I
know how to print bars clearly, so let me go ahead and do this, and then do this,
and then fix the indentation. I don't want to say Score 1 everywhere. I want to say
a Score 2, Score 2. I mean you're probably being rubbed the wrong way that this is
both tedious and sloppy, and why? What am I doing poorly now design-wise? AUDIENCE:
Copying and pasting code. DAVID J. MALAN: Like copy-pasting almost always bad,
right? There's redundancy here, but that's fine. Let's prioritize correctness, at
least, for now. So let me go ahead and make Score 0. All right, no mistakes--
./score0. And then Tab it. Let me go ahead now and run-- OK, we got 100 the first
time. We got 50 the-- oh, that's a bug. What did I do there? See, this is what
happens when you copy-paste. So let's fix this. That should say Score 2, so
Control+C will quit a program. Make score 0 will recreate it. ./0, Enter-- all
right, here we go. 100, 50. Let's split the difference-- 75. All right, so this is
a simple bar chart horizontally drawn of each of my three scores, where this is
100, this is 50, and this is 75. But there's opportunities for improvement here. So
one, it rubbed some folks the wrong way already that we were literally copying and
pasting code. So where is one opportunity for improvement here? What should I do
instead of copying and pasting that code again and again? What ingredient can you
bring? OK, so we can use a loop and actually just do the same thing three times. So
let's try that. Let me go ahead and do this. So let's go ahead and delete the copy-
paste I did, and let me go ahead and say, OK, well, for int i get zero, i less than
3, i++. Let me create a bracket. I can highlight multiple lines and hit Tab, and
they'll all indent for me, which is convenient. And can I do this now, for
instance? Say it a little louder. AUDIENCE: If you [INAUDIBLE] to a specific
[INAUDIBLE].. DAVID J. MALAN: Yeah, I'm a little worried. As you're noting here,
we're using on line 13 here the same variable, so mm. So it's good instincts, but I
feel like the fact that this program, unlike last week, we're now collecting
multiple pieces of data. Loops are breaking down for us. Yeah. AUDIENCE:
[INAUDIBLE] function [INAUDIBLE] takes in-- like you can have it [INAUDIBLE]. DAVID
J. MALAN: OK. AUDIENCE: So like an input of how many scores you wanted to enter.
DAVID J. MALAN: OK. AUDIENCE: And then [INAUDIBLE]. DAVID J. MALAN: Yeah, we can
implement another function that factors out some of this functionality. Any other
thoughts? AUDIENCE: Store your scores in an array. DAVID J. MALAN: OK, so we could
also store our scores in an array. So let's do these in order then, in fact. So
loops are wonderful when you want to do something again and again and again, but
the whole purpose of a function, fundamentally, is to factor out common
functionality. And there might still be a loop in the solution, but the real
fundamental problem with what I was doing a moment ago was I was copying and
pasting functionality-- shouldn't need to do that, because in both C and Scratch,
we had the ability to make our own functions. So let's do that. Let me undo my loop
changes here, just to get us back to where we were a moment ago. And let me go
ahead
and, instead, clean this up a little bit. Let me go ahead and create a new
function down here that I'm going to call, say, Chart, just to create a chart for
myself. And it's going to take as input a score, but I could call this anything I
want. It's void as its return type, because I don't need it to hand me something
back. Like I'm not getting a string from the user. I'm just printing a char. It's a
so-called side effect or output. Now I'm going to go ahead and do my loop here for
int i get 0. I is less than-- how many hashes do I want to print if I'm being
passed in the user score? Like, is this 3 here? AUDIENCE: The score. DAVID J.
MALAN: The score, so if I'm being handed a number that's 0 to 100, that's what I
want to iterate over. If my goal here, ultimately-- let me finish this thought-- i+
+ is [? 2 ?] inside this loop print out one hash per point in 1's total score. And
just to keep things clean, I'm going to go ahead and put a new line at the very end
of this. But I think now, I factored out a good amount of the redundancy. It's not
everything, but I've at least now given myself a function called Chart. So up here,
it looks like I can kind of remove this loop, which is what I factored out. That's
almost identical, except the variable name was hardcoded. And I think I could now
do chart like this, and then I maybe could do a little copy-paste, if that's OK,
like if maybe I can get away with just doing this, and then say 2, and then say 3,
and then say 3, and then say 2. So it's still copy-paste, but it's less. And it
looks better. It literally fits on the screen, so it's progress-- not perfect, but
progress. Better design, but not perfect. So is this going to compile? I'm going to
have errors why? AUDIENCE: Essentially, it's [INAUDIBLE] the program [INAUDIBLE]..
DAVID J. MALAN: OK. Yeah. AUDIENCE: We need to declare a [INAUDIBLE].. DAVID J.
MALAN: OK, good. So let me induce the actual error, just so we know what problem
we're solving. Let me go ahead and sort of innocently go ahead and compile Score 0
hoping all is well, but of course, it's not because of a familiar error up here. So
notice, implicit declaration of function chart is invalid in C99. So again,
implicit declaration of function just tends to mean Clang does not know what you're
talking about. And you could run help50, and it would probably provide you with
similar advice. But the gist of this is that chart is not a C function. It doesn't
come with C. I wrote it. I just wrote it a little too late. So one solution that we
didn't used last week would be, OK, well, if you don't know what chart is, let me
just go put it where you'll know about it. And now run make score 0. OK, problem
solved. So that fixes it, but we fixed it in a different way last week. And why
might we want to stick with last week's approach and not just copy-paste my
function and put it at the top instead of the bottom? AUDIENCE: [INAUDIBLE]. DAVID
J. MALAN: Yeah, I mean it's kind of a minor concern at the moment, because this is
a pretty short program. But I'm pushing the main part of my program, literally
called Main, farther and farther down. And the whole point of reading code is to
understand what it's doing. So if I open this file, and I have to scroll, scroll,
scroll, scroll, scroll, just looking for the main function, it's just bad style.
It's just kind of nice, and it's a good human convention. Put the main code, the
main function, when green flag clicks equivalent, at the very top. So C does offer
us a solution here. You just have to provide it with a little hint. Let me go ahead
and cut this from here, put it back down at the bottom here, and then go ahead and
copy-paste only or retype only the value-- whoops-- the value of that first line,
which is its so-called prototype. Give Clang enough information so that it knows
what arguments the function takes, what its return type is, and what its name is,
semi-colon, and that's the so-called declaration or-- and then implement it with
the curly braces and all the logic down below. So let's go ahead and run this. And
if I scroll up here, we'll see-- whoops. We'll see make score 0. All right, now
we're on our way, score 0. Enter. Score 1 is 100, 50, 75, and now we seem to have
some good functionality. But there's still an opportunity, I dare say, for
improvement. And I think the fundamental problem is that I'm still copy-pasting the
little stuff, but I think the fundamental problem is that I don't have the
expressiveness to store multiple values, unless I, in advance, as the programmer,
give them all unique names, because if I use the same variable for everything, I
couldn't collect all three variables at the top, and then iterate over all three at
the bottom, if I only have one variable. So I do need three variables, but this
doesn't scale very well. And who knows? If I want to take in five scores, 10
scores, or more scores, then I'm really copying and pasting excessively. So it
turns out, indeed, the answer is an array. So an array, at the end of the day, is
just a side effect of storing stuff in memory back to back to back to back. But
what's powerful about this reality of memory is the following. I can go ahead here
and in, say, a new and more improved version of this program, do this. Let me go
ahead and open this one, which I wrote in advance, called scores2.c. And in
scores2.c, notice we have the following code. In my main function, I've got a new
feature and a new bit of syntax. This line here that I've highlighted says, hey,
Clang, give me a variable called Scores of type integer, but please give me three
of them. So the new syntax are your square brackets, and inside of which is the
number of variables you want of that type. And you don't have to give them unique
names. You literally call them collectively, Scores, and in English, I deliberately
chose a plural to connote as much. This is an array of values, not a single value.
What can I do next? Well, here's my for loop for int i get zero i is less than 3 i+
+, and now I've solved that earlier problem that was proposed. Well, just put it in
a loop. Now I can, because now my variables are not called Score 1, Score 2, Score
3, which I literally had to hard code. They're just called Scores, and now that
they're called Scores, and I have this square bracket notation, notice what I can
do. I can get an int, and I can say, give me score%i, and plug in i plus 1. I
didn't want to say "zero," because humans don't count from zero in general. So this
is counting from one, two, and three, but the computer is doing this. So Scores is
a variable. Bracket, i, close bracket says store the i-th value there. So i-th is
just non-English. That means go to bracket 0, bracket 1, bracket 2. So what this
effectively means is on the first iteration of the loop, when i equals 0, this
looks like this, effectively. When i then becomes 1 on the next iteration, then
you're doing this. When i becomes 2 on the final iteration, it looks like this.
When i becomes 3, well, 3 is not less than 3, and so it doesn't execute again. So
by using i inside of these square brackets, am I indexing into an array? To index
into an array means go to a specific location, the so-called i-th location, but you
start counting at zero. Just to make this more real, then, if you go back to this
picture of your computer's memory, this might, therefore, be bracket i, bracket 1--
bracket 0, bracket 1, bracket 2, bracket 3, bracket 4, bracket 50, or wherever. You
can now, using square brackets, get at any of these blocks of memory to store
values for you. Any questions on what we've just done? All right, then on the flip
side, we can do the exact same thing. Now when I print my scores, I can similarly
iterate from 0 to 3, and then print out the scores by passing to chart the same
value, the i-th score. Again, the only new syntax here is variable name, square
bracket, and then a number, like 0, 1, 2, or a variable like i, and then my chart
function down here is exactly the same. It has no idea an array is even involved,
because I'm just passing in one score at a time. Now it turns out there's still one
bad design decision in this program. There's still some redundancy, something that
I keep typing again and again and again. Do any values jump out at you as repeated?
AUDIENCE: The for loop. DAVID J. MALAN: The for loop. OK, so I've got the for loop
in multiple places. Sure. And what other value seems to be in multiple places? It's
subtle. Total number. Yeah, 3. Three is in a few places. It's up here. It's when I
declare the array and ask myself for three scores. It's here when I'm iterating.
It's here when I'm iterating. It's not here, because this is a different iteration.
That's just for the hashes. So in, ironically, three places, have I written 3. So
what does this mean? Well, suppose next year you take more tests or whatever, and
you need more scores. You open up your program, and all right, now I've got five
scores and five-- whoops, typo already-- five, like this kind of pattern where
you're typing the same thing again and again. And now the onus is on me, the
programmer, to remember to change the same [? damn ?] value in multiple places--
bad, bad, bad design. You're going to miss one of those values. Your program's
going to get more complex. You're going to leave one at 3 and change the other to
5, and logical errors are eventually going to happen. So how do we solve this? The
function's not the solution here, because it's not functionality. It's just a
value. Well, we could use a variable, but a certain type of variable. These numbers
here-- 5, 5, 5 or 3, 3, 3-- are what humans generally refer to as magic numbers.
Like they're numbers, but they're kind of magical, because you just arbitrarily
hardcoded them in random places. But a better convention would be, often as a
global variable, to do this-- int, let's call
it "count," equals 3. So declare a variable of type int that is the number of
things you want, and then type that variable name all throughout your code so that
later on, if you ever want to change this program, you change it-- whoops-- in one
place, and you're done after recompiling the program. And actually, I should do a
little better than this. It turns out that if you know you have a variable that
you're never going to change, because it's not supposed to change-- it's supposed
to be a constant value-- C also has a special keyword called const, where before
the data type, you say, const int, and then the name and then the value, and this
way, the compiler, Clang, will make sure that you, the human, don't screw up and
accidentally try to change the count anywhere else. There's one other thing
notable. I also capitalize this whole thing for some reason-- human convention.
Anytime you capitalize all of the letters in a variable name, the convention is
that that means it's global. That means it's defined way up top, and you can use it
anywhere, therefore, because it's outside all curly braces. But it's meant to imply
and remind you that this is special. It's not just a so-called local variable
inside of a function or inside of a loop or the like. Any questions on that? Yeah.
AUDIENCE: What is [INAUDIBLE]? Why do you have i plus 1? DAVID J. MALAN: Oh, why do
I have i plus 1? Let me run this program real quick. Why do I have i plus 1 in this
line here, is the question. So let me go ahead and run make scores 2-- whoops-- in
my directory. Make scores 2 ./scores2, Enter. I wanted just the human to see Score
1 and Score 2 and Score 3. I didn't want him or her to see Score 0, Score 1, Score
2, because it just looks lame to the human. The computer needs to think in terms of
zeros. My humans and my users do not, so just an aesthetic. Other questions. Yeah.
AUDIENCE: [INAUDIBLE]. DAVID J. MALAN: Ah, really good question. And I actually
thought about this last night when trying to craft this example. Why don't I just
combine these two for loops, because they're clearly iterating an identical number
of times? Was this a hand or just a stretch? No, stretch. So this is actually
deliberate. If I combine these, what would change logically in my program? Yeah.
AUDIENCE: After every [INAUDIBLE] input, you would [INAUDIBLE].. DAVID J. MALAN:
Yeah, so after every human input of a score, I would see that user's chart, the row
of hashes. Then I'd ask them for another value. They'd see the chart, another
value, and they'd see the chart. And that's fine, if that is the design you want.
Totally acceptable. Totally correct. I wanted mine to look a little more
traditional with all of the bars together, so I effectively had to postpone
printing the hashes. And that's why I did have a little bit of redundancy by
getting the user's input here and then iterating again to actually print the user's
output as a chart, so just a design decision. Good question. Other questions? All
right, so what does this look like? Actually, you know what? I can probably do a
little better. Let me open up one final example involving scores and this thing
called an array. In Scores 4 here, let me go ahead and do this. Now I've changed my
chart function to do a little bit more, and you might recall from week 0 and 1, we
had the call function, and we kept enhancing it to do more and more, like putting
more and more logic into it. Notice this. Chart function now takes a second
argument, which is kind of interesting. It takes one argument, which is a number,
and then the next argument is an array of scores. So long story short, if you want
to have a function that takes as input an array, you don't have to know in advance
how big that array is. You should not, in fact, put a number in between the square
brackets in this context. But the thing is you do need to know, at some point, how
many items are in the array. If you've programmed in Java, took AP CS, Java just
gives you .length, if you recall that feature of objects. C does not have this.
Arrays do not have an inherent length associated with them. You have to tell
everyone who uses your array how long it is. So even though you don't do that
syntactically here, you literally just say, I expect an argument called scores that
is an array per the square brackets. You have to pass and almost always a second
variable that is literally called whatever you want, but is the number of things in
that array, because if the goal of this function is just to iterate over the number
of scores that are passed in, and then iterate over the number of points in that
score in order to print out the hashes, you need to know this count. So what does
this function do, just to be clear? This iterates over the total number of scores
from 0 to count, which is probably 3 or 5 or whatever. This loop here, using J,
which is just a convention, instead iterates from 0 to whatever that i-th score is.
So this is what's convenient. Now I've passed in the array, and I can still get at
individual values just by using i, because I'm on my i-th iteration here. So you
might recall this from Mario, for instance, or any other example in which you had
nested loops-- just very conventional to use i on the outside, j on the inside. But
again, the only point here is that you can, indeed, pass around arrays, even as
arguments, which we'll see why that's useful before long. Any questions? OK, so
this was a lot, but we can do so much more still with arrays. It gets even more and
more cool. In fact, we'll see, in just a bit, how arrays have actually been with us
since last week. We just didn't quite realize it under the hood, but let's go ahead
and take a breather, five minutes. We'll come back and dive in. All right. So I
know that was a bit of a cliffhanger. Where else could arrays have actually been?
But, of course, this is how we might depict it pictorially. We called it an array,
and it turns out that last week, when we introduced strings, strings, sequences of
characters, are literally just an array by another name. A string is an array of
chars, and chars, of course, is another data type. Now what are the actual
implications of this, both in terms of representation, like how a computer's
representing information, and then fundamentally, programmatically, what can we do
when we know all of our data is so back to back to back or so proximal to one
another? Well, it turns out that we can apply this logic in a few different ways.
Let me go ahead and open up, for instance, an example here called String 0. So in
our code for today, in our Source 2 folder, let me go ahead and open up String 0,
and this example looks like this. Notice that we first, on line 9, get a string
from the user. Just say, input, please. We store that value in a string, s, and
then we say, here comes the output. And notice what I'm doing in the following
line. I'm iterating over i from 0 to strlen, whatever that is. And then in line 13,
I'm printing a character one at a time. But notice the syntax I'm using, which we
didn't use last week. If you have a string called s, you can index into a string
just like it's an array, because it, indeed, is underneath the hood. So s bracket
i, where i starts at 0 and goes up to whatever this value is is just a way of
getting character 0, then character 1, then character 2, then character 3, and so
the end result is actually going to look like this. Let me go ahead and do, make
string-- whoops-- make string 0. Oops. Not in the directory. Make string 0,
./string0, Enter, and I'll type in, say, Zamyla, and the output now is Z-A-M-Y-L-A.
It's a little messy, because I don't have a new line here, so let me actually--
let's clean that up, because this is unnecessarily sloppy. So let me go ahead and
print out a new line. Let me recompile with make string 0, dot-- whoops--
./string0. Input shall be Zamyla, Enter, and now Z-A-M-Y-L-A. So why is that
happening? Well, if I scroll down on this code, it seems that I am, via this printf
line here, just getting the i-th character of the name in s, and then printing out
one character at a time per the %c, followed by a new line. So you might guess,
what is this function here doing? Strlen-- slightly abbreviated, but you can,
perhaps, glean what it means. Yeah, so it's actually string length. So it turns out
there is a function that comes with C called strlen, and humans back in the day and
to this day like to type as few characters when possible. And so strlen is string
length, and the way you use it is you just need one more header file. So there's
another library, the so-called string library that gives you string-related
functions beyond what CS50's library provides. And so if you include string.h, that
gives you access to another function called strlen, that if you pass it, a variable
containing a string, it will pass you back as a return value the total number of
characters. So I typed in Z-A-M-Y-L-A, and so that should be returning to me six,
thereby printing out the six characters in Zamyla's name. Yeah. AUDIENCE:
[INAUDIBLE]. DAVID J. MALAN: Uh-huh. AUDIENCE: [INAUDIBLE] useful to get the
individual digits [INAUDIBLE].. DAVID J. MALAN: Really good question. In the credit
problem of the problem set, would this have been useful? Yes, absolutely. But
recall that in the credit pset, we encourage you to actually take in the number as
a long, so as an integral value, which thereby necessitated arithmetic. But yes, if
you had, instead, in a problem involving credit card numbers, gotten the human's
input as a long string of characters and not as an actual number like an int or a
long, then, yes, you could actually get at those individual characters, which
probably would have made things even easier but deliberate. Yeah. AUDIENCE:
[INAUDIBLE]. DAVID J. MALAN: Really good question. If we're defining string in
CS50, are
we redefining it in string? No. So string, even though it's named string.h,
doesn't actually define something called a string. It just has string-related
functions. More on that soon. Yeah. AUDIENCE: [INAUDIBLE] individual values
[INAUDIBLE]?? DAVID J. MALAN: Ah, really good question. Could you edit the
individual values? So short answer, yes. We could absolutely change values, and
we'll soon do that in another context. Other questions? All right, so turns out
this is correct, if my goal is to print out all of the characters in Zamyla's name,
but it's not the best design. And this one's a little subtle, but this is, again,
what we mean by design. And to a question that came up during the break, did we
expect everyone to be writing good style and good design last week? No. Up until
today, like we've introduced the notion of correctness in both Scratch and in C
last week, but now we're introducing these other axes of quality of code like
design, how well-designed it is, and how pretty does it look in the context of
style. So expectations are here on out meant to be aligned with those
characteristics, but not in the past. So there's a slight inefficiency here. So on
the first iteration of this loop, I first initialize i to 0, and then I check if i
less than the length of the string, which hopefully, it is, if it's Zamyla, which
is longer than 0. Then I print the i-th character. Then I increment i. Then I check
this condition. Then I print the i-th character. Then I increment i. Then I check
this condition and so forth. We looped through loops last week, and you've used
them, perhaps, by now in problems. What question am I redundantly asking seemingly
unnecessarily? I have to check a condition again and again, because i is getting
incremented. But there's another other question that I don't need to keep asking
again just to get the same answer. AUDIENCE: What is the length [? of the
string? ?] DAVID J. MALAN: Yeah, there's this function call in my loop of strlen s,
which is fine. This is correct. I'm checking the length of the string, but once I
type in Zamyla, her name is not changing in length. I'm incrementing i, so I'm
moving in the string, if you will. But the string itself, Z-A-M-Y-L-A, is not
changing. So why am I asking the computer, again and again, get me the strlen of s,
get me the strlen of s, get me the strlen of s. So I can actually fix this. I can
improve the design, because that must take some amount of time. Maybe it's fast,
but it's still a non-zero amount of time. So you know what I could do? I could do
something like this-- int n get string length of s. And now just do this. This
would be better design, because now I'm only asking the question once of the
function. I'm remembering or caching, if you will, the answer, and then I'm just
using a variable. And just comparing variables is just faster than comparing a
variable against a function, which has to be called, which has to return a value,
which you can then compare. But honestly, it doesn't have to be this verbose. We
can actually be a little elegant about this. If you're using a loop, a secret
feature of loops is that you can have commas after declaring variables. And you can
actually do this and make this even more elegant, if you will, or more confusing-
looking, depending on your perspective. But this now does the same thing but
declares n inside of the loop, just like I'm declaring i, and it's just a little
tighter. It's one fewer lines of code. Any questions, then? AUDIENCE: [INAUDIBLE].
DAVID J. MALAN: Good question. In the way I've just done it cannot reuse this
outside of the curly braces. The scope of i and n exists only in this context right
now. The other way, yes. I could have used it elsewhere. AUDIENCE: What if you
[INAUDIBLE] other loops, and you also had [INAUDIBLE]?? DAVID J. MALAN: Absolutely.
AUDIENCE: Using different letters of the alphabet, you could just use n and not be
[INAUDIBLE].. DAVID J. MALAN: Correct. If I want to use the length of s again,
absolutely. I can declare the variable, as I did earlier, outside of the loop, so
as to reuse it. That's totally fine. Yes. And even i-- i exists only inside of this
loop, so if I have another loop, I can reuse i, and it's a different i, because
these variables only exist inside the for loop in which they're declared. So it
turns out that these strings don't have anything in them other than character after
character after character. And in fact, let me go ahead here and draw a picture of
what's actually going on underneath the hood of the computer here. So when I type
in Zamyla's name, I'm, of course, doing something like Z-A-M-Y-L-A. But where is
that actually going? Well, we know now that inside of your computer is RAM or
memory, and you can think of it like a grid. And honestly, I can think of this
whole screen as just being in a different orientation, a grid of memory. So for
instance, maybe we can divide it into rows and columns like this, not necessarily
to scale, and there's more rows and columns. So on the screen here, I'm just
dividing things into the individual bytes of memory that we saw a moment ago. And
so, indeed, underneath the hood of the computer is this layout of memory. The
compiler has somehow figured out or the program has somehow figured out where to
put the z and where the a and the m and the y and the l and the a, but the key is
that they're all contiguous, back to back to back. But the catch is if I'm typing
other words into my program or scores into my program or any data into my program,
it's going to end up elsewhere in the computer's memory. So how do you know where
Zamyla begins and where Zamyla ends, so to speak, in memory? Well, the variable,
called s, essentially is here. There's some remembrance in the computer of where s
begins. But there's no obvious way to know where Zamyla ends, unless we ourselves
tell the computer. So unbeknownst to us, any time a computer is storing a string
like Z-A-M-Y-L-A, it turns out that it's not using one, two, three, four, five, six
characters. It's actually using seven secretly. It's actually putting a special
character of all zeros in the very last bytes. Every byte is eight bits, so it's
putting secretly eight zeros there, or we can actually draw this more
conventionally as /0. It's what's called the null character, and it just means all
zeros. So the length of the string, Zamyla, is six, but how many bytes does it
apparently take up, just to be clear? So it actually takes up seven. And this is
kind of a secret implementation detail that we don't really have to care about, but
eventually, we will, because if we want to implement certain functionality, we're
going to need to know what is actually going on. So for instance, let me go ahead
and do this. Let me go ahead and create a program called strlen itself. So this is
not a function but a program called strlen.c. Let me go ahead and include the CS50
library at the top. Let me go ahead and include stdio.h. Let me go ahead and type
out main void, so all this is same as always. And then let me go ahead and prompt
the user for, say, his or her name, like so. And then you know what? Let me
actually, this time, not just print their name out, because we've done that ad
nauseam. Let's just count the number of letters in his or her name. So how could we
do that? Well, we could just do this-- int n get strlen of s, and then say, printf
"The length of your name is %i." And then we can plug in n, because that's the
number we stored the length in. But to use strlen, I have to include what header
file? String.h, which is the new one, so string.h. And now if I type this all
correctly, make strlen, make strlen, good. ./strlen-- let's try it-- Zamyla. Enter.
OK, the length of her name is six. But what is strlen doing? Well, strlen is just
an abstraction for us that someone else wrote, and it's wonderfully convenient, but
you know, we don't strictly need it. I can actually do this myself. If I understand
what the computer is doing, I can implement this same functionality myself as
follows. I can declare a variable called n and initialize it to 0, and then you
know what? I'm going to go ahead and do this. While s bracket n does not equal all
zeros, but you don't write all zeros like this. You literally do this-- that /0 to
which I referred earlier in single quotes. That just means all zeros in the bytes.
And now I can go ahead and do n++. If I'm familiar with what this means, remember,
that this is just n equals n plus 1, but it's just a little more compact to say, n+
+. And then I can print out the name of your n-- the name of your n-- the name of--
the length of your name is %i, plugging in n. So why does this work? It's a little
funky-looking, but this is just demonstrating an understanding of what's going on
underneath the proverbial hood. If n is initialized to zero, and I look at s
bracket n, well, that's like looking at s bracket 0. And if the string, s, is
Zamyla, what is s bracket 0? Z. And then it does not equal /0. It equals z,
obviously. So we increment n. So now n is 1. Now n is 1. So what is s bracket 1 in
Zamyla's name? A and so forth, and we get to Z-A-M-Y-L-A, then all zeros, the so-
called null character, or /0. That, of course, does equal /0, so the loop stops,
thereby leaving the total count or value of n at what it previously was, which was
6. So that's it. Like all underneath the hood, all we have is memory laid out like
this, top to bottom, left to right, and yet all of the functionality we've been
using for a week now and henceforth just boils down to some relatively simple
primitives, and if you understand those primitives, you can do anything you want
using the computer, both computationally code-wise, but also memory-wise. We can
actually see, in fact, some of the stuff we looked at two weeks ago as follows. Let
me go ahead and open up an example called
ASCII 0. Recall that ASCII is the mapping between letters and numbers in a
computer. And notice what this program's going to do. Make-- let me go into this
folder. Make ascii0, ./ascii0, Enter. The string shall be, let's say, Zamyla,
Enter. Well, it turns out that if you actually look up the ASCII code for Zamyla's
name, z is 90, lowercase a is 97, m is 109, and so forth. There are those
characters, and actually, we can play the same game we did last week. If I do this
again on "hi," there's your 72, and there's your 73. Where is this coming from?
Well, now that I know how to manipulate individual strings, notice what I can do. I
can get a string from the user, just as we always have. I can iterate over the
length of that string, albeit inefficiently using strlen here. And then notice this
new feature today. I can now convert one data type to another, because a char, a
character is just eight bits, but presented in the context of characters. Bytes is
also just eight bits that you could treat as an integer, a number. It's totally
context-sensitive. If you use Photoshop, it's a graphic. If you use a text program,
it's a message and so forth. So you can encode-- change the context. So notice
here, s bracket i is, of course, the i-th character of Zamyla's name, so Z or A or
M or whatever. But I can convert that i-th character to an integer doing what's
called casting. You can literally, in parentheses, specify the data type you want
to convert one data type to, and then store it in exactly that data type. So s
bracket i-- convert it to a number. Then store it in an actual number variable, so
I can print out its value. So c-- this is show me the character. Show me the letter
as by plugging in the character, and then the letter-- sorry, the character and the
number that I've just converted it to. And you don't actually even have to be
explicit. This is called explicit casting. Technically, we can do this implicitly,
too. And the computer knows that numbers are characters, and characters are a
number. You don't have to be so pedantic and even do the explicit casting in
parentheses. You can just do it implicitly with data types, and honestly, at this
point, I don't even need the variable. I can get rid of this, and down here, I can
literally just print the same thing twice, but tell printf to print the first in
the context of a character and the second in the context of an int, just treating
the exact same bits differently. That's implicit casting. And it just demonstrates
what we did in week 0 when we claimed that letters are numbers, and numbers can
also be colors, and colors can be images, and so forth. Is this a question?
AUDIENCE: Would've been useful for credit. DAVID J. MALAN: Also, yes. It all comes
back to credit. Yeah. Indeed. Other questions? No. All right, so what else can we
actually do with this appreciation? So super simple feature that all of us surely
take for granted, if we even use it anymore these days. Google Docs, Microsoft
Word, and such can automatically capitalize words for you these days. I mean your
phone can do it nowadays. They just sort of AutoCorrect your messages. Well, how is
that actually working? Well, once you know that a string is just a bunch of
characters back to back to back, and you know that these characters have numbers
representing them, and like capital A is 65, and lowercase A is 97, apparently, and
so forth, we can leverage these patterns. If I go ahead and open up this other
example here called Capitalize 0, notice what this program is going to do for me
first by running it. Make capitalize 0 ./capitalize0. Let me go ahead and type in
Zamyla's name just as before, but now it's all capital. So this is a little
extreme. Hopefully, your phone is not capitalizing every letter, but you can
imagine it capitalizing just the first, if you wanted it. So how does this work?
Well, let me go ahead and open up this example here. And so what we did-- so here,
I'm getting a string from the user, just as we always do. Then I'm saying, after,
just to kind of format the output nicely. Here, I'm doing a loop pretty efficiently
from i equals 0 up to the length of the string. And now notice this neat
application of logic. It's a little cryptic, certainly, at first glance. But
whoops. And now it's gone. And what am I doing exactly with these lines of code?
Well, with every iteration of this loop, I'm asking the question, is the i-th
character of s, so the current character, is it greater than or equal to lowercase
A, and is it less than or equal to lowercase Z? Put another way, how do you say
that more colloquially in English? Is it lowercase, literally. But this is the more
programmatic way of expressing, is it lowercase? All right, if it is, go ahead and
do this. Now this is a little funky, but print out a character, specifically the i-
th character, but subtract from that lowercase letter whatever the difference is
between little A and big A. Now where did that come from? So it turns out-- OK,
capital A is 65. Lowercase A is 97. So the difference between those is 32. And
that's true for B, so capital B is 66, and lowercase B is 98. Still 32, and it
repeats for the whole alphabet. So I could just do this. If I know that lowercase
letters have bigger numbers, like 97, 98, and I know that lowercase numbers have
lower letters, like 65, 66, I can just literally subtract off 32 from my lowercase
letters. As you point out, it's a lowercase letter. Subtract 32, and that gives us
what result? The capitalized version. It uppercases things for us. But honestly,
this feels a little hackish that, like, OK, yes, I can do the math correctly, but
you know what? It's better practice, generally, to abstract this away. Don't get
into the weeds of counting how many characters are away from each other. Math is
cheap and easy in the computer. Let it do the math for you by subtracting whatever
the value of A is, of capital A is from the value of lowercase A. Or we could just
write 32. Otherwise, go ahead and just print the character unchanged. So in this
case, the A-M-Y-L-A in Zamyla's name got uppercased, and everything else, the Z,
got left alone, just by understanding what's going on with how the computer's
represented. But honestly, God, I don't want to keep writing code like this. Like,
I'm never going to get this. I'm new to programming, perhaps. I'm never going to
get this sort of sequence of all the cryptic symbols together, and that's OK,
because we can actually implement this same program a little more easily, thanks to
functions and abstractions that others have written for us. So in this program,
turns out I can simplify the questions I'm asking by literally calling a function
that says, is lower. And there's another one called, is upper, and there's bunches
of others that just literally are called, is something or other. So is lower takes
an argument like the i-th character of s, and it just returns a bull-- true or
false. How is it implemented? Well, honestly, if we looked at the code that someone
else wrote decades ago for is upper, odds are-- or is lower-- odds are he or she
wrote code that looks almost like this. But we don't need to worry about that level
of detail. We can just use his or her function, but how do we do that? Turns out
that this function-- and you would only know this by having been told or Googling
or reading a reference-- is in a library called ctype.h. And you need the header
file called ctype.h in order to use it. And we'll almost always point you to
references and documentation to explain that to you. Toupper is another feature,
right? This math-- like, my god. I just want to uppercase a letter. I don't want to
really keep thinking about how far apart uppercase letters are from lowercase.
Turns out that in the C type library, there's another function called toupper that
literally does the exact same thing in the previous program we wrote. And so that,
too, is OK. But you know what? This feels a little verbose. It would be nice if I
could really tighten this program up. So how those toupper work? Well, it turns out
some of you might be familiar with CS50 Reference Online, our web-based app that we
have that helps you navigate available functions in C. Turns out that all of the
data for that application comes from an older command line program that comes in
Linux and comes in the sandbox called Man for manual. And anytime you type "man" at
the command prompt, and then the name of a function you're interested in, if it
exists, it will tell you a little something about it. So if I go to toupper, man
toupper, I get slightly cryptic documentation here. But notice, toupper and some
other functions convert uppercase or lowercase. That's the summary. Notice that in
the synopsis, the man page, so to speak, is telling me what header file I have to
include. Notice that under Synopsis, it's also telling me what the signature or
prototype is of the function. In other words, the documentation in Man, the Linux
programmer's manual, is very terse. So it's not going to hold your hand in this
black and white format. It's just going to convey, well, implicitly, you better put
this on top of your file. And by the way, this is how you use the function. It
takes an argument called C, returns a value of type int. Why is it int? Let me wave
my hands at that. It effectively returns a character for our purposes today. And if
we scroll down, OK, description. Ugh, I don't really want to read all of this, but
OK, here we go. If c is a lowercase letter, toupper returns its uppercase
equivalent, if an uppercase representation exists in the current locale. That just
means if it's punctuation, it's not going to do anything. Otherwise, it returns C,
And that's kind of the key detail. If I pass it lowercase A, it's going to give me
capital A, but if I pass it capital A, what's it going to give me? AUDIENCE:
Capital
A. DAVID J. MALAN: Also, capital A. It returns the original character, c. That's
the only detail I cared about. When in doubt, read the manual. And it might be a
little cryptic, and this is why CS50 Reference takes somewhat cryptic documentation
and tries to simplify it into more human-friendly terms. But at the end of the day,
these are the authoritative answers. And if I or one of the staff don't know, we
literally pull up the Man page or CS50 Reference to answer these kinds of
questions. Now what's the implication? I don't need any of this. I can literally
get rid of the condition and just let toupper do all of the legwork, and now my
program is so much more compact than the previous versions were, because I've read
the documentation. I know what the function does, and I can let toupper uppercase
something or just pass it through unchanged. We can better design, because we're
writing fewer lines of code that are just as clear, and so we can now actually
tighten things up. Any questions on this particular approach? All right. So we're
getting very low level. Now let's make these things more useful, because clearly,
other people have solved some of these problems for us, as by having these
functions and the C type library and the string library. What more is there? Well,
recall that every time we run Clang, or even run make, we're typing multiple words
at the command prompt. You're typing make hello or make Mario, a second word, or
you're typing clang-o, hello, hello.c, like lots of words at the prompt. Well, it
turns out that all this time, you're using, indeed, command line arguments. But in
C, you can write programs that also accept words and numbers when the user runs the
program. Think back, after all. When you ran Mario, you did ./mario, Enter. You
couldn't type any more words at the prompt. When you did credit, you did ./credit,
Enter. No more words at the prompt. You used get string or get long to get more
input, but not at the command line. And it turns out that we can, relatively
simply, in C, but it's a little cryptic at first glance. Let me go ahead and-- let
me go ahead and, here, pull up this signature here, which looks like this. This is
the function that we're all used to by now for writing a main function. And up
until now, we've said void. Main doesn't take any inputs, and indeed, it just runs.
But it turns out if you change your existing programs or future programs, not to
say void, but to say, int argc, string argv, it's a little cryptic at first glance.
But what's a recognizable symbol now? Yeah, there's brackets here. So it turns out
that every time you write a program, if you don't just say void, you actually
enable this feature by writing int argc, string argv. You can actually tell Clang,
you know what? I want this program to accept one or more words or numbers after the
name of the program, so I can do ./hellodavid, or ./hellozamyla. I don't have to
wait for the program to be running to use string. And just as with the earlier
example, where you were able to chart an array, main is defined as taking an array,
called argv historical reasons-- argument vector. Vector means array. Argument
vector, bracket, closed bracket just means this is-- this contains one or more
words, each of which is a string. Argc is argument count, so this is the variable
that main gets access to that tells it how many arguments, how many strings are
actually in argv. So how can we use this in a useful way? Well, let me go ahead
here and open up the sandbox. And let me go ahead and create a new file called,
say, argv0, argv0.c-- again, argument vector, just list or array of arguments. And
let me go ahead and, as usual, include cs50.h, include stdio.h, and then int main
not void, but int argc, string argv-- argv-- open bracket, closed bracket. And even
if that doesn't come naturally at first, it will eventually. And I'm going to do
this. If the number of arguments passed in equals 2, then I'm going to go ahead and
do this-- printf, hello %s, comma, and here in the past, I've typed a variable
name. And I now actually have access to a variable. Go ahead and do argv bracket 1.
Else, if the user does not type, apparently, two words, let me go ahead and just by
default, say, hello world, as we always have. Now why-- what is this doing, and how
is it doing it? Well, let's quickly run it. So make-- whoops. Make argv0, ./argv0,
Enter, Hello World. But if I do Hello-- or dot-- the program would be better named
if we called it Hello, but Zamyla, Enter. Hello Zamyla. If I change it to David,
now I have access to David. If I had David Malan, no. It doesn't support that. So
what's going on? If you change main in any program write to take these two
arguments, argc and argv of type string int and then an array of strings, argc
tells you how many words were typed at the prompt. So if the human typed two words,
I presume the first word is the name of the program, dot slash argv0, the second
word is presumably my name, if he or she is actually providing their name at the
prompt. And so I print out argv bracket 1. Not 0 because that's the name of the
program, but argv bracket 1. Else, down here, if the human doesn't provide just
Zamyla, or just David, or just one word more generally, I just print the default,
"Hello world." But what's neat about this now is notice that argv is an array of
strings. What is a string? It's an array of characters. And so let's enter just one
last piece of syntax that gets kind of powerful here. Let me go ahead and do this.
Let me go ahead and, in a new file here, argv 1 dot c. Let me go ahead and paste
this in. Close this. Let me go ahead and do this. Rather than do this logical
checking, let me do this, for-- let's say for int, i get 0. i is less than argc--
i++. Let's go ahead and, one per line, print out every word that the human just
typed, just to reinforce that this is indeed what's going on. So argv bracket 0,
save. Make argv 1, enter. And now let's go ahead and run this program-- dot slash,
argv 1, David Malan. OK, you see all three words. If we change it to Zamyla, we see
just those two words. If we change it to Zamyla Chan, we see those three words. So
we clearly have access to all of the words in the array, but let's take this one
step further. Rather than just print out every word in a string, let's go ahead and
do this. For intj get 0. n equals the string length of the current argument, like
this-- j is less than n, j++-- oops, oops, oops-- j++. Now let me go ahead and
print out not the full string, but let me do-- oops, oops-- let me go ahead and
print out this-- not a string, but a character, n bracket i bracket j, like this.
All right. So what's going on? One, this outer loop, and let's comment it, iterate
over strings in argv. This inner loop, iterate over chars in argv bracket i. So the
outer loop iterates over all of the strings in argv. And the inner loop, using a
different variable, starting at 0, iterates over all of the characters in the ith
argument, which itself is a string. So we can call string length on it. And then we
do this up until n, which is the length of that string. And then we print out each
character. So just to be clear-- when I run arv1 and correct it, at first glance,
why it's implicitly declaring library function sterling, what's almost always the
solution when you do this wrong? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah. So I
forgot this, so include string.h and help50 would help with that as well. Let's
recompile with make argv1. All right. When I run argv1, of, say, Zamyla Chan, what
am I going to see? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah. Is that the right
intuition? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: I'm going to see Zamyla Chan,
but-- AUDIENCE: [INAUDIBLE] DAVID J. MALAN: One character on each line, including
the program's name. So in fact, let me scroll this up so it's a little bigger.
Enter. OK, it's a little stupid, the program, but it does confirm that using arrays
do I have access not only to the words, but I can kind of have the second
dimension. And within each word, I can get at each character within. And we do
this, again, just by using not just single square brackets, but double. And again,
just break this down into the first principles. What is this first bracket? This is
the ith argument, the ith string in the array. And then if you take it further,
with bracket j, that gives you the j character inside of this. Now, who cares about
any of this kind of functionality? Well, let me scroll back and propose one
application here. So recall that CS is really just problem solving. But suppose the
problem that you want to solve is to actually pass a secret message in class or
send someone a secret for whatever reason. Well, the input to that problem is
generally called plain test, a message you want to send to that other person. You
ideally want ciphertext to emerge from it, which is enciphered and scrambled,
somehow encrypted information so that anyone in the room, like the teacher, can't
just grab the note and read what you're sending to your secret crush or love across
the room, or in any other context as well. But the problem is that if the message
you want to send, say, is our old friend Hi!, with an exclamation point, you can
encode it in certain contexts as just 72, 73, 33. And I daresay most classes on
campus if you wrote on a piece of paper 72, 73, 33, passed it through the room, and
whatever professor intercepts it, they're not going to know what you're saying
anyway. But this is not a good system. This is not a cryptosystem. Why? It's not
secure. [INAUDIBLE] [INTERPOSING VOICES] DAVID J. MALAN: Yeah. Anyone has access to
this, right, so long as you attend like week 1 or 0 of CS50, or you just have
general familiarity with Ascii. Like this is just a code. I mean Ascii is a system
that maps letters to numbers. And anyone else who knows
this code obviously knows what your message is, because it's not a unique secret
to you and the recipient. So that's probably not the best idea. Well, you can be a
little more sophisticated. And this is back-- actually, a photograph from World War
I of a message that was sent from Germany to Mexico that was encoded in a very
similar way. It wasn't using Ascii. The numbers, as you can perhaps glean from the
photo, are actually much larger. But in this system, in a militaristic context,
there was a code book. So similar in spirit to Ascii, where you have a column of
numbers and a column of letters to which they correspond, a codebook more generally
has like numbers, and then maybe even letters or whole words that they correspond
to, sometimes thousands of them, like literally a really big book of codes. And so
long as only, in this context the Germans and the recipients, the Mexicans, had
access to that same book, only they could encrypt and decrypt, or rather encode and
decode information. Of course, in this very specific context-- you can read more
about this in historical texts-- this was intercepted. This message, seemingly
innocuous, though definitely suspicious looking with all these numbers, so
therefore not innocuous, the British, in this case actually, intercepted it. And
thanks to a lot of efforts and cryptanalysis, the Bletchley Park style code
breaking, albeit further back, were they able to figure out what those numbers
represented in words and actually decode the message. And in fact, here's a
photograph of some of the words that were translated from one to the other. But
more on that in any online or textual references. Turns out in this poem too there
was a similar code, right? So apropos of being in Boston here, you might recall
this one. "Listen my children, and you shall hear of the midnight ride of Paul
Revere. On the 18th of April in '75, hardly a man is now alive who remembers that
famous day and year. He said to his friend, if the British march by land or sea
from the town tonight night, hang a lantern aloft in the belfry arch of the North
Church tower as a signal light, one if by land, and two if by sea. And I on the
opposite shore will be ready to ride and spread the alarm through every Middlesex
village and farm for the country folk to be up and to arm." So it turns out some of
that is not actually factually correct, but the one if by land and the two if by
sea code were sort of an example of a one-time code. Because if the revolutionaries
in the American Revolution kind of decided secretly among themselves literally
that-- we will put up one light at the top of a church if the British are coming by
land. And we will instead use two if the British are instead coming by sea. Like
that is a code. And you could write it down in a book, unless you have a code book.
But of course, as soon as someone figures out that pattern, it's compromised. And
so code books tend not to be the most robust mechanisms for encoding information.
Instead, it's better to use something more algorithmic. And wonderfully, in
computer science is this black box to-- we keep saying, the home of algorithms. And
in general, encryption is a problem with inputs and outputs, but we just need one
more input. The input is what's generally called the key, or a secret. And a secret
might just be a number. So for instance, if I wanted my secret to be 1, because
we'll keep the example simple, but it could really be any number. And indeed, we
saw with the photograph a moment ago, the Germans used much larger than this,
albeit in the context of codes. Suppose that you now want to send a more private
message to someone across the room in a class that, I love you. How do you go about
encoding that in a way that isn't just using Ascii and isn't just using some simple
code book? Well, let me propose that now that we understand how strings are
represented, right-- we're about to make love really, really lame and geeky-- so
now that you know how to express strings computationally, well, let's just start
representing "I love you" in Ascii. So I is 73. L is 76. O-V-E Y-O-U. That's just
Ascii. Should not send it this way, because anyone who knows Ascii is going to know
what you're saying. But what if I enciphered this message, I performed an algorithm
on it? And at its simplest, an algorithm can just be math-- simple arithmetic, as
we've seen. So you know, let me just use my secret key of 1. And let me make sure
that my crush knows that I am using a secret value of 1. So he or she also knows to
expect that value. And before I send my message, I'm going to add 1 to every
letter. So 73 becomes 74. 76 becomes 77. 80, 87, 70, 90, 80, 86. Now this could
just be sent in the clear. But then, I could actually send it as a textual message.
So let's convert it back to Ascii. 74 is now J. 77 is now M. 80 is now P. And you
can perhaps see the pattern. This message was, I love you. And now, all of the
letters are off by one, I think. I became J. L became M. O became P, and so forth.
So now the claim would be, cryptographically, I'm going to send this message across
the room. And now no one who has a code book is going to be able to solve this. I
can't just steal the book and decode it, because now the key is only up here, so to
speak. It's just the number 1 that he or she and I had to agree upon in advance
that we would use for sending our secret messages. So if someone captures this
message, teacher in the room or whoever, how would they even go about decoding this
or decrypting it? Are there any techniques available to them? I daresay we can kind
of chip away at this love note. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: What's that?
Guess and check. OK, we could try all-- there still kind of some spacing. So you
know honestly, we could do like kind of a cryptanalysis of it, a frequency attack.
Like, I can't think of too many words in English that have a single letter in them.
So what does J probably represent? [INTERPOSING VOICES] DAVID J. MALAN: I,
probably. Maybe A, but probably I. And there's not too many other options. So we've
attacked one part of the message already. I see a commonality. There's two what in
here? Two P. And I don't necessarily know that that maps to O, but I do know it's
the same character. So if I kind of continue this thoughtful process or this trial
and error, and I figure out, oh, what if that's an O? And then that's an O. And
then wait a minute. They're passing from one to another. Maybe this says, I love
you. Like you actually can, with some probability, decrypt a message by doing this
kind of analysis on it. It's at least more secure than the code book, because
you're not compromised if the book itself is stolen. And you can change the key
every time, so long as you and the recipient actually agree on something. But at
least we now have this mechanism in place. So with just the understanding of what
you can do with strings, can you actually now do really interesting domain-specific
things to them? And in fact, back in the day, Caesar, back in militaristic times
literally used a cipher quite like this. And frankly, when you're the first one to
use these ciphers, they actually are kind of secure, even if they're relatively
simple. But hopefully, not just using a key of 1, maybe 2, or 13, or 25, or
something larger. But this is an example of a substitution cipher, or a rotational
cipher where everything's kind of rotating-- A's becoming B, B's becoming C. Or you
can kind of rotate it even further than that. Well, let's take a look at one last
example here of just one other final primitive of a feature today, before we then
go back high level to bring everything together. It turns out that printing out
error messages is not the only way to signal that something has gone wrong. There's
a new keyword, a new use of an old keyword in this example, that's actually a
convention for signaling errors. So this is an example called exit.c. It apparently
wants the human to do what, if you infer from the code? AUDIENCE: Exit [INAUDIBLE].
DAVID J. MALAN: Yes. Say again? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Well, it
wants the-- well, what does it what the human to do implicitly, based on the
printf's here? How should I run this program? Yeah? AUDIENCE: [INAUDIBLE] just
apply [INAUDIBLE].. DAVID J. MALAN: Yeah. So for whatever reason, this program
implicitly wants me to write exactly two words at the prompt. Because if I don't,
it's going to yell at me, missing command line argument. And then it's going to
return 1, whatever that is. Otherwise, it's going to say, Hello, such and such. So
if I actually run this program-- let me go back over here and do make exit-- oops--
in my directory, make exit. OK, dot slash exit, enter, I'm missing a command line
argument. All right, let me put Zamyla's name. Oh, Hello Zamyla. Let me put Zamyla
Chan. Nope, missing command line argument. It just wants the one, so in this case
here. I'm seeing visually the error message, but it turns out the computer is also
signaling to me what the so-called exit code is. So long story short, we've already
seen examples last week of how you can have a function return a value. And we saw
how [? Erin ?] came up on stage, and she returned to me a piece of paper with a
string on it. But it turns out that main is a little special. If main returns a
value like 1 or 0, you can actually see that, albeit in a kind of a non-obvious
way. If I run exit, and I run it correctly with Zamyla as the name, if I then type
echo, dollar sign, question mark, of all things, enter, I will then see exactly
what main returned with, which in this case is 0. Now, let me try and be
uncooperative. If I actually run just dot slash exit, with no word, I see, missing
command line argument. But if I do the same cryptic command, echo, dollar sign,
question mark, I see that main exited with 1. Now, why is this
useful? Well, as we start to write more complicated programs, it's going to be a
convention to exit from main by returning a non-zero value, if anything goes wrong.
0 happens to mean everything went well. And in fact, in all of the programs we've
written thus far, if you don't mention return anything, main automatically for you
returns 0. And it has been all this time. It's just a feature, so you don't have to
bother typing it yourself. But what's nice about this, or what's real about this,
is if on your Mac or PC, if you've ever gotten an annoying error message that says,
error negative 29, system error has occurred, or something freezes, but you very
often see numbers on the screen, maybe. Like those error codes actually tend to map
to these kinds of values. So when a human is writing software and something goes
wrong and an error happens, they typically return a value like this. And the
computer has access to it. And this isn't all that useful for the human running the
program. But as your programs get more complex, we'll see that this is actually
quite useful as a way of signaling that something indeed went wrong. Whew. OK,
that's a lot of syntax wrapped in some loving context. Any questions before we look
at one final domain? No? All right. So it turns out that we can answer the "who
cares" question in yet another way too. It turns out-- let me go ahead and open up
an example of our array again here-- that arrays can actually now be used to solve
problems more algorithmically. And this is where life gets more interesting. Like
we were so incredibly in the weeds today. And as we move forward in the class,
we're not going to spend so much time on syntax, and dollar signs, and question
marks, and square brackets, and the like. That's not the interesting part. The
interesting part is when we now have these fundamental building blocks, like an
array, with which we can solve problems. So it turns out that an array, you know,
you can kind of think of it as a series of lockers, a series of lockers that might
look like this, inside of which are values-- strings, or numbers, or chars, or
whatnot. But the lockers is an apt metaphor because a computer, unlike us humans,
can only see and do one thing at a time. It can open one locker and look inside,
but it can't kind of take a step back, like we humans can, and look at all of the
lockers, even if all of the doors are open. So it has to be a more deliberate act
than that. So what are the actual implications? Well, all this time-- we had that
phone book example in the first week, and the efficiency of that algorithm, of
finding Mike Smith in this phone book, all assumed what feature of this phone book?
AUDIENCE: That it's ordered alphabetically. DAVID J. MALAN: That it was ordered
alphabetically. And that was a huge plus, because then I could go to the middle,
and I could go to the middle of the middle, and so forth. And that was an
algorithmic possibility. On our phones, if you pull up your contacts, you've got a
list of first names, or last names, all alphabetically sorted. That is because,
guess what data structure or layout your phone probably uses to store your
contacts? It's an array of some sort, right? It's just a list. And it might be
displayed vertically, instead of horizontally, as I've been drawing it today. But
it's just values that are back, to back, to back, to back, to back, that are
actually sorted. But how did they actually get into that sorted order? And how do
you actually find values? Well, let's consider what this problem is actually like
for a computer, as follows. Let me go ahead here. Would a volunteer mind joining us
up here? I can throw in a free stress ball. OK, someone from the back? OK, come on
up here. Come on. What's your name? ERIC: Eric. DAVID J. MALAN: Aaron. All right.
So Aaron's going to come on up. And-- ERIC: Eric. DAVID J. MALAN: I'm sorry? Oh,
Eric. Nice to meet you. All right. Come on over here. So Eric, now normally, I
would ask you to find the number 23. But seeing is that's a little easy, can you go
ahead and just find us the number 50 behind these doors, or really these yellow
lockers? 8? Nope. 42? Nope. OK. Pretty good. That's three, three out of seven. How
did you get it so quickly? ERIC: I guessed. DAVID J. MALAN: OK, so he guessed. Is
that the best algorithm that Eric could have used here? ERIC: Probably not. DAVID
J. MALAN: Well, I don't know. Yes? No? AUDIENCE: Yeah. DAVID J. MALAN: Why? Why
yes? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: He has no other information. So yes,
like that was the best you can do. But let me give you a little more information.
You can stay here. And let me go ahead and reload the screen here. And let me go
ahead and pull up a different set of doors. And now suppose that, much like the
phone book, and much like the phones are sorted, now these doors are sorted. And
find us the number 50. All right. So good. What did you do that time? AUDIENCE:
Well, [INAUDIBLE]. It was 50 is 116. So I just-- DAVID J. MALAN: Right. So you
jumped to the middle, initially, and then to the right half. And then technically--
so we're technically off by 1, right? Because like binary search would have gone to
the middle of the-- that's OK, but very well done to Eric. Here, let me at least
reinforce this with a stress ball. So thank you. Very well done. So with that
additional information, as you know, Eric was able to do better because the
information was sorted on the screen. But he only had one insight to a locker at a
time, because only by revealing what's inside can he actually see it. So this seems
to suggest that once you do have this additional information in Eric's example, in
your phone example, in the phone book example, you open up possibilities for much
much, much more efficient algorithms. But to get there, we've kind of been
deferring this whole time in class how you actually sort these elements. And if you
wouldn't mind-- and this way, we'll hopefully end on a more energized note here
because I know we've been in the weeds for a while-- can we get like eight
volunteers? OK, so 1, 2, 3, 4-- how about 5, 6, 7, 8, come on down. Oh, I'm sorry.
Did I completely overlook the front row? OK. All right, next time. Next time. Come
on down. Oh, and Colton, do you mind meeting them over there instead? All right.
Come on up. What's your name? [? CAHMY: ?] [? Cahmy. ?] DAVID J. MALAN: [?
Cahmy? ?] David. Right over there. What's your name? MATT: Matt. DAVID J. MALAN:
Matt? David. [? JUHE: ?] [? Juhe. ?] DAVID J. MALAN: [? Juhe? ?] David. MAX: Max.
DAVID J. MALAN: Max, nice to meet you. JAMES: James. DAVID J. MALAN: James, nice to
see you. Here, I'll get more chairs. What's your name? ,PEYTON: Peyton. DAVID J.
MALAN: Peyton? David. And two more. Actually can what have you come down to this
end here? What's your name. ANDREA: Andrea. DAVID J. MALAN: Andrea, nice to see
you. And your name? [? PICCO: ?] [? Picco. ?] DAVID J. MALAN: [? Picco, ?] David.
Nice to see you. OK, Colton has a T-shirt for each of you, very Harvard-esque here.
And each of these shirts, as you're about to see, has a number on it. And that
number is-- well, go ahead put them on, if you wouldn't mind. OK, thank you so
much. So I daresay we've arranged our humans much like the lockers in an array.
Like we have humans back, to back, to back, to back. But this is actually both a
blessing and a constraint, because we only have eight chairs. So there's really not
much room here, so we're confined to just this space here. And I see we have a 4,
8, 5, 2, 3, 1, 6, 7. So this is great. Like they are unsorted. By definition, it's
pretty random. So that's great. So let's just start off like this. Sort yourselves
from 1 to 8, please. OK. All right. Well, what algorithm was that? [LAUGHTER]
AUDIENCE: Look around, figure it out. DAVID J. MALAN: Look around, figure it out.
OK, well-- MATT: Human ingenuity. DAVID J. MALAN: Human ingenuity? Very well done.
So can we-- well, what was like a thought going through any of your minds? MATT:
Find a chair and sit down. DAVID J. MALAN: Find the chair-- find the right chair.
So go to a location. Good. So like an index location, right? Arrays have indices,
so to spea-- 0, 1, 2, all the way up to 7. And even though our shirts are numbered
from 1 to 8, you can think in terms of 0 to 7. So that was good. Anyone else? Other
thoughts? [? CAHMY: ?] I mean, this is something we implicitly think of, but no one
told us that it was ordered right to left. Like we could have done it left to
right. DAVID J. MALAN: OK. Absolutely. Could have gone from right to left, instead
of left to right. But at least we all agreed on this convention too, so that was in
your mind. OK. So good. So we got this sorted. Go ahead and re-randomize yourself,
if you could. And what algorithm was this? Just random awkwardness? OK, so that's
fine. So it looks pretty random. That will do. Let's see if we can now reduce the
process of sorting to something a little more algorithmic so that, one, we can be
sure we're correct and not just kind of get lucky that everyone kind of figured it
out and no one was left out, and two, then start to think about how efficient it
is, right? Because if we've been gaining so much efficiency for the phone book, for
our contacts, for [? error ?] coming up, we really should have been asking the
whole time, sure, you save time with binary search and divide and conquer, but how
much did it cost you to get to a point where you can use binary search and divide
and conquer? Because sorting, if it's super, super, super expensive and time-
consuming maybe it's a net negative. And you might as well just search the whole
list, rather than ever sort anything. All right. So let's see here. 6 and 5, I
don't like this. Why? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: 6 is supposed to come
after 5. And so, can we fix this, please? All right. And then let's see.
OK, 6 and 1-- ugh, don't really like this. Yeah, can we fix this? Very nice. 6 and
3, OK, you really got the short end of the stick here. So 6 and 3, could we fix
this? And 6-- yeah, OK. Ooh, OK, 6 and 7-- good. All right, so that's pretty good.
7 and 8, nice. 8 and 4, sorry. Could we switch here? All right. And then 8 and 2?
OK, could we switch here? OK. And let me ask you a somewhat rhetorical question.
OK, am I done? OK, no. Obviously not, but I did fix some problems, right? I fixed
some transpositions, numbers being out of order. And in fact, I-- what's your name
again? [? CAHMY: ?] [? Cahmy. ?] DAVID J. MALAN: [? Cahmy, ?] kind of bubbled to
the right here, if you will. Like you were kind of farther down, and now you're
over here. And like the smaller numbers, kind of-- yeah 1. Like, my god, like he
kind of bubbled his way this way. So things are percolating, in some sense. And
that's a good thing. And so you know what? Let Me try to fix some remaining
problems. So 1 and 5-- good. Oh 3 and 5, could you switch? 5 and 6, OK. 6 and 7? 7
and 4, could you switch? OK. And 7 and 2, could you switch? And now, I don't have
to speak with [? Cahmy ?] again, because we know you're in the right place. So I
actually don't have to do quite as much work this time, which is kind of nice. But
am I done? No, obviously not. But what's the pattern now? Like what's the
fundamental primitive? If I just compare pairwise humans and numbers, I can
slightly improve the situation each time by just swapping them, swapping them. And
each time now-- I'm sorry, [? Picco ?] is in number 7's place. I don't have to talk
to him anymore, because he's now bubbled his way all the way up to the top. So even
though I'm doing the same thing again and again, and looping again and again isn't
always the best thing, so long as you're looping fewer and fewer times, I will
eventually stop, it would seem. Because 6 is going to eventually go in the right
place, and then 5, and then 4, and so forth. So if we can just finish this
algorithm. Good. Good. Good. Not good. OK, 6 and 2, not good. If you could swap?
OK, and what's your name again? PEYTON: Peyton. DAVID J. MALAN: Peyton is now in
the right place. I have even less work now ahead of me. So if I can just continue
this process-- 1 and 3, 3 and 5, 4 and 5, OK, and then 2 and 5. And then, what's
your name again? MATT: Matt. DAVID J. MALAN: Matt is now in the right place. Even
less work. We're almost there. 1 and 3, 3 and 4, 4 and 2, if you could swap. OK,
almost done. And 1 and 3, 3 and 2, if you could swap. Nice. So this is interesting.
It would seem that-- you know, in the first place, I kind of compared seven pairs
of people. And then the next time I went through, I compared how many pairs of
people maximally? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Just six, right? Because we
were able to leave [? Cahmy ?] out. And then we were able to leave [? Picco ?] out,
and then Peyton. And so the number of comparisons I was doing was getting fewer and
fewer. So that feels pretty good. But you know what? Before We even analyze that,
can you just randomize yourselves again? Any human algorithm is fine. Let's try one
other approach, because this feels kind of non-obvious, right? I was fixing things,
but I had to keep fixing things again and again. Let me try to take a bigger bite
out of the problem this time by just selecting the smallest person. OK, so your
name again is? [? JUHE: ?] [? Juhe. ?] DAVID J. MALAN: [? Juhe, ?] number 2--
that's a pretty small number, so I'm going to remember that in sort of a mental
variable. 4? No, you're too big. Too big. Too big. Too big. Oh, what was your name
again? JAMES: James. DAVID J. MALAN: James. James is a 1. That's pretty nice. Let
me keep checking. OK, James, in my mental variable is the smallest number. I know I
want him at the beginning. So if you wouldn't mind coming with me. And I'm sorry,
we don't have room for you anymore. If you could just-- oh, you know what? Could
you all just shuffle down? Well, hm, I don't know if I like that. That's a lot of
work, right? Moving all these values, let's not do that. Let's not do that. Number
2, could you mind just going where-- where-- JAMES: It's James. DAVID J. MALAN:
--James was? OK, so I've kind of made the problem a little worse in that, now,
number 2 is farther away from the goal. But I could have gotten lucky, and maybe
she was number 7 or 8. And so let me just claim that, on average, just evicting the
person is going to kind of be a wash and average out. But now James is in the right
place. Done. Now I have a problem that's of size 7. So let me select the next
smallest person. 4 is the next smallest, not 8, not 5, not 7-- ooh, 2. Not 3, 6.
OK, so you're back in the game. All right, come on back. And can we evict number 4?
And on this algorithm, if you will, I just interpretively select the smallest
person. I'm not comparing everyone in quite the same way and swapping them
pairwise, I'm doing some of more macroscopic swaps. So now I'm going to look for
the next smallest, which is 3. If you wouldn't mind popping around here? [?
Cahmy, ?] we have to, unfortunately, evict you, but that works out to our favor.
Let me look for the next smallest, which is 4. OK, you're back in. Come on down.
Swap with 5. OK, now I'm looking for 5. Hey, 5, there you are. OK. So go here. OK,
looking for 6. Oh, 6, a little bit of a shuffle. OK. And now looking for 7. Oh, 7,
if you could go here. But notice, I'm not going back. And this is what's important.
Like my steps are getting shorter and shorter. My remaining steps are getting
shorter and shorter. And now we've actually sorted all of these humans. So two
fundamentally different ways, but they're both comparative in nature, because I'm
comparing these characters again, and again, and again, and swapping them if
they're out of order. Or at a higher level, going through and swapping them again,
and again, and again. But how many steps am I taking each time? Even though I was
doing fewer and fewer and I wasn't doubling back, the first time, I was doing like
n minus 1 comparisons. And then I went back here. And in the first algorithm, I
kind of stopped going as far. In the second algorithm, I just didn't go back as
far. So it was just kind of a different way of thinking of the problem. But then I
did what? Like seven comparisons? Then six, then five, then four, then three, then
two, then one. It's getting smaller, but how many comparisons is that total? I've
got like n people, n being a number. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Is not
as bad as factorial. We'd be here all day long. But it is big. It is big. Let's
go-- a round of applause, if we could, for our volunteers. You can keep the shirts,
if you'd like, as a souvenir. [APPLAUSE] Thank you, very much. Let me see if we
can't just kind of quantify that-- thank you, so much-- and see how we actually got
to that point. If I go ahead and pull up not our lockers, but our answers here, let
me propose that what we just did was essentially two algorithms. One has the name
bubble. And I was kind of deliberately kind of shoehorning the word in there.
Bubble sort is just that comparative sort, pair by pair, fixing tiny little
mistakes. But we needed to do it again, and again, and again. So those steps kind
of add up, but we can express them as pseudocode. So in pseudocode-- and you can
write this any number of ways-- I might just do the following. Just keep doing the
following, until there's no remaining swaps-- from i from 0 to n -2, which is just
n is the total number of humans. n -2 is go up from that person to this person,
because I want to compare him or her against the person next to them. So I don't
want to accidentally do this. That's why it's n -2 at the end here. Then I want to
go ahead and, if the ith and the ith +1 elements are out of order, swap them. So
that's why I was asking our human volunteers to exchange places. And then just keep
doing that, until there's no one left to swap. And by definition, everyone is in
order. Meanwhile, the second algorithm has the conventional name of selection sort.
Selection sort is literally just that, where you actually select the smallest
person, or number of interest to you, intuitively, again and again. And the number
keeps getting bigger, but you start ignoring the people who you've already put into
place. So the problem, similarly, is getting smaller and smaller. Just like in
bubble sort, it was getting more and more sorted. The pseudocode for selection sort
might look like this. For i from 0 to n -1, so that's 0 in an array. And this is n
-1. Just keep looking for the smallest element between those two chairs, and then
pull that person out. And then just evict whoever's there-- swap them, but not
necessarily adjacently, just as far away as is necessary. And in this way, I keep
turning my back on more and more people because they are then in place. So two
different framings of the problem, but it turns out they're actually both the same
number of steps, give or take. It turns out they're roughly the same number of
steps, even though it's a different way of thinking about it. Because if I think
about bubble sort, the first iteration, for instance, what just-- actually, well,
let's consider selection sort even. In selection sort, how many comparisons did I
have to do? Well, once I found my smallest element, I had to compare them against
everyone else. So that's n -1 comparisons the first time. So n -1 on the board.
Then I can ignore them, because they're behind me now. So now I have how many
comparisons left out of n people? n -2, because I subtracted one. Then again, n -3,
then n -4, all the way down to just one person remaining. So I'll express that sort
of generally, mathematically, like this. So n -1 plus n -2 plus whatever plus one
final comparison, whatever that is. It turns out that if you
actually read the back of the math book or your physics textbooks where they have
those little cheat sheets as to what these recurrences are, turns out that n -1
plus n -2 plus n -3 and so forth can be expressed more succinctly as literally just
n times n -1 divided by 2. And if you don't recall that, that's OK. I always look
these things up as well. But that's true-- fact. So what does that equal out to?
Well, it's like n squared minus n, if you just multiply it out. And then if you
divide the two, then it's n squared divided by 2 minus n over 2. So that's the
total number of steps. And I could actually plug this in. We could plug in 8, do
the math, and get the total number of comparisons that I was verbally kind of
rattling off. So is that a big deal? Hm, it feels like it's on the order of n
squared. And indeed, a computer scientist, when assessing the efficiency of an
algorithm, tends not to care too much about the precise values. All we're going to
care about it's the biggest term. What's the value in the formula that you come up
with that just dominates the other terms, so to speak, that has the biggest effect,
especially as n is getting larger and larger? Now, why is this? Well, let's just do
sort of proof by example, if you will. If this is the expression, technically, but
I claim that, ugh, it's close enough to say on the order of, big O of n squared, so
to speak, let's use an example. If there's a million people on stage, and not just
eight, that math works out to be like a million squared divided by 2 steps minus a
million divided by 2, total. So what does that actually work out to be? Well,
that's 500 billion minus 500,000. And what does that work out to be? Well, that's
499 billion, 999 million, 500,000. That feels pretty darn close to like n squared.
I mean, that's a drop in the bucket to subtract 500,000 from 500 billion. So you
know what? Eh, it's on the order of n squared. It's not precise, but it's in that
general order of magnitude, so to speak. And so this symbol, this capital 0, is
literally a symbol used in computer science and in programming to just kind of
describe with a wave of the hand, but some good intuition and algorithm, how fast
or slow your algorithm is. And it turns out there's different ways to evaluate
algorithms with just different similar formulas. n squared happens to be how much
time both bubble sort and selection sort take. If I literally count up all of the
work we were doing on stage with our volunteers, it would be roughly n squared, 8
squared, or 64 steps, give or take, for all of those humans. And that would be
notably off. There's a good amount of rounding error there. But if we had a million
volunteers on stage, then the rounding error would be pretty negligible. But we've
actually seen some of these other orders of magnitude, so to speak, before. For
instance, when we counted someone, or we searched for Mike Smith one page at a
time, we called that a linear algorithm. And that was big O of n. So it's on the
order of n steps. It's 1,000. Maybe it's 999. Whatever. It's on the order of n
steps. The [? twosies ?] approach was twice as fast, recall-- two pages at a time.
But you know what? That's still linear, right? Like two pages at a time? Let me
just wait till next year when my CPU is twice as fast, because Intel and companies
keep speeding up computers. The algorithm is fundamentally the same. And indeed, if
you think back to the picture we drew, the shapes of those curves were indeed the
same. That first algorithm, finding Mike one page at a time looked like this.
Second algorithm finding him looked like this. Only the third algorithm, the divide
and conquer, splitting the phone book was a fundamentally different shape. And so
even though we didn't use this fancy phrasing a couple of weeks ago, these first
algorithms, one page at a time, two pages at a time, eh, they're on the order of n.
Technically, yes, n versus n divided by 2, but we only care about the dominating
factor, the variable n. We can throw away everything in the denominator, and we can
throw away everything that's smaller than the biggest term, which in this case is
just n. And I alluded to this two weeks ago-- logarithmic. Well, it turns out that
any time you divide something again, and again, and again, you're leveraging a
logarithmic type function, log base 2 technically. But on the order of log base n
is a common one as well. The beautiful algorithms are these-- literally, one step,
or technically constant number of steps. For instance, like what's an algorithm
that might be constant time? Open phone book. OK, one step. Doesn't really matter
how many pages there are, I'm just going to open the phone book. And that doesn't
vary by number of pages. That might be a constant time algorithm, for instance. So
those are the lowest you can go. And then there's somewhere even in between here
that we might aspire to with certain other algorithms. So in fact, let's just see
if-- just a moment-- let's just see if we can do this a little more succinctly.
Let's go ahead and use arrays in just one final way, using merge sorts. So it turns
out, using an array, we can actually do something pretty powerfully, so long as we
allow ourselves a couple of arrays. So again, when we just add sorting with bubble
sort and selection sort, we had just one array. We had eight chairs for our eight
people. But if I actually allowed myself like 16 chairs, or even more, and I
allowed these folks to move a bit more, I could actually do even better than that
using arrays. So here's some random numbers that we'll just do visually, without
any humans. And they're in an array, back, to back, to back, to back. But if I
allow myself a second array, I'm going to be able to shuffle these things around
and not just compare them, because it was those comparisons and all of my footsteps
in front of them that really started to take a lot of time. So here's my array. You
know what? Just like the phone book-- that phone book example got us pretty far in
the first week-- let me do half of the problem at a time and then kind of combine
my answer. So here's an array-- 4, 2, 7, 5, 6, 8, 3, 1-- randomly sorted. Let me go
ahead and sort just half of this, just like I searched for Mike initially in just
half of the phone book. So 4, 2, 7, 5-- not sorted. But you know what? This feels
like too big of a problem, still. Let me sort just the left half of the left half.
OK, now it's a smaller problem. You know what? 4 and 2, still out of order. Let me
just divide this list of two into two tiny arrays, each of size 1. So here's a
mini-array of size 1, and then another one of like size 7, but they're back to
back, so whatever. But this array of size 1, is it sorted? AUDIENCE: No. DAVID J.
MALAN: I'm sorry? AUDIENCE: No. DAVID J. MALAN: No? If this array has just one
element and that element is 4-- AUDIENCE: There's only one thing you can do. DAVID
J. MALAN: Yes, then it is sorted, by definition. All right, so done. Making some
progress. Now, let me kind of mentally rewind. Let me sort the right half of that
array. So now I have another array of size 1. Is this array sorted? Yeah, kind of
stupidly. We don't really seem to be doing anything. We're just making claims. But
yes, this is sorted. But now, this was the original half. And this half is sorted.
This half is sorted. What if I now just kind of merge these sorted halves? I've got
two lists of size 1-- 4 and 2. And now if I have extra storage space, if I had like
extra benches, I could do this a little better. don't I go ahead and merge these
two as follows? 2 will go there. 4 will go there. So now I've taken two sorted
lists and made one bigger, more sorted list by just merging them together,
leveraging some additional space. Now, let me mentally rewind. How did I get to 4
and 2? Well, I started with the left half, then the left half of the left half. Let
me now do the right half of the left half, if you will. All right, let me divide
this again. 7, list of size 1, is it sorted? Yes, trivially. 5, is it sorted? Yes.
7 and 5, let's go ahead and merge them together. 5 is, of course, going to go here.
7, of course, is going to go here. OK. Now where do we go? We originally sorted the
left half. Let's go sort the right-- oh, right. Sorry. Now, we have the left half.
And the right half of the left half are sorted. Let's go ahead and merge these. We
have two lists now of size 2-- 2, 4 and 5, 7, both of which are sorted. If I now
merge 2, 4 and 5, 7, which element should come first in the new longer list,
obviously? 2. And then 4, then 5, and then 7. That wasn't much of anything. But OK,
we're just using a little more space in our array. Now what comes next? Now, let's
do the right half. Again, we started by taking the whole problem, doing the left
half, the left half of the left half, the left half of the left half of the left
half. And now we're going back in time, if you will. So let's divide this into two
halves, now the left half into two halves still. 6 is sorted. 8 is sorted. Now I
have to merge them-- 6, 8. What comes next? Right half-- 3 and 1. Well, left half
is sorted, right half is sorted-- 1 and 3. All right, now how do I merge these? 6,
8, 1, 3, which element should obviously come first? 1, then 3, then 6, then 8. And
then lastly, I have two lists of size four. Let me give myself a little more space,
one more array. Now let me go ahead and put 1, and 2, and 3, and 4, and 5, and 6,
and 7, and 8. What just happened? Because it actually happened a lot faster, even
though we were doing this all verbally. Well notice, how many times did each number
change locations? Literally three, right? Like one, two, three, right? It moved
from the original array, to the secondary array, to the tertiary array, to the
fourth array, whatever that's called. And then it was ultimately in place. So each
number had to move
one, two, three spots. And then how many numbers are there? AUDIENCE: [INAUDIBLE]
DAVID J. MALAN: Well, they were already in the original array. So how many times do
they have to move? Just one, two, three. So how many total numbers are there, just
to be clear? There's eight. So 8 times 3. So let's generalize this. If there's n
numbers, and each time we moved the numbers we did like half of them, than half,
then half, well, how many times can you divide 8 by 2? 8 goes to 4. 4 goes to 2. 2
goes to 1. And that's why we bottomed out at one element, lists of size 1. So it
turns out whenever you divide something by half, by half, by half, what is that
function or formula? Not power, that's bad. That's the other direction. AUDIENCE:
[INAUDIBLE] DAVID J. MALAN: It's a logarithm. So again, logarithm is just a
mathematical description for any function that you keep dividing something again,
and again, and again. In half, in half, in half, in third, in third, in third,
whatever it is, it just means division by the same proportional amounts again, and
again, and again. And so if we move the numbers three times, or more generally log
of n times, which again just means you divided n things again, and again, and
again, you just call that log n. And there's n numbers, so n numbers moved log n
times, the total arithmetic here in question is one of those other values on our
little cheat sheet, which looked like this. In our other cheat sheet, recall that
we had formulas that looked like this, not just n squared and n, and log n, and 1,
we have this one in the middle-- n times log n. So again, we're kind of jumping
around here. But again, each number moves log n places. There's n total numbers. So
n times log n is just, by definition, n log n. But why is this sorted this way?
Well log n, recall from week 0 with the phone book example, the green curve is
definitely smaller than n. n was the straight lines, log n was the green curved
one. So this indeed belongs in between, because this is n times n. This is n. This
is n times something smaller than n. So what's the actual implication? Well, if we
were to run these algorithms side by side and actually compare them with something
like this-- let me go ahead and compare these algorithms using this demo here-- if
I go ahead and hit play, we'll see that the bars in this chart are actually
horizontal. And the small bars represent small numbers, large bars represent long
numbers. And then each of these is going to run a different algorithm-- selection
sort on the left, bubble sort in the middle, merge sort, as we'll now call it, on
the right. And here's how long each of them take to sort those values. Bubble's
still going. Selection's still going. And so that's the appreciable difference,
albeit with a small demo, between n squared and something like log n. And so what
have we done here? We've really, really, really got into the weeds of what arrays
can actually do for us and what the relationships are with strings, because all of
it kind of reduces to just things being back, to back, to back, to back. But now
that we kind of come back, and we'll continue along this trajectory next time to be
able to talk at a much higher level about what's actually going on. And we can now
take this even further, by applying other sort of forms of media to these same
kinds of questions. And we'll conclude it's about 60 seconds long. These bars are
vertical, instead of horizontal. And what you'll see here is a visualization of
various sorting algorithms, among them selection sort, bubble sort, and merge sort,
and a whole assortment of others, each of which has even a different sound to it
because of the speed and the pattern by which it actually operates. So let's take a
quick look. [VIDEO PLAYBACK] [MUSIC PLAYING] This is bubble sort. And you can see
how the larger elements are indeed bubbling up to the top. [? And you can kind of
hear the ?] periodicity, or the cycle that it's going in. And there's less, and
less, and less, and less work to do, until almost-- This is selection sort now. So
it starts off random, but we keep selecting the smallest human or, in this case,
the shortest bar. And you'll see here the bars correlate with frequency, clearly.
So it's getting higher and higher and taller and taller. This is merge sort now
which, recall, does things in halves, and then halves of halves, and then merges
those halves. So we just did all the left work, almost all the right work. That
one's very gratifying. [LAUGHS] This is something called [? nom ?] sort, which is
improving things. Not quite perfectly, but it's always making forward progress, and
then kind of doubling back and cleaning things up. [END PLAYBACK] Whew. That was a
lot. Let's call it a day. I'll stick around for one-on-one questions. We'll see you
next time. [APPLAUSE]

Das könnte Ihnen auch gefallen