0% found this document useful (0 votes)
8 views

DS Unit 1 (1) (1)

The document covers the fundamentals of Abstract Data Types (ADTs) and Object-Oriented Programming (OOP) in Python, detailing concepts such as classes, inheritance, and encapsulation. It explains the structure and behavior of classes, instance and class variables, and various types of inheritance including multilevel and multiple inheritance. Additionally, it introduces namespaces and their role in managing identifiers within a program.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views

DS Unit 1 (1) (1)

The document covers the fundamentals of Abstract Data Types (ADTs) and Object-Oriented Programming (OOP) in Python, detailing concepts such as classes, inheritance, and encapsulation. It explains the structure and behavior of classes, instance and class variables, and various types of inheritance including multilevel and multiple inheritance. Additionally, it introduces namespaces and their role in managing identifiers within a program.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 29

CD3291 DATA STRUCTURES ALGORITHMS

UNIT I ABSTRACT DATA TYPES

Abstract Data Types (ADTs) – ADTs and classes – introduction to OOP – classes
in Python – inheritance – namespaces – shallow and deep copying Introduction to
analysis of algorithms – asymptotic notations – recursion – analyzing recursive
algorithms

Abstract Data Types (ADTs)

An ADT is a mathematical model of a data structure that specifies the type


of data stored, the operations supported on them, and the types of parameters of the
operations. An ADT specifies what each operation does, but not how it does it.
Python supports abstract data types using a mechanism known as an abstract
base class (ABC).
Some examples of ADT are Stack, Queue, List etc.
Let us see some operations of those mentioned ADT

 Stack − A stack is an abstract data type that stores elements in a lastin-first-


out (LIFO) order. Elements are added and removed to/from the top only.
 Queue − A queue is an abstract data type that stores elements in a first-in-
first-out order. Elements are added at one end and removed from the other.
 List − A list is an abstract data type that implements an ordered collection
of values, where the same value may occur more than once
ADTs and classes
Class Definitions
A class serves as the primary means for abstraction in object-oriented
programming.A class provides a set of behaviors in the form of member functions
(also known as methods), with implementations that are common to all instances
of that class.
A class also serves as a blueprint for its instances, effectively determining
the way that state information for each instance is represented in the form of
attributes

Example: CreditCard Class

class CreditCard:
def init (self, customer, bank, acnt, limit):
self. customer = customer
self. bank = bank
self. account = acnt
self. limit = limit
self. balance = 0
def get customer(self):
return self. customer
def get bank(self):
return self.
bank
def get account(self):
return self. account
def get limit(self):
return self. limit
def get balance(self):
return self. Balance
def charge(self,
price):
if price + self. balance > self. limit:
return False
else:
self. balance += price
return True
def make payment(self,
amount): self. balance −=
amount

The self Identifier


self represents the instance of the class. By using the “self” keyword we
can access the attributes and methods of the class in python

The Constructor
A user can create an instance of the CreditCard class using a syntax as:
cc = CreditCard( John Doe, 1st Bank , 5391 0375 9387 5309 , 1000)
Internally, this results in a call to the specially named init method that serves
as the constructor of the class.

Introduction to OOP

Object Oriented programming (OOP) is a programming paradigm that relies


on the concept of classes and objects. It is used to structure a software program
into simple, reusable pieces of code blueprints (usually called classes), which are
used to create individual instances of objects. There are many object-oriented
programming languages including JavaScript, C++, Java, and Python.
Object-Oriented Design Goals
Software implementations should achieve robustness, adaptability, and
reusability.
 Robustness - Capable of handling unexpected inputs that are not explicitly
defined for its application. For example, if a program is expecting a positive
integer (perhaps representing the price of an item) and instead is given a
negative integer, then the program should be able to recover gracefully from
this error.

 Adaptability - adaptability (also called evolvability). Related to this concept


is portability, which is the ability of software to run with minimal change on
different hardware and operating system platforms. An advantage of writing
software in Python is the portability provided by the language itself.

 Reusability - The same code should be usable as a component of different


systems in various applications

Object-Oriented Design Principle

 Modularity - Modularity refers to an organizing principle in which different


components of a software system are divided into separate functional units.
In Python, we have already seen that a module is a collection of closely
related functions and classes that are defined together in a single file of
source code. Python‟s standard libraries include, for example, the math
module, which provides definitions for key mathematical constants and
functions, and the os module, which provides support for interacting with the
operating system.

 Abstraction - An ADT is a mathematical model of a data structure that


specifies the type of data stored, the operations supported on them, and the
types of parameters of the operations. An ADT specifies what each
operation does, but not how it does it

 Encapsulation - Containing information in an object, exposing only


selected information. Encapsulation means containing all important
information inside an object, and only exposing selected information to the
outside world.

Encapsulation requires defining some fields as private and some as public.

 Private/ Internal interface: methods and properties, accessible from other


methods of the same class.
 Public / External Interface: methods and properties, accessible also from
outside the class.
 Inheritance - Child classes inherit data and behaviors from parent class.
Inheritance supports reusability.

 Polymorphism: many methods can do the same task. Polymorphism means


designing objects to share behaviors. Polymorphism allows the same
method to execute different behaviors in two ways: method overriding and
method overloading.

Classes in Python

• Class- Classes are defined by the user. The class provides the basic structure
for an object. It consists of data members and method members that are used
by the instances(object) of the class.

• Object- A unique instance of a data structure that is defined by its class. An


object comprises both data members and methods. Class itself does nothing but
real functionality is achieved through their objects.

• Data Member: A variable defined in either a class or an object; it holds the


data associated with the class or object.

• Instance variable: A variable that is defined in a method, its scope is only


within the object that defines it.

• Class variable: A variable that is defined in the class and can be used by all the
instance of that class.

• Instance: An object is an instance of a class.

• Method: They are functions that are defined in the definition of class and are
used by various instances of the class.

• Function Overloading: A function defined more than one time with different
behavior. (different arguments)

• Encapsulation: It is the process of binding together the methods and data


variables as a single entity i.e. class. It hides the data within the class and makes
it available only through the methods.

• Inheritance: The transfer of characteristics of a class to other classes that are


derived from it.
• Polymorphism: It allows one interface to be used for a set of actions. It
means same function name(but different signatures) being used for different types.

• Data abstraction: It is the process of hiding the implementation details and


showing only functionality to the user.

Classes-
• Python is OOP language. Almost everything in python is an object with its
properties and methods.

• Object is simply a collection of data(variables) and methods(functions) that


acts on those data.

Creating Classes:
A class is a block of statement that combine data and operations, which are
performed on the data, into a group as a single unit and acts as a blueprint for the
creation of objects.

Syntax:
class ClassName:
„ Optional class documentation string
#list of python class variables
# Python class constructor
#Python class method definitions

• In a class we can define variables, functions etc. While writing function in


class we have to pass atleast one argument that is called self parameter.

• The self parameter is a reference to the class itself and is used to access
variables that belongs to the class.
Example: Creating class in .py file
class student:
def display(self): # defining method in class
print("Hello Python")

• In python programming self is a default variable that contains the memory


address of the instance of the current class.

• So we can use self to refer to all the instance variable and instance methods.

Objects and Creating Objects-


• An object is an instance of a class that has some attributes and behavior.
• Objects can be used to access the attributes of the class.

Example:
class student:
def display(self): # defining method in class
print("Hello Python")
s1=student() #creating object of class
s1.display() #calling method of class using
object Output:
Hello Python

Example: Class with get and put method


class car:
def get(self,color,style):
self.color=color
self.style=style
def put(self):
print(self.color)
print(self.style)
c=car()
c.get('Brio','Red')
c.put()
Output:
Brio
Red

Instance variable and Class variable:

• Instance variable is defined in a method and its scope is only within the object
that defines it.

• Every object of the class has its own copy of that variable. Any changes made
to the variable don‟t reflect in other objects of that class.

• Class variable is defined in the class and can be used by all the instances
of that class.

• Instance variables are unique for each instance, while class variables
are shared by all instances.

Example: For instance and class variables


class sample:
x=2 # x is class variable
def get(self,y): # y is instance variable
self.y=y
s1=sample()
s1.get(3) # Access attributes
print(s1.x," ",s1.y)
s2=sample()
s2.y=4
print(s2.x," ",s2.y)
Output:
23
24

Inheritance:

The mechanism of designing and constructing classes from other classes is


called inheritance.
Inheritance is the capability of one class to derive or inherit the properties from
some another class.
The new class is called derived class or child class and the class from which this
derived class has been inherited is the base class or parent class. The benefits of
inheritance are:
1. It represents real-world relationships well.
2. It provides reusability of a code. We don‟t have to write the same code again
and again. Also, it allows us to add more features to a class without modifying it.
3. It is transitive in nature, which means that if class B inherits from another class
A, then all the subclasses of B would automatically inherit from class
Syntax:
Class A:
# Properties of class A
Class B(A):
# Class B inheriting property of class A
# more properties of class B

Example 1: Example of Inheritance without using constructor

class vehicle: #parent


class name="Maruti"
def display(self):
print("Name= ",self.name)
class category(vehicle): # drived class
price=400000
def disp_price(self):
print("price= ",self.price)
car1=category()
car1.display()
car1.disp_price()
Output:
Name= Maruti
price= 400000

Example 2: Example of Inheritance using constructor

class vehicle: #parent class


def init (self,name,price):
self.name=name
self.price=price
def display(self):
print("Name= ",self.name)
class category(vehicle): # drived
class def init (self,name,price):
vehicle. init (self,name,price) #pass data to base
constructor def disp_price(self):
print("price= ",self.price)
car1=category("Maruti",400000)
car1.display()
car1.disp_price()
car2=category("Honda",600000)
car2.display()
car2.disp_price()
Output:
Name= Maruti
price= 400000
Name= Honda
price= 600000

Multilevel Inheritance:

In multilevel inheritance, features of the base class and the derived class are further
inherited into the new derived class. This is similar to a relationship representing a
child and grandfather.

Syntax:
Class A:
# Properties of class A
Class B(A):
# Class B inheriting property of class A
# more properties of class B
Class C(B):
# Class C inheriting property of class B
# thus, Class C also inherits properties of class A
# more properties of class C
Example 1: Python program to demonstrate multilevel inheritance
#Mutilevel Inheritance
class c1:
def display1(self):
print("class c1")
class c2(c1):
def display2(self):
print("class c2")
class c3(c2):
def display3(self):
print("class c3")
s1=c3()
s1.display3()
s1.display2()
s1.display1()
Output:
class c3
class c2
class c1
Example 2: Python program to demonstrate multilevel inheritance
# Base class
class Grandfather:
grandfathername =""
def grandfather(self):
print(self.grandfathername)
# Intermediate class
class Father(Grandfather):
fathername = ""
def father(self):
print(self.fathername)
# Derived class
class Son(Father):
def parent(self):
print("GrandFather :", self.grandfathername)
print("Father :", self.fathername)
# Driver's code
s1 = Son()
s1.grandfathername = "Srinivas"
s1.fathername = "Ankush"
s1.parent()
Output:
GrandFather : Srinivas
Father : Ankush

Multiple Inheritance:
When a class can be derived from more than one base classes this type of
inheritance is called multiple inheritance. In multiple inheritance, all the features of
the base classes are inherited into the derived class.

Syntax:
Class A:
# variable of class A
# functions of class
A Class B:
# variable of class B
# functions of class
B Class C(A,B):
# Class C inheriting property of both class A and B
# more properties of class C
Example: Python program to demonstrate multiple inheritance
# Base class1
class Father:
def display1(self):
print("Father")
# Base class2
class Mother:
def display2(self):
print("Mother")
# Derived class
class Son(Father,Mother):
def display3(self):
print("Son")
s1 = Son()
s1.display3()
s1.display2()
s1.display1()
Output:
Son
Mother
Father
Hierarchical Inheritance:
When more than one derived classes are created from a single base this type of
inheritence is called hierarchical inheritance. In this program, we have a parent
(base) class and two child (derived) classes.

Example : Python program to demonstrate Hierarchical inheritance


# Base class
class Parent:
def func1(self):
print("This function is in parent class.")
# Derived class1
class Child1(Parent):
def func2(self):
print("This function is in child 1.")
# Derived class2
class Child2(Parent):
def func3(self):
print("This function is in child 2.")
object1 = Child1()
object2 = Child2()
object1.func1()
object1.func2()
object2.func1()
object2.func3()
Output:
This function is in parent class.
This function is in child 1.
This function is in parent class.
This function is in child 2.

Namespaces
A namespace is an abstraction that manages all of the identifiers that are
defined in a particular scope, mapping each name to its associated value.

A name in Python is a way to access a variable like in any other languages. Python
is more flexible when it comes to the variable declaration. We can declare a
variable by assigning a name to it. We can use names to reference values.

Example:
num =
5 str =
'Z'
seq = [0, 1, 1, 2, 3, 5]
#We can even assign a name to a function.
def function():
print('It is a function.')
foo = function
foo()

A namespace is a simple system to control the names in a program. A


namespace is a dictionary of variable names (keys) and their corresponding objects
(values). Namespaces play a key role in the execution of function calls and the
normal control flow of a program. Python implements namespaces in the form of
dictionaries. It maintains a name-to-object mapping where names act as keys and
the objects as values. Multiple namespaces may have the same name but pointing
to a different variable.

Instance and Class Namespaces

instance namespace, which manages attributes specific to an individual object. For


example, each instance of our CreditCard class maintains a distinct balance, a
distinct account number, a distinct credit limit, and so on. Each credit card will
have a dedicated instance namespace to manage such values.
There is a separate class namespace for each class that has been defined. This
namespace is used to manage members that are to be shared by all instances
of a class,

Name Resolution and Dynamic Dispatch


The process that is used when retrieving a name in Python‟s object-oriented
framework. When the dot operator syntax is used to access an existing member,
such as obj.foo, the
Python interpreter begins a name resolution process, described as follows:
1. The instance namespace is searched; if the desired name is found, its
associated value is used.
2. Otherwise the class namespace, for the class to which the instance belongs, is
searched; if the name is found, its associated value is used.
3. If the name was not found in the immediate class namespace, the search
continues upward through the inheritance hierarchy, checking the class namespace
for each ancestor (commonly by checking the superclass class, then its superclass
class, and so on). The first time the name is found, its associate value is used.
4. If the name has still not been found, an AttributeError is raised.

Types of Namespaces
1. Local Namespace
Creation of local functions creates the local namespace. This namespace covers the
local names inside a function. Python creates this namespace for every function
called in a program. It remains active until the function returns.
2. Global Namespace
When a user creates a module, a global namespace gets created. This namespace
covers the names from various imported modules used in a project. Python creates
this namespace for every module included in your program. It‟ll last until the
program ends.
3. Built-in Namespace
This namespace covers the built-in functions and built-in exception names. Python
creates it as the interpreter starts and keeps it until you exit. The built-in namespace
encompasses global namespace and
global namespace encompasses local namespace. Some functions like print(), id()
are examples of built in namespaces.

Lifetime of a namespace:
A lifetime of a namespace depends upon the scope of objects, if the scope of an
object ends, the lifetime of that namespace comes to an end. It is not possible to
access inner namespace‟s objects from an outer namespace.
Scopes
A scope refers to a region of a program where a namespace can be directly
accessed, i.e. without using a namespace prefix. The scope of a name is the area of
a program where this name can be unambiguously used, for example, inside of a
function. A name's namespace is identical to its scope. Scopes are defined
statically, but they are used dynamically.
During program execution there are the following nested scopes available:
the innermost scope is searched first and it contains the local names
the scopes of any enclosing functions, which are searched starting with the
nearest enclosing scope
the next-to-last scope contains the current module's global names
the outermost scope, which is searched last, is the namespace containing the
built-in names.

shallow and deep copying

A shallow copy is one which makes a new object stores the reference of another
object. While, in deep copy, a new object stores the copy of all references of
another object making it another list separate from the original one.

Thus, when you make a change to the deep copy of a list, the old list doesn‟t get
affected and vice-versa. But shallow copying causes changes in both the new as
well as in the old list.

This copy method is applicable in compound objects such as a list containing


another list.

Syntax for Shallow Copy

import copy
copy.copy(object_name)

Syntax for Deep copy

import copy

copy.deepcopy(object_name)

Example:

import copy
a = [ [1, 2, 3], [4, 5, 6] ]
b = copy.copy(a)
c = [ [7, 8, 9], [10, 11, 12] ]
d = copy.deepcopy(c)
print(a)
print(b) a[1]
[2] = 23
b[0][0] = 98
print(a)
print(b)
print("\n")
print(c)
print(d) c[1]
[2] = 23
d[0][0] = 98
print(c)
print(d)
Output:
Shallow Copy [1,2,3]
[4,5,6]
[1,2,3][4,5,6]
[98,2,3][4,5,23]
[98,2,3][4,5,23]
Deep Copy
[7, 8, 9], [10, 11, 12]
[7, 8, 9], [10, 11, 12]
[7, 8, 9], [10, 11, 23]
[98, 8, 9], [10, 11, 12]
Introduction to analysis of algorithms
Efficiency of Algorithms:
The performances of algorithms can be measured on the scales of time and space.
The performance of a program is the amount of computer memory and time needed
to run a program. We use two approaches to determine the performance of a
program. One is analytical and the other is experimental.
In performance analysis we use analytical methods, while in performance
measurement we conduct experiments.
Time Complexity: The time complexity of an algorithm or a program is a function
of the running time of the algorithm or a program. In other words, it is the amount
of computer time it needs to run to completion.
Space Complexity: The space complexity of an algorithm or program is a function
of the space needed by the algorithm or program to run to completion. The time
complexity of an algorithm can be computed either by an empirical or theoretical
approach.
The empirical or posteriori testing approach calls for implementing the complete
algorithms and executing them on a computer for various instances of the problem.
The time taken by the execution of the programs for various instances of the
problem are noted and compared. The algorithm whose implementation yields the
least time is considered as the best among the candidate algorithmic solutions.
Analyzing Algorithms
Suppose M is an algorithm, and suppose n is the size of the input data. Clearly the
complexity f(n) of M increases as n increases. It is usually the rate of increase of
f(n) with some standard functions.
The most common computing times are O(1), O(log2 n), O(n), O(n log2 n),
O(n2), O(n3), O(2n)
Example:
The total frequency counts of the program segments A, B and C given by 1, (3n+1)
and (3n2+3n+1) respectively are expressed as O(1), O(n) and O(n2). These are
referred to as the time complexities of the program segments since they are
indicative of the running times of the program segments. In a similar manner space
complexities of a program can also be expressed in terms of mathematical
notations, which is nothing but the amount of memory they require for their
execution.

Asymptotic Notations:
It is often used to describe how the size of the input data affects an algorithm‟s
usage of computational resources. Running time of an algorithm is described as a
function of input size n for large n.
Big oh(O): Definition: f(n) = O(g(n)) (read as f of n is big oh of g of n) if there
exist a positive integer n0 and a positive number c such that |f(n)| ≤ c|g(n)| for all n
≥ n0 . Here g(n) is the upper bound of the function f(n).

Theta(Θ): Definition: f(n) = Θ(g(n)) (read as f of n is theta of g of n), if there


exists a positive integer n0 and two positive constants c1 and c2 such that c1 |g(n)|
≤ |f(n)| ≤ c2 |g(n)| for all n ≥ n0. The function g(n) is both an upper bound and a
lower bound for the function f(n) for all values of n, n ≥ n0 .
Little oh(o): Definition: f(n) = O(g(n)) ( read as f of n is little oh of g of n), if f(n)
= O(g(n)) and f(n) ≠ Ω(g(n)).

Reasons for analyzing algorithms:


To predict the resources that the algorithm requires
1. Computational Time(CPU consumption).
2. Memory Space(RAM consumption).
3. Communication bandwidth consumption.
4. To predict the running time of an algorithm
5. Total number of primitive operations executed.

Recursion - analyzing recursive algorithms


A function is said to be recursive if it calls itself again and again within its body
Types of Recursion:
Recursion is of two types depending on whether a function calls itself from within
itself or whether two functions call one another mutually. The former is called
direct recursion and the later is called indirect recursion. Thus there are two
types of recursion:
Direct Recursion
Indirect Recursion

Recursion may be further categorized as:


Linear Recursion
Binary Recursion
Multiple Recursion

Linear Recursion:
It is the most common type of Recursion in which function calls itself repeatedly
until base condition [termination case] is reached. Once the base case is reached
the results are return to the caller function.
If a recursive function is called only once then it is called a linear recursion.
Binary Recursion:
Functions with two recursive calls are referred to as binary recursive
functions.
Example1: The Fibonacci function fib provides a classic example of binary
recursion. The Fibonacci numbers can be defined by the rule:
fib(n) = 0
if n is 0,
return 1
if n is 1,
= fib(n-1) + fib(n-2) otherwise
For example, the first seven Fibonacci numbers are
Fib(0) = 0
Fib(1) = 1
Fib(2) = Fib(1) + Fib(0) = 1
Fib(3) = Fib(2) + Fib(1) = 2
Fib(4) = Fib(3) + Fib(2) = 3
Fib(5) = Fib(4) + Fib(3) = 5
Fib(6) = Fib(5) + Fib(4) = 8
# Program to display the Fibonacci sequence up to n-th term where n is provided
by the user
# change this value for a different result
nterms = 10
# uncomment to take input from the user
#nterms = int(input("How many terms?
")) # first two terms
n1 = 0
n2 = 1
count = 0
# check if the number of terms is valid
if nterms <= 0:
print("Please enter a positive integer")
elif nterms == 1:
print("Fibonacci sequence upto",nterms,":")
print(n1)
else:
print("Fibonacci sequence upto",nterms,":")
while count < nterms:
print(n1,end=' , ')
nth = n1 + n2
# update values
n1 = n2
n2 = nth
count += 1
Tail Recursion:
Tail recursion is a form of linear recursion. In tail recursion, the recursive call is
the last thing the function does. Often, the value of the recursive call is returned.
As such, tail recursive functions can often be easily implemented in an iterative
manner; by taking out the recursive call and replacing it with a loop, the same
effect can generally be achieved. In fact, a good compiler can recognize tail
recursion and convert it to iteration in order to optimize the performance of the
code.
A good example of a tail recursive function is a function to compute the GCD, or
Greatest Common Denominator, of two numbers:
def factorial(n):
if n == 0: return 1
else: return factorial(n-1) * n
def tail_factorial(n, accumulator=1):
if n == 0: return 1
else: return tail_factorial(n-1, accumulator * n)
Recursive algorithms for Factorial, GCD, Fibonacci Series and Towers of
Hanoi:
Factorial(n)
Input: integer n ≥
0 Output: n!
1. If n = 0 then return (1)
2. else return prod(n, factorial(n − 1))
GCD(m, n)
Input: integers m > 0, n ≥ 0
Output: gcd (m, n)
12
1. If n = 0 then return (m)
2. else return gcd(n,m mod
n) Time-Complexity: O(ln n)
Fibonacci(n)
Input: integer n ≥ 0
Output: Fibonacci Series: 1 1 2 3 5 8 13………………………………..
1. if n=1 or n=2
2. then Fibonacci(n)=1
3. else Fibonacci(n) = Fibonacci(n-1) + Fibonacci(n-2)

Towers of Hanoi
Input: The aim of the tower of Hanoi problem is to move the initial n different
sized disks from needle
A to needle C using a temporary needle B. The rule is that no larger disk is to be
placed above the
smaller disk in any of the needle while moving or at any time, and only the top of
the disk is to be
moved at a time from any needle to any needle.
Output:
1. If n=1, move the single disk from A to C and return,
2. If n>1, move the top n-1 disks from A to B using C as temporary.
3. Move the remaining disk from A to C.
4. Move the n-1 disk disks from B to C, using A as
temporary. def TowerOfHanoi(n , from_rod, to_rod,
aux_rod):
if n == 1:
print "Move disk 1 from rod",from_rod,"to rod",to_rod
return
TowerOfHanoi(n-1, from_rod, aux_rod, to_rod)
print "Move disk",n,"from rod",from_rod,"to
rod",to_rod TowerOfHanoi(n-1, aux_rod, to_rod,
from_rod)
n=4
TowerOfHanoi(n, 'A', 'C', 'B')

You might also like