3.17: Algorithm Efficiency

Purpose:

The purpose of this lesson is to help students understand how to make an efficient program and optimize it and understand its importance to the CSP curriculum.

What is Algorithmic Efficiency?

  • The ability of an algorithm to solve a problem in an efficient way
    • An efficient algorithm solves a problem quickly and with a minimum amount of resources, such as time and memory.
  • How do we determine if an algorithm is efficient or not?
    • One way we can do this is by determining the time complexity of the algorithm.
    • Another way is through space complexity.

Traveling Merchant Problem Hacks:

What did you and your team discuss? (record below)

  • An heuristic solution is an approach to a problem that produces a solution that isn't necessarily optimal but can be used when normal methods take forever

Describe the method used to solve the traveling merchant problem. (record below)

  • The way you would approach solving the travelling merchant problem is to view each as separate problems and then combine them together as in you should try and visualize the shortest routes you can take to get to each city from your starting point and then do the same approach to each of the cities you visit and then combine them together to get the shortest route possible or at least you get a very close approximation to the shortest route possible.

3.18: Undecidable Problems

Purpose:

The purpose of this lesson is to introduce students to the concept of undecidable problems in computer science and to explain why these problems are important.

Key vocabulary:

  • Decision problem
  • Decidable problem
  • Undecidable problem

Decision Problem

A decision problem is a problem in computer science and mathematics that can be solved by a yes-no answer, also known as a binary answer. In other words, a decision problem is a problem for which there are only two possible outputs:"yes" or "no". There are two types of decision problems that Collegeboard goes over:

  • Decidable Problems
  • Undecidable Problems

A decidable problem is a problem in computer science and mathematics for which an algorithm can be created that can always produce a correct answer or solution. In other words, a decidable problem is a problem for which there exists an algorithm that can be used to determine whether a given input is a valid solution or not.

An undecidable problem problem is a problem in computer science and mathematics for which it is impossible to create an algorithm that can always provide a correct answer or solution. This means that it is not possible for an algorithm to always determine whether a given input is a valid solution to an undecidable problem.

Decidable Problems

A decidable problem is an algorithm that can always have an output of yes or no given any input. It is always correct.

Example of a Decidable Problem

The procedure below tests to see if a number is divisible by 13. If it is, it returns true. If it isn't, it returns false.

def divideThirteen(number):
    if number % 13 == 0:
        return True
    else:
        return False

print(divideThirteen(26))
print(divideThirteen(30))
True
False

Undecidable Problems

An Example of a Forever Running Code

The code keeps adding 1 to the variable number until number is no longer an integer(This is not the python data type "integer", it's the integer in number theory). However, there is no end to this code, making the computer run forever. There is no halt to the code.

i = 0
number = 1
def integerTest(n):
    # Testing if the number is an integer
    if n%1 ==0:
        return True
    else:
        return False
# Using while loop to keep searching an a non-integer above 1. Note that the computer runs forever.
while i == 0:
    number += 1
    if integerTest(number) == False:
        i +=1
        print("Done")

The Halting Problem

The halting problem is an example of an undecidable problem. It states that it is not always possible to correctly determine whether a code halts or runs forever.

There is no way to write an algorithm to analyze and determine whether a body of code can run forever or not.

Halting Problem Example:

  • In order to understand this, suppose that an algorithm was able to analyze whether a code halts or not. Let's call this algorithm HaltChecker.
  • HaltChecker analyzes the program,program P, and its input,input I. If program P halts with input I, HaltChecker returns an output of "halts". If program P doesn't halt(runs forever) with input I, HaltChecker returns an output of "never". For example, in the code where it tests if variable number, the code runs forever, so HaltChecker returns an output of "never".
  • Then, we add another algorithm called Reverser which reverses HaltChecker's output. So, if "never" is the output of HaltChecker, then the output of Reverser is "halts". It's also the same the other way around: if HaltChecker has an output of "halts", then Reverser has an output of "never".
  • We combine these algorithms into one entire body of code.
  • Since Reverser is the algorithm at the end, hence giving the ultimate output, notice how it prints "never" when in fact there is an end(As proved by HaltChecker), and how it also prints "halts" when there is in fact is no end to the code(Also proved by HaltChecker). As a result, HaltChecker is inaccurate and this is an undecidable problem.

This Diagram Sums up the Entire Process in the Bulleted List:

reverser

Credits of diagram and example to Khan Academy

FAQ

  • Q: If Reverser is causing the problem, why not remove it?
  • A: Removing Reverser will remove the problems, however, we are looking for ways which create the problem of not outputting a correct result. One example is enough to prove that it is an undecidable problem since it proves that the code is not completely accurate.

Extra Things to Notice

  • Note that while a computer may take a long time to run a section of code, it does not mean that the computer is going to run forever.
  • Humans are able to solve some undecidable problems. The entire Halting Problem example was to prove that computers cannot solve undecidable problems.

Hacks

Come up with one situation in which a computer runs into an undecidable problem. Explain why it is considered an undecidable problem.

An example of an undecidable program is the following: Such as the example of a store owner who wants to design a store that sells certain goods however there is not an algorithm that can determine how it should be done as it cannot predict the flow of customers the demand for certain products and the amount of money that will be made. This is an undecidable problem because there is no way to predict the best store layout or the different factors that will affect the store making it an undecidable problem.

3.17 Homework

Your homework for Algorithmic Efficiency is pretty simple.

  1. Use the 1st code below and graph it (Desmos, TI Inpire Cas, e.t.c), change the x value only!
  2. Label the number of loops done as x and the time (microseconds) to find the index as y
  3. Connect the points
  4. Do the same thing with the 2nd code
  5. Compare the two graphs and explain which one of the two is more efficient and why (min. 2 sentences)
  6. Insert images of the graph either in your blog or on review ticket
import time

def linear_search(lst, x):
    start_time = time.perf_counter_ns() # records time (nanoseconds)
    for i in range(len(lst)): # loops through the entire list 

        if lst[i] == x: # until the x value we are looking for is found
            end_time = time.perf_counter_ns() # records time again
            total_time = (end_time - start_time) // 1000 # subtracts last recorded time and first recorded time
            print("Found element after {} loops in {} microseconds".format(i+1, total_time)) # prints the results
            return print("Your number was found at", i)
            
    end_time = time.perf_counter_ns() # records the time again
    total_time = (end_time - start_time) // 1000 # subtracts last recorded time and first recorded time
    print("Element not found after {} loops in {} microseconds".format(len(lst), total_time)) # prints the results
    return "Your number wasn't found :("


lst = list(range(1, 10001)) # list with numbers 1-10000

x = 1 # replace with an integer between 1 and 10000 (I suggest big numbers like 500, 2000, so on)

linear_search(lst, x) # runs procedure
Found element after 1 loops in 0 microseconds
Your number was found at 0

Graph 1

import time 

def binary_search(lt, x):
    start_time = time.perf_counter_ns() # starts timer
    low = 0 # sets the lower side 
    mid = 0 # sets mid value
    high = len(lt) -1 # sets the higher side
    num_loops = 0 # number of loops the search undergoes to find the x value

    while low<=high: # Loop ran until mid is reached
        num_loops += 1 # adds one loop each time process is repeated
        mid = (low + high) // 2 # takes the lowest and highest possible numbers and divides by 2 and rounds to closest whole #

        if lt[mid] == x:
            end_time = time.perf_counter_ns() # records time
            total_time = (end_time - start_time) // 1000 # time in microseconds
            print("Element found after {} loops in {} microseconds".format(num_loops, total_time)) # prints the results
            return mid # returns the index value

        elif lt[mid] > x: # if mid was higher than x value, then sets new highest value as mid -1 
            high = mid -1 

        elif lt[mid] < x:
            low = mid + 1 # if mid was lower than x, sets the new low as mid + 1
            
    end_time = time.perf_counter_ns()
    total_time = (end_time - start_time) // 1000 
    print("Element not found after {} loops in {} microseconds".format(num_loops, total_time)) # prints the results
    return "Your number wasn't found :("


lt = list(range(1, 10001)) # list with numbers 1-10000

x = 1 # replace with an integer between 1 and 10000 (I suggest big numbers like 500, 2000, so on)

binary_search(lt, x) # runs procedure
Element found after 13 loops in 3 microseconds
0

Graph 2

Reflecton

As illustrated in the graphs is appears that the second function appears to be that the first graph appears to have smaller slop when a linear regression is created based on their data when comparing larger values for their inputs. If the function do not scale with linear time complexity there may be errors in my results however it seems that second funtion is more effiecint than the first function as the values get larger and can produce an output in less time however the first function is more effiecint when the input is smaller as it can produce an output in less time.

3.18 Homework:

  1. Use the Jupyter notebook to write an algorithm that solves a decidable problem. You can use math or whatever else you would like to do.
  2. Write code to get the computer to run forever. Check this example if you need help, but please come up with your own idea.
Homeworks, hacks, and classwork(filled in blanks) for both 3.17 and 3.18 are due on Thursday at 9:00 pm. -0.1 points for each day late.
numlist = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

def oddOrEven(numbers):
    output = []

    for i in range(len(numbers)):
        number = numbers[i]
        if number % 2 == 0:
            output.append(f"The number at position {i} is even ({number})")
        else:
            output.append(f"The number at position {i} is odd ({number})")
    
    for position in output:
        print(position)

oddOrEven(numlist)

# This is a decidable provlem as it can be evaluted that any 
# number in the list either even or odd and then return its position in the list.
# This was made to run forever by alterin the code so that it would only check if the inputed 
# number wass even if so it would return true for even and false for odd.
# which was sent into a loop so that it would only have condition for an even input 
# and as the input was odd it would run forever.
The number at position 0 is odd (1)
The number at position 1 is even (2)
The number at position 2 is odd (3)
The number at position 3 is even (4)
The number at position 4 is odd (5)
The number at position 5 is even (6)
The number at position 6 is odd (7)
The number at position 7 is even (8)
The number at position 8 is odd (9)
The number at position 9 is even (10)
def evenornot(number):
    if number % 2 == 0:
        return True
    else:
        return False

infiniteloop = 33

while True:
    if evenornot(infiniteloop) == True:
        print("This number is even")
        break
    else:
        pass

# This is an infinite loop as the condition is always true as 33 is not an even number as result this code will run forever
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[9], line 10
      7 infiniteloop = 33
      9 while True:
---> 10     if evenornot(infiniteloop) == True:
     11         print("This number is even")
     12         break

Cell In[9], line 2, in evenornot(number)
      1 def evenornot(number):
----> 2     if number % 2 == 0:
      3         return True
      4     else:

KeyboardInterrupt: