Jump to content
New account registrations are disabed. This website is now an archive. Read more here.
  • entries
    15
  • comments
    11
  • views
    14,483

Lesson 6: Flow Control - Decision Making

Sign in to follow this  
kellessdee

679 views

Lesson 6: Flow Control - Decision Making


In the previous lesson, I taught you about boolean logic. Hopefully, you have a good basis to the concept. This lesson will actually put that logic to good, practical use.

This lesson is the first part in the series on Flow Control.

Now, for the obligatory "What is flow control...?"
At this point, all your programs have had a very basic flow, just execute each line, one by one. However, when you start making real applications/programs, you'll find that this one directional program flow is too simple for your application. This is where flow control comes in, we want to be able to control the flow of the program.

The most basic form of flow control is decision making.

In programming, decision making is possible through the use of `if` statements. An if statement is what we use to make our boolean expressions do something. "if this, then do that."
So, in ruby...
if <condition>
 # What we want to do
end


where <condition> is an expression.
note that an if statement must be closed with the "end" keyword.

The code we embed within the "if clause" or "if statement" is tabbed, or by ruby's conventions, 2 spaces from the left margin. This is not necessary for the code to run, but as a coding convention this makes your code much easier to read and understand.

What happens when the Ruby interpreter reaches an if statement? The interpreter will first evaluate <condition>, if <condition> evaluates to true, the code inside the statement will be executed. If the <condition> is false, the code inside the statement will be ignored, and the interpreter will continue from the line with "end"
What about this?
if 1
 puts "1 is true"
end


If you were to run this code, the output would be:

1 is true


But why? 1 is not a boolean!
Well, this is because of how ruby handles boolean expressions. When the interpreter gets to the "if ..." it assumes the expression to be a boolean expression, and with ruby, anything except nil and false is interpreted as true, and nil and false interpreted as false.
So, when the interpreter gets to "if 1", it evaluates the expression, in this case 1 returns 1, and since the value is not nil and not false (1), the code within the if statement is executed.
So, let's try an example. I want you to create a script this time, call it "age.rb"
So let's do some decision handling. We are going to make a program that takes the user's age, and tells them if they can buy alcohol. In Canada, where I live, you need to be 19 or older. You can incorporate your local age limit for drinking into the program if you'd like.

# Prompt the user to enter their age
# we use print as that does not append a new line character
print ("Enter your age: ")

# Now we should get the users age
# and convert to an integer:
age = gets.to_i

# Lets see if the user is old enough
if (age >= 19)
 # They are of age!
 puts ("You are old enough to buy alcohol.")
end


So, why not try it out!

ruby age.rb


Upon running your script, you will notice, unless you enter 19 or greater, the script will just end. Only if you enter age greater than or equal to 19, you will get "You are old enough to buy alcohol."
Well, we should let the user know if they aren't old enough to buy alcohol as well...and hey, why not let them know how many more years until they can?

The next part of the "if" statement, is the "else" clause.

if <condition>
 # Code if condition true
else
 # Code if condition not true
end


The block of code in the "else" clause will only be executed if the original block or any other block within the if statement is NOT executed. In this case, if the <condition> is false, the first block will not be executed. Because of this, the second block under "else" will be executed instead.

if (age >= 19)
 # They are of age!
 puts ("You are old enough to buy alcohol.")
else
 # They are not of age!
 years_left = 19 - age  # How many years until they can buy alcohol
 puts ("You are not old enough. Come back in #{years_left} years!")
end



If you add that to your age.rb script, you will notice if you enter an age less than 19, the program will print out "You are not old enough. Come back in X years!"

So now, you can test a value, and can act based upon whether or not that test is true.

But, what if we want to act depending on several possibilities?
This is where "elsif" come into play. The "elsif" clause allows us to create multi-sided responses, based on multiple conditions.

if <condition>
 # Code
elsif <condition>
 # Code
else
 # Code
end


The ruby interpreter will first enter the if statement at "if <condition>," if that condition is true, it will execute the following block of code, then jump to the "end." If that condition is false, the interpreter will then move to the first "elsif <condition>." If that condition is true, the interpreter will execute the block of code within the elsif clause, then jump to the "end." If false, the interpreter will continuously check any following "elsif" and if none are true, finally the "else" block will be executed instead.
So, this means you could have any arbitrary number of elsifs:

if <condition>
 # Code
elsif <condition>
 # Code
elsif <condition>
 # Code
elsif <condition>
 # Code
else
 # Code
end



With the if statement, you can have as many elsif clauses as you'd like, however you can only have ONE if clause, and ONE else clause. You should also know, that the else clause is optional, whereas the if clause is NOT optional


Be careful, if you have programmed in other languages, you may notice that the "elsif" equivalent may be represented differently, ex. else if © or elif (python), in ruby it is "elsif"


So, why might you need to test multiple conditions? Well, let's try this example:
Let's pretend we want to make a guessing game. The Game will have a set number, and the user will try to guess which number is set. If they guess too low, the program will say: "Too low!" and if the number is too high, the program will say: "Too high!", and if the number is equal, the computer will say "Wow! You got it!"
We will not be making this guessing game fully functional just yet, in a real guessing game, you'd want the number to be different each time, and probably want the program to run until the user quits. We will finish this game in later lessons

So, how would we do this?
# Set a number to guess
number = 10
# Prompt the user to guess what number it is:
print ("I am thinking of a number. What is it? ")
# Get the guess
guess = gets.to_i
if guess < number
 puts ("Too low!")
elsif guess > number
 puts ("Too high!")
else
 puts ("Wow! You got it!")
end


Try putting this in a script called "guessinggame.rb" and running it.

As you may have noticed, the if statement could have also been written like this:
if guess < number
elsif guess > number
elsif guess == number
end


This would work exactly the same. However, I chose to catch that in an else clause, as we know that if guess is NOT less than number, and NOT greater than number, it HAS to be equal to number. So, it would be more efficient to group that code under an else clause. In order to determine whether to execute a block under an if/elsif, the interpreter has to execute and evaluate the condition. With else, that condition is not executed, saving us some CPU processing.



Let's try another example. Let's make a program that gets the current month as a number, and prints out the English name of that month.
# Get month:
prints ("Please enter the current month as a number: ")
month = gets.to_i
if month == 1
 puts ("January")
elsif month == 2
 puts ("February")
elsif month == 3
 puts ("March")
elsif month == 4
 puts ("April")
elsif month == 5
 puts ("May")
elsif month == 6
 puts ("June")
elsif month == 7
 puts ("July")
elsif month == 8
 puts ("August")
elsif month == 9
 puts ("September")
elsif month == 10
 puts ("October")
elsif month == 11
 puts ("November")
elsif month == 12
 puts ("December")
end


Whew, that was a lot of work. It works, but that seems like an awful a lot for a simple translation. As you can see, in some cases you may want to test for a specific value, and each value could represent something specific. Well, thankfully, ruby has a much more elegant way of doing this, with "switch" or "case" statements.

case <variable>
when value then 
 #code
when value then
 #code
when value then
 #code
else
 #code
end


So how does this work? The case statement takes <variable> then tests that variable against each value, testing if they are equal to each other. The first value that matches, it's corresponding code is executed. You can have an arbitrary number of when clauses, and else works exactly like in if. If no other values are equal to <variable> then else will be executed. Like in the if statements, else is optional here as well.

The "then" part of "when value then" is optional, as long as you have the block of code on a new line.
when value
 # Code must be here


However, with the "then" portion, you can actually put the block on the same line:

when value then # Code could be here


Also, I would like to point out that the "else" portion SHOULD NEVER have a "then," if you want to put the else block on the same line, use a semi-colon (;) (ruby interprets this as a line terminator)

else; # Code could be here


So, lets fix our nasty if statement:
case month
when 1 then
 puts ("January")
when 2 then
 puts ("February")
when 3 then
 puts ("March")
when 4 then
 puts ("April")
when 5 then
 puts ("May")
when 6 then
 puts ("June")
when 7 then
 puts ("July")
when 8 then
 puts ("August")
when 9 then
 puts ("September")
when 10 then
 puts ("October")
when 11 then
 puts ("November")
when 12 then
 puts ("December")
end


Well, that looks much neater. It's still long, but neater and a little less typing. However, I feel that the output is too "hard coded." What if later, we decide we want to put the month into a variable, for later usage? We'd have to change each "puts" to "variable =." With modern IDEs, this would be a simple task, but still unnecessary. One major rule of the ruby community (or even any programming community) is "Don't Repeat Yourself", and I feel both "puts" or "variable =" that many times, is repeating yourself.
Conveniently, we can get around this. Remember how everything in ruby is an object? And every expression returns an object? Well, case is no different.
case will "return" (or send back what the block returns) whichever block of code passes the test. In this case, it sends back "puts ("month")". Well, as you may know, the line of code "string" will return a string object, with the value "string." So what about this:

puts case month
 when 1 then "January"
 when 2 then "February"
 when 3 then "March"
 when 4 then "April"
 when 5 then "May"
 when 6 then "June"
 when 7 then "July"
 when 8 then "August"
 when 9 then "September"
 when 10 then "October"
 when 11 then "November"
 when 12 then "December"
 end


Now, that, is much neater. Normally, we would want to put the block of code in a when ... clause on the next line, but in the case where we are simply returning a single string, I find it actually more readable like this.
Then, if we wanted to change it to store the month in a variable:

# Get month
prints ("Please enter the current month as a number: ")
month_no = gets.to_i
month_word = case month
 when 1 then "January"
 when 2 then "February"
 when 3 then "March"
 when 4 then "April"
 when 5 then "May"
 when 6 then "June"
 when 7 then "July"
 when 8 then "August"
 when 9 then "September"
 when 10 then "October"
 when 11 then "November"
 when 12 then "December"
 end
puts ("The month is #{month_word}.")



Now we only had to change one line of code, rather than 12 lines of code.
You'll probably find yourself using "if" statements MUCH more than case/switch statements; however for certain things, especially menus (where you have several choices, and the current choice is stored in a single variable) case statements are much easier to employ.

The case/switch statement is mainly used to test a single variable against multiple different values, however it IS possible to use it like an if...elsif statement:
case
when variable == 5
when other_variable < 5
end


However, this isn't often used, as if...elsif would be preferred in this case.



The case/switch statement uses the === operator to test the variable against when value rather than the == operator. For the most part, === works EXACTLY like ==. The reason for this is so one can define their own object's "case" behavior. I will explain how one can take advantage of this in later lessons; so do not worry about it for now. I just wanted to give you a heads up, in case you might be learning with other tutorials as well.


One last thing I would like to teach you, is the "conditional assignment operator." As you start writing your own programs, you may find yourself in a situation where you only wish to assign a variable a certain value, depending on one condition, otherwise, set it to something else. This is called conditional assignment. For example, what if we want the absolute (non-negative) value of a number? We could do something like this:
if number < 0
 absolute = -number
else
 absolute = number
end


That works, and is neat; but it's a lot of extra typing. This is where the conditional assignment operator comes in. It's a little bit trickier than if statements, but can save a lot of typing.

variable = <condition> ? <true> : <false>


<condition> is any boolean expression
<true> is the value assigned to variable if <condition> is true
<false> is the value assigned to variable if <condition> is false

The important things to watch for is, <condition> MUST be followed by a `?` and must be separated by spaces. The true / false values MUST be separated by `:`

So, let's try our example:

absolute = number < 0 ? -number : number


So, if number is less than 0, give absolute the value of -number (which would be positive, since number is < 0) otherwise, just give absolute the value of number.

Now, it looks a bit neater, and much less typing.

You do not NEED to use conditional assignment. However, if you are comfortable with using it, I would suggest doing so, as it can save you a lot of typing in the long run.


There are also other "forms" of conditional assignment, that are used less often. I will leave those out for now, and include them at another time.

So, that's it for now. Before I go, I would like to leave you with a few practice coding challenges:

The following challenges are borrowed from "Practical C Programming" by Steve Oulline. I feel these are vary practical and challenging uses of decision making, and couldn't think of anything better :( (and I wanted you to have a lot more practice at this point) (By the way, they are actually easier to solve in ruby than in C)


1. Write a program that takes a student's grade as a number/percentage, and prints out their letter grade.
Grade Table
Letter Grade Numeric Grade
F 0-60
D 61-70
C 71-80
B 81-90
A 91-100

(HINT: Remember how the interpreter executes an if statement)

2. Modify the previous program, to also display a + or - after the letter. The +/- modifier is based on the last digit of the numeric grade:
Grade Modifications
Last Digit Modifier
1-3 -
4-7 <blank>
8-0 +

ex. 81 = B-, 94 = A, 68 = D+. Remember, and F is only an F, you cannot get an F-
(HINT: Remember the modulus operator? `%` ?
EVEN BIGGER HINT:

irb(main):001 > 1 % 10
=> 1 
irb(main):002 > 11 % 10
=> 1 
irb(main):003 > 21 % 10
=> 1



3. Given an amount of money (less than $1.00), compute the number of quarters, dimes, nickels, and pennies needed.

4. A leap year is any year divisible by 4, unless the year is divisible by 100, but not 400. Write a program to tell if a year is a leap year.

5. Write a program that, given the number of hours an employee worked and the hourly wage, computes the employee's weekly pay. Count any hours over 40 as overtime at time and a half.

6. EXTRA HOMEWORK CHALLENGE:
If the previous challenges did not satisfy you, here's the REAL challenge. For this challenge, you will need to go out and learn about Ruby's "Range" data type. Do not worry, I will teach you about this data type in the future.
http://www.ruby-doc.....9.3/Range.html

Now, the challenge will be to redo the first 2 challenges, using the Range class.
HINTS

To create a Range data type, the shorthand is:

variable = 0..5
variable = 0...5


0 is the (minimum) starting number
5 is the (maximum) ending number
.. means 0 to 5 inclusive (0, 1, 2, 3, 4, 5)
... means 0 to 5 exclusive (0, 1, 2, 3, 4)
To solve this new challenge, you will need to find out how Ranges behave within a case statement (HINT: look up the === method)



MORE ON IF STATEMENTS:

The following is a few extra points about "if" statements. These are non-essentially to learning if statements, but you may find them QUITE useful (I know I do). I left them out of the main lesson because I didn't want to overload you with too much. I would advise that you first ensure you are comfortable with using if statements, before learning these.

Unless...elsif...else...end

For almost every ruby statement, there is an opposite to that statement. For example, puts/gets. puts prints a string and adds a new-line; gets takes a string and adds a new-line.
For if, we have "unless."

unless variable == 5
 # Do this
end


That would be the same as writing:

if variable != 5
 # Do this
end



or, more specifically

if !(variable == 5)
 # Do this
end



Unless works exactly like if, except you cannot use "elsif", and of course, it evaluates the <condition> then inverses the result. Unless works best with single sided-decisions, and statement modifiers (easiest to read), so, be careful, as unless can get confusing fast, especially when testing complex statements, like:

unless (true and false) or true
 puts "false"
else
 puts "true"
end



The output would be:

true



This is because (true and false) or true is true, (true and false) = false, (false or true) = true, then unless executes the else block, because the condition was true. So,

unless !(true and false or true)
 puts "false"
else
 puts "true"
end



would produce:

false



Confusing? It can be. Use your discretion when using the unless statement.

Statement Modifiers

Sometimes, we only need do execute one line of code, based on a specific condition, ex.
if <condition>
 variable = 0
end


However, ruby provides us a simple, neat way to write this in one line of code:

variable = 0 if <condition>


this Works EXACTLY like the previous statement. With statement modifiers, you can ONLY have a one-sided decision, and can ONLY have one line that is executed.
This also works for unless statements:

variable = 0 unless <condition>



Technically, you can have more than one line of code executed, with use of parentheses and semi-colons, ex.
(puts variable; variable = 0) if variable != 0


However, this is not preferable. It makes the code more difficult to read. But, nonetheless, in some cases it might be okay to do this, just do what feels best for you.

Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
×
×
  • Create New...