Skip to content

Control Flow, Array and string

Kaisinel edited this page Apr 11, 2021 · 5 revisions

We already know that when a program is executed, it goes line-by-line through all the code you have written. However, there are times you might want to control the flow of an application to either: skip a few lines, repeat them or exit. These simple operations, combined with what we already know, enable us to solve most of our problem. Controlling the flow of code will be the focus of our lesson.

In addition, in this lesson we will be discussing arrays (how to store many things in a single variable) and basic string operations.

Control Flow

Let's say we have a function:

static void Run()
{
    Console.WriteLine("one  ");
    Console.WriteLine("three");
    Console.WriteLine("five ");
    Console.WriteLine("seven");
    Console.WriteLine("leven");
    Console.WriteLine("rteen");
    Console.WriteLine("enteen");
    // repeat n times
}

What does this code do? It might not be obvious at first, but it prints prime numbers, but only the last 5 characters (if there are less than 5 characters, it adds padding on the right). We could be more specific- it also starts at 1 and the same pattern is repeated a given number of times n. What we did could be defined as an algorithm:

1. start at number = 1
2. does number divide from any other number?
    true - do nothing
    false - print
        1. take the first 5 characters
        2. pad right by 5 (add spaces until string size totals to at least 5 characters)
3. add +1 to number
4. is number >= n
    true - end
    false - go back to 2.    

Without control flow, implementing such code is impossible. Let's make it possible!

If-else

The most basic control statement is an if statement. The syntax is as follows:

if(logical expression)
{
    // then do something here.
}

Logical expression is an expression that evaluates to true or false. The result of such an expression is of bool type.

We get a logical expression when we compare something: >, <, ==, >=, <=, !=. Please don't confuse = and ==. = - is the assignment operator, == is the equality (comparison) operator. One sets the value, the other checks if the values are equal.

For example:

int age = 12;
if(age >= 18)
{
    Console.WriteLine("Driving");
}

This block of code will do nothing because the age is under 18.

Let's update our example:

int age = 12;
bool isAdult = age >= 18;
if(isAdult)
{
    Console.WriteLine("Driving");
}

This code does the exact same thing but reads a little bit better. Through code, we explained that age >= 18 logically means whether a person isAdult. The code then reads easily, almost like natural English.

if statements can be chained multiple times. This is done through an else if(logical expression) and else statements. For example:

int age = 12;
bool isAdult = age >= 18;
bool hasBike = false;
if(isAdult)
{
    Console.WriteLine("Driving");
}
else if(hasBike)
{
    Console.WriteLine("Biking");
}
else
{
    Console.WriteLine("Walking");
}

This will print Walking to the console.

Logical expressions support algebra, just like normal numbers. But the algebra of boolean expressions follows slightly different rules. We can:

!- invert
&& - logical and (only true if both the left and right are true)
|| - logical or (either) (true if either the left or the right are true)

For example:

int age = 21;
bool hasACar = true;
bool isUnderaged = age < 18;
bool hasMoney = true;
if(!isUnderaged  && hasACar)
{
    Console.WriteLine("Driving");
}
else
{
    Console.WriteLine("Walking");
}

This prints Driving to the console, because the requirement for that is NOT being underaged and having a car. In other words !false && true -> true && true -> true.

Let's take another example:

I can eat only if I have food or if money is enough to buy food.

bool haveFood = false;
decimal myMoney = 10m;
decimal neededMoneyToBuyFood = 5m;
bool enoughMoneyToBuyFood = myMoney  > neededMoneyToBuyFood ;
if(haveFood || enoughMoneyToBuyFood)
{
  Console.WriteLine("Eating");
}

This code will print Eating to the console because false || true -> true.

Here is the full chart of boolean operations:

Operator Left operand Right operand Result Comment
&& true true true both must be true
&& true false false
&& false true false
&& false false false
|| true true true either must be true
|| true false true
|| false true true
|| false false false
! true false not
! false true

Does a number divide?

Let's go back to our problem. One of the steps of the algorithm requires checking whether a number can be divided from other numbers. In most programming languages, this can be achieved with a modulo operator- %. Dividing a number using it will return only the remainder. For example: 4 % 2 = 0, 4 % 3 = 1, 1 % 2 = 1.

We are very close to solving the key to our algorithm, determining whether a number is prime or not.

Switch

Let's talk about a switch statement. It is very similar to many if-else statements. The problem at the start requires us to convert the numeric number value to a word. We can make use of an if-else:

static string GetWord(int number)
{
    if(number == 0)
    {
        return "zero";
    }
    else if(number == 1)
    {
        return "one";
    }
    // more numbers, up to 100
    else
    {
        return "unrecognized";
    }
}

If we call GetWord(1) it returns "one".

Let's refactor this code to use a switch:

static string GetWord(int number)
{
    switch(number)
    {
        case 1:
            return "zero";
        case 2:
            return "two";
        // more numbers up to 100
        default:
            return "unrecognized";
    }
}

The code above does exactly the same as the if-else version. However, it reads much better. The number in switch(number) specifies where the value of a case comes from. case value is the same as if(number == value). Another case is the same as else if and default is equivalent to else.

There is one pitfall of switch cases. If you do not return a value in your case block you will also need to break. A break is like a stop of further checks within the switch statement. Without it, each case would be re-checked, which makes no sense here, because the variable can have a single value.

Last thing to mention, a switch statement, in this situation, can be simplified. If we just want to return a value based on another value, we can:

static string GetWord(int number)
{
    return number switch
    {
        1 => "one",
        2 => "two",
        // more numbers up to 100
        _ => "unrecognized"
    };
}

Here => is used to pair up case with a return value, _ is used for a default scenario, when no other case matches.

For

A loop is something that is repeated multiple times. In programming, when we want to repeat the same block of code we will normally be using some kind of loop. The most common and basic kind of loop is a for loop.

Every loop has 2 parts:

1. Exit condition
2. Code to be repeated

A for loop adds 2 more parts: the "start" and the "change". A basic for loop looks like this:

for(int index = 0; index < timesToRepeat; index++)
{
    // Code to repeat.
}

This for loop starts at 0 (int index = 0;). Every iteration, changes its value by 1 (index++) and since it is incrementing each iteration by 1, it will repeat itself timesToRepeat times (index < timesToReapeat).

For example:

int timesToRepeat = 5;
for(int index = 1; index < timesToRepeat; index++)
{
    Console.WriteLine(index);
}

The loop starts at 1 and stops at 4 (because when index is 5, 5 < timesToRepeat == false - so the loop stops). And on every iteration it prints the index:

1
2
3
4

Therefore, what happens in our for loop can be summarized as:

Start at some index  
REPEAT:  
    1) Can a loop continue?  
        - True - continue  
        - False - stop  
    2) Execute loop body  
    3) Increment loop index  

Loops can be combined together. Let's involve some spice from hero movies. Do you remember the song from the old Batman TV series? It goes like this: nananananananana Batman! nananananananana Batman! nananananana Batman! nanananana Batman! Batman! Batman

Basically na is repeating 8 times, followed by Batman!, then 7 times followed by Batman!, then 6, until we eventually say just 3 Batman! Batman! Batman!. How to program this?

We will use a for loop to repeat the same code 4 times. Then we will use another for loop to repeat na a given number of times. We will even have a for loop for repeating Batman!. And we will have an if statement to check if the song is ending so that we can repeat Batman! 3 times (instead of 1). We will start from a function to print word to a console a given number of times:

static void Repeat(string word, int times)
{
    for(int i = 0; i < times; i++)
    {
        Console.Write(word);
    }
}

Then we will create a function to repeat na and Batman a given number of times:

static void PrintVerse(int naRepeatTimes, int batmanRepeatTimes)
{
    Repeat("na", naRepeatTimes);
    // Finish with ! and give space for batmans.
    Console.Write("! ");
    Repeat("Batman! ", batmanRepeatTimes);
    // Finish the verse by starting a new line.
    Console.WriteLine();
}

And lastly the final loop for the full song:

static void PrintBatmanSong()
{
    int lastVerse = 3;
    int nanaRepeatStart = 8;
    for(int verse = 0; verse <= lastVerse; verse++)
    {
        bool isLast = verse == 3;
        int batmanRepeat;
        if(isLast)
        {
            batmanRepeat = 3;
        }
        else
        {
            batmanRepeat = 1;
        }

        PrintVerse(nanaRepeatStart - verse, batmanRepeat);
    }
}

If you run the code, it prints:

nananananananana! Batman!
nanananananana! Batman!
nananananana! Batman!
nanananana! Batman! Batman! Batman!

batman

Is a number prime?

The ability to write a loop and understand logical statements is all that we really need to start implementing many if not every algorithm. In our scenario, in order to find out if a number is prime, we will need to loop through the number itself. If the number divides from its part- it is not a prime. Also, a number cannot divide without a fraction from a number bigger than its half. Also, all numbers divide from 1, so we can skip that too. With that said, we write a function to determine whether a number is prime or not:

static bool IsPrime(int number)
{
    for(int divider = 2; divider <= number / 2; divider++)
    {
        if(number % divider == 0)
        {
            // if a number divides from another number, other than 1 and itself- then it is not a prime.
            return false;
        }
    }
    
    // otherwise, the number is prime.
    return true;
}

Print all primes up to n

To get closer to the solution of the initial problem, all we have to do now is to add our function in another loop:

static void PrintPrimes(int n)
{
    for(int number = 1; number < n; number++)
    {
        if(IsPrime(number))
        {
            Console.WriteLine(number);
        }
    }
}

If we call PrintPrimes(17) this prints all prime numbers up to 17. However, it prints numeric values and we need words.

For now, we will pause for a moment to talk about other kinds of loops and then we will come back to the original problem.

While

A while loop requires a lot of manual care. There is only a stop condition and a repeat block. The syntax looks like this:

while(condition)
{
  // repeat block of code
}

For example:

while(true)
{
    Console.WriteLine("Hello");
}

This code prints Hello to the console. How many times? An infinite amount! while(true) loop should be avoided because we risk never-ending our program.

Let's take the most simple for loop:

for(int i = 0; i < 5; i++)
{
    Console.WriteLine(1);
}

This print numbers 0 through 4. A while loop equivalent would be:

int i = 0;
while(i < 5)
{
  Console.WriteLine(i);
  i++;
}

The important bit in a while loop is that you have to make a change yourself, a change which will eventually result in the loop end- otherwise you risk never stopping your program.

Do...while

Do while is like a while loop, but with 1 spice. It will always execute at least once. For example:

int i = 0;
do
{
  Console.WriteLine(i);
  i++;
} while(i < 0)

This code will print 0 to the console. A while loop equivalent would not print anything. This kind of loop is pretty rare and is not so preferred because there are way fewer conditions when we always want to execute the first loop iteration.

Skipping a single loop iteration

What is the best way to just jump to the next loop iteration? We could always write an if statement for the whole body of a loop and then do nothing in the else so that it just moves on. For example, let's print all numbers from 1 to 100, except 56 or prime numbers.

We could do this:

for(int number = 0; number < 100; number++)
{
    if(number != 56 && !IsPrime(number))
    {
        Console.WriteLine(number);
    }
}

But the readability is a bit difficult. It would be harder if there were more conditions or if the body of an if was bigger. We can reduce nesting to some degree by skipping invalid loop iteration. This can be done using the continue keyword:

for(int number = 0; number < 100; number++)
{
    if(number == 56) continue;
    if(IsPrime(number) continue;
    
    Console.WriteLine(number);
}

I personally prefer putting continue in the same line as the if, because to me that reads better, but some people put it within a one-line scope. Use continue when you have multiple if statements and you just want to skip a loop interaction if some validation fails.

Breaking from a loop

Sometimes, we might need to just stop the loop altogether. We already know one way to do it- using the return keyword. However, if we have a loop within a loop, a return will break all the loops. If we want to break from a single loop, we should use a break keyword. Remember the nasty while(true) loop awhile back? Let's use do the same "print numbers 0 through 4" loop using while(true) and a break.

int number = 0;
while(true)
{
    if(number == 5) break;

    Console.WriteLine(number);
    number++;
} 

Which Loop to Choose When?

  • for- when a loop index is used in an algorithm.
  • while- when loop end condition does not depend on a single number.
  • do while- same as while, but when you want a loop to execute at least once.
  • foreach- when you want to do the same thing with all elements within a collection. About that later in this lesson.

Array

An array is a basic type to hold many elements of the same type within the same variable. For example, if a single height is of type float and is in a variable float height = 157.11;, then 3 heights: 157.11, 180.09, 199.50 could be stored in heights array. An array is declared using []. For example float[] heights. But how could we use the array to store the 3 heights?

Initializing an Array

An array has a fixed size. In order to make use of an array, we must first initialize it by giving it an initial size.

float[] heights = new float[3];

This creates an array that is able to store 3 float values.

To assign the 3 mentioned values, we use an array index.

heights[0] = 157.11;
heights[1] = 180.09;
heights[2] = 199.50;

We start from 0, because the first element in an array is of index 0.

When we know the initial values of an array, we can actually simplify such assignment:

float[] heights = {157.11f, 180.09f, 199.50f};

Using an Array to Assign Words to Numbers

How could an array be useful in our scenario of converting numeric value into a word? We can list all the unique words through an array. Array index will represent a number- its value- a word.

string[] numbers = {"zero", "one", "two", "three"}; // more numbers...

Getting the word of 0 is as simple as string zero = numbers[0];. Console.WriteLine(numbers[0]); would print zero to the console. This approach can be used in our prime number program.

Null and NullReferenceException

An array must be initialized before it can be used. What happens if you try to use an uninitialized array? You get an error- specifically- NullReferenceException. An uninitialized array has a default value of null. null is the default value for objects (about them in future lessons), but for now, that just means that interacting with such a value (other than simply passing around) will result in an error.

Expanding and Shrinking

Array size is immutable. However, it is a normal use case to want to add a new element to it. Adding a new element to an array is a problem of a few steps:

  1. Create a new array, slightly bigger than the first one.
  2. Copy all element from the old array to a new array.
  3. Add the new element at the end

For example:

We have 3 heights:

float[] heights = {157.11f, 180.09f, 199.50f};

In order to add the 4th one, we would need to:

  1. Create a new array with size bigger by 1
float[] heightsMore = new float[heights.Length+1];

Please note that using heights.Length we managed to get the length of an array.

  1. Copy elements from the old array to the new one:
Array.Copy(heights, heightsMore, heights.Length);

Array.Copy takes a source array as the first argument and copies the elements to the destination array (second argument). How many elements get copied is determined by the first argument. In this case, we copy all element from heights to heightsMore.

  1. Now we can add the new height 200f:
heightsMore[heightsMore .Length - 1] = 200f;

In short:

static float[] Expand(float[] old, float newElement)
{
    float[] expanded= new float[old.Length+1];
    Array.Copy(old, expanded, old.Length);
    expanded[expanded.Length - 1] = 200f;
    
    return expanded;
}

Don't Go Out Of Bound

What happens if you have an empty array and try to get its first element? Will it return the default value? No, it will throw an error IndexOutOfBoundsException. For example:

var numbers = new int[0];
var first = numbers[0]; // error

Try to form a healthy habit, from the early days, to verify the bounds of an array before getting a value.

Foreach

A foreach loop is used when we want to go through all elements inside an array. It looks like this:

foreach(var element in elements)
{
    // Do something with element.
}

For example, let's print every height in heights[]:

foreach(var height in heights)
{
    Console.WriteLine(height);
}

This prints:

157.11 
180.09
199.50
200

String operations

We already know that a string type stores text. It would be nice to know a few common problems that come with string and how to solve them.

Comparing strings

Comparing two strings, by default, is case-sensitive. For example, "a" == "A" will return false, because the capitalization differs.

Which string is "Bigger"?

Strings can be compared just like numbers, but their comparison does not involve > or < signs. In order to find out which string goes first alphabetically, we will need to call a CompareTo method, which returns 1 - string is bigger, 0 - strings are equal, -1 - string is smaller. For example: "a".CompareTo("b") returns -1, because a goes before b.

Case-insensitive Comparison

Let's say we have two string: string one = "abc" and string two = "aBc". If you want to compare strings, regardless of their casing, there are 2 options:

  1. Make sure both strings have the same capitalization. This is achieved by executing .ToUpper() on both strings. For example one.ToUpper() == two.ToUpper() will return true.
  2. Use Equals method with argument StringComparison.OrdinalIgnoreCase. for example one.Equals(two, StringComparison.OrdinalIgnoreCase) will return true.

Is Not Empty or Null string

In order to check if a string is empty or null, you might be tempted to text == null || text.Length == 0, but there is a much better way. Instead, you can and should: string.IsNullOrEmpty(text), which does exactly the same, yet in a more readable way.

Concatenating a Long string Dynamically

You already know that adding two strings will produce a single concatenated string. However, if you have multiple strings which you want to add together, simply "+"ing them is not efficient. There is a class for that called StringBuilder. It works like this:

First create a new StringBuilder: var sb = new StringBuilder(). Then, for every line you want to append, call AppendLine() or Append. Finally, in order to get a full appended string, call ToString() on sb.

A full example is here:

var sb = new StringBuilder();
for(int i = 0; i < 10; i++)
{
  sb.Append(i.ToString());
}

string result = sb.ToString();
Console.WriteLine(result);

This prints 123456789.

Array To string

Have you ever wondered what happens if you try to print an array to a console? Tough luck, it won't print the actual contents of an array, but rather its type. It's not difficult to extract the contents of an array as a string. We can use string.Join for that purpose.

string.Join takes 2 arguments: delimiter and an array. The output is elements separated by a delimiter. For example, let's say we have an array:

var numbers = {1,2,3,4,5,6,7,8,9,10};

If we want to print all of them in a single line, separated by an empty space we can:

var numbersText = string.Join(" ", numbers);
Console.WriteLine(numbersText);

This prints: 1 2 3 4 5 6 7 8 9 10 to the console.

Spliting

The opposite (splitting text into words) is also possible using the Split method. Split is a string method, therefore it only needs 1 argument, the delimiter to split on. For example, if we have string text = "My name is Almantas";, then by doing this: text.Split(" ") I will have the same output as this: string[] words = {"My", "name", "is", "Almantas"};

Replace

Sometimes we might want to remove some character or a block of text and replace it with something else. This can be done using the Replace method. For example: string greet = "Hello world"; and then greet = greet.Replace("Hello", "Goodbye"); becomes "Goodbye world".

Padding

Padding is an operation of adding empty spaces at the start or end of a given text. However, the spaces will only be added if the text is smaller than the padding amount. Let's say we have a string word = "abc";

Calling word.PadLeft(5) will return " abc". Calling word.PadRight(5) will return "abc ". Calling word.PadLeft(2) will leave the string as-is.

Please note that in all cases, "result" is a new string returned from the padding method.

Removing Padding

A more usual case is when a string has random spaces at the start or in the end. To "clean" the string of such random spaces, we can use Trim method. For example, " abc ".Trim() will produce "abc".

Cutting

In order to cut a string you can use a Substring method. It takes two arguments: where to start the cut and how many characters should be cut. For example, if we have a string greet = "Hello world!";, greet.Substring(0, 5); will return "Hello", greet.Substring(5, greet.Length-5) will return " World!".

Back to the initial problem

Taking last 5 characters of any word (returning blanks if it's too small)

static string TakeLast5Characters(string word)
{
    var formatted = word.PadRight(5);
    return formatted.Substring(word.Length - 5, 5);
}

Number to Word

static string[] teens =
{
    "zero", "one", "two", "three", "four", "five",
    "six", "seven", "eight", "nine", "ten",
    "eleven", "twelve", "thirteen", "fourteen", "fifteen",
    "sixteen", "seventeen", "eighteen", "nineteen", 
};

static string[] ten =
{
    "ten", "twenty", "thirty", "forty", "fifty", 
    "sixty", "seventy", "eighty", "ninety", 
    "hundred"
};

private static string thousand = "thousand";


static string ToWord(int number)
{
    var thousands = number / 1000;
    var left = number % 1000;
    var thousandsWord = ToUpToThousand(thousands, thousand);
    var leftWord = ToUpToThousand(left);

    return $"{thousandsWord} {leftWord}".Trim();
}

static string ToUpToThousand(int number, string ofWhat = "")
{
    if (number == 0) return "";

    var hundreds = number / 100;
    var left = number % 100;

    var word = "";
    if (hundreds > 0)
    {
        word = $"{teens[hundreds]} hundred";
    }

    if (left == 0) return $"{word} {ofWhat}".Trim();

    var isTeen = left < 20;
    if (isTeen)
    {
        word += " " + teens[left];
    }
    else
    {
        var tens = left / 10;
        word += " " + ten[tens-1];
        
        left = number % 10;
        if (left != 0)
        {
            word += " " + teens[left];
        }
    }

    if (string.IsNullOrEmpty(ofWhat))
    {
        return word.Trim();
    }

    return $"{word} {ofWhat}".Trim();
}

The code above is definitely not optimal, but that is the v1 of the solution.

Wrapping it all Together

static void PrintPrimes(int n)
{
    for (int number = 1; number < n; number++)
    {
        if (IsPrime(number))
        {
            var word = ToWord(number);
            Console.WriteLine(TakeLast5Characters(word));
        }
    }
}

Homework

Create a method for each operation:

  • Sort an array
  • Add element at the start of an array
  • Add element at the end of an array
  • Add element at any position of an array
  • Remove element at the start of an array
  • Remove element at the end of an array
  • Remove element at a given index of an array
  • Login: takes username and password. If they match a hidden username and password- a message "Logged in!" is printed. Username capitalization does not matter, password capitalization must match.

Restrictions

  • Don't use Array.Sort()

Did you understand the topic?

  • Why do we need an if statement?
  • When would you use a switch over an if statement?
  • How does break differ from continue and return?
  • When would you pick a while loop over a for loop?
  • Why do we need an array?
  • What happens when we try to access an uninitialized array?
  • How to remove trailing spaces from a string?
  • How to "cut" a string?
  • How to print array contents?
Clone this wiki locally