Secret Code

Logical Decisions

Making Choices with Code

Oliver Higgins

Issue 6, December 2017

How do decisions get made? Do we make smart ones? Do we make efficient ones, or do we often just make the ones that use the simplest code?

The switch statement lets us to test a variable against a list of designated values.

The result of our expression cascades down through the code blocks. These code blocks are the "case" by which the variable is "switched" on when the case is valid. In this diagram, we also have a default case to catch anything that did not prove to be true. To exit these cases the code block must use the break keyword or the next case will be evaluated before arriving at the default.

switch flowchart

Part 1: Switch Case

The decision-making process is fundamental to making programs do what we want them to do. However, evaluating the information you have is just as important. Most decision making will allow you to simply branch off to another subset of code, which is fairly straightforward; or if there are variations, they can be quite similar to each other.

HOW IT WORKS

In the past, we have discussed the humble “if” statement, which forms the basis for basic branching. We can enhance its capabilities by using the “else” and “else if” (“elif” in Python) statements:

C:

if (x == 0){
  //do something
}

PYTHON:

if x == 0:
  #do something

We can use multiple “if” statements or even nest them inside of each other. There is nothing wrong with this at a speed and compilation level, but at a human reading the code level, it becomes very messy, very quickly. Trying to diagnose and fix coding and logic errors becomes very tedious.

C:

if (x == 0){
  //do something
}
if (x == 1){
  //do something
}
if (x == 2){
  //do something
}

Using the above code sequence results in each “if” being checked independently; each section of code is essentially independent of one another.

A much better way of approaching this idea is to use a series of “if”, “else” and “else if”:

int x = 0;
if (x == 0) {
  Serial.print("0- This is a true expression value");
} else if (x == 1) {
  Serial.print("1- This is a true expression value");
} else if (x == 2) {
  Serial.print("2- This is a true expression value");
} else {
  Serial.print("This is a false expression value");
}

PYTHON:

x = 0
if x == 0:
  print("0 - This is a true expression value")
elif x == 1:
  print("1 - This is a true expression value")
elif x == 2:
  print("2 - This is a true expression value")
else:
  print ("This is a false expression value")

SWITCH CASE

Although the above works, it is not always the most efficient way of doing things; nor is it the most readable. For some, coding may simply be about getting it done, but producing readable code is important. You may know what that block is for now, while you are coding it, but in a year’s time, we guarantee you will not be able to follow.

Enter Switch Case. This presents two clear advantages over the if/else if scenario. The code is much more readable and will make your coding life easier. Secondly, the larger the list of things you need to traverse, the faster it will become. The Switch Case allows us to check one variable and have predetermined outputs; and depending on the compiler it will create a hash table. This makes a huge difference when using strings. You’ll find sending user messages via strings and a hash table much faster, come run time than using the else if method.

C:

int x = 0;
switch (x) {
  case 0:
    return "zero";
    break;
  case 1:
    return "one";
    break;
  case 2:
    return "two";
    break;
  default:
    return "nothing";
    break;
}; 

DEFAULT: You can see in the above code that the final case is actually called "default". This is the catch all. It means that the expression did not match any of the above critera. You do not have to include it in a switch but it does allow you to make sure that some code gets executed in the switch.

BREAK: The break is another optional extra but it is best practice to include this when using the switch case. It allows us to exit the current switch and continue on with the rest of our code. If you do not include the break then the code will continue on and evaluate the expression again against the other case’s in the switch. This way we ensure that the code is executed as planned.

PYTHON:

Python is special and unique. It is one of the only languages that we have used that does not include the Switch Case syntax. Instead, it uses dictionary mapping. On the surface, this looks completely different, but at the compiler level, it does nearly the same thing.

The below Python function is analogous to the previous C example (it is set out as a function, in this case), and it will return a string as per the 0-2 integer input.

x = 0
def numbers_to_strings(x):
  switcher = {
    0: "zero",
    1: "one",
    2: "two",
  }
  return switcher.get(x, "nothing")

If Python is your choice of language, we encourage you to take the time to learn how to implement a dictionary. Python is essentially an interpreted language whereas C and C++ are compiled. For the Python programmer or Raspberry Pi enthusiasts, it means at runtime a Python program doing a similar function is likely to be slower, even on the same development platform. Using dictionaries will enable your code to run significantly faster when it matters.

At a high level, the difference is negligible but at a low level when translated into a hash table, it becomes much quicker.

A NOTE ABOUT USING SWITCH CASE: In traditional C you can have a maximum of 257 case statements and C++ 16,384, however when using an Arduino you would probably run into some memory issues before you hit this limit. The expression that is used in the switch must be an integer type (int, char or enum). Two case labels can not have the same value, this will result in a compiler error. You should always use default if the evaluation varies to the point of needing similar labels. Code written after the switch and before the first case is not executed.

PSEDUO CODE:

switch (x) {
  x = x + 1;  // This statement is not executed
  case 1: print("Choice is 1");
  case 2: print("Choice is 2");
  default: print("Choice other than 1 and 2");
}

When you need to have your code clear and concise with a straightforward layout and output pattern, it is recommended you use the Switch Case. When speed is crucial, use a Switch Case or dictionary to create a hash table. If it is simple, then use “if” and “else if” – just make it readable!

What’s A Hash Table?

The simplest explanation of “hashing” is to assign an integer value to any data. This integer is then stored in an array or table. The integer then becomes our “key” to finding the piece of data, whatever it may be. Using a standard 16-bit integer, we can have up to 65536 unique keys. This may seem quite trivial, but the increase in speed becomes logarithmic for your coding. Each time the processor needs to search, it only has to use 16 bits. The number/integer 1143 can be represented easily within this range. However, if you were to store the string “1143” you would require significantly more time. Don’t believe us? Try this code in your Arduino IDE:

C:

void setup() 
{
  Serial.begin(9600);
}
void loop() 
{
  int x = 1143;
  unsigned long Counter1 = 0;
  unsigned long Counter2 = 0;
  unsigned long Counter3 = 0;
  unsigned long Counter4 = 0;
  
  String z = "1143";
  Serial.println("Starting test");
  Counter1 = micros();
  if (x == 1143) 
  {
    Counter2 = micros();
  }
  Counter3 = micros();
  if (z == "1234")
  {
    Counter4 = micros();
  }
  Serial.print("Integer Compare Time: ");       Serial.print(Counter2 - Counter1); 
  Serial.println("us");
  Serial.print("String Compare Time: ");        Serial.print(Counter4 - Counter3); 
  Serial.println("us");
  delay(5000);
}

RESULT:

Starting test
Integer Compare Time: 4us
String Compare Time: 8us

Some Arduinos have a resolution of 4us for the micros(), others have a resolution of 8us, so the result is always a multiple of 4us or 8us. A more accurate result can be achieved by using 5 instances of:

if (x == 1143) 
    {
    Counter2 = micros();
    }

or 5 instances of:

if (z == "1234")
    {
    Counter4 = micros();
    }

The results then are:

Starting test
Integer Compare Time: 16us
String Compare Time: 40us

That's a 2.5x difference. Integer compares are clearly much faster than string compares.

So what does this have to do with hash tables?

The integer is fastest to compare against. If we were searching every string each time, and progressing through a list of 10,000 entries, it would be slow. However, comparing 10,000 integers will be far quicker and you now have the “key” to finding your data.

Part 2: Operators

We can’t make good decisions without making comparisons, but what comparisons can we make?

All languages have operators; without them we could not do comparisons, concatenation or math on our data. These operators are grouped into Postfix, Prefix (or Unary), Normal and Boolean.

Postfix

(C style only, Python has made this redundant)

Postfix operators are used suffixed to an expression; that is, they come at the end. These are used for incrementation and decrementation, and are one of our favourite shortcuts. If you come from a background where you do not have this you will be surprised; or if you start using a language that does not have it, you will miss it very quickly.

Assume x is an integer:

x++;

This causes the value of the operand to be returned. After the result is obtained, the value of x is incremented by 1.

int x = 3;
x++; // The value of x is now 4.
Serial.print(x);  // This outputs the number 4.
Serial.print(x++)  // This outputs the number 4, but x is now 5
Serial.print(x);  // This outputs the number 5.

x--;

This is the same but the value of x is decremented by 1.

The addition comes after the expression has been evaluated. If you look at the second last line, you can see that the code prints out the current value of x; then it increments it by 1, resulting in 5. The same applies when we use x--. The expression is evaluated, then the number is decremented. This is because the operand comes after the variable in question.

Prefix (or Unary)

These are operands that come before the expression; that is, they are evaluated and incremented/decremented before we assess the value of the variable.

++x;

This causes the value of x to be incremented by 1. The new value is then returned.

--x;

This is similar, but the value of x is decremented by 1.

NormaL

These are the bread and butter operators. For the purpose of this exercise, x = 5 and y = 7.

x + y = 12

The result of this is the sum of x and y.

x - y = -2

The result of this is the value of x minus the value of y.

x * y = 35

The result of this is the value of x multiplied by y.

x / y = 0.714285714285714

The result of this is the value of x divided by y.

x % y = 5

The result of this is the value of the remainder, after dividing x by y. This is also called the “modulo” operator.

Boolean

Compared to many programming terms, Boolean is always hard to explain to non-programmers. The name honours George Bool, and is often declared as the “data type bool”. Boolean data is not considered the representation of an integer, as used in traditional algebra, but the expression of true of false.

The data type is primarily associated with conditional statements, which allow different actions and change control flow, depending on whether a programmer-specified Boolean condition evaluates to true or false.

The basic operations of the Boolean Dataset are:

AND(conjunction), denoted x&&y (sometimes x AND y or Kxy)
OR(disjunction), denoted x||y (sometimes x OR y or Axy)
NOT(negation), denoted !x (sometimes NOT x or Nx)

BASIC USAGE

X Y AND / && OR / ||
0 0 0 0
0 1 0 1
1 0 0 1
1 1 1 1
boolean diagram if (conditional) and ==, !=, <, >, =>, <= (comparison operators)

== (EQUALS):

The simplest is equals, which is represented by the double equals ==. Be mindful that = and == are two different things. = is used to assign a value to a variable or similar, whereas == is the comparator for 'does this “thing” equals that “thing”' and will return true or false.

C:

if (x == 1 ){
  //do something
}

PYTHON:

if x == 1:
  #do something

!= (DOES NOT EQUAL):

The opposite of the “equal” is the “does not equal”. This is written as “!=" and returns true when the variable in question does not equal to what it is being compared to. This may seem somewhat redundant, but there are times when you may need to exclude an item, and this is a clean and simple way to do it.

C:

if (x != 1){
  //do something
}

PYTHON:

if x != 1:
  #do something

The next comparators can be grouped together. These are greater than ( > ), less than ( < ), greater than or equal to ( >= ) and less than or equal to ( <= ).

When we use the “greater than” we ask the question “is this variable greater (more than) a particular number?” If it is, then it returns true, otherwise returns false. The thing to note here is that the comparison is “greater than”, so if you compare 1 to 1 then you will get false, as 1 is not greater then 1. If you wanted a comparison to include 1 then we need to use >=, “greater than or equal to”. In this case, if you compare 1 to 1 then you will get true.

C:

int x = 1;
  if (x > 1) { //Returns False
    //do nothing
  }
  if (x < 1) { //Returns False
    //do nothing
  }
  if (x >= 1) {
    //do something
  }
  if (x <= 1) {
    //do something
  }

PYTHON:

if x > 1:
  #do nothing
if x < 1:
  #do nothing
if x >= 1:
  #do something
if x <= 1:
  #do something

Sometimes the required comparison may depend on multiple variables to return true or false together. For this, we use the Boolean comparators, AND and OR. Most languages will allow you to use both the keywords AND and OR, as well as && and ||.

AND (&&):

C:

if ((x > 4) && (y < 8))
{
  // Do Something
}

PYTHON:

if x > 4 and y < 8:
  # Do Something

OR (||):

C:

if ((x > 4) || (y < 8))
{
  //Do Something
}

PYTHON:

if x > 4 or y < 8:
  #Do Something

No Braces

You can use the “if” statement without braces but the block can only contain a single statement before you are required to use braces { }.

We avoid this type of statement from a best practice point of view and would not recommend it; however, be mindful that if you are reading somebody else’s code and if you add an additional line of code, you will get a compile error.

C:

if (x > 2)
  firstFunction(); // do something
  secondFunction(); // do something else  

This will not compile, because it cannot execute the second line without braces. However this will compile:

if (x > 2)
  firstFunction();   // do something
else
  secondFunction();  // do something else  

This will also compile:

if (x > 2)
  onlyFunction();  // do something  

IN CONCLUSION

Using Switch Case may ultimately be a decision based upon personal preference rather than a necessity. It does however make for much more readable code when multiple decision branches are applied. If your code is suffering from speed issues, implementing a Switch Case structure may provide a faster and more stable system at runtime.

Using boolean operators may seem like more hassle then what it is worth at first, but with a little bit of patience you will find that you will be able to make quite complex decisions and evaluations in just one line of code.