Now that we have ifs, there are lots of interesting problems we can solve, and tricks we can do. This section won’t have any new rules, just lots of examples.
Sometimes you need a few ifs to try to compute a value. One trick is, pick one of the values to start with. If all of the ifs are false, we say it “falls through” and keeps that starting value. It’s like having a “none of the above” (except it’s none-of-the-below.)
This is just a cutesy, not-really-useful way to rewrite an if-else, but it shows how the trick works:
Here’s the same trick to to make a “which is larger” if:
A more interesting example is to rewrite finding positive, negative, or 0. In this next example, "zero" is the fall-through:
Then, here’s a made-up example, where most insurance costs $100, but helicopters and canoes are different:
A trick to these is to think of it as a process. We’re not saying that cost is definitely 100 – we’re just starting it off as 100. The other trick is that ints aren’t like mashed potatoes. Adding or subtracting to a pile of mashed potatoes makes a mess, but we can easily adjust ints all we want.
We often want to program a lookup table with ranges and cut-offs. For example the standard grade 90=A, 80=B, 70=C rules. An obvious, awkward way is to hand-check each range, using this ugly, too-long code:
Notice how each breakpoint is repeated twice. 70 is in the 3rd if and 4th if (70-80 and 60-70.) And I had to be careful with > and >=. It would be easy to miss a number (like using >80 and <80 in both places, so we never give a grade to 80.)
A nicer way is using if-else’s and nested ifs. This works, and follows the by-the-book rules for indenting and nesting ifs, but is still long and confusing:
The &&’s are gone, which is nice, but there’s a lot of confusing {}’s and indents. It’s also harder to see that this is really just a table look-up.
For the final version, we’ll re-arrange it to look better. This is the most common way people write a range-slicing cascading if:
Again, this isn’t a new rule, just a clever way to rewrite things. It’s still one giant if (it’s exactly like the thing above.) Moving those if’s to the line right after each else is just a way to make it look better.
It runs line-by-line, quitting when it sees one it likes. One way to figure it out is to imagine it running for every possible number. The first if “slices off” everything 90 or more for an A. The numbers that get to the second line are the left-overs, all less than 90. The second line slices off 80 or more for a B. 79 and less goes on to the third line, and so on.
Looking at it another way, the line if(score>=70) grade="C"; would tell you 97 was a C if it was by itself. But it’s not by itself. It’s part of an assembly line. 97 never makes it past line 1.
This idea works going down or going up. Here’s the grade-checker starting from F and going up:
We can analyze it the same way: line one slices off 0-59 for an F, sending 60+ on.
Line two slices off 60-69 for a D, sending on 70+.
Here’s a different one. The old text-based dungeon MUDs didn’t tell you the damage as a number – that was considered to be lazy and mood-breaking. Instead it turned the damage amount into a word. If you hit for 8, it told you “you cut the orc.” Here’s a damage-to-word table:
This cascading if runs it (assume hit has the damage). Notice how each important number from the table occurs once:
A common error in a cascading-if is to forget an else in the middle. The program will still be legal, but it will run wrong. Here, I left out the else on the D line:
This is now two ifs. The first is a 3-line A/B/C if. The second is a simple if/else checking for D/F. Suppose grade is 93. The first if gives an A. But the second if gives a D. Forgetting the else makes it so everyone has a D or an F.
When you have several if’s in a row, each one could be true or false – all true, all false, or a mix could all happen. Often that’s what you want. This example could say a number is only positive, or only 1-digit, or positive and 1-digit, or positive and close to 10, or all three:
But sometimes, it works out that only one of them can happen. Here’s my “number to word” from before (written before we had elses):
Logically only one of those is true, but you can’t tell that right away. Rewriting it as a cascading if makes it more obvious that this is really a table look-up, and only one will be true:
We didn’t need to write it like that, but most people think it looks nicer.
Cascading if’s can also be used to make a “none of the above,” by adding a final else. This example has a look-up table for what sound an animal makes, with a default sound “rrr-rrr”:
The other way to do this is with the fall-through trick, setting w to "rrr-rrr" at the top.
When most people see a cascading if, they know a final else is “none of the
above,” and no final else means the whole thing could do nothing.
Sometimes we use a cascading if to make sure that only one if can fire. This is a rare case, and is a little tricky, Here’s a made-up example:
Suppose some formula adds 5 to numbers under 10, doubles numbers from 10-100, and divides larger numbers by 3. It’s just a nonsense formula, but for real you sometimes have things like that.
My first try of putting that in the computer is to check each thing with an if. Here’s the code, but it’s got a sneaky logic error:
The problem is that it might incorrectly do two of them. Say the number is 7. Line one adds 5 to get 12, like it should. But now stupid line two sees n is 12 and incorrectly doubles it! It doesn’t know 12 isn’t the input n, it’s the answer n. We get the same problem with 60: line two correctly doubles to 120, then stupid line three incorrectly divides it!
A clever person could rewrite it to maybe avoid double-ifs. But an easy way is to turn them into a cascading if:
This isn’t really an if problem, but it always happens inside of an if. Sometimes we want two variables, say n1 and n2, to be in order. If n1 is 5 and n2 is 3, we want to switch them to be 3 and 5.
Switching the values of two variables is a little trickier than it seems. Suppose you write n1=n2; n2=n1;. That just makes two copies of n2.
To correctly switch variables, we need an extra spot, and a little dance. We usually call that a swap. This sorts n1 and n2 in order, if they weren’t already:
You can hand-trace this for fun – make boxes for n1, n2 and temp, and run the 3 lines. Changing the order usually makes it not work, but there is one other way to write it.
Often your instinct is to want a formula that just gives the answer. Like n=
(insert equation here.) A lot of problems are easier to solve a little bit at a
time.
Often we want to count up how many items fit some rule, like how many of a, b and c are positive. Here’s my first try, which is long and not done:
A neat trick is, pretend you’re counting on your fingers. Start the total at 0, look at each item, and add 1 if you like it:
We just push the answer a little at a time. By the time we finish, it’s
correct.
Here’s the same idea, but it counts how many are between 40 and 60:
Finding the largest of four numbers can also go a little at a time. It works like the ”Price is Right” prize spin-off.
One person spins the wheel and steps into the winner’s circle. They haven’t won, but they’re the winner so far. The next person spins and has to beat them. And so on. Each person only has to beat the previous best.
Here’s how it looks in code, finding the largest of 4 numbers:
The trick is, largest is always the largest so far.
Just for fun, here’s finding the largest of a,b,c,d using just a big if-else. It’s longer and uglier:
This version gets even worse with 5 numbers. In the incremental methods, we just need one more if for each extra item.
This is a more oddball example of computing something a little at a time.
Suppose we have some sort of darts or guessing game. 70 is the target, worth 5 points. We get 3 points for being within five, 2 points within ten, and 1 point within thirty (the exact numbers aren’t really important.)
One way to solve it is with an ugly “hammer&tongs” approach, where we just brute figure the ranges and check them: “within 5” is 65-75, but not 70; “within 10 but not within 5” is 60-64 and 76-80; “within 30 but not 10 is” 40-59 and 81-100; and 0 points for “more than 30 away” can be the fall-through.
That’s a lot of numbers. I’d probably make a number-line and a little chart, to keep track of them. Then we write an if for each. This works, but is hard to read:
One obvious way it seems like we could improve this is with a range-slicing cascading if. The trick to making a good one is figuring out what to slice off first. Usually you go low to high, or high to low. But for this one, it makes more sense to start at the target, and to go away from it. I think this looks not-too-bad:
This uses the same slicing principles. The second if is really “65 to 75, but not
70,” because 70 was sliced off in the first line.
Finally, here’s what I think is the best way to solve this problem. The really neat thing about some computer code is you can think “how would a human solve this?” and then program the computer to do that.
As a human, I’d first see that the points table is written as “how much away.” So, I’d figure out how far n was away from 70, as a positive number. Then I’d look up that number in the “points for how far away” table.
Here’s how that plan looks in the computer:
It’s easier to see the plan, and the cascading if is easier to read (it’s the very first way I described it, before I confused myself with all the math.)
We have several gold mines. Each can have 1 or 2 workers. With 1 worker, they produce 2 gold. With 2 workers make 3 gold. We want to know how much total gold we can mine.
There are plenty of computer science word problems like this. They might be
math, or might be about using if’s. They’re probably not difficult, once you figure
them out.
My first thoughts are to list facts: if we have more mines than workers, then mines don’t matter – everyone works by themself, getting 2 gold. Workers past double the mines won’t matter – every mine produces 3 gold. We can never have a mine with 2 workers, and another empty – we want a worker in each mine before we start to double-up.
From the darts example, I know writing code based on that will turn into a mess. An old trick is to pick some “typical” numbers, get the answers by hand, and see it that leads to a formula:
Without thinking about it, I computed only the workers in each mine. That sounds about right. We can easily compute the gold from that. It also seems as if there are 3 cases. Workers <= mines; workers between mines and double mines, and workers >= double the mines.
This partly done cascading if should properly select between the cases:
I should test it (maybe just in my head) with some of those sample numbers.
Then I’ll have to work out the equations for each one. The middle is the most difficult. With 7 mines and 12 workers, the first 7 workers go to each mine, then the next 5 double-up. 5 came from 12-7. So workers-mines is how many doubled-up mines we have. All three equations, inside the if:
Integers have a remainder operation named modulo (we stole the idea and the name from math.) It can be used in some sneaky ways. First, how it works.
We know integer division drops the fraction. 16/5 is 3, not 3.2. If you think of it this way, division really has two answers – the number of times it goes in, and the remainder. From second grade, 16/5 is 3, remainder 1. Both answers are perfectly good numbers.
The / symbol means to divide and give me the first answer – how many times it goes in. % means to divide and give me the second answer – the remainder. Obviously, if you divide by 5, the remainder is between 0 and 4. It looks like this:
No one ever uses modulo in big equations, like x+y%5*2. But, just so you know, modulo counts as division, so has the same precedence as * and / (it happens before plus and minus.) Also, modulo with negative numbers works funny (does 5 go into -8 minus one or minus two times?) People rarely use modulo on negative numbers.
Here are some common tricks with modulo:
If you divide by 2, the remainder is 0 or 1. Remainder 0 meant that 2 went in evenly, 1 means it didn’t. So, checking even/odd integers works like this:
This is also a fun example of math inside an if. Try it with 7: Two goes into 7 three times, remainder 1. So 7%2==0 is false.
You can think of the if as “if 2 goes into n with no remainder.”
Another fun trick with modulo is breaking a number into digits. n%10 is always the one’s place, just because of how base-10 numbers work. Try it: take 569%10. Ten goes into 569 fifty-six times, with 9 left over.
We can get the 10’s and 100’s places by shifting them into the 1’s place. Since integers drop the fraction, dividing by 10 merely shifts: 569/10 is 56. The 10’s place was moved into the 1’s place. In use:
Now we can do some fun modulo examples, where we grow the answer.
A fun one is converting pennies into quarters, dimes, nickels and left over pennies. As a human, you take out the quarters, then take dimes out of the remainder, and so on. We can do that in a program. For simplicity, say we always have 99 or less pennies. The first part:
Finding quarters is just dividing by 25 and dropping the remainder, which is exactly how integer math works (you knew there was a reason.) The left-over pennies are the remainder, which modulo will get:
Testing just that, if we enter 93 for pennies, running gives us 3 quarters and 18 pennies (in the Inspector.) Not completely correct, but correct so far. Using 12 for pennies gives 0 quarters and 12 pennies, which is also correct, so far. The pennies variable is now the 0-24 left-over pennies.
Then we do the same thing for dimes and nickels:
Those two prints labelled debug are typical left-over testing lines, commented out
instead of deleted until we’re completely sure this is working correctly. With it
all done, 63 for pennies, should give quarters=2, dimes=1, nickles=0 and
pennies=3.
Here’s a similar example, converting seconds to hours, minute & seconds like ”00:05:32”. It starts with the modulo trick:
Leaving seconds-per-hour as (60*60) is a common trick to make programs easier
to read (3600 looks funny to me, but I can remember there are 60 seconds per minute
and 60 minutes per hour.) It feels like you should pre-do as much math as possible,
but the compiler does that for you. Trying to keep formulas readable is more
important.
For more fun with ifs, we can try to print it as 00:00:00. We turn each number into a string, then add a "0" in front if needed. This would come at the end of Start:
This would print 8 hours, 31 minutes and 4 seconds as 08:31:04.
Long expressions using AND and OR mixed-up can be difficult to read. Like
everything else, they usually make sense when you know what they’re for. But
sometimes trying to figure that out can be a pain.
Suppose I like any color cats and white dogs. This if correctly checks that:
It works because && has higher precedence than ||. In other words, the
computer puts invisible parens around the “dog and white” part. It’s like
4+3*5. You could put parens around them yourself, if you think that looks
better.
Here’s a longer one that likes cats, white dogs, goats or red skunks. For real, I might add parens on white dog and red skunk, and break it over two lines, as in the second one:
Back to just dogs and cats, if you wanted white no matter what, and a dog or cat, you could write it like this, with parens around “cat or dog”:
We need the parens. Without them, it would be a cat or a white dog.
Some longer ones; this is “white or black” and “cat or dog” (so white dogs, white cats, black dogs and black cats). The parens around the OR groups are required:
For fun, if we forgot the parens, we’d get something completely different:
That would accept lots of things it shouldn’t: anything white and any color dog.
This is a little tricky, and not vital. I just think you should see it. It’s about tricks
using not (!).
The not symbol (!) flips between true and false. For example if(!(x>0)) is “if x is not greater than 0.” It counts as a real math symbol, so it goes inside the official if parens, with all the other math. It has higher precedence than almost everything, so you usually have to use parens with it.
It can often make things easier to write. For example, suppose I want to check if x is not between 10 and 20. Here are two ways to write that:
The second one is longer, but I’m used to seeing “between 10 and 20”, and the
!-bang means not, so I can directly read it as ”not 10 to 20.”
Suppose I don’t want a white dog or cat. Writing it directly is tricky (I’ll do it later.) Writing “white dog-or-cat” and not-ing it might be easier:
The inside is “either a cat or a dog, has to be white,” which is a white cat or
dog. Then the ! in front make it the opposite. This might look terrible to
you now, but once you get more practice using if’s it starts being a neat
trick.
In general, the trick is: whenever you’re writing the inside of an if, see if writing
the opposite might be easier. If it is, write that, and put a ! in front of
it.
You can also use the else. This looks silly, but it works:
For fun, here’s a direct “not a white cat or dog.” Notice the ||’s and !=’s:
The official rule for bringing a not through an AND/OR grouping is DeMorgan’s Law. In algebra, you can take a(b+c) and distribute the a to get ab+ac. DeMorgan’s law says you can do something similar with true/false and not. Except it’s more complicated. You have to !(not) each part and flip the AND/OR. Here’s an example:
Here’s an example with ||, showing that it flips to &&. The inside part is “5, 9 or 16” and then I not it, to say “anything except 5, 9 and 16”, then I work out it using DeMorgan’s:
We can test this one – the &&’s really are the correct symbol (|| would always be
true – 5 isn’t 9 or 16.)
The most useful things to remember about this are 1) slapping a big !() around everything is fine, but moving the !s into the parts is extra tricky, 2) if you really, really need to convert true/false formulas using !s, there’s an equation for it. If you only remember “French pirate sounding name,” that should be enough for you to find it years from now.
There’s a limited version of an if that isn’t useful for anything, and is kind
of complicated to learn. But it’s semi-common, so you should at least see
it.
Here’s an example if and then the replacement switch
A switch statement to do the same thing looks like this:
It looks so strange that you figure it must run faster, or be better in some way, but they aren’t. In the old days, we tried a lot of crazy ways to write computer languages. If you’re curious, reading about a switch is like going into computer language history.