Welcome back! This is the third post in an introductory series about learning programming. In the last post I covered how we can represent data and operate on it in the context of a computer program. That makes for a natural bridge to today’s topic: algorithms. So, what is an algorithm?
You might have an idea of what an algorithm is based on things you’ve heard other people say. I’ve watched friends scroll through their Facebook news feed and complain about how the “the algorithm” is always showing them the same posts or the same people. While this is true, algorithms affect our lives in many other places besides social media. Every time we play a video game, search Google, or swipe a credit card we are relying on algorithms to produce the results we desire. Software and algorithms go hand in hand.
Simply put, algorithms are precise, deterministic instructions for performing a specific task with data.
The key words here are instructions and data. An algorithm must use data that a computer can represent. We talked about what that data looks like and what operations are available to a computer for manipulating it in the previous post. Now we need to discuss how to build a complex set of instructions for the computer to carry out. We will cover three fundamental building blocks for creating algorithms: variables, input/output, and control flow.
Variables
One thing we neglected to cover when we were talking about data is the concept of variables. In a computer program, a variable is a placeholder for data. It represents a location in memory where a value can be stored. We can define variables by giving them a name and, depending on the programming language, possibly a data type.
Storing data in variables is a key component of creating useful algorithms. We often need to save off pieces of data to be used in a later step in the algorithm, and variables provide us a method of doing that.
Variables are powerful because they allow us to store the state of previous calculations, but they are a double-edged sword. As the number and scope of variables grows, so does the complexity of the program. It’s best to be judicious about their use in order to write programs that are understandable and maintainable.
Input and Output
It’s great to be able to store data in variables, but where does that data come from? And where does it ultimately end up? This is where input and output comes in. You might see it abbreviated as “I/O”, and hear people say it like, “Eye Oh”. You could make fun of these people, but that wouldn’t be very nice.
I/O is an extremely important feature for allowing programs to be useful to humans. If I didn’t input my personal data into Facebook, they wouldn’t be able to sell it, err I mean, I wouldn’t have a personalized profile to connect with friends and family.
If computers couldn’t output data to the screen, my enemies would have no way of knowing when I’m dancing over their lifeless corpses in Fortnite. This is all very important stuff.
Input
Input comes in lots of different forms. One example is data that users enter manually, like their name and email address when subscribing to my blog. Input could be arrow key presses and mouse movement when playing a first person shooter. It could even be a credit card number when swiping one’s credit card at a fast food restaurant.
Output
Likewise, output can come in many forms. It could be the computer graphics displayed on your monitor when playing a video game or watching Netflix, an Excel file, or it could even be a web page, like the one you are currently reading.
At the most basic level, input and output could even be as simple as a single piece of data. A large part of programming useful algorithms is figuring out how to chain together numerous smaller components using their inputs and outputs.
Control Flow
The last building block we need to cover is control flow. Here we are dealing with determining the order in which instructions are carried out. While there are many ways to give instructions, not all of them are good.
When programming it’s important to keep in mind who your audience is. The compiler/interpreter is one member of that audience, but yourself and other programmers are arguably more important to cater to. If programs are difficult to comprehend they will be difficult to modify and improve. That’s why we use something called structured programming.
Before the idea of structured programming came along it was like the wild west out there in Nerd Land. People used GoTo statements in their code to just jump around to wherever they wanted. Sounds awesome, right? No restrictions, unlimited freedom. What could go wrong?
Well, it turns out this was a pretty terrible idea. People wrote spaghetti code that was unmaintainable. It got so bad that a dude named Edgar Dijkstra came along and said, “Hey! Stop this madness! It’s harmful!“. And just like that, structured programming was born.
Structured programming is the concept of restricting which kinds of control patterns are acceptable when giving the computer instructions. Most modern programming languages force these restrictions upon you, and for good reason. There are two main advantages to doing this. Both of these advantages are to help make programmers’ lives easier:
- Structured programming makes it easier to avoid mistakes.
- Structured programming makes it easier for you or somebody else to understand your program.
There are only a few different patterns used in structured programming, but they are sufficient for expressing any kind of algorithm you would like to implement. We will refer to these patterns as sequence, choice, and repetition, and we will use flow charts to help describe their behavior. The rectangles represent actions, and the diamonds represent decisions.
Sequence
The sequence pattern is the simplest of the three. It simply involves executing one task after another in succession.
To the right is an example demonstrating an algorithm for finding more joy, losing weight, and making chicks dig you, using only the sequence pattern.
Choice
Choice represents a fork in the road. We ask a question, and then one of two things will occur depending on the answer. The answer is expressed as a piece of Boolean data, that is, either true or false. There are two basic variants of this pattern. The one-way branch either does something or nothing at all, while the two-way branch chooses between two options.
Below is an algorithm for selecting the greatest quarterback of all-time, using only the choice pattern. Both the one-way and two-way branches are demonstrated.
Repetition
With repetition, a set of instructions are repeated until some condition is met. “Loop” is another term people will use to describe this pattern.
There are two basic variants here as well, the pre-test loop and the post-test loop. The difference between them is when the condition is checked. Here is an example illustration demonstrating the algorithm used by the New England Patriots.
Unfortunately, this particular example appears to be an infinite loop, which we will try to avoid in our programs if we don’t want them to crash. We can avoid such scenarios by combining these three patterns into more complicated ones that allow us to create more complicated logic. Let’s put everything we’ve learned together and take a look at how to do that with a full example algorithm.
Example Algorithm: Fizz Buzz
Fizz Buzz is a good example algorithm to demonstrate these concepts. Our goal is to print out every number from 1 to 100. However, we need to replace each multiple of three with the word “fizz”, and each multiple of five with the word “buzz”. If the number is a multiple of both three and five, then we need to replace it with the word “fizzbuzz“.
To the left is an example flowchart demonstrating an algorithm for Fizz Buzz. If you examine it closely, you’ll realize that we are using all three of the building blocks covered in this post: variables, input/output, and control flow. We are also using data operations we learned about in the previous post.
We start by creating the “number” variable and setting its value to 1. Then we create a pre-test loop where we check if the number variable is less than or equal to 100.
Inside this loop are various decisions for determining when to output “fizz”, “buzz”, the contents of the number variable, or a new line. We use concepts like the modulo operator, the AND operator, and equality checks to make these decisions.
We continue performing these calculations until the value of the number variable causes the initial pre-test condition to return false.
After this program completes, the screen would have output that looks like this, with the pattern continuing all the way up to 100 (or in this case, “buzz”):
1 2 fizz 4 buzz fizz 7 8 fizz buzz 11 fizz 13 14 fizzbuzz ...
Summary
We started this post by posing a simple question: what is an algorithm? We learned that an algorithm is a set of instructions for performing a task with data, and we talked about the three building blocks for creating them: variables, input/output, and control flow. In our final example, we learned how to combine these building blocks to create complicated algorithms and model them with flow charts.
In the next post we will start learning about what we use to turn flow charts into code that computers can understand: programming languages. Until then, make sure to subscribe to stay up to date with the latest content.