0% found this document useful (0 votes)
45 views26 pages

DS Unit 1

The document covers Abstract Data Types (ADTs) and Object-Oriented Programming (OOP) concepts in Python, including classes, inheritance, encapsulation, and polymorphism. It explains how ADTs define data structures and operations, and provides examples of classes and their functionalities. Additionally, it discusses various inheritance types, namespaces, and the importance of design principles in creating robust and reusable software.

Uploaded by

lokeshraj525
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)
45 views26 pages

DS Unit 1

The document covers Abstract Data Types (ADTs) and Object-Oriented Programming (OOP) concepts in Python, including classes, inheritance, encapsulation, and polymorphism. It explains how ADTs define data structures and operations, and provides examples of classes and their functionalities. Additionally, it discusses various inheritance types, namespaces, and the importance of design principles in creating robust and reusable software.

Uploaded by

lokeshraj525
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/ 26

AD3251 DATA STRUCTURES DESIGN

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