Review

if-else statements

Syntax:

if (condition) {

} else if (condition 2) {

} else {

}

for loops

for (let i = number; i < number; i++) {
    // code
}

Conditionals vs Booleans

Conditionals and booleans can be equivalent.

For example, let's say there are two booleans: rainy and sunny.

Let's look at the following code:

sunny = true; 
rainy = false; 
if (sunny) {
    umbrella = false; 
} else if (rainy) {
    umbrella = true; 
} else {
    umbrella = false; 
}

console.log(umbrella); 
false

The code above is the same as below:

umbrella = !sunny && rainy;

console.log(umbrella); 
false

To determine if two conditionals and booleans are the same, you can substitute the four possibilities that the two booleans (sunny and rainy) can be (listed below) into the conditional and boolean and see if both cases match:

sunny = true, rainy = true

sunny = true, rainy = false

sunny = false, rainy = true

sunny = false, rainy = false

Developing Algorithms

Algorithms can be written in different ways and still accomplish the same tasks. Algorithms that look similar often yield differnet outputs. To solve the same problem, many different algorithms can be used.

Therefore, algorithms are very important for programmers, and today we're going to explore how to determine the outcome of algorithms, how to deteremine the output of similar algorithms, how to edit existing algorithms, and how to develop our own algorithms.

Determine the outcome of algorithms

Consider the following algorithm.

def mystery(num, num2):
    if (num % num2 == 0):
        print("True")
    else:
        print("False")

mystery(20, 4)
True
  1. What does the algorithm do? Please explain in words. : The algorithm takes in two numbers, num and num2, and checks to see if num is divisible by num2. If num is divisible by num2, it prints "True", and if not, it prints "False".
  2. What if I put in 30 as num and 4 as num2. What would be the output?: False

Determine the outcome of similar algorithms

What is the output of this algorithm?

temp = 95
if (temp >= 90):
    print("it is too hot outside")
else:
    if (temp >= 65):
        print("I will go outside")
    else:
        print("it is too cold outside")
it is too hot outside

What is the output of this algorithm? it looks similar but the output is different!

temp = 95
if (temp >= 90):
    print("it is too hot outside")
elif (temp >= 65):
    print("i will go outside")
if (temp < 65):
    print("it is too cold outside")
it is too hot outside

Editing Algorithms

Task: Please edit the algorithm above to have it yield the same results as the previous algorithm! (no matter what temp you put in)

Developing Algorithms

To develop algorithms, we first need to understand what the question is asking. Then, think about how you would approach it as a human and then try to find what pattern you went through to arrive at the answer. Apply this to code, and there you have it! An algorithm!

Let's say you wanted to sum up the first five integers. How would you do this in real life? Your thought process would probably be:

  • The sum of the first integer is 1.
  • Then, increase that integer by 1. I now add 2 to my existing sum (1). My new sum is 3.
  • Repeat until I add 5 to my sum. The resulting sum is 15.

Now let's translate this into code.

sum = 0 # start with a sum of 0
for i in range (1, 6): # you will repeat the process five times for integers 1-5
    sum = sum + i # add the number to your sum
print(sum) # print the result
15

Task: Write an algorithm in python that sums up the first 5 odd integers. You can use the following code as a starter.

sum = 0
counter = 1
for i in range (0, 5):
    sum = sum + counter
    counter = counter + 2
print(sum)
25

Searching Introduction

What is searching?

In certain computer programs and applications, one might find the need to locate and retrieve a data value and/or it's index. Searching algorithms could be done in either intervals or sequences, and certain algorithms could be more efficient than others, with benefits and drawbacks to each.

The Naive Approach

The most intuitively obvious solution to the searching problem is to sequentialy check each successful value in the data structure until either a matching value is found, or the entire structure has been transversed. This thought process could be explained graphically in this example

This algorithm could also be expressed in code, as shown below.

def sequentialSearch(arr, target):
    N = len(arr)                     # Declare N as length of array
    for i in range(N):               # Iterate over the list
        if arr[i] == target:         # Check for match
            return i                 # Match found, return index and end function call
    return -1                        # Element not found

Sequential Search - Larger Inputs

Although for selection sort is seemingly fast for smaller inputs, it is clear that it cannot keep up with increasing input sizes. Because sequential search checks every value of the given array, the algorithm's overall runtime increases "linearly" with the input size.

i.e. Pretend that one check takes one second, and that we are searching for the last element in an array. If the array length is 4, it would take 4 seconds to find the last element, whereas if the array length is 86400 indices long, it would take a whole day to find the element.

Hence, although selection sort is known for its simplicity, it is unfeasible for large inputs

Below, we have created three sorted lists of length 100,1000000,100000000.

import time
arr1 = [i for i in range(100)]
arr2 = [i for i in range(1000000)]
arr3 = [i for i in range(100000000)]

To analyze the sequential search algorithm, we will check for the worst case scenario, where runtime is maximized. This is because when measuring the efficiency of our algorithm, we want to be able to guarantee an upper limit or set amount of time for our program to finish running. To do this, we will attempt to search for the last element in the array

Binary search is an efficient way to iterate through a SORTED list to find a requested value. This is done through checking the middle value of a list and checking if the requested value is greater than or less than the middle value. You can start to see why the requested list must be sorted. If the list is not sorted, this logic is flawed, and the binary search algorithm will no longer work.

Unlike the sequential search method, binary search doesn't check for each successive element until a match is found. In every iteration the algorithm is making a binary decision; if the selected element is larger or smaller than the target.

How exactly does this work? Lets look at these amazing ms paint drawings:

This algorithm is extremely efficient as the maximum number of cycles in binary search is equal to log base 2 of the closest, next power of two, to length of list.

If the array is 8 items long, the maximum possible cycles would be 3 (log base 2 of 8 is 3)

If the array is 7 items long, the maximum possible cycles would STILL be 3 as the closest power of 2 to 7 is 8.

If the array is 9 items long, the maximum possible cycles INCREASES to 4, as the closest, next power of two, is 16.

def binarySearch(array, target):            # Parameters: array is the given array and target is what we are looking for
    low = 0                                 # the starting lower bound
    high = len(array)-1                     # the starting upper bound
    while high >= low:                      # we will keep running until we run out of possible subarrays...
        mid = (high + low) // 2             #   define the middle of the list to be the item at the index of the average of the lower and upper bound
        if array[mid] == target:            #   if item is in the middle of the list... we found what we are looking for!
            return mid                      #       therefore, we return the index of where we found the item.
        elif array[mid] > target:           #   if item is less than the middle of the list, this must mean that the item is on the lower half of the list
            high = mid-1                    #       therefore, we set the upper bound of the search to be the last item of the lower half
        else:                               #   if item is neither less than or equal to the middle of the list, this must mean that the item is on the upper half of the list
            low = mid+1                     #       therefore, we set the lower bound of the search to be the first item of the upper half
                                            # if nothing is returned by the time the while loop ends, that means item MUST be missing from list
    return False                            #   therefore we tell the user that the requested item was not found

Likewise, we can also take a recursive approach to this problem, note the similarities

Challenges and Homework

You have one homework problem.

Yes just one.

Don't get excited though.

Problem: Given a specific integer N, return the square root of N (R) if N is a perfect square, otherwise, return the square root of N rounded down to the nearest integer

Input: N (Integer)

Output: R (Integer)

Constraints: Do not use any built-in math operations such as sqrt(x) or x**(0.5), Try complete the problem in logarithmic time.

Hint 1: Maybe you can use Binary Search to try and reduce the number of checks you have to perform?

Hint 2: Is there a mathematical pattern amongst numbers and their square roots that could help you reduce the number of searches or iterations you must execute? Is there some value or rule you can set before applying binary search to narrow the range of possible values?

Run the very last code segment below to load test cases and submission function

def sqrt(N):
    if N == 0 or N == 1:
        return N
    start = 1
    end = N
    while start <= end:
        mid = (start + end) // 2
        if mid*mid == N:
            return mid
        if mid*mid < N:
            start = mid + 1
        else:
            end = mid - 1
    return end
from math import sqrt as sq
test_cases = [0,1,4,85248289,22297284,18939904,91107025,69122596,9721924,37810201,1893294144,8722812816,644398225]
answers = [int(sq(x)) for x in test_cases]

def checkValid():
    for i in range(len(test_cases)):
        if sqrt(test_cases[i]) == answers[i]:
            print("Check number {} passed".format(i+1))
        else:
            print("Check number {} failed".format(i+1))

checkValid()
Check number 1 passed
Check number 2 passed
Check number 3 passed
Check number 4 passed
Check number 5 passed
Check number 6 passed
Check number 7 passed
Check number 8 passed
Check number 9 passed
Check number 10 passed
Check number 11 passed
Check number 12 passed
Check number 13 passed

Homework

Create an algorithm that will start with any positive integer n and display the full sequence of numbers that result from the Collatz Conjecture. The COllatz Conjecture is as follows:

  1. start with any positive integer
  2. if the number is even, divide by 2
  3. if the number is odd, multiply by 3 and add 1
  4. repeat steps 2 and 3 until you reach 1

Example: if the starting number was 6, the output would be 6, 3, 10, 5, 16, 8, 4, 2, 1

def collatz(n):
    while n != 1:
        if n % 2 == 0:
            n = n / 2
            print(n)
        else:
            n = n * 3 + 1
            print(n)

collatz(5)
16
8.0
4.0
2.0
1.0

Challenge / wasn't able to figure it out

Using JavaScript, create an algorithm that takes in an IP address and a subnet mask and computes the network address.

Overview

As we've seen in Unit 4.1, an IP address is a 32 bit number that uniquely identifies each device. (See this for a recap). Something extra is that an IP address also comes with a subnet mask. A subnet mask is also a 32 bit number that identifies what network an IP address in in through a process that uses the bitwise AND.

In ANDing:

0 + 0 = 0

0 + 1 = 0

1 + 0 = 0

1 + 1 = 1


The following are the steps to determine the network that an IP address is in given the subnet mask:

Example: IP address: 192.168.0.1

Subnet mask: 255.255.255.0

  1. Convert the IP address into binary: 192.168.0.1 -> 11000000.10101000.00000000.00000001
  2. Convert the subnet mask into binary: 255.255.255.0 -> 11111111.11111111.11111111.00000000
  3. Do a bitwise AND operation on the binary IP address and subnet mask:
 11000000.10101000.00000000.00000001
+11111111.11111111.11111111.00000000
=11000000.10101000.00000000.00000000
  1. Convert the result back to decimal: 11000000.10101000.00000000.00000000 -> 192.168.0.0

The network address is 192.168.0.0

3.9 Binary Search Notes

  • Searching when it comes to computer programs and applications involves locating and retrieving a data value/and or its index
    • Searching algorithms can be done in intervals or sequentially
  • Sequential Search - checks every value of the given array
    • Cannot keep up with increasing input sizes, as the longer the array, the more time that it will take for the search to check
    • In other words, as the input lists gets larger, the runtime of the program increases "linearly" as well
  • Binary Search - a more efficient way to check through a sorted list to find a specific value
    • Involves checking the medium or middle value of a list and checking if the requested value is higher or lower than the middle value
    • If the list is not sorted or in a proper order, the binary search algorithm cannot work anymore
    • The maximum number of cycles in a binary search is equivalent to the log base 2 of the closest, next power of 2, to the length of the list
    • When it comes to execution time, using the binary search method will overall take less time than using the sequential search method and is therefore more efficient and worthy of one's time

3.11 Algorithms (Can be Applied to both Python & JavaScript) Notes

  • Algorithms are specific steps designed to accomplish a certain task
  • In developing algorithms, it is crucial to understand what the question is asking
  • Example: if one wanted to sum up the first five integers, they would probably think about how...
    • The sum of the first integer is 1
    • Increase that integer by 1 before adding 2 to the existing sum. At this point, the new sum is 3
    • This process would be repeated until 5 is added to the sum. To recap, this thought process would look something like this for humans:
      • 1+2+3+4+5 = 15
    • The resulting sum of the first five integers is 15.
sum = 0 # start with a sum of 0
for i in range (1, 6): # you will repeat the process five times for integers 1-5
    sum = sum + i # add the number to your sum
print(sum) # print the result
15