0% found this document useful (0 votes)
17 views6 pages

3 Algorithm Analysis-2

The document discusses algorithm analysis and runtime calculation. It provides two methods (methodA and methodB) to check if a string is a palindrome. Benchmark tests on different string lengths show methodB has slightly better performance. The document then discusses primitive operations like variable assignment, function calls, arithmetic operations etc. It explains how to calculate runtime by breaking down code into primitive operations and determining complexity class. An example function to find the maximum value in a list is analyzed in detail.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
0% found this document useful (0 votes)
17 views6 pages

3 Algorithm Analysis-2

The document discusses algorithm analysis and runtime calculation. It provides two methods (methodA and methodB) to check if a string is a palindrome. Benchmark tests on different string lengths show methodB has slightly better performance. The document then discusses primitive operations like variable assignment, function calls, arithmetic operations etc. It explains how to calculate runtime by breaking down code into primitive operations and determining complexity class. An example function to find the maximum value in a list is analyzed in detail.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 6

Algorithm Analysis

With the discussion about mathematical concepts, we will proceed with the analysis of
algorithms.
This is very important as we need to identify which algorithm is good or not based on a given
situation.

Of course, not everything is software-dependent, as we also have to consider the hardware


environment such as CPU (computing power) and RAM (memory capacity).

Consider the problem below:

Create an algorithm that will reverse a given string, and check if it is a palindrome or not,
without depending too much on Python built-in functions.
A palindrome refers to word in which its reverse has the same order of letters.

In [ ]: def methodA(s : str) -> bool:


is_pal = True
new_s = ""

#Create new string that is a reverse of the given


for i in range(1, len(s)+1):
new_s = new_s + s[len(s)-i]

#Check each character from both strings


for i in range(0, len(s)):
#Reverse flag if found a non-equal character
if s[i] != new_s[i]:
is_pal = False
break

return is_pal

In [ ]: def methodB(s: str) -> bool:


is_pal = True
i = 0
while i < len(s):
#Immediately check with one string only
#Ignore case with casefold()
if s[i].casefold() != s[len(s)-(i+1)].casefold():
is_pal = False
break
i = i + 1
return is_pal

In [ ]: %timeit -n 50000 methodA("racecar") #length 7


%timeit -n 50000 methodA("saippuakivikauppias") #length 12
%timeit -n 50000 methodA("AmanaplanacanalPanama") #short joined sentence
%timeit -n 50000 methodA("anotsecretsinisasiniflaidtoresttserrotdialfinisasinistercest
1.12 µs ± 52.1 ns per loop (mean ± std. dev. of 7 runs, 50,000 loops each)
2.48 µs ± 48.4 ns per loop (mean ± std. dev. of 7 runs, 50,000 loops each)
1.75 µs ± 29.2 ns per loop (mean ± std. dev. of 7 runs, 50,000 loops each)
5.62 µs ± 168 ns per loop (mean ± std. dev. of 7 runs, 50,000 loops each)

In [ ]: %timeit -n 50000 methodB("racecar")


%timeit -n 50000 methodB("saippuakivikauppias")
%timeit -n 50000 methodB("AmanaplanacanalPanama") #short joined sentence
%timeit -n 50000 methodB("anotsecretsinisasiniflaidtoresttserrotdialfinisasinistercest

1.07 µs ± 48.7 ns per loop (mean ± std. dev. of 7 runs, 50,000 loops each)
2.58 µs ± 81.3 ns per loop (mean ± std. dev. of 7 runs, 50,000 loops each)
2.76 µs ± 25.5 ns per loop (mean ± std. dev. of 7 runs, 50,000 loops each)
3.78 µs ± 39.4 ns per loop (mean ± std. dev. of 7 runs, 50,000 loops each)

Primitive Operations
The two methods will be broken down a while later. But first, let us discuss primitive operations.
They usually have a constant execution time in the lowest level of instruction - machine.

However, to reduce the complexity, its 'accurate' execution time is set as 1. The list of primitive
operations are shown below:

1. Variable declaration and instantiation


x = 5
The code above has two primitive operations. One is to get the value of 5 from the
memory, then store it into the variable x .

y = arr[0]
In this other block of code, it contains four primitive operations. First is to get the value of
0 from the memory, then another retrieval operation for the arr variable, then a
subscripting operation to access the value of the element at that index of the list which is
denoted by arr[0] , and finally storing it into the variable y .

1. Function/method calling

print() # Function
SomeClass.method() #Method
Function calling without a return costs 1 primitive operation to do. Method calling,
however, will cost 2 primitive operations as we the computer needs to get the context of
SomeClass first, before calling method() .

print("Hello World")
When function/method calling with parameters, each parameter will add 2 additional
primitive operations. One is to retrieve the value that will be supplied to the argument, and
storing that value into the argument.

1. Returning from a function/method call

def add(x,y):
return x + y
def getX():
return x

Returning from a function/method call is always constant. However, the number of operations
may vary depending on the return process. For example, the function add(x,y) has four
primitive operations in total that is: (1,2) get the values of x and y from memory, (3) add the two
values, (4) return the value.

Meanwhile, the second function, getX() only contains two primitive operations that is: (1) Get
the value of x from the memory, and (2) return the value.

1. Performing arithmetic operations

z = x + y
Every arithmetic operation is always a constant operation, 1 . The operation above has four
operations mainly: (1,2) Retrieving the value of x and y from the memory, (3) adding the
two numbers, (4) storing the result into z

2. Comparing two numbers

if x > y:
#code here
This is similar to number 3. Here, we have three operations in total: (1,2) getting the value
of x and y, and (3) processing the > operation.

3. Indexing into a list

z = my_list[1]
The code above has five operations. Firstly is getting the value of 1 from the memory,(2)
next is retrieving the my_list[] from the memory, (3) followed by the subscripting operation
my_list[1] , (4) next is retrieving the value of my_list[1] from the memory, (5) and
storing that value into z .

4. Following object reference (OOP)

z = car.getModel()
Applicable when using classes, whenever we call a class, there is always one primitve
operation involved. The code above has four operations, (1) get the car reference from the
memory, (2) call the method getModel() , (3) retrieving the return value from
getModel() and (4) storing the value into z .

Focusing the Worst Case


In the entire discussion of data structures and algorithms, we will be concerning ourselves with
the worst-case scenario. That is, if we are given the worst input possible for our algorithm, how
will it behave and how long it will take?
The worst-case scenario is usually illustrated in the term, Big-Oh notation => O(n) , where n
is the input.

The convenience when dealing with the worst-case is that we are concerned with the largest
degree in a given polynomial. We ignore the coefficients and such, as the term a 0
n
x1
has the
largest factor amongst a polynomial.

3 4 2 4
5n + n + 2n + 100n + 50 => O(n )

Calculating Runtime
In this section, we will be discussing how to calculate algorithm runtimes.
Firstly, let us deal with a simplier function: findMax() , where we will find the maximum value
from a given numeric list. We have to assume that the list is ordered from least to greatest in
order to work up with the worst-case scenario.

In [ ]: def findMax(l : list) -> int:


max = l[0] # 5

i = 0 # 2
while i < len(l): # 5 * (n + 1)
if l[num] > max: # 6 * n
max = l[num] # 5 * n
i = i + 1 # 4 * n
return max # 2
# Total => 20n + 12
# O(n)

We can ignore the first line of the code block above.

Line 2

Line 2 has five total operations, which is (1) getting the value of 0 , (2) getting the reference of
l[] , (3) subscripting operation l[0] , (4) retrieving the value of l[0] , and (5) storing it into
max .

Line 4

Line 4 has 5 primitive operations and one linear-scale operation, and an additional 5 operations
when the operation will become false.

The 6 primitive operations are: (1) retrieving the value of l , (2) supplementing l into len()
as an argument, (3) retrieving the value of len(l) through the function's return, (4) retrieving
the value of 1 , (5,6) supplementing the arguments 1, len(l) into range() , (7) retrieving
the value of the range() , and (8 - linear) for every element on the sequence returned by
range() , sequentially assign the value to num.

Point 8 can be broken down into the following: (1) Get the value of the current index from the
immutable sequence returned by range() , (2) store that value into num

Line 4 can be summarized into: 5n + 5

Line 5

Remember that we are dealing with the worst-case scenario here. We will assume that the if
condition will run on every iteration of the loop.

Firstly, we get the number of primitive operations then multiplying it by n .


Line 5 can be broken down into: (1) get the value of num, (2) get the reference of l[] , (3)
subscript operation l[num] , (4) get the value of l[num] , (5) get the value of max , and (6)
process the > operation.

We then multiply 6 by n as this will repeat for every iteration of the for-loop .

Line 5 can be summarized into 6n.

Then we proceed back to the two methods, methodA() and methodB() .

Line 6

Line 6 has 5 operations namely: (1) get the value of num, (2) get the reference of l[] , (3)
subscript operation l[num] , (4) get the value of l[num] , and (5) store it into max .

We then multiply the number of primitive operations, which is 5, by n .

Thus, Line 6 can be summarized as: 5n.

Line 7

Line 7 has 4 primitive operations that will run n times.

The 4 primitive operations are namely: (1,2) Get the value of i and 1 from the memory, (3)
execute the + operation, and (4) store the value into i .

Thus, line 7 can be summarized as: 4n.

Line 8

There are two primitive operations in line 7 identified as: (1) get the value of max, and (2) return
the value of max.

Total

To get the total runtime, we simply add the runtime for each line which is:

5 + (5n + 5) + 6n + 5n + 4n + 2

= 20n + 12
Big-Oh

When using Big-Oh Notation, O(), we ignore the constants and only get the variable with the
highest exponential value. In this case, we have O(n).

You might also like