Algorithm Features
Hopefully by now you are in no doubt about what constitutes an algorithm, and having created one or two during the previous chapter, you will appreciate that each algorithm is made up of a set steps, which are required to accomplish a particular task.
In addition, and as pointed out in the previous chapter, it is vital that we use a finite number of steps in our algorithm. Thus, the aim is for our algorithm to terminate when the last step has been executed. A well designed algorithm is always guaranteed to terminate.
However, having a finite number of steps (although a step in the right direction) does not necessarily guarantee the termination of our algorithm.
Let’s re-consider the “running” algorithm from the previous chapter:
1- Put on running clothes and shoes.
2- Open door.
3- Go out of door.
4- Close door behind me.
5- Start jogging to warm up.
6- Start running and wheezing.
7- Cool down.
8- End run.
It seems straight forward enough, and it is, under normal circumstances. But, what would happen if you encounter a problem along the way, say in step 2. The door does not open, it is jammed or locked and the key is nowhere to be found. Suddenly, the task of getting out of the door (step 3) does not seem that straight forward.
For a human, this might present a setback, and cause a delay in the time taken to get to the final step within the algorithm. A deviation will become inevitable while we make the necessary arrangements that will help us overcome “the door problem”, i.e. making a phone call to the locksmith for instance. A computer on the other hand, faced with such an algorithm, where one of its steps cannot be successfully executed will crash and throw out an error message along with some technical jargon, which is its way of saying “bad algorithm”.
To overcome this shortfall in our algorithm, we need to introduce a mechanism that allows the algorithm to make “decisions” along the way to indicate the correct path to follow based on a given set of circumstances.
At this point, it is worth stating that for an algorithm to be useful and efficient, it must incorporate the following features:
· Sequence
· Decision
· Repetition
Sequence
The order in which the steps appear within our algorithm is important to a certain degree. If we divide step 1 of our algorithm “Put on running clothes and shoes” into three separate steps:
1a- Put on running shorts.
1b – Put on running t-shirt
1c – Put on running shoes.
We can safely claim that executing step 1b before 1a should not make any difference to our overall result. The same apply to swapping 1a and 1c, although this might make the process of executing step 1a a little harder, especially if you have large shoes and small shorts. However, swapping steps 2 and 3; the result could be painful.
Decision
Decisions help us to find alternative routes based on a given condition, i.e. what to do in the situation where the door does not open, or equally as bad, if it does not close behind you. Do you cancel your run, find a workaround or ignore the problem?
Repetition
You can think of step 6 “Start running and wheezing” as a continuous repetition of the same action, namely putting one foot in front of the other.
More on repetition in the next chapter. Meanwhile, the remainder of this chapter will introduce the “decision” feature and the different way in which it can be incorporated into various algorithms.
Going back to our problematic algorithm, consider the following enhancement:
1- Put on running clothes and shoes.
2- Open door.
3- If the door is open then go out of door, otherwise, cancel the run.
4- Close door behind me.
5- Start jogging to warm up.
6- Start running and wheezing.
7- Cool down.
8- End run.
Although an improvement on the previous attempt – the algorithm is sure to terminate regardless of the possibility of encountering a problem at step 2 – it is worth pointing out the following:
· The action taken in response to our problem at step 3 represents one alternative only, other equally viable solutions do exist, and it is your job as a programmer to pick the solution that represents the best alternative, taking into account the various factors that surround your particular project.
· The same approach can be repeated at step 4, if you encounter a problem during the “door closing” process. Bearing in mind that ignoring this particular problem does not prevent you from terminating the algorithm, you can still go for a run while leaving the door open. So again, a decision have to be made on whether a new “decision” feature is required for your algorithm or not.
Thus far, we have concentrated on “decisions” that enables us to recover from exceptions that could occur (door not opening) during the execution of our algorithm. However, we can also use “decisions” to aid us in controlling and selecting the most optimum route for our algorithm to follow, so that our end goal can be achieved in the most efficient way possible.
Running as a hobby, is a beneficial activity, which is practiced by many people, with the aim of maintaining their ideal weight and reducing their stress levels. Although the weight loss benefit could occur regardless of whether you run in an enchanting park or inside and under cover complex, however, the stress reduction benefit could be easily optimised by choosing the environment that surrounds the jogger during the activity.
Based on the above, lets revisit and further enhance our “running” algorithm:
1- Put on running clothes and shoes.
2- Open door.
3- If the door is open then go out of door, otherwise, cancel the run.
4- Close door behind me.
5- If not raining then turn right into the park, or else turn left to the undercover complex.
6- Start jogging to warm up.
7- Start running and wheezing.
8- Cool down.
9- End run.
Regardless of whether we turn left or right at step 5, the rest of the algorithm will continue the execution as planned until it reaches the final step and terminates. The only difference is that by turning right (if the weather allows it) we stand to achieve more benefits by completing the same algorithm in the park rather than using the undercover complex. The same concept can be applied to solving many of the programming challenges that you will encounter along the way during this course.
Algorithm Tools
So how do we relate what we have discussed so far to mastering the Processing development environment and the Java programming language to produce state of the art code, which generate state of the art ... art?
In chapter 2 you learned about the importance of compilers and interpreters as a tool for translating high-level programming code created using languages like Java into instructions that can be understood and executed by the underlying machine running the program.
The algorithms that we have generated thus far in our attempt to solve a particular problem, can be seen as a tool that can aid us in translating a problem, which is specified using the standard English language into a high-level programming code for a language such as Java.
The solution to a particular problem always starts out as a concept, and identifying the individual steps that make up the solution for the problem along with specifying their order – as we did with our “running” algorithm – is an excellent starting point. But these individual steps are only one way of representing algorithms, and other complementary methods do exist. They include Pseudo code and Flowcharts.
Flowcharts use graphical notation that helps us to visualise clearly the structure of our algorithms, and are particularly useful for representing “decision” branches and “repetition” loops.
Pseudo code, on the other hand is a textual based representation that can be arrived at by rewriting our original steps using more precision and less vocabulary.
Thus converting our “running” algorithm into pseudo code will result in something that resembles the following:
1- Get dressed
2- Open door
3- If door open
Go out
Else
Cancel run
4- Close door
5- If not raining
Turn right
Else
Turn left
6- Start jogging
7- Start running
8- Cool down
9- End run
Dividing steps 3 and 5 into indented multiple lines serves two purposes. One, it helps to communicate clearly , to anyone who views the algorithm, that based on the condition specified in the first line of each step, one of the two actions specified between the “Else” will be executed. Two, it aids us in converting the algorithm into Java code. After all, this is what you’ll be using to create your master pieces, images, animations and the rest.
The If Statement
So, let’s start with the basics, and let’s revisit – for the last time – out “running” algorithm, to see how we can re-write our first “if statement” example using the Java syntax:
if (doorOpen)
goOut();
The first point to make about the above Java statement, is that regardless of the fact that it spans over two lines, it is still nonetheless one statement, namely the “if statement”. All Java statements terminate with a semicolon. It is equally valid to write the above statement as:
If (doorOpen) goOut();
However, it is good practice to use multiple lines and indentations to separate the various elements that make the “if statement” so that readability can be improved. This will become more evident as soon as we start using the “else” part of the “if statement” in our next example.
Meanwhile, in this example the “if statement” takes the form shown below:
if (condition)
true statement;
Here “if” is a Java keyword, and just like everything else in Java it is case sensitive. The purpose of the “if statement” is to evaluate a condition in order to determine the appropriate action to execute. The condition that we are evaluating must be enclosed within two brackets. The condition itself can be very simple, as in our example, “dooOpen”, which will be evaluated to the Boolean value of true or false.
Evaluating “doorOpen” to be true would cause the execution to move to the next line to process the “goOut()” function. More on functions in chapter 10.
But what would happen if “doorOpen” evaluates to false? Well, for that we need to extend our example to include the “else” keyword:
if (doorOpen)
goOut();
esle
cancelRun();
The main point to make about the above example is that one and only one action can be performed following the evaluation of the “doorOpen” condition; either “goOut()” or “cancelRun()”.
The generic form for representing this example would be:
if (condition)
true statement;
else
false statement;
Notice how using multiple indented lines aid in visualising the multiple execution branches that are available within the "if statement".
Using the above two variations of the “if statement” will allow you to take control of the execution flow within your programs, thus you are able to recover from potential exceptions and select the optimum path to follow when solving problems. However, by slightly modifying your “if statement” you will be able to do more, or at least achieve the same result by writing less code, as can be demonstrated by using the following features:
· Compound statements
· Multiple Conditions
· Multiple Branches
· Nested “if statements”
Compound Statements
If the “doorOpen” condition evaluates to false, the run will be cancelled and the algorithm will terminate. But wouldn’t be nice if you are able to do some tidying up beforehand rather than terminating abruptly? You might want to get changed back to your regular clothes for example, and call a locksmith to resolve the door issue:
if (doorOpen)
goOut();
else
{
changeClothes();
callLockSmith();
cancelRun();
}
Notice the use of the curly brackets to contain the group of statements that will be executed as a result of a false evaluation. Together they are referred to as a Compound Statement, and the curly brackets represent its boundaries. If you omit the curly brackets for the “else” branch, then only the first statement, changeClothes(), will be executed before the “else” branch is exited.
Although not required, it is good programming practice to always include the curly brackets even if you only have a single statement within your execution branch. Otherwise you might find yourself in the situation where you start with a single statement and no brackets, and subsequently you decide to add another statement as your program evolves, but forget to add the brackets. Your program will still run at this stage but you might end up with unexpected results.
The use of compound statements is not limited to the “if statement”, and as you will discover, have their uses with various other constructs within the Java language.
Multiple Conditions
In some situations it is necessary to evaluate at least two conditions in order to decide on the path of execution. For example, as well as ensuring that the door is open, we also need to ensure that we have a key for the door before we go out, thus avoiding locking ourselves out.
To facilitate the use of multiple conditions within the “if statement”, we need the aid of Java’s Logical Operators, where the words “and” and “or” are denoted by “&&” and “” respectively. A detailed description of these two operators among others is presented in chapter 9.
if (doorOpen && keyInMyHand)
{
goOut():
}
else
{
cancelRun();
}
Note that according to the above example, if the door was open and you had the key in your pocket, execution will not transfer into the false statement branch, cancelRun(). Although to us humans having the key in the pocket will serve the same purpose as having it in the hand, to the computer they are completely two different things. Thus we can remedy this by:
if((doorOpen) && (keyInMyHand keyInMyPocket))
{
goOut();
}
else
{
cancelRun();
}
The “if statement” can now be read as:
If the door is open and the key is either in my hand or in my pocket then go out, otherwise cancel the run.
Multiple Branches
Every “if statement” that we have seen so far had at most two branches, one represented the path of execution if the condition was evaluated to true, while the other was reserved for the path to follow if the condition was evaluated to false. It is possible however to modify the “if statement” to include more branches as demonstrated in the following example:
if (doorOpen)
{
goOutThroughTheDoor();
}
else if (windowOpen)
{
goOutThoughTheWindow();
}
else
{
cancelRun();
}
Here the conditions are tested from top to bottom, and as soon as one of the conditions evaluates to true, its corresponding branch is executed. Again, one and only one branch will be executed as a result of this “if statement”. You can think of the “else” branch as a catch all branch that will be executed if all of the conditions that precedes it fail to be evaluated to true. Thus the cancellation of the run will only occur if both the door and the window are closed.
There is no limit to the number branches that you can include within the “if statement”, however, if you find yourself in a situation where you are using more than three branches, it is worth considering whether you can achieve the same result more eloquently by using Java’s “switch” construct.
Nested “if statements”
Finally, consider the following simple algorithm, which takes as an input the names and ages of two persons, and displays the name of the oldest person among them:
RhysAge = 4;
RhiannonAge = 3;
if(RhysAge > RhiannonAge)
{
DisplayRhysName();
}
else
{
DisplayRhiannonName();
}
So far so good, Rhys is older otherwise Rhiannon is. However, if we introduce a third person (MorganaAge = 1), we can still compute the oldest person between the three by simply modifying the above “if statement” to include multiple conditions as well as multiple branches. Feel free to have a go at implementing the details for solving the problem using this option, as it will be a good exercise to consolidate the knowledge acquired through the “running” algorithm examples.
Meanwhile, we will demonstrate below the use of the nested “if statement” option to achieve the same outcome:
RhysAge = 4;
RhiannonAge = 3;
Morganna = 1;
if(RhysAge > RhiannonAge)
{
if (RhysAge > Morgana)
{
DisplayRhysName();
}
else
{
DisplayMorganaName();
}
}
else
{
if (Rhiannon > Morgana)
{
Display RhiannonName();
}
else
{
DisplayMorganaName();
}
}
Here the outer “if statement” determines the oldest person between Rhys and Rhiannon, while the inner / nested “if statement” concludes by comparing the oldest between Rhys and Rhiannon with Morgana to determine the oldest person overall.
The same concept can also be applied if we add yet another person (HelenAge = 8), however, this will require a further level of nesting that could soon render your code unreadable. If this happens, then maybe it is time to look for alternative ways for solving the same problem, by using repetition and loops for instance, which is incidentally the subject of our next chapter.
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment