 Welcome back to 105. Wow, that's an echo. Yikes. All right, so thank you for joining me today. Today we get to continue our journey on, well, making our computer do something other than blindly following statement after statement. We actually taught it how to jump around a little bit. So we'll be doing more of that today and getting some more practice. So we saw a bunch of relational if statements. We saw a bunch of ways to just have ands and ores and more logic statements. So we saw comparing numbers. Well, we know that characters are actually just numbers represented by the computer. You don't actually need to know which character corresponds to which number, but you should know some generally how they are ordered because you are allowed to compare characters and sometimes it will be useful. So remember, characters are ASCII encoded. So they're converted into bytes and the way they are ordered. So the important ones are, well, all of the digits are ordered in order in terms of value. So zero gets the lowest actual number. Again, doesn't really matter what that number is. And then nine gets the highest of them. So if you did an inequality between all of them, so the character zero is actually less than the character one. And then you don't actually need to know what the character capital A is, but you should know in terms of order, well, a capital A is greater than any digit. So that's something you should know. And then all of the capital characters are all in order. So A is less than B, B is less than C, all the way up until Z. And then after that comes all of the lowercase letters. So we have lowercase a, then is less than lowercase b, is less than lowercase c, all the way up to lowercase z. So these are fairly important numbers to know because they will be, you know, they'll be actually useful as we will see today. So like I said, we can do arithmetic just on the letters. So the digits zero through nine, they're sequential. So their values increase by one. Again, doesn't really, we don't really care what the value of zero is, but it might be nice to know that, hey, if we take the character zero, so the characters between the single quotes, we can do math on it like anything else. So it's a bit weird looking, but we can just add the integer two to it. And then that will actually result in the character two. So because, you know, one away from the character zero is the digit one, and then two away from the character zero is the digit two. And similarly, if we just take the character zero and add five to it, we get the character five. Bit weird, but these are how the rules work. So again, all of the uppercase characters, they're sequential. So their values increased by exactly one, as well as the lowercase letters a through z. And if you want to know, well, an uppercase letter plus 32 results in a lowercase version of that character. But that's something you probably will never, ever use for this course. So if there's anything to forget, you can forget about that one. But if we actually did some examples with it, well, if we took the uppercase a, and then we added two, that's just two more letters in the alphabet. So a plus one would be capital B, uppercase a plus two would be uppercase c. And then if I had lowercase a plus three, well, that's lowercase d. And if I, you could also subtract if you really wanted to. So if I wanted to know the letter before O, I could just say O minus one. And then I would get N. So just fun things we can do with characters that may or may not be fun depending on who you are. So we can actually write a program now that kind of looks for users input and tells what the user is actually inputting. So, oh, Jesus. So we can write a program that actually looks for a letter. So here we can start the program off. Remember, if we argue about executing a program, it always starts at main. So the first thing our program would do would output, enter a character, and then here we will create a character. I will very creatively name C. So I'll create a character name C. And I'll initialize it. So in this case, this is a way of initializing it to a zero. So there is a special zero character that if you do an escape code and a zero, that means a character with a value of zero. Now I do a scan F. So I let the user input a character. Oh, yep, sorry. Yep. So on line six, this is actually equivalent of saying this if I really wanted to. They mean the same thing. But yeah, generally, if it's a character, character should just be in single quotes, because that's what a character is. You can play with the numbers if you want and let it kind of implicitly convert things. But you don't have to. All right. So then we do a scan F. So we let the user input a single character. And then this is going to be our job to say, Hey, did you enter a letter or did you not enter a letter? So we have an if statement. So if a condition I need to write is true, I want it to print off you entered a letter. Otherwise, I wanted to say I didn't enter a letter. And again, this is an if else statement. So only one of these things are going to print at whenever I run my program. So right now, since I just have if one, remember, that's just saying if true. So that means this code should always execute no matter what I input. So let's just make sure that that is true. And I haven't taken my crazy pills today. So enter a character, I can enter something that's not a letter. So let's say seven. And now because I wrote my program wrong, it says you have entered a letter. So that is no good. So let us start developing this relational statement we have to have in this if so that it will actually check that character and then see if it is a letter or not. So if we were to write, you know, actual English or actual like math notation, we probably might write something like, hey, well, we want a capital A to be between C and then between Z, something like that. Turns out we can't actually write that in C because, well, if we actually thought about how C calculates this value, what it would do, remember, C can only do like one operation at a time with two operands. So how it would do this is it would first, because we have less left associativity, so it would compare A to C. So in this case, if A is less than C, then this would be true. And then it would compare is true less than Z, which is probably not what you want. So we want to make sure our character in this case is between capital A and less than lower case Z. So in this case, yep, sorry? The YouTube crash? The stream crashed? It says it's working, so. All right, well, if it crashes, I have a recording on my laptop, I'll upload it later. Yep, yeah, so got a comment that in this case, if I want both of those things to be true, so I want to be between a value, well, I should pretty much just translate it like exactly what I said in English. So I want to make sure the character is greater than a capital A and also less than a capital Z. So then it's between those bounds if both of those conditions are true. So in this case, I might want to check, well, if C is greater than or equal to A, so I want to include A. So this means that it is some value that is capital A or greater. So I also, in this case, there's some letters and other things that are after the capital Z. So I want to make sure that it is also less than or equal to the capital Z. So I should say, I also want this to be true. So now if this is my if statement, that looks a bit better now. So now this if statement is only true if the character I entered is greater than or equal to capital A and it's also less than or equal to capital Z. So now we can compile this and try it. So now if I enter just an A, well, that should be true. So that conditional statement whenever C computes it, it's true. So I get this part of the if statement. So I get you enter the letter and now if I enter, I don't know, say a digit, well, a digit shouldn't be within those bounds. It shouldn't be a capital letter. So if I enter again, let's say six, I see it prints off. You did not enter a letter because this is false. So right now we are close to done. Yep. Yeah. So right now the scanf only looks for a single character. So if I enter more than one character, it'll do something weird. We'll figure out how to handle that later. But now we're just always assuming whoever is typing into the keyboard is nice to us. Yep. Yeah. Yeah. So in this case, well, I'm not quite done yet because there are lowercase letters. So if I run this program and I type, I don't know, a lowercase j, it says you did not enter a letter. So I still have some work to do. So this is basically just checking if I entered a capital character. So I also want to check if I I'm also allowed to enter a lowercase character. Sorry. Yeah. So if I want to include lowercase characters, well, if I was, we're to say it in English, I want to be able to accept an uppercase character or a lowercase character. Either is fine. I wouldn't want an and, I don't, no character can be both an uppercase and a lowercase at the same time. That doesn't make sense. So I will have to check if it is an uppercase or a lowercase. So in this case, well, I'm not going to worry about the precedence rules. So I'm just going to throw this whole logic statement all in brackets. So I just bracketed that uppercase check. And now I can write out my or and then start a new set of brackets. And then in here, I will check if it is a lowercase. So I want C to be less than or equal to lowercase a. And I also want C to be less than or equal to a lowercase Z. So now if I read this out right here, this little condition will check if it is that character I entered is between capital A and capital Z. So if that's true, well, that's an uppercase letter. And that's true. If I true or anything, it doesn't really matter what the anything is. The whole expression is true. And if it is false, well, then what's going to happen is we'll compute and see if the character is a lowercase letter. So we'll check if it is greater equal to lowercase a. And that character is also less than lowercase Z. So if we go ahead and compile and run this now, well, if I type a J, it says, hey, you entered a letter. So it seems to actually work now. And to just check that we didn't screw up anything, we can also just try, you know, a capital letter again to make sure that it still works. And now, hey, it says you entered a letter. We can try a digit and says you did not enter a letter. So it seems to work now. So any questions about that? Yep. Yeah. So the question is, well, what about if I just did some check where I just checked if it was greater equal to a uppercase a? Whoops. And then less than a lowercase Z. So turns out there are some values actually in between the uppercase and the lowercase letters. Let's see what they are. Oh, I don't have it handy. So I believe if I do this and I entered a tilde character, I think that's between. Yeah. So tilde character is between. So now it says that's a letter. That's not a letter. Wow. I pulled that one out of my rear. All right. Yeah. Yeah. So the question is when we had, when we had this statement where we were warring, does the order matter? So turns out it kind of matters, which we'll get into later. But in terms of the logic, it doesn't matter at all. They are equivalent saying A or B is the same as saying B or A. So in terms of the logic, no, in terms of C, C matters a little bit and we'll see why in a second. All right. Any other questions based off that? Whoops. All right. So sometimes that whole if statement is fairly ugly. Anyone agree with me that that's kind of ugly? So one way of making things more clear in programming, especially if you're just beginning is to give things names. So instead of doing all that complicated if condition, if you notice yourself making giant brackets around everything, that probably means you should just give something a name. So when I described it to you, I said, well, I checked for an uppercase letter and then I checked for a lowercase letter. So remember there is a data type called a Boolean and that is what the relational operators give you. So I could actually create a Boolean that is called is uppercase letter. So then in this Boolean, well, I can do that check. So I can just write it here. So it's an uppercase letter. If the character is greater or equal to uppercase a and less than or equal to lowercase z. And that makes it a bit more clear what I actually mean and what I'm checking. So now, well, if like a month later, you came back to this code and you looked at it, you might be like, well, what the hell are you doing? I don't even understand what you're trying to check for here. It looks a bit weird. So instead, like I said, we just give things a name. So now to check for a lowercase character. Well, I just do the same thing, but except I use a lowercase character. And now my if statement would look a lot clearer because now I just type if is an uppercase letter or a lowercase letter do the same thing. So now logically, the programs are exactly the same. But does everyone agree this is probably a bit more readable? And you can actually understand what it's doing here. So in this case, you should aim to write programs that are actually meaningful to you that you even have a hope of reading later. So I would suggest trying to name stuff as soon as you get if conditions that are really, really complicated. Just give it a name and say in English, because we can name variables whatever we want, try and give it an English description of what you're actually doing. Oops. All right. So here on the slides, here is the code. So you have it. And here is our version that is a lot more readable because we just made names and actually described what our conditions were actually checking. So this is where the order actually kind of matters. So the C compiler will do some fairly smart things. So if I write something that's like a complicated condition or another complicated condition. So in the case that this first complicated condition evaluates to true, then C is just like, yeah, well, I'm not even going to bother to compute the other side because true or anything is true. So I don't even, it doesn't matter what the value is. So why would I even compute it? So in that case, it would not evaluate this complex condition to and evaluate is just another word for C, computing that condition or your computer actually like computing the final value. So in the case I had before, if the character was an uppercase character and that was true, well, it wouldn't bother checking if it was a lowercase character, because it doesn't matter at that point. It's a letter and it can't be both at the same time. So why even bother? So that's the rules for a or statement because, well, the rule for an or is if either of the conditions are true, then the entire thing is true. So if the left hand side is true, doesn't matter what the value of the right hand side is. So there is a similar optimization for the and logic operator. So remember with and both of the conditions have to be true for the final result to be true. Or in other words, if at least one of them is false, then the entire thing is false. So if I write some complicated condition and another complicated condition, and this complicated condition is false, well, I already know ahead of time they both can't be true. So the only way the whole result is true is if both of them are true. So if one is already false, that means the entire thing is going to be false. So if we evaluate complex condition one, and it is false, we just skip evaluating complex condition two. And this not having to compute it because the value doesn't matter. Well, the name we give it is say C has lazy evaluation, which means it's not going to compute anything that doesn't matter anyways. So if it doesn't matter, C will not just waste time actually computing it. So any questions about that? So the order logically is not going to matter. But in this case, if you have and like A and B, if it's more likely than one is going to be false, you probably want that one first, but doesn't really matter. All right. And there was that. All right. So sometimes it is useful to rewrite these logic statements. And there are something called the Morgan's laws that if you've done, you know, discreet electronics yet, maybe you haven't, maybe you have, you might have seen these laws before. So they're just basically some logic laws. So the law state that not A or B, well, that is exactly the same as saying not A and not B. So how does that make sense? Well, in this case, the only time that this will be false is if they're both false. So in this case, I can kind of take this not, which just flips the value and kind of commute it in here. And I can change the A to be not A and the B to be not B. And then I just change the connective logic gate from an or to an and so these things are exactly equivalent. And I also have the version if inside here is an and then I can go ahead bring in the not and then I'll change the symbol over to a or. So in this case, the only way this is going to be false is if either of the values are false. So it's equivalent of saying it's either not A or not A is false or not B. They mean the same thing. So if I want to check that a character is not a letter, well, generally you want to kind of not have brackets everywhere to make it a lot easier to read. So instead of saying not an uppercase letter or a lowercase letter, I could use the Morgan flaw to change that into an equivalent statement that just gets rid of one of the parentheses. So the thing I could rewrite this as that is exactly equivalent is, well, I can just say if it's not a uppercase character and it's not a lowercase character. So they mean the exact same thing. And yeah, so there's not much memorization in the course, but you should actually kind of commit the Morgan flaws to memory. They might make the if you have a complicated if expression, it might make it easier to understand if you just rewrite it. So when you use these laws, we have to be aware of the precedence rules and some bugs you might have or some issues with your programs might be you didn't exactly use the brackets to get exactly what you wanted. So if I just took this, so say I just said I meant not an uppercase letter or a lowercase letter and I decided to just remove a set of brackets. So I changed it to not uppercase letter or lowercase letter are these two things equivalent. So these two things are not going to be equivalent because remember the unary operator so like not that flipped the value. Well it is a higher precedence than anything else. So what this will do is just flip the value of not an uppercase character. So this expression will be true if you do not have an uppercase character or you don't have a lowercase character instead of not having either or right. So these two expressions are absolutely not equivalent and you might get into problems if you just start removing brackets willy-nilly. So the second example if I reintroduced the brackets to it I would see that well the not comes first so it would be like not uppercase letter and that would be done first and then it would or with or lowercase letter. So don't really have to know the precedence rules just remember that unary operators have the highest precedence over pretty much any other binary operator but assignments and you should never use assignments and expressions anyways. All right another fun thing to be aware of is that a semicolon by itself is actually a statement. So remember the syntax for a if statement is just like if expression then a statement. Well if I write something silly like this I might write if you know uppercase letter or lowercase letter and then the really subtle thing I have is I just have a semicolon right here. So if I have a semicolon right there that is the statement that will run if that expression is true. So if that expression is true well my computer will execute this empty statement that does nothing and then this print f or if this whole expression is false it will skip over that semicolon and run this print f. So in either case if I just accidentally put a semicolon there I'll always get the result. You entered a letter printed off which I would not expect and would likely be wrong because it's not likely you're always going to enter a letter. So just remember do not use a semicolon there. Yep yeah so the question is are we going to get the this does this compile type questions on the exam and hopefully not because well then you could compile it but in this case this does compile fine it just doesn't work. Possibly so you can look at the past exams I don't think they did exactly that but yeah yeah so if I actually wrote this doesn't give me an error doesn't give me anything it just kind of runs weirdly. So no matter what it would always print the it uh the it would always do this print f and it would be weird so I should just don't write a semicolon here weird things will happen. So like I said before the print f always executes every single time just be really really careful about it if you write if and then parentheses do not put a semicolon so that's also the reason why I said just no matter what after that parentheses for the if just do a curly bracket. So do a curly bracket and you will not be disappointed you will not kind of shoot yourself in the foot. All right other things we can do we can chain if statements together so what I can do is I can write something like if a and then inside of the curly brackets after the a well that code and those statements will run only if a is true and then instead of just an else I can put an else if so I can actually check another condition so in this case if a is false so it would not it would not run these lines it would go over to the else and then check the condition for b and then if b is true then it will run this code so this code I'm currently hovered over will only run in the event that a is false and then b is true if b is also false then it will just skip right to the end there and sometimes this is useful and we'll get an example of that at the very end but any questions about that yep yeah so in this case I could still throw an else at the very end if I wanted to and end it with an else so I could always have some code run if all of the conditions are false so here is the flow of that program so it we would always start from the top it would run whatever code is wherever that start comment is and then it would check the condition for a if a is true it will run the code within the a is curly brackets and then it would skip right to the end in the event that a is true if a is false well then it goes into the elf is side checks the value of b if b is true it will run the code it will run the code here after b is curly brackets and that will run only in the event that a is false and b is true and then after it's done running that it would skip to the end and in the event that b is false it would skip right to the end and like the comment said if we wanted to we could write an else at the end so that if a is false and b is false then it always runs some code here before going to the end and joining up with the other ones other things we can do is we could nest if statements together so we could write if a and then within the curly brackets of a we could write if b and then have some statements in there so what that will do is if a is true then we will go into the code that will check if b and then if b is true we will run these statements so each time you begin a nested if that adds another level of indentation so you should shove your code over four spaces just to make it easier to read so generally if you write something like this so if you write if a and then directly inside of it you write if b well these statements will only run in the event that a and b are true so whenever you program you want to have as little lines of indentation as you can get away with so in general you don't like nesting unless you absolutely have to so if i wanted to write the equivalent thing without having two if statements well we should actually just use our logic statement so these statements only ran if a and b are true so instead of that i should actually just write well if a and b and then run the statements so like i said in general the less nesting you have the easier it is to read and the better your life will be so let us do our exercise of the day and we will write a program that actually finds the maximum of three integers so we're going to have our user just input three numbers we're going to cleverly call them x y and z so in our scan f we'll have you know three integers so %d %d %d and we'll put them in the variables x y and z so they will go in that order so our job is to do something here and actually assign the value to max which uh as the larger of whatever the variables are so say i enter so if i enter three two and one well the largest number out of all those is three so at the end i should print off a three in this case well i didn't initialize max and i just printed out so it should just have some old garbage value so it likely won't be any of these so if i do that hey i get 43 000 so again looks weird that's why i said initialize your variables so in this case right my maximum could be x or it could be y or it could be z so i have to write some logic to figure out which one it is so generally when you do this we can write out our options so at the end what our options are well we either want max to be equal to x max to be equal to y or max to be equal to z and we want only one of those to run so that means well we need an if statement for that because well we don't want all three of them to run we only want a single one to run so in this case it's generally easier to reason about it on a case by case basis so first i might want to figure out the conditions that i want to assign the max value to x so if i'm going to assign the max value to x that means x should be bigger than everything else so one of the things x should be bigger than is let's say x should definitely be greater than or equal to y so in that case we know that y is one of the biggest values well what's another case so that's not sufficient so right here i know that my biggest number is going to be x or z in this case right yeah yeah i'll get to yeah yeah we'll get to that so for now i'm just writing code as it comes to me and we can clean it up a bit later so if i'm here well i know that x is at least as big as y so my maximum number is either x or z so i can try to distinguish between those two cases so if x is greater or equal to z so in this case which would be the largest number x or z votes for x this is mattering votes for z votes for completely asleep yeah all right so in this case well if i'm here i know x is bigger than y and if i make it into this if statement i know x is bigger than y and x is bigger than z or equal to it so in this case my max is going to be x and then here i can write i should consider well what happens if that is not true well i know that x is at least as big as y so if x isn't also at least as big as z that means the maximum number is going to be z so that's one case where the maximum value is equal either x or possibly z so we can think of another case so here i should write an else if because well i actually want this code to run only in the case that i haven't run the other one yet and i still need to check for other things so i might want to check that well i want to check if y is my greatest value so i want to think about the cases where i want to assign the max equal to y so i can check well is y greater or equal to x so now if y is greater two or equal to x well then i know my maximum value is equal either a y or a z so i could do the same type of thing where i could check hey is y greater or equal to z as well and if y is also greater or equal to z well then my maximum value is y and then in the case that that is false i could write an else here and say that the max is equal to z okay well now we have to consider the last case here so if this is false and this is false well i don't have to really check anything else because i know x can't be the largest number and i know y can't be the largest number so by process of elimination i should just write else max equals z so in this case i know z is the maximum number so that should cover all my cases so now either this is true and i either assign max to x or z or this is true and then i assign max equals to y or z or otherwise i know for sure that the maximum value is whatever i wrote in z so now if i compile this and run it and i type again my three two one well in this case x which is the first letter because order matters should be bigger than everything else so i'll get maximum three and that's because if we follow what our program did well x was three so three was greater or equal to y and three was also greater or equal to one so just this happened so if this line happens then nothing else would execute until we get to the print f line all right any questions about this program yep yeah don't worry about all right yep yeah so the question is is the last else statement necessary so if i just got rid of this so if i got rid of this well in this case i might never actually assign anything to uh to max so if i write the numbers in this case let's say i do one two and three uh that works sorry i do oh yeah so in this case we don't actually really need the else unless there's something i'm forgetting but i could in order to be safe instead of writing that else i could just initialize max equal to z at the beginning and then i don't also don't have to i really don't have to write that else all right can we clean this up anymore to make it a bit more readable because well can get rid of that nesting of if statements yep yeah so in this case if i set max equal to z well i don't need this else because the maximum is already zed so i don't need to just reassign max from z to z so i can just get rid of this and also get rid of this and now if i have this code well it looks very similar to my nested like if a and then inside of that if b i could instead write well if a and b so this looks exactly the same so i can actually just rewrite it so i'll just move that condition out so i'll say if x is greater or equal to y and also x is greater or equal to z then i can just get rid of this because i know if both of those things are true then the maximum value is equal to x and then similarly for the other branch here well if y is greater or equal to x and y is greater or equal to z then the maximum is equal to y and in this case my entire solution actually fits on a screen and it's a bit easier to to read so now if i compile and run this it should be exactly equivalent of what i had for i just got rid of my nested if so if i run this say one three two well then my maximum in this case is just going to be three so it seems to actually work in this case yep no because that's only false of both if either one of them are false yeah yeah all right any other quick questions all right otherwise i will be around here for a few more minutes and we can leave three minutes early so just remember pulling for you we're all in this together