Programming is an art as much as it is a science. In both respects it demands that the programmer has a full understanding of the underlying concepts, and an ability to solve problems. Those who are more inclined towards the engineering fields may disagree outright with my assertion of programming being an art. In my defense I believe that those who disagree on this point are typically those who, while probably well-versed in a programming language and able to implement fancy algorithms to solve problems, lack the passion that is required for the creation of novel solutions to problems. I am by no means the best programmer that has ever existed, and I know that I'm not the most intelligent amongst my colleagues. So what makes me qualified to write a book on the introduction to programming, and what value would my book add to the plethora of existing introduction texts? My answer: Passion. I am absurdly passionate about programming, and have what I would consider a deep understanding of the underlying concepts of programmatic problem solving.
The need I am attempting to meet with my text is the thorough teaching of the concepts behind the coding process that abstracts programming from memorizing a language's syntax and common routines into a methodological approach to solving problems. Far too often I see undergraduate and self-taught programmers attempting to comb websites for existing code to fit their needs without knowing what exactly the code does, or how that solution was found. In the University that I attended the introduction to programming class was done in C# (C-Sharp), we then immediately went into C++ never touching C# again. While I understand their approach and don't see it as a wrong, or bad, I saw first-hand what happens to aspiring programmers when they don't understand that code is meant to express an idea, and not the other way around. Our introduction class was mostly copy-and-pasting a tutorial book with minimal thought, and by the time that we entered into C++ we held onto that mentality.While it may seem that after time the connection would be made that you have to think before you code, what I observed was that a majority of students by their senior year were stuck in the mind-set of memorizing code and not learning the problem solving process as it related to their assignments and personal endeavors. I don't mean to imply that the teachers at my University were bad at teaching, in-fact their passion for teaching was the greatest influence for my deciding to write this. I believe that the shortcomings came directly from the students not taking the time to play, and experiment with the knowledge that they were given. Up until my Sophomore year, I also was plagued with the inability to attack a coding problem in an intelligent manner. It wasn't until an Artificial Intelligence class taught by my advisor (who was a recent addition to the staff ) did I hear Don't think about the assignment in-terms of 'What code do I need to do X,' think about the assignment in-terms of 'what am I trying to accomplish?' At the time I thought that what he was saying was ridiculous, but after struggling with the implementation of algorithms in that class I saw what he meant. I was doing exactly what he had said not to do, and so were my fellow students. The way that I was able to break out of this mindset was through my passion for learning programming techniques, and the implementation of new ideas in my own programs. At one point I realized that I was studying concepts that I wasn't due to cover until my senior year.
My approach in this text will be different than any approach that I have yet to find in any book, or online text. I intend on introducing some basic concepts in a language independent manner, and move from how we understand a process as a human, to how a computer understands a process so-that we can meet the problem solving process in the middle through code. I hope that by the end of this text the reader no matter their level of understanding when coming into it, has a better understanding of taking an idea and turning it into a process able to be understood by a computational system.
Before we leave the introduction, I would like to say thank you for taking the time to get even this far. The fact that you are reading this means you are potentially already a step ahead of other programmers. You've already received the golden nugget that was told to me by my greatest influence and if you choose not to go any further I hope that you always remember what he told me.
How to make a sandwich.
No matter what language you're programming in, everything in its simplest form is a set of instructions. The differences between languages is syntax, and their methods for handing instructions to the computer. To introduce the concept of listing instructions in different manners to accomplish a task we will be making a sandwich. Starting off we'll assume that the peanut butter, jelly, bread, and knife are sitting on the table in front of us. In simple english the instructions could be listed as follows:
“Using the knife, spread the peanut butter on a slice of bread, and then spread jelly onto another. Once completed put the two pieces together and voila, a peanut butter and jelly sandwich has been made!”
We as humans will typically have no issue translating these instructions into actions. When we read 'Using the knife' we understand that we have to use our hands to grip the knife at a particular position. This can be considered a type of abstraction in-that the instructions used have a set of implied instructions that each human of reasonable age understands (Don't grab the knife by the serrated edge.) If we wanted to break-down the making of a peanut butter and jelly to more specific instructions, we my do-so like this:
“With your dominant hand grab the knife by the handle. Place the serrated edge of the knife into the peanut butter and make a scooping motion to extract the peanut butter from the jar. With your opposite hand pick up a piece of bread, and spread the peanut butter on the bread by gently wiping the knife against it. Once the peanut butter is spread evenly on one side of the bread, place the bread back onto the table with the peanut butter side facing upwards. Now repeat the process using jelly, and a separate piece of bread. Place the knife on the counter, pick up both pieces of covered bread and place together the peanut butter, and jelly sides.”
As you can tell, this seems overwhelmingly drawn-out when described in plain english. That in-addition to the complexity of the english language is why instructions for computer systems are made to be simple. One way that processes like making a sandwich can be represented in a near-english manner is through what is called pseudo-code (ˈso͞odōˌkōd). The idea behind pseudo-code is to represent the solution, or method to a problem independent of an actual programming language's syntax. The pseudo-code of making a sandwich in a somewhat abstracted manner could be the following:
obtain two pieces of bread, peanut butter, jelly, knife apply peanut butter to bread using knife apply jelly to bread using knife join covered bread pieces
Looking at the pseudo-code, we can infer certain steps such-as opening the jars, and how someone uses the knife as this would be considered specific to jar type, and the user's preference of handling the knife (Perhaps they prefer to use their foot to hold the knife, which could be considered syntax specific.) The beauty behind pseudo-code is its ability to translate human readable instructions into logical steps that can then be easily put into any programming language.
From here, the idea of an instruction will be broken down into some basic concepts. These concepts are the underpinnings of all linear instruction sets, and therefore impact all programming languages.
Flow Control and Logical Operators
When we start talking about instructions, it soon becomes evident that we may not want to execute certain steps, or we may even want to repeat certain steps given a specific situation. Handling different situations is done through what is called flow control. The reason we call it flow control is that we are altering the linear execution of steps otherwise known as the program 'flow.' The basic flow control techniques are 'if statements, for loops, and while loops.' Different flow control techniques do exist, but for now these are the basic ones that will be covered.
What I would consider the most basic flow control is the 'if' statement. The purpose of the if-statement is to execute a set of instructions if a condition evaluates to TRUE. While the if-statement doesn't always reduce the amount of code that we have to write, it ensures that we don't do something more than necessary. Lets check the condition of peanut butter not being on the table.
if peanut butter is not on the table: obtain peanut butter
In this case, if the condition “peanut butter is not on the table” is TRUE then we will obtain the peanut butter. However, what if we wanted to handle what happened if the condition evaluated to FALSE? This event is what the accompanying 'else' instruction can be given to create what is known as an 'if-else' chain.
If peanut butter is on the table: obtain knife else: obtain peanut butter obtain knife
The 'else' operation here isn't very specific; If the peanut butter isn't on the table we are instructed to obtain the peanut butter and a knife, but what if we already have a knife? Now we have two knives! This case and others might demand further condition checking to ensure we don't repeat an action, or attempt to perform an action without the necessary 'ingredients.' To chain together more specific conditions an 'if' condition can be joined with an 'else' condition to create an 'else-if.'
If condition: action else if condition_2: action_2 else if condition_3: action_3 else: default action
It is important to note that the previous 'if-else' chain's corresponding actions won't all run. Only a single action will run based on the given conditions, with the condition-less 'else' (the last 'else' present in the previous example) being the default action of the 'if-else' chain (what will run in the event of the previous conditions evaluating to FALSE.)
To expand on flow control in terms of if-statements we can use what are called 'logical operators.' The basic logical operators are 'NOT,' 'OR,' and 'AND.' They work intuitively, but can be tricky if they are combined into a single statement. The next bit of code will check whether or not there is peanut butter and jelly are on the table, if they aren't then we'll obtain them.
If peanut butter AND jelly isn't on the table: obtain peanut butter, jelly
This seems simple, but lets look at the code and see if any issue may arise if we follow the rules in the most strict sense. In the event that neither ingredient is on the table, we will obtain both and we will be all set. However, in the event that the peanut butter is already on the table but the jelly is not, then our condition wouldn't trigger the 'obtain' instruction. We won't get out our jelly! This is because one of the conditions was met, while the other was not. For an 'AND' operator to be true, both sides of the condition must be true. What may work better here is the 'OR' operation. In an 'OR' operation only one of the two sides must be true for the 'obtain' instruction to execute.
If peanut butter OR jelly isn't on the table: obtain peanut butter, jelly
Lets look at the possibilities that this statement might run into. In the event that nothing is on the table yet, then we will obtain both the peanut butter and the jelly. If we already have the jelly out, then we will obtain both the peanut butter and the jelly. Now we have two jars of jelly! This isn't what we want at all! We only need one jar of jelly, and one jar of peanut butter. I think its time to introduce the 'NOT' operator.
If NOT peanut butter on the table: obtain peanut butter If NOT jelly on the table: obtain jelly
That should work just fine! Lets take a look at how this might operate. In the event that we have neither peanut butter, nor the jelly, we will check the existence of each and obtain them. In the event that we already have peanut butter, but not jelly we ignore 'obtain peanut butter' because the 'NOT' command is false (peanut butter is on the table). Next, we check if jelly is on the table. Since the jelly is not on the table, we execute the 'obtain jelly' instruction as the 'not' operation is true (jelly is not on the table).
Now that we've covered the basic understandings of these logical operators, I think its important to show how these operators are typically displayed. Since code has a strong mathematical foundation, the use of these operators take on their symbolic counterparts in most language's syntax.
Operation Symbolic Value and && or || not !
If peanut butter on the table && jelly on the table: Peanut butter and jelly are on the table If peanut butter on the table || jelly on the table: Either the peanut butter or the jelly are on the table If ! peanut butter on the table : The peanut butter is not on the table
We've now covered very simple logical conditions that we may run into, and I think its time to introduce some more complex conditions to solidify the ideas behind the operations. In the following examples I've surrounded certain conditions with parenthesis for clarity.
If (!peanut butter on the table) && (!jelly on the table): Neither peanut butter, nor jelly are on the table. This is read as: “If not peanut butter on the table and not jelly on the table.” If( ! (!peanut butter on the table)) && (jelly on the table): Peanut butter is on the table, and so is the jelly! “If not peanut butter not on the table and jelly on the table.”
The double negation example can be a bit confusing at first, but if you look closely at each condition from the inner-most parenthesis and read it out, you will see that what is being evaluated is :
“If the peanut butter not being on the table isn't the case && the jelly is on the table”
It may seem convoluted and unnecessary, but there are instances in programming where negating a negative is actually a way to represent something in a clearer manner. Once we get into expressing these ideas with actual code this will become quite apparent.
There are a couple types of loops that can be implemented in coding to help with repetition of code. Loops are advantageous when we want to do something multiple times without writing the same instructions over and over again. If we were having a sandwich party, and needed to prepare peanut butter and jelly sandwiches for all of the guests we could copy and paste our previous pseudo-code for making peanut butter and jelly sandwiches, or we can implement what is called a 'while' loop. A while loop runs the body of code it encompasses until the condition given is no-longer true. With that said, if the initial condition starts off as false, the loop will never run! If we wanted to prepare sandwiches for all of our guests, and had all of the ingredients on the table we could implement an instruction set similar to this :
while (guests don't have a sandwich): obtain two pieces of bread, peanut butter, jelly, knife apply peanut butter to bread using knife apply jelly to bread using knife join covered bread pieces give sandwich to guest
This loop would create, and hand a sandwich to each guest until all of the guests had a sandwich (assuming that we invited a finite number of guests).
It is important to note that loops can cause some major issues in the event that they never reach what is considered the end condition. That-is if the while loop's condition never became false the program would run forever. This is why in our example we need to ensure that we invite a finite number of guests!
Another type of loop that we can implement is called a 'for' loop. This loop will do something a specified number of times before exiting. Different language's define their for loops in different ways so for the sake of our pseudo-code we will write it our own way. The for loop example below will make 3 peanut butter and jelly sandwiches.
for ( 3 times ): obtain two pieces of bread, peanut butter, jelly, knife apply peanut butter to bread using knife apply jelly to bread using knife join covered bread pieces
The for loop can be represented in multiple instances outside of numerical increments. For instance, it is not uncommon to see a for loop used in a similar manner to the while loop mentioned above.
for( each guest ): obtain two pieces of bread, peanut butter, jelly, knife apply peanut butter to bread using knife apply jelly to bread using knife join covered bread pieces give sandwich to guest
The difference between the two for loops shown is the potential number of times each executes their instructions. The first for loop will only ever run 3 times, while the second will run once for every guest at the sandwich party. The second loop has the same potential issues as the while loop if we were to invite an infinite number of guests. The idea of an infinite number of guests may seem absurd, but having accidental infinite loops is a common mistake for those new to programming. In-addition to the mistake made of an infinite loop, another common mistake made is having not invited anyone to the party and wondering why no sandwiches were made. In the second for loop as-well-as the while loop, if nothing is given the loops won't run at all.
At this point I want you to consider how you might go about solving every-day problems in pseudo-code. How might you instruct someone to tie their shoes? What about cleaning the dishes? Write out a few different examples in pseudo-code on how to describe certain actions. Remember, that there are no wrong answers in pseudo-code as-long-as the process is completed. Understanding how to describe these processes in pseudo-code is a great exercise no-matter your skill level. You might be surprised with how many different ways you can describe the same action in a syntax-free manner.
for( each shoe ) if ( laces are untied ) tie laces
while(shoes aren't tied) grab current shoe's laces tie current shoe
while(shoe laces aren't tied) grab laces of shoe pull laces tight tie laces
obtain wash rag obtain soap apply soap to wash rag with water for(each dish that is dirty) if( wash rag is dry ) apply soap and water to wash rag apply rag to dish, and wash thoroughly
obtain wash rag, soap apply soap, water to wash rag for (each dirty dish) while(dish ! clean) scrub dish
using washrag covered in soap and water while(dishes ! Done) while(current dish ! clean) scrub current disk
As you can see there are a near unlimited number of ways to express these ideas in pseudo-code. This is because we aren't limited by syntax, or any mindset of what we need to do to get the shoes tied, or the dishes cleaned. We are free to let our imagination run and just get the job done. The truth is, that even though programming languages do limit us in terms of how we express the answer to a problem it doesn't limit the method of approach with our known flow control. Now that we have a basic understanding of problem solving with our flow controls, lets move onto how a computer looks at a problem.