What are control structures?

Control Structures control the flow of a program. They determine what and when segments of code are executed.

There are two types of control structures: conditionals and iteratatives. Conditionals consist of if/else statements, which execute a code segment based on if a condition has been met. In iteration, we mainly use for loops and while loops, which repeat the code segment inside of the loop until a certain condition has been met.

All control structures make heavy use of booleans and boolean expressions.

Using If, Else if, and Else with Boolean Expressions

An if statement will execute if a certain condition is true. If the condition is false, then the else if statement is executed. The point of an else if statement is to test out if a new condition is true, if the previous condition was false. An else statement is executed if all of the above the conditions were false.

import java.util.Scanner;

public class GuessingGame {

    // Constructor for the class, assigining the target number to a value.
    public GuessingGame(int chosenTarget) {
        this.target = chosenTarget;
    }

    public int target;

    public static void main(String[] args) {
        // Creating a new game, where the target is 15.
        GuessingGame game = new GuessingGame(15);
        // 5 is too low. So when we guess 5, we expect the else if segment to be executed.
        game.guess(5);
        // 20 is too high. So when we guess 20, we expect the if segment to be executed.
        game.guess(20);
        // 15 is the right number. So, this should execute the else segment.
        game.guess(15);
    }

    // Method is called to perform a guess.
    public boolean guess(int guessedNumber) {
        // The statement inside of the parenthesis is called a boolean expression.
        // guessNumber > target returns true if guessedNumber is bigger than the target, and false otherwise.
        if (guessedNumber > target) {
            System.out.println("Your number is too high. ");
            return false;
        }
        // If guessedNumber > target was false, then the second condition is executed.
        // If guessedNumber is smaller than the target, the else if condition will be true, and the code segment will run.
        else if (guessedNumber < target) {
            System.out.println("Your number is too low. ");
            return false;
        }
        // If both of the above conditions were false, then that means that the guessedNumber and the target are the same.
        // We execute the below segment if that happens.
        else {
            System.out.println("Good job! You guessed the number. ");
            return true;
        }
    }
}
GuessingGame.main(null);
Your number is too low. 
Your number is too high. 
Good job! You guessed the number. 

Using Many Conditions and the Switch Case

Sometimes, there can be a long string of if/elseif/else statements. In general, these can often be simplified, but sometimes they are needed. One way to make that kind of code more readable is to use a switch case, which selects a code segment to be executed based on a certain input of case. This is useful when you have predefined cases, rather than boolean expressions.

public class HomeworkCategory {

    public int statsAssignments = 0;
    public int civicsAssignments = 0;
    public int linalgAssignments = 0;
    public int csaAssignments = 0;
    public int enmAssignments = 0;

    public void AddAssignment(int assignment) {
        // Based on an arbitary choice, each assignment corresponds to a number.
        // Based on the chosen number, an assignment is added.
        // Note that this could be much better represented with a dictionary, list, or hash map. 
        // Use case would be in a Database, linking assignment category to ID. 
        // For instructional purposes, this is largely hardcoded. The goal is to show how if statements work with multiple conditions.
        if (assignment == 0) {
            System.out.println("Added stats assignment. ");
            statsAssignments += 1;
        }
        else if (assignment == 1) {
            System.out.println("Added civics assignment. ");
            civicsAssignments += 1;
        }
        else if (assignment == 2) {
            System.out.println("Added linalg assignment. ");
            linalgAssignments += 1; 
        }
        else if (assignment == 3) {
            System.out.println("Added csa assignment. ");
            csaAssignments += 1;
        }
        else {
            System.out.println("Added enm assignment. ");
            enmAssignments += 1;
        }
    }

    public static void main(String[] args) {
        HomeworkCategory sahilHw = new HomeworkCategory();
        // Adding assignments for different assignment ID's.
        sahilHw.AddAssignment(0);
        sahilHw.AddAssignment(0);
        sahilHw.AddAssignment(2);
        sahilHw.AddAssignment(2);
        sahilHw.AddAssignment(2);
        sahilHw.AddAssignment(3);
        sahilHw.AddAssignment(3);
        sahilHw.AddAssignment(4);

        // Displaying the assignments that I have.

        System.out.println("Final assignments tally: ");
        System.out.println("Stats Assignments: " + sahilHw.statsAssignments);
        System.out.println("Civics Assignments: " + sahilHw.civicsAssignments);
        System.out.println("Linear Algebra Assignments: " + sahilHw.linalgAssignments);
        System.out.println("CSA Assignments: " + sahilHw.csaAssignments);
        System.out.println("ENM Assignments: " + sahilHw.enmAssignments);
    }
}
HomeworkCategory.main(null);
Added stats assignment. 
Added stats assignment. 
Added linalg assignment. 
Added linalg assignment. 
Added linalg assignment. 
Added csa assignment. 
Added csa assignment. 
Added enm assignment. 
Final assignments tally: 
Stats Assignments: 2
Civics Assignments: 0
Linear Algebra Assignments: 3
CSA Assignments: 2
ENM Assignments: 1

The above code definitely looks like it could use a better organizational pattern than a string of if/elseif/else. Below, I demonstrate how this can be refactored into a switch case.

Another note is that the switch-case has more value when the cases need to have completely different functionality associated with them. In my example, the switch-case makes the code more readable, but does not take advantage of this key tenet. However, to make the understanding of switch case as understandable as possible, a very simple example is used to show how it works.

public class HomeworkCategorySwitch {

    public int statsAssignments = 0;
    public int civicsAssignments = 0;
    public int linalgAssignments = 0;
    public int csaAssignments = 0;
    public int enmAssignments = 0;

    public void AddAssignment(int assignment) {
        // The switch statement takes an argument of what case to use.
        // Here, we pass in the integer assignment, so the cases will be different integers.
        switch(assignment) {
            // Based on the values of the integer assignment, different code segments are executed.
            case 0:
                // For the case the assignment is 0, this code segment is executed.
                System.out.println("Added stats assignment. ");
                statsAssignments += 1;
                break;
            case 1:
                System.out.println("Added stats assignment. ");
                statsAssignments += 1;
                break;
            case 2: 
                System.out.println("Added linalg assignment. ");
                linalgAssignments += 1; 
                break;
            case 3:
                System.out.println("Added csa assignment. ");
                csaAssignments += 1;
                break;
            case 4:
                System.out.println("Added enm assignment. ");
                enmAssignments += 1;
                break;
            default:
                System.out.println("Not a valid assignment number.");
                break;
        }
    }

    public static void main(String[] args) {
        // Note that the output is exactly the same as the code when used without switch cases.
        HomeworkCategorySwitch sahilHw = new HomeworkCategorySwitch();
        // Adding assignments for different assignment ID's.
        sahilHw.AddAssignment(0);
        sahilHw.AddAssignment(0);
        sahilHw.AddAssignment(2);
        sahilHw.AddAssignment(2);
        sahilHw.AddAssignment(2);
        sahilHw.AddAssignment(3);
        sahilHw.AddAssignment(3);
        sahilHw.AddAssignment(4);
        sahilHw.AddAssignment(10);

        // Displaying the assignments that I have. 

        System.out.println("Final assignments tally: ");
        System.out.println("Stats Assignments: " + sahilHw.statsAssignments);
        System.out.println("Civics Assignments: " + sahilHw.civicsAssignments);
        System.out.println("Linear Algebra Assignments: " + sahilHw.linalgAssignments);
        System.out.println("CSA Assignments: " + sahilHw.csaAssignments);
        System.out.println("ENM Assignments: " + sahilHw.enmAssignments);
    }
}
HomeworkCategorySwitch.main(null);
Added stats assignment. 
Added stats assignment. 
Added linalg assignment. 
Added linalg assignment. 
Added linalg assignment. 
Added csa assignment. 
Added csa assignment. 
Added enm assignment. 
Not a valid assignment number.
Final assignments tally: 
Stats Assignments: 2
Civics Assignments: 0
Linear Algebra Assignments: 3
CSA Assignments: 2
ENM Assignments: 1

De Morgan's Law/Compound Boolean Expressions

De Morgan's Law deals with expressions in boolean algebra. This basically means using a bunch of boolean operators together. This can usually be simplified, and is only truely useful in very complex scenarios that depend on various boolean parameters.

public class DeMorganGeneric {
    public DeMorganGeneric (boolean bool1, boolean bool2, boolean bool3, boolean bool4) {
        this.firstParam = bool1;
        this.secondParam = bool2;
        this.thirdParam = bool3;
        this.fourthParam = bool4;
    }
    public boolean firstParam;
    public boolean secondParam;
    public boolean thirdParam;
    public boolean fourthParam;

    public void DeMorgan(DeMorganGeneric test) {
        // Example of De Morgan's Law.
        // Logic needs all of the following conditions to be true:
        // First and Second must both be different (ie false and true or true and false)
        // One of Third and Fourth must be true.
        // First must be true.
        if (!(test.firstParam && test.secondParam) && (test.thirdParam || test.fourthParam) && test.firstParam == true) {
            System.out.println("Executed.");
        }
        else {
            System.out.println("Not executed. ");
        }
    }

    public static void main(String[] args) {
        DeMorganGeneric testExecSuccess = new DeMorganGeneric(true, false, false, true);
        testExecSuccess.DeMorgan(testExecSuccess);

        // This breaks the first condition, as First and Second are both true.
        DeMorganGeneric testExecFail = new DeMorganGeneric(true, true, false, true);
        testExecFail.DeMorgan(testExecFail);

        
    }

}
DeMorganGeneric.main(null);
Executed.
Not executed. 

Truth Tables

Truth tables are mainly a learning tool to visualize the outputs of different boolean operators. In general, you have a column for each variable, and then columns for the outputs of operators like XOR, OR, AND. A key fact is that the number of rows is the number of possible combinations of 1's and 0's: this is just 2 raised to the power of the number of variables.