KEMBAR78
Ucest105-Algorithmic Thinking With Python Notes | PDF | Python (Programming Language) | Heuristic
0% found this document useful (0 votes)
119 views126 pages

Ucest105-Algorithmic Thinking With Python Notes

Uploaded by

ALWINZZ
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
119 views126 pages

Ucest105-Algorithmic Thinking With Python Notes

Uploaded by

ALWINZZ
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 126

B.

Tech 2024 –S1/S2

SEMESTER S1
ALGORITHMIC THINKING WITH PYTHON

(Common to All Branches)

Course Code UCEST105 CIE Marks 40

Teaching Hours/Week
3:0:2:0 ESE Marks 60
(L: T:P: R)

Credits 4 Exam Hours 2 Hrs. 30 Min.

Prerequisites (if any) None Course Type Theory

Course Objectives:

1. To provide students with a thorough understanding of algorithmic thinking and its practical
applications in solving real-world problems.
2. To explore various algorithmic paradigms, including brute force, divide-and-conquer, dynamic
programming, and heuristics, in addressing and solving complex problems.

SYLLABUS
Module Contact
Syllabus Description
No. Hours
PROBLEM-SOLVING STRATEGIES:- Problem-solving strategies defined,
Importance of understanding multiple problem-solving strategies, Trial and
Error, Heuristics, Means- Ends Analysis, and Backtracking (Working
backward).

THE PROBLEM-SOLVING PROCESS:- Computer as a model of


1 7
computation, Understanding the problem, Formulating a model, Developing
an algorithm, Writing the program, Testing the program, and Evaluating the
solution.

APJ Abdul Kalam Technological University 32


B.Tech 2024 –S1/S2
ESSENTIALS OF PYTHON PROGRAMMING:- Creating and using
variables in Python, Numeric and String data types in Python, Using the math
module, Using the Python Standard Library for handling basic I/O -
print, input, Python operators and their
precedence.
ALGORITHM AND PSEUDOCODE REPRESENTATION:- Meaning and
Definition of Pseudocode, Reasons for using pseudocode, The main
constructs of pseudocode - Sequencing, selection (if-else structure, case
structure) and repetition (for, while, repeat- until loops), Sample problems*

FLOWCHARTS** :- Symbols used in creating a Flowchart - start and end,


arithmetic calculations, input/output operation, decision (selection), module
name (call), for loop (Hexagon), flow-lines, on-page connector, off-page
connector.

2 9
* - Evaluate an expression, d=a+b*c, find simple interest, determine the
larger of two numbers, determine the smallest of three numbers, determine
the grade earned by a student based on KTU grade scale (using if-else and
case structures), print the numbers from 1 to 50 in descending order, find the
sum of n numbers input by the user (using all the three loop variants),
factorial of a number, largest of n numbers (Not to be limited to these
exercises. More can be worked out if time permits).

** Only for visualizing the control flow of Algorithms. The use of tools like
RAPTOR (https://raptor.martincarlisle.com/) is suggested. Flowcharts
for the sample problems listed earlier may be discussed
SELECTION AND ITERATION USING PYTHON:- if-else, elif, for loop,
range, while loop.

Sequence data types in Python - list, tuple, set, strings, dictionary, Creating
and using Arrays in Python (using Numpy library).

DECOMPOSITION AND MODULARISATION* :- Problem decomposition


3 as a strategy for solving complex problems, Modularisation, Motivation for 10
modularisation, Defining and using functions in Python, Functions with
multiple return values
APJ Abdul Kalam Technological University 33
B.Tech 2024 –S1/S2
RECURSION:- Recursion Defined, Reasons for using Recursion, The Call
Stack, Recursion and the Stack, Avoiding Circularity in Recursion, Sample
problems - Finding the nth Fibonacci number, greatest common divisor
of two positive integers, the
factorial of a positive integer, adding two positive integers, the sum of
digits of a positive number **.

* The idea should be introduced and demonstrated using Merge sort, the
problem of returning the top three integers from a list of n>=3 integers as
examples. (Not to be limited to these two exercises. More can be worked
out if time permits).
** Not to be limited to these exercises. More can be worked out if time
permits.
COMPUTATIONAL APPROACHES TO PROBLEM-
SOLVING(Introductory diagrammatic/algorithmic explanations only. Analysis
not required) :-

Brute-force Approach -
- Example: Padlock, Password guessing
Divide-and-conquer Approach -
- Example: The Merge Sort Algorithm
- Advantages of Divide and Conquer Approach
- Disadvantages of Divide and
Conquer Approach Dynamic Programming
Approach
- Example: Fibonacci series
4
- Recursion vs Dynamic 10
Programming Greedy Algorithm
Approach
- Example: Given an array of positive integers each indicating the
completion time for a task, find the maximum number of tasks that can
be completed in the limited amount of time that you have.
- Motivations for the Greedy Approach

APJ Abdul Kalam Technological University 34


B.Tech 2024 –S1/S2
- Characteristics of the Greedy Algorithm
- Greedy Algorithms vs Dynamic
Programming Randomized Approach
- Example 1: A company selling jeans gives a coupon for each pair of
jeans. There are n different coupons. Collecting n different coupons
would give you free jeans. How many jeans do you expect to buy
before getting a free one?
Example 2: n people go to a party and drop off their hats to a hat-check
person.
When the party is over, a different hat-check person is on duty and
returns the n hats randomly back to each person. What is the expected
number of people who get back their hats?
-Motivations for the Randomized Approach

Course Assessment Method


(CIE: 40 marks, ESE: 60 marks)

Continuous Internal Evaluation Marks (CIE):

Continuous Internal Internal


Internal
Assessment Examination-1 Examination-
Attendance Examination- Total
(Accurate 2
(Written 3
Execution of
Examination) (Written
Programming (Lab Examination)
Examination)
Tasks)
5 5 10 10 10 40

APJ Abdul Kalam Technological University 35


B.Tech 2024 –S1/S2

End Semester Examination Marks (ESE)


In Part A, all questions need to be answered and in Part B, each student can choose any one full
question out of two questions

Part A Part B Total


 2 Questions from each  Each question carries 9 marks.
module.  Two questions will be given from each module, out of
 Total of 8 Questions, each which 1 question should be answered.
60
carrying 3 marks  Each question can have a maximum of 3 sub
divisions.
(8x3 =24marks) (4x9 = 36 marks)

Course Outcomes (COs)


At the end of the course students should be able to:
Bloom’s
Course Outcome Knowledge
Level (KL)
Utilize computing as a model for solving real-world problems.
CO1
K2

Articulate a problem before attempting to solve it and prepare a clear


CO2
and accurate model to represent the problem. K3

Utilize effective algorithms to solve the formulated models and


CO3
translate algorithms into executable programs. K3

Interpret the problem-solving strategies, a systematic approach to


CO4 solving computational problems, and essential Python programming K2
skills
Note: K1- Remember, K2- Understand, K3- Apply, K4- Analyse, K5- Evaluate, K6- Create

APJ Abdul Kalam Technological University 36


B.Tech 2024 –S1/S2

CO-PO Mapping Table:

PO1 PO2 PO3 PO4 PO5 PO6 PO7 PO8 PO9 PO10 PO11 PO12
CO1 3 3 3 3
CO2 3 3 3 3
CO3 3 3 3 3
CO4 3 3 3 3

Reference Books
Name of the Edition
Sl. No Title of the Book Name of the Author/s
Publisher and Year
1 Problem solving & programming Maureen Sprankle, Jim Pearson 2012
concepts Hubbard
2 How to Solve It: A New Aspect George Pólya Princeton University 2015
of Press
Mathematical Method
3 Creative Problem Solving: An Donald Treffinger., Scott
Prufrock Press 2005
Introduction Isaksen, Brian Stead-
Doval
Psychology (Sec.. Problem Spielman, R. M., Dumper,
Solving.) K., Jenkins, W., Lacombe,
4 H5P Edition 2021
A., Lovett, M., &
Perlmutter, M
5 Computer Arithmetic Algorithms Koren, Israel
AK Peters/CRC Press 2018

6 Introduction to Computation and Guttag John V


PHI 2/e., 2016
Programming using Python
7 Python for Everyone Cay S. Horstmann, Rance
Wiley 3/e, 2024
D. Necaise
8 Computational Thinking: A G Venkatesh Madhavan
Mylspot Education 2020
Primer Mukund Services Pvt Ltd
for Programmers and Data
Scientists

APJ Abdul Kalam Technological University 37


B.Tech 2024 –S1/S2

Video Links (NPTEL, SWAYAM…)

Module No. Link ID

1 https://opentextbc.ca/h5ppsychology/chapter/problem-solving/

2 https://onlinecourses.nptel.ac.in/noc21_cs32/preview

1. Continuous Assessment (5 Marks)


Accurate Execution of Programming Tasks

▪ Correctness and completeness of the program


▪ Efficient use of programming constructs
▪ Handling of errors
▪ Proper testing and debugging

2. Evaluation Pattern for Lab Examination (10 Marks)

1. Algorithm (2 Marks)

Algorithm Development: Correctness and efficiency of the algorithm related to the question.

2. Programming (3 Marks)

Execution: Accurate execution of the programming task.

3. Result (3 Marks)

Accuracy of Results: Precision and correctness of the obtained results.

4. Viva Voce (2 Marks)

Proficiency in answering questions related to theoretical and practical aspects of the subject.

Sample Classroom Exercises:

1. Identify ill-defined problem and well-defined problems


2. How do you differentiate the methods for solving algorithmic problems: introspection,
simulation, computer modelling, and experimentation?
3. Use cases for Trial and error, Algorithm, Heuristic and Means-ends analysis can be applied in
proffering solution to problems
APJ Abdul Kalam Technological University 38
B.Tech 2024 –S1/S2
4. Use a diagram to describe the application of Tower of Hanoi in choosing and analysing an action
at a series of smaller steps to move closer to the goal
5. What effect will be generated if the stage that involves program writing is not observed in the
problem-solving process?
6. What effect will be generated if the stage that involves program writing is not observed in the
problem-solving process?
7. Evaluate different algorithms based on their efficiency by counting the number of steps.
8. Recursive function that takes a number and returns the sum of all the numbers from zero to that
number.
9. Recursive function that takes a number as an input and returns the factorial of that number.
10. Recursive function that takes a number ‘n’ and returns the nth number of the Fibonacci number.
11. Recursive function that takes an array of numbers as an input and returns the product of all the
numbers in the list.

LAB Experiments:

1. Demonstrate about Basics of Python Programming


2. Demonstrate about fundamental Data types in Python Programming. (i.e., int, float, complex,
bool and string types)
3. Demonstrate different Arithmetic Operations on numbers in Python.
4. Create, concatenate, and print a string and access a sub-string from a given string.
5. Familiarize time and date in various formats (Eg. “Sun May 29 02:26:23 IST 2017”)

6. Write a program to create, append, and remove lists in Python using numPy.
7. Programs to find the largest of three numbers.
8. Convert temperatures to and from Celsius, and Fahrenheit. [Formula: c/5 = f-32/9]
9. Program to construct the stars (*) pattern, using a nested for loop
10. Program that prints prime numbers less than 20.
11. Program to find the factorial of a number using Recursion.
12. Recursive function to add two positive numbers.
13. Recursive function to multiply two positive numbers
14. Recursive function to the greatest common divisor of two positive numbers.
15. Program that accepts the lengths of three sides of a triangle as inputs. The program output should
indicate whether or not the triangle is a right triangle (Recall from the Pythagorean Theorem that
in a right triangle, the square of one side equals the sum of the squares of the other two sides).
Implement using functions.

APJ Abdul Kalam Technological University 39


B.Tech 2024 –S1/S2
16. Program to define a module to find Fibonacci Numbers and import the module to another
program.
17. Program to define a module and import a specific function in that module to another program.
18. Program to check whether the given number is a valid mobile number or not using functions?
Rules:
1. Every number should contain exactly 10 digits.
2. The first digit should be 7 or 8 or 9

APJ Abdul Kalam Technological University 40


MODULE 1

PROBLEM-SOLVING STRATEGIES

Problem-solving strategies are steps to overcoming the obstacles to achieving a goal. The iteration
of such strategies over the course of solving a problem is the "problem-solving cycle".

Common steps in this cycle include recognizing the problem, defining it, developing a strategy to
fix it, organizing knowledge and resources available, monitoring progress, and evaluating the
effectiveness of the solution. Once a solution is achieved, another problem usually arises, and the
cycle starts again.

Insight is the sudden solution to a problem, the birth of a new idea to simplify a complex situation.
Solutions found through insight are often more incisive than those from step-by-step analysis.

A quick solution process requires insight to select productive moves at different stages of the
problem-solving cycle.

Well-defined problems have a clear initial state, goal state, and allowable operations, with a known
solution. Ill-defined problems lack these characteristics, making them more ambiguous and
difficult to solve.

Well-defined problem examples:

1. A math equation:
Given the equation x + 5 = 10, the goal is to find the value of x (x = 5), with a clear
solution path.
2. A jigsaw puzzle:
The initial state is the scattered pieces, the goal is to complete the picture, and the
allowable operations are moving and connecting the pieces.
3. Chess:
The initial state is the board setup, the goal is to checkmate the opponent's king, and
the allowable moves are well-defined.
4. A recipe:
The initial state is the ingredients, the goal is to produce the dish according to the
instructions, and the allowable operations are the steps in the recipe.
5. Solving a Sudoku puzzle:
The initial state is a partially filled grid, the goal is to fill the grid with numbers
according to the rules, and the allowable operations are placing numbers in empty cells
while following the constraints.
Ill-defined problem examples:

1. Solving global poverty:


While the goal is clear, the initial state (various factors contributing to poverty) and the
allowable operations (possible solutions) are complex and varied.

2. Finding a good job:


The initial state is your skills and experience, the goal is to find a suitable job, but the
allowable operations (job searching, networking) and the solution (the perfect job) are
not well-defined.
3. Creating a work of art (e.g., painting, writing):
The initial state is a blank canvas or page, the goal is to create something meaningful,
but the allowable operations (brushstrokes, words) and the solution (the final artwork)
are subjective and open to interpretation.
4. Designing a new product:
The initial state is a need, the goal is to create a product that meets that need, but the
allowable operations (research, brainstorming) and the solution (the final product) are
not clearly defined.
5. Improving a city's traffic flow:
The initial state is the existing traffic patterns, the goal is to reduce congestion, but the
allowable operations (road improvements, public transportation) and the solution
(optimal traffic flow) are complex and depend on many factors

Importance of understanding multiple problem-solving strategies

Having multiple solutions to a problem enhances decision-making, encourages creativity, and


helps manage risks. It also boosts team motivation by involving everyone in generating ideas.

Flexibility and Adaptability:Different problems call for different solutions. A single


strategy may not always work, and knowing multiple options allows for flexibility and
adaptation to the specific problem at hand.

Increased Efficiency:

By understanding a wider range of strategies, individuals can identify the most efficient
approach, leading to faster and more effective problem resolution.

Stronger Critical Thinking:

Exploring various problem-solving techniques helps develop critical thinking skills by


encouraging analysis, comparison, and evaluation of different solutions.

Personalized Learning:
Different individuals may learn and apply knowledge in different ways. Understanding
multiple strategies allows for leveraging individual strengths and preferences to effectively
solve problems.

Enhanced Understanding of Root Causes:

By understanding the underlying principles of different strategies, individuals can gain a


deeper understanding of the problem's root cause, leading to more effective and sustainable
solutions.

Improved Decision-Making:

The ability to evaluate different solutions and select the most appropriate one is a key
decision-making skill that is developed through understanding multiple problem-solving
strategies.

Preparation for Complex Issues:

Complex problems often require a combination of different strategies. Understanding


multiple approaches provides a foundation for tackling complex issues and developing
creative solutions.

Trial and Error problem-solving strategies

Trial and error is a problem-solving method where you try different potential solutions until you
find one that works, rejecting those that don't. This approach is common and effective, especially
when the problem doesn't have an obvious solution. It's essentially about testing ideas and making
adjustments until you reach a satisfactory outcome.

Working:

Initial Attempts: You start by proposing a solution or a series of actions.

Evaluation: You evaluate whether the results are satisfactory. If not, you reject the solution
or actions.

Iteration: You repeat the process, trying different solutions or adjusting existing ones until
you find one that works.

Testing: You implement these solutions or actions and observe the results.

Advantages :
● Versatile: It can be used for a wide range of problems, from simple technical issues
to complex interpersonal problems.
● Intuitive: It's a natural and often effortless approach to problem-solving.
● Flexible: You can adapt the process and try different solutions as needed.

Disadvantages :

● Time-consuming: It can be slow and inefficient, especially if you're trying


numerous solutions.
● Not always suitable: Some problems might require a more structured or analytical
approach.
● Potential for error: If the consequences of failure are significant, trial and error
might not be the best choice.

Example 1: Finding the Correct Lock

Imagine you have a lock with many keys, and you need to open it. You could try one key at a time
until you find the one that works. This process is a simple example of trial and error. You're trying
different "solutions" (keys) until you find the one that fits and unlocks the door

Example 2: Troubleshooting a broken printer

If a printer isn't working, you might try different solutions like checking ink levels, making sure
the paper tray isn't jammed, or restarting the printer, until you identify the issue and it starts
working again.

Heuristics problem-solving strategies

Heuristics are problem-solving techniques that result in a quick and practical solution. In contrast
to business decisions that involve extensive analysis, heuristics are used in situations where a short-
term solution is required.
Although heuristics may not result in the most optimal and ideal solution, it allows companies to
speed up their decision-making process and achieve an adequate solution for the short term.

In situations where perfect solutions may be improbable, heuristics can be used to achieve
imperfect but satisfactory decisions. Heuristics can also include mental shortcuts that help speed
up the decision-making process.

Heuristics are particularly useful when:

1.Time is limited: When making quick decisions is necessary.

2.Information is scarce: When a full analysis of the situation is not possible.

3.The decision is not critical: When the consequences of a less-than-perfect solution are
not severe.

Limitations of Heuristics:

1.They can lead to suboptimal solutions: Heuristics may not always lead to the best possible
outcome.

2.They can be influenced by biases: Personal experiences and preferences can shape the
use of heuristics, leading to biased decision-making.

3.They may not be suitable for complex problems: Heuristics are best used for problems
where a quick, practical solution is needed, rather than a thorough, detailed analysis.

Example 1: Finding the right book


Imagine you're at a bookstore and want to find a good book. You could spend hours browsing,
reading reviews, or searching online. Instead, you might use the heuristic of familiarity – you might
choose a book by an author you've enjoyed in the past, trusting that you've already established a
good taste in that author's writing.

Example 2: Choosing a route

You need to get to work and can take one of two routes. Instead of analyzing traffic conditions and
travel times for each route, you might use the heuristic of availability – you might choose the route
you usually take, because it's more readily accessible in your memory. This might be a quick way
to make a decision, even if it's not the fastest route.

Means-ends analysis problem-solving strategies

Means-ends analysis is a problem-solving strategy where you identify the differences between
your current state and your goal state, then create subgoals and actions to reduce those differences
and achieve the end goal. It's a heuristic used to solve problems by focusing on reducing
discrepancies between the starting point and the desired outcome.

Steps in Means-Ends Analysis:

1.Define the problem: Clearly identify the current state and the desired goal state.

2.Identify differences: Determine the gaps or obstacles between the current state and the
goal state.

3.Create subgoals: Break down the problem into smaller, more manageable subgoals that
address the differences.

4.Develop actions: Outline the steps or actions needed to achieve each subgoal.

5.Implement and evaluate: Take action on your analysis, monitor progress, and adjust as
needed.
Working:

● Forward and Backward Reasoning:Means-ends analysis can involve both forward and
backward reasoning. Forward reasoning starts with the current state and works towards the
goal, while backward reasoning starts with the goal and works back towards the current
state.
● Iterative Process:The process is often iterative, where you identify and address differences,
then repeat the process with the new state.
● Focus on Differences: The core principle is to reduce the differences between the current
state and the goal state.

Advatanges:

● Structured Approach: Provides a structured framework for problem-solving.


● Focus on Subproblems: Breaks down complex problems into manageable subproblems.
● Helps Identify Obstacles: Pinpoints the specific obstacles that need to be overcome.

Disadvantages:

● Cognitive Load: Can be cognitively demanding, especially for complex problems.


● May Not Always Be Efficient: Not always the most efficient strategy for all types of
problems.
● Can Be Time-Consuming: May require significant time and effort to analyze and develop
solutions.

Example 1: Reaching a New City

● Goal: Reach the city of Paris from your current location.


● Means-ends analysis:
1. Subgoal 1: Get to the airport.
2. Subgoal 2: Fly to Paris.
3. Subgoal 3: Arrive at the final destination in Paris.
4. Actions: Book a flight, travel to the airport, check in, board the plane, and travel
from the airport to your final destination.

Example 2: Building a Website

● Goal: Build a functioning website.


● Means-ends analysis:
1. Subgoal 1: Register a domain name and web hosting.
2. Subgoal 2: Design the website's layout.
3. Subgoal 3: Develop the website's content.
4. Subgoal 4: Code and implement the website.
5. Subgoal 5: Test and debug the website.
6. Actions: Research and choose a domain name, select a web hosting provider, design
a website layout, write content, code and implement interactive elements, test the
website on different browsers and devices, and fix any bugs or inconsistencies.

Backtracking problem-solving strategies

A backtracking algorithm is a way to solve problems by trying out different options one by one,
and if an option doesn’t work, it "backtracks" and tries the next option.

It’s like exploring a maze: you try one path, and if you hit a dead end, you go back and try a
different path until you find the exit. The goal is to explore all possible paths until you find the
correct solution.

Working:

1. Start at the Initial Position


The algorithm begins at the initial position or the root of the decision tree. This is the
starting point from where different paths will be explored.

2. Make a Decision
At each step, the algorithm makes a decision that moves it forward. This could be moving
in a certain direction in a maze or choosing a particular option in a decision tree.

3. Check for Validity


After making a decision, the algorithm checks if the current path is valid or if it meets the
problem’s constraints. If the path is invalid or leads to a dead end, the algorithm backtracks
to the previous step.
4. Backtrack if Necessary
If a dead end is reached or if the path doesn't lead to a solution, the algorithm backtracks
by undoing the last decision. It then tries a different option from the previous decision
point.

5. Continue Exploring
The algorithm continues to explore different paths, making decisions, checking validity,
and backtracking when necessary. This process repeats until a solution is found or all
possible paths have been explored.

6. Find the Solution or Exhaust All Options


The algorithm stops when it finds a valid solution or when all possible paths have been
explored and no solution exists.

Advantages
● Simple and Intuitive: Easy to understand and implement for a wide range of problems.
● Flexibility: Can be applied to many types of problems, especially combinatorial and
constraint satisfaction problems.
● Exhaustive Search: Guarantees finding a solution if one exists by exploring all possible
options.
● Effective Pruning: Can reduce the search space significantly by eliminating paths that
cannot lead to a solution.
● Handles Complex Problems: Suitable for problems with complex constraints that other
algorithms may struggle with.

Disadvantages
● Time complexity: Backtracking can be very slow for large problems, as it explores a large
search space that may contain many dead ends. This can result in a high time complexity,
especially if the problem has a large number of constraints and a complex search space.
● Space complexity: Backtracking can also be memory-intensive, as it requires storing the
state of the partial solution at each step of the search. This can result in a high space
complexity, especially for problems with a large number of variables and constraints.
● Inefficiency: Backtracking can be inefficient, as it may explore the same parts of the search
space multiple times. This can result in a large number of redundant calculations, which
can significantly slow down the solution time.
● Limited applicability: Backtracking is not suitable for problems with real-time constraints,
as it may take a long time to find a solution. Additionally, it may not be appropriate for
problems with continuous variables, as it can only handle discrete variables and constraints.

Example 1: Maze Solving

Problem: Find a path from the starting point to the destination in a maze.
Solution: The algorithm starts at the beginning, explores possible paths (e.g., up, down, left, right),
and marks visited cells. If it reaches a dead end or a wall, it backtracks to the previous position and
tries a different path. This continues until a solution is found or all possible paths have been
exhausted

Example 2: Sudoku Solver

Problem: Fill a Sudoku grid with numbers so that each row, column, and 3x3 square contains all
numbers from 1 to 9 without repetition.
Solution: The algorithm starts with an empty grid and tries placing numbers in empty cells. It
checks if the placement violates Sudoku rules (no repeated numbers in rows, columns, or 3x3
squares). If a placement leads to an invalid state, it backtracks to the previous cell and tries a
different number.

PROBLEM-SOLVING PROCESS
The problem-solving process is a systematic approach to identify, analyze, and resolve issues to
achieve desired outcomes. It typically involves defining the problem, analyzing its root causes,
generating potential solutions, evaluating options, selecting the best solution, implementing it, and
then evaluating its effectiveness. The problem-solving process can be adapted to different
situations and problem types, but the core steps remain the same.
1. Define the Problem: Clearly state what the problem is and what needs to be achieved. Gather
relevant information and identify the specific issues causing the problem.
2. Analyze the Problem: Investigate the root causes of the problem. This may involve identifying
the factors that contribute to the issue and understanding its impact.
3. Generate Potential Solutions: Brainstorm various possible solutions to the problem.
Encourage creativity and consider different approaches.
4. Evaluate Solutions: Compare the potential solutions based on factors like feasibility, cost, and
effectiveness. Assess which solution best addresses the problem and aligns with the desired
outcomes.
5. Select and Implement the Solution: Choose the most suitable solution and develop a plan for
implementing it. This may involve assigning tasks, setting timelines, and allocating resources.
6. Evaluate the Outcome: Monitor the implementation of the solution and evaluate its
effectiveness. Assess whether the problem has been resolved and whether the desired outcomes
have been achieved.
7. Revise and Improve: If the solution is not fully effective, review the problem-solving process
and make necessary adjustments. Continuously improve the solution or the process to achieve
better results.

COMPUTER AS A MODEL OF COMPUTATION

Understanding the Problem

Understanding the problem is the crucial first step in any problem-solving process. It involves
clearly defining the issue, its scope, goals, and constraints, and gathering relevant information to
ensure you are addressing the right problem effectively. Ask clarifying questions to gain a deeper
understanding of the problem's nature, its impact, and the desired outcome. Examine the problem
from different angles, considering its root causes, underlying assumptions, and relevant factors.
Collect data, consult with relevant stakeholders, and gather information from various sources to
build a comprehensive understanding of the problem. Then clearly define what you hope to achieve
by solving the problem and acknowledge any limitations or restrictions.

Example 1:Delays in Project Completion

Problem:A software development project is running behind schedule and is experiencing delays
in key milestones.

Understanding the Problem:The project manager needs to identify the factors contributing to the
delays. This might include issues with coding, testing, documentation, or resource availability.
They need to understand if the problem is with individual team members, specific coding tasks, or
the overall project management process.

Solution:

1. Resource Allocation: Ensure that resources are adequately allocated to different tasks and
that team members have the necessary skills and support.
2. Communication: Implement clear communication channels and regular progress updates
to ensure that all team members are aware of the project's status.
3. Process Improvement: Identify and implement improvements to the project management
process, such as using project management tools, establishing clear deadlines, and
implementing regular reviews.
4. Risk Management: Develop a risk management plan to identify and address potential issues
before they impact the project schedule.
5. Training and Support: Provide additional training or support to team members who are
struggling with specific tasks or coding challenges.

Formulating a Model

Formulating a model in the problem-solving process involves translating a real-world problem into
a simplified representation that can be analyzed and potentially solved. This process typically
includes understanding the problem, building a conceptual model, and then formulating it
mathematically or using other suitable representation.

● Conceptual Model:Develop a simplified representation of the problem, focusing on the key


elements and relationships. This could involve creating a flowchart, diagram, or even a
mental map
● Initial Analysis: Conduct preliminary research and analysis to gain a better understanding
of the problem, its causes, and potential consequences.
● Mathematical or Other Representation:Translate the conceptual model into a form suitable
for analysis. This might involve mathematical equations, simulations, or other analytical
tools.

Developing An Algorithm

Once you have a clear understanding of the problem statement, the next step in algorithmic
problem-solving is to design an algorithm to solve the problem. An algorithm is a step-by-step set
of instructions that, when followed, will solve the problem. Here are some key points to consider
when designing an algorithm:
● An algorithm is a precise set of instructions that, when followed, will solve a specific
problem or perform a specific task.
● Algorithms can be written in natural language, pseudocode, or a programming language
like Python, Java, or C++.
● Algorithms can be simple or complex, depending on the problem they are designed to
solve.
● Understand the problem statement: Before you can design an algorithm, you need to
have a clear understanding of the problem you are trying to solve.
● Break down the problem: Complex problems can often be broken down into smaller,
more manageable subproblems. Break the problem down into smaller parts and design an
algorithm to solve each part.
● Choose an algorithmic strategy: There are many different algorithmic strategies that can
be used to solve a problem. Choose the strategy that is best suited to the problem you are
trying to solve.
● Design the algorithm: Once you have chosen an algorithmic strategy, design an algorithm
to solve the problem. This may involve writing pseudocode or a detailed step-by-step set
of instructions.
● Brute force: Brute force is a straightforward approach to solving a problem that involves
trying every possible solution until the correct one is found. This approach can be slow and
inefficient for large problems.
● Greedy: Greedy algorithms make decisions based on the current state of the problem
without considering the future consequences. This approach can be fast and efficient for
some problems but may not always produce the optimal solution.
● Divide and conquer: Divide and conquer is a strategy that involves breaking a problem
down into smaller subproblems, solving each subproblem independently, and then
combining the solutions to solve the original problem. This approach can be fast and
efficient for many problems.

Writing The Algorithm


The interesting part of the process! Coding! After the best algorithm is determined, you implement
it as an executable program. The program or the code is a set of instructions that is more or less, a
concrete representation of the algorithm in some programming language. While coding, always
follow the incremental paradigm – start with the essential functionalities and gradually add more
and more to it.

Test the program

Once coding is complete, the program must be tested to verify its correctness. Testing checks if
the program produces the expected output for a variety of inputs. Since testing all possible inputs
is impractical, a test suite—a representative set of inputs—is used. If the program works correctly
on this suite, it is likely to work well overall. Automated testing tools can assist in generating
these test cases. Debugging is the process of identifying and fixing errors (bugs) found during
testing. Testing and debugging should be repeated until the program is error-free.

Evaluate the solution

The final step in problem-solving is to evaluate the solution to ensure it effectively meets the
problem's objectives. This involves defining clear evaluation criteria, such as efficiency,
feasibility, and scalability. It also includes assessing potential deployment risks. Feedback—both
quantitative and qualitative—should be collected from stakeholders. Based on this, necessary
improvements should be made, and the refined program must undergo rigorous testing to ensure
reliability and effectiveness.

ESSENTIALS OF PYTHON PROGRAMMING

Python is a high-level, interpreted programming language known for its simplicity and versatility.
Its key features include:

● Interpreted Language: No need for compilation; code is executed directly by the


interpreter.

● Simple and Readable Syntax: Code is easy to understand and resembles pseudocode or
algebraic notation.

● Highly Interactive: Code can be executed interactively via an interpreter, allowing


immediate feedback.

● Object-Oriented: Supports OOP concepts, enabling better modeling of real-world


systems.
● Scalable: Ideal for both beginners and advanced users, including those in research and
software development.

How to Run Python Code

Python code can be executed in two ways:

1. Using the Python Shell (Interactive Mode)

● Open a terminal and type python3.

● The Python shell starts with a prompt >>>.

● Enter Python commands one at a time, and see immediate results.

$ python3

>>> "Hello, Python!"

'Hello, Python!'

2. Running as a Standalone Script (Script Mode)

● Write your code in a .py file (e.g., sample.py) using a text editor like gedit or vim.

● Save the file and open a terminal in the directory where the file is saved.

● Run the script using:

$ python3 sample.py

Constants, Variables, Keywords, Data Types, and Statements in Python

Constants

● A constant is a fixed value that does not change during program execution.
● Example: 3, 100, "Hello".

Variables

● A variable is a named memory location used to store data that may change.

● Rules for naming variables:

1. Must start with a letter or underscore _.

2. Can include letters, digits, and underscores.

3. Are case-sensitive (COUNT ≠ count).

4. Cannot use Python keywords as names.

Keywords

● Keywords are reserved words with special meaning.

● Examples: if, else, for, while, def, class, True, False, None.

Data Types

Python supports several built-in data types. Two major ones are:

1. Numbers

○ int: Whole numbers (e.g., 700, -5)

○ float: Decimal numbers (e.g., 3.14, 1.6e-3)

○ complex: Complex numbers (e.g., 3+4j)

○ bool: Boolean values True or False (subtype of int)


2. Strings

○ A string is a sequence of characters enclosed in quotes (' ' or " ").

○ Multi-line strings use triple quotes (''' or """).

○ Example: "Hello", 'Python', """multi-line string"""

Statements

● A statement is an instruction the interpreter can execute.

● Types:

○ Expression statements: Perform computations (e.g., a + b).

○ Control statements: Manage flow (e.g., if, while, for).

Data input and output

In Python, I/O (Input/Output) operations are handled primarily with built-in functions like
print() for output and input() for input.

Example 1:

>>> print("Hello World!")

Output : Hello World!

Example 2:

>>> a = 7

>>> print("The value of a is ",a)

Output : The value of a is 7

Example 3:

>>> x=5
>>> y=3

>>> print("The value of",x,"and",y,"is",x+y)

Output : The value of 5 and 3 is 8

Example 4:To receive input from the user, Python provides the input() method which accepts the
input as a string.

>>> myName = input("What is your name?")

What is your name?Python language

>>> print("I am ",myName)

Output: I am Python language

When using the input() function in Python, the data is always read as a string, even if you enter
a number. So, if you want to do arithmetic operations, you must convert the string to the
appropriate numeric type using type conversion functions.
Escape sequence

When using the print() function in Python, most things inside quotes are printed exactly as they
are.However, if you want to include special characters (like new lines, quotes, tabs, etc.), you need
to use escape sequences.

Math module

A module is a file that contains a collection of related functions, constants, or variables.


Python has many built-in modules that help perform specific tasks — like mathematics, file
handling, system operations, etc.

Some commonly used modules:

● math → for mathematical operations


● sys → system-related functions
● os → operating system tasks

Step 1: Import the module

import math

Step 2: Access functions or constants using math.

print(math.sqrt(25)) # Square root: 5.0

print(math.sin(0)) # Sine of 0: 0.0

print(math.exp(1)) # e^1: 2.718...


print(math.pi) # π: 3.14159...

print(math.e) # e: 2.71828…

Category Function / Description Example


Constant

Basic Math math.ceil(x) Returns the smallest math.ceil(4.2) → 5


integer ≥ x

math.floor(x) Returns the largest math.floor(4.8) → 4


integer ≤ x

math.trunc(x) Truncates x to an math.trunc(-4.8) →


integer -4

math.fabs(x) Absolute value of x math.fabs(-3.5) →


(float) 3.5

Power & Roots math.sqrt(x) Square root of x math.sqrt(16) → 4.0

math.pow(x, x raised to the power y math.pow(2, 3) →


y) 8.0
math.exp(x) e raised to the power x math.exp(1) →
2.718...

math.log(x) Natural log of x math.log(10)

math.log10(x) Base-10 logarithm of x math.log10(100) → 2

math.log2(x) Base-2 logarithm of x math.log2(8) → 3

Trigonometry math.sin(x) Sine of x (in radians) math.sin(math.pi/2)


→ 1

math.cos(x) Cosine of x math.cos(0) → 1

math.tan(x) Tangent of x math.tan(0) → 0

math.degrees( Converts radians to math.degrees(math.p


x) degrees i) → 180

math.radians( Converts degrees to math.radians(180) →


x) radians π

Constants math.pi π (pi) ≈ 3.14159...


math.e e ≈ 2.71828...

math.tau τ = 2π ≈ 6.28318...

Combinatorics math.factoria Returns x! (x factorial) math.factorial(5) →


l(x) 120

math.comb(n, Number of ways to math.comb(5, 2) →


k) choose k items from n 10

math.perm(n, Number of ways to math.perm(5, 2) →


k) arrange k items from n 20

Special Functions math.gcd(a, Greatest common math.gcd(12, 15) →


b) divisor 3

math.lcm(a, Least common math.lcm(4, 6) → 12


b) multiple (Python 3.9+)

math.isclose( Checks if a ≈ b math.isclose(0.1+


a, b) (tolerance-based) 0.2, 0.3)

math.isfinite Checks if x is a finite math.isfinite(10.


(x) number 5)
math.isnan(x) Checks if x is NaN math.isnan(float(
'nan'))

math.isinf(x) Checks if x is infinity math.isinf(float(


'inf'))

Operators in Python

Python language supports the following types of operators.

• Arithmetic Operators

• Comparison (Relational) Operators

• Assignment Operators

• Logical Operators

• Bitwise Operators

• Membership Operators

• Identity Operators

● Assignment Operators : Assign values to variables.


Example:
a, b, c = 1, 2.5, "ram"
● Compound Assignment Operators:

Expression Equivalent

a += b a = a + b
a -= b a = a - b

a *= b a = a * b

a /= b a = a / b

a //= b a = a // b

a %= b a = a % b

● Comparison(Relational) Operator : Results in True or False.

==, !=, <, <=, >, >=

Chaining is allowed:
x < y <= z → True only if both conditions are True.

● Logical Operators
a. Logical AND
b. Logical OR
c. Logical NOT

● Bitwise operators:Bitwise operators operate on the binary (bit-level)


representation of numbers. They compare or manipulate individual bits of
integers.Each number is converted into its binary form, and operations are done bit
by bit starting from the least significant bit (LSB) on the right to the most significant
bit (MSB) on the left.
○ One’s complement operator:One’s complement, denoted by the symbol
~, inverts each bit in the binary representation of an integer—changing all
0s to 1s and all 1s to 0s. It operates only on integer-type operands and
effectively returns -(n + 1) for any integer n.

Example:

>>> ~98

Output: -99

>>> ~102

Output :-103

Explanation:Let us understand the results obtained. Take 102. Its


binary is 01100110.

Flipping the bits, yields 10011001 = -103.

○ Logical bitwise operators :There are three logical bitwise operators in


Python: bitwise AND (&), bitwise OR (|), and bitwise exclusive OR (^).
These operators work on two integer-type operands by comparing each pair
of corresponding bits—& returns 1 only if both bits are 1, | returns 1 if at
least one bit is 1, and ^ returns 1 only if the bits are different.
Example 1:

>>> a=20

>>> b=108

>>> a&b

Output : 4

Example 2:

>>> a|b

124

>>> a^b

Output :120

Explanation:
○ Bitwise shift operators:The two bitwise shift operators are left shift (<<)
and right shift (>>). The expression x << n shifts the bits of x to the left
by n positions, filling the rightmost bits with zeros. Similarly, x >> n
shifts the bits of x to the right by n positions, filling the leftmost bits with
zeros.

Example 1:

>>> 120>>2

Output: 30

Example 2:

>>> 10<<3

Output: 80

Explanation:
● Membership Operators : These operators test for the membership of a data item in a
sequence, such as a string.

Two membership operators are used in Python.

• in – Evaluates to True if it finds the item in the specified sequence and False otherwise.

• not in – Evaluates to True if it does not find the item in the specified sequence and False
otherwise

Example :

>>> 'A' in 'ASCII'

Output: True

>>> 'a' in 'ASCII'

Output:False

>>> 'a' not in 'ASCII'

Output: True
MODULE 2

ALGORITHM AND PSEUDOCODE REPRESENTATION

An algorithm is a step-by-step, finite sequence of instructions for solving a particular problem or


performing a computation. It uses plain natural language (like English) to describe what needs to
be done—such as “read inputs,” “compute this,” and “display the result”—without specifying any
programming language syntax or exact commands In essence, it's a conceptual blueprint outlining
the logic and order of operations, entirely independent of implementation details.

Pseudocode is a more structured, semi-formal way to write algorithms. It blends natural language
with elements resembling programming constructs—like assignment symbols (← or :=), loops
(WHILE, FOR), and conditionals (IF–THEN–ELSE)—to express logic clearly, without adhering
to any specific programming language's syntax . By using both descriptive phrases and
programming-like notation, pseudocode helps developers and readers understand the intended
flow and operations just before actual coding begins. It's especially useful for planning and
documentation because it's easy to translate into real code.

Example: evaluating d = a + b × c

Algorithm (plain English):

1. Start.

2. Read values for variables a, b, and c.

3. Multiply b by c.

4. Add the product to a.

5. Store the result in d.

6. Display d.

7. End.

Pseudocode:

START
INPUT a, b, c

temp ← b * c

d ← a + temp

PRINT d

STOP

Reasons for using pseudocode

1. Ease of understanding: Since the pseudocode is programming language independent,


novice developers can also understand it very easily.
2. Focus on logic: A pseudocode allows you to focus on the algorithm’s logic without
bothering about the syntax of a specific programming language.
3. More legible: Combining programming constructs with English phrases makes
pseudocode more legible and conveys the logic precisely.
4. Consistent: As the constructs used in pseudocode are standardized, it is useful in sharing
ideas among developers from various domains.
5. Easy translation to a program: Using programming constructs makes mapping the
pseudocode to a program straightforward.
6. Identification of flaws: A pseudocode helps identify flaws in the solution logic before
implementation.

Constructs of a pseudocode

A good pseudocode should follow the structured programming approach. Structured coding aims
to improve the readability of pseudocode by ensuring that the execution sequence follows the order
in which the code is written. Such a code is said to have a linear flow of control. Sequencing,
selection, and repetition (loop) are three programming constructs that allow for linear control flow.
These are also known as single entry – single exit constructs.

1. Sequence

Syntax:

S1

S2

Sn
Straight-line execution—each step runs in order. No skipping or repetition.

2. Decision / Selection

2.1 If (single-case)

Syntax:

IF (condition)

true_instructions

ENDIF

Executes only if the condition is true; otherwise skips the block.

2.2 If–Else (two-way)

Syntax:

IF (condition)

true_instructions

ELSE

false_instructions

ENDIF

Chooses exactly one of two blocks based on the condition.

2.3 If–Else If–Else (multi-way)

Syntax:

IF (cond1)

block1

ELSE IF (cond2)
block2

ELSE

block_default

ENDIF

Checks conditions in order, executes the first matching block (or else case).

3. Case / Switch (multi-way alternative)

Syntax:

CASE OF (expression)

CASE value1:

block1

BREAK

CASE value2:

block2

BREAK

DEFAULT:

block_default

ENDCASE

Tests one expression against multiple values. If BREAK is omitted, execution falls through to
subsequent blocks.

4. Loops / Repetition

4.1 While (entry-controlled)

Syntax:
WHILE (condition)

loop_instructions

ENDWHILE

Continues as long as condition is true; may run zero times.

4.2 Repeat–Until (exit-controlled)

Syntax:

REPEAT

loop_instructions

UNTIL (condition)

Guarantees at least one execution; stops once condition becomes true.

4.3 For (definite iteration)

Syntax Variants:

FOR var = begin TO end

instructions

ENDFOR

FOR var = begin DOWNTO end

instructions

ENDFOR

FOR var = begin TO end BY step


instructions

ENDFOR

Highly structured: known number of iterations, optional stepping direction/value.

These six fundamental building blocks — Sequence, If, If-Else, If-Else If-Else, Case, and Loops
(While/Repeat/For) — form the backbone of most pseudocode styles.

Flowcharts

A flowchart is a visual diagram that represents an algorithm or process. It uses different shapes to
indicate stages like start/end, decisions, processing, and I/O, connected by arrows showing the
flow of control,

● They translate algorithms into visual form, enhancing clarity and facilitating
communication across technical and non-technical audiences
● Help detect logical errors or inefficiencies before coding begins.
● Useful for documentation, process modeling, or quality control

Advantages:

● Enhanced Clarity & Communication


● Efficient Problem-Solving and Debugging
● Effective Documentation
● Modular Analysis

Symbol:

● Always start with a Start terminal and end with an End terminal.
● Maintain clear direction (typically top-to-bottom, left-to-right).
● Use one arrow for most blocks; exception: decision blocks will have two or more exit
arrows.
● Label arrowed paths clearly (e.g., "Yes"/"No").
● Avoid crossing lines—use connectors when necessary for clarity.

Problem 1: To find sum of two numbers

Algorithm:
1. Start
2. Input the first number A
3. Input the second number B
4. Add A and B SUM = A + B
5. Display the result (SUM)
6. Stop

Flowchart:

Pseudocode:

BEGIN

PRINT "Enter first number:"

READ A

PRINT "Enter second number:"

READ B

SUM = A + B
DISPLAY "Sum is:", SUM

END

Problem 2: To find sum of three numbers

Algorithm:

1.Start

2. Input the numbers A,B,C

3.Add SUM = A + B+C

4. Display the result (SUM)

5.Stop

Flowchart:
Pseudocode:

BEGIN

PRINT "Enter numbers:"

READ A,B,C

SUM = A + B+C

DISPLAY "Sum is:", SUM

END

Problem 3: To find the simple interest

Algorithm:

1. Start

2. Read principal, rate, years

3.Compute SI = (principal × rate × years) / 100

4. Print SI

5. Stop

Flowchart:
Pseudocode:

Start

Input principal

Input rate

Input years

SI = (principal * rate * years) / 100

Print "Simple Interest is: ", SI

Stop

Problem 4: To determine the largest of two numbers

Algorithm:

1. Start

2. Input two numbers a and b


3. Check if a > b
4. If true, then set large = a
5. Else, set large = b

6. Print the value of large


7.Stop

Flowchart:
Pseudocode:

Start

Read(a, b)

if (a > b)

large = a

Else

large = b

endif

Print(large)

Stop

Problem 5: To determine the smallest of three numbers

Pseudocode:

1. Start

2. Read(a, b, c)

3. if (a < b)

4. small = a

5. Else

6. small = b

7. endif

8. if (c < small)

9. small = c

10. endif

11. Print(small)
12. Stop

Flowchart:

Problem 6:To determine the entry-ticket fare in a zoo based on age as follows:

Age Fare

< 10 7
>= 10 and < 60 10

>= 60 5

Problem 7: To print the colour based on a code value as follows:

Grade Message

R Red

G Green

B Blue

Any other value Wrong code


Problem 8:To print the numbers from 1 to 50 in descending order.
Problem 9:To find the factorial of a number.
Problem 10: To determine the largest of n numbers.
Problem 11:To determine the average age of students in a class. The user will stop giving the
input by giving the age as 0.

Exercise 1 :Evaluate an expression: d = a + b * c

d = a + (b * c)

Eg:- a=2 b=3 and c=4

d=2+3*4

d = 2 + 12
d = 14

Exercise 2 :Evaluate an expression: d = a + b ** c

d=a+b^c

Eg:- a=2 b=3 and c=4

d=2+3^4

d=2+81

d=83

Exercise 3:Evaluate an expression: d = a + b *** c

Invalid Expression

Exercise 4:Evaluate an expression: d = a + b * c/d-(f+g)

a=1 b=2 c=6 d=3 f=4 g=2

d=1+2*6/3-(4-2)

d=1+12/3-2

d=1+4-2

d=5-2

d=3

RAPTOR

RAPTOR (Rapid Algorithmic Prototyping Tool for Ordered Reasoning) is a flowchart-based


programming environment designed to help students visualize algorithms clearly. It emphasizes
logic and flow over complex syntax.

● Visual Programming: Algorithms are created and executed as flowcharts.


● Simplified Syntax: Minimal textual coding—perfect for beginners.
● Step-by-step Execution: Watch your algorithm run visually, making it easier to understand
how logic flows.
● Learning Support: Especially useful in early programming education as it removes the
distraction of language-specific syntax.
● Debugging Made Easy: You can trace the path of execution directly on the flowchart.
● Helps students understand algorithmic thinking.
● Makes programming less intimidating.
● Encourages logical problem-solving skills.
● Proven to increase student success in algorithm development compared to traditional
coding or hand-drawn flowcharts.
MODULE 3

SELECTION STATEMENTS USING PYTHON


This section explores several types of selection statements that allow the computer to make
choices.
One-way selection statement
First, we discuss the if statement, also known as conditional execution. The following example
tests whether a variable x is positive or not.
>>> x=10
>>> if x>0:
... print (x,"is positive")
...
10 is positive
It is possible to combine multiple conditions using logical operators. The following code snippet
tests whether a number x is a single-digit number or not.
>>> x=int(input("Enter a number"))
Enter a number7
>>> if x > 0 and x < 10:
... print(x,"is a positive single digit.")
...
7 is a positive single digit.
Python provides an alternative syntax for writing the condition in the above code that is similar to
mathematical notation:
>>> x=int(input("Enter a number"))
Enter a number8
>>> if 0<x<10:
... print(x,"is a positive single digit.")
...
8 is a positive single digit.

Two-way selection statement


The if-else statement, also known as alternative execution, is the most common type of selection
statement in Python. See the following example to check if a person is major or minor.
>>> age=12
>>> if age>=18:
... print("Major") ...
else:
... print("Minor")
...
Minor
Multi-way selection statement
Multi-way selection is achieved through the if-elif-else statement. elif is the abbreviation of “else
if”. The following code compares two variables and prints the relation between them.
>>> x=10
>>> y=5
>>> if x < y:
... print(x, "is less than", y)
... elif x > y:
... print(x, "is greater than", y)
... else:
... print(x, "and", y, "are equal")
...
10 is greater than 5
The multi-way if statement is called chained conditional execution

REPETITION STATEMENTS OR LOOPS


Python supports two types of loops – those that repeat an action a fixed number of times (definite
iteration) and those that act until a condition becomes false (conditional iteration).
Definite iteration: The for loop
We now examine Python’s for loop, the control statement supporting definite iteration. We use for
in association with the range() function that dictates the number of iterations. To be precise,
range(k) when used with for causes the loop to iterate k times. See the example below that prints
“Hello” 5 times.
>>> for i in range(5):
... print("Hello")
...
Hello
Hello
Hello
Hello
Hello
range(k) starts counting from 0, incrementing after each iteration, and stops on reaching k − 1.
Thus to print numbers from 0 to 4, you write:
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
You are reminded that range(5) counts from 0 to 4, not 5. To get the numbers on the same line you
need to write print(count,end=" "). By using end =" ", the Python interpreter will append
whitespace following the count value, instead of the default newline character ('\n') See below:
>>> for i in range(5):
... print(i,end=" ")
...
01234
To count from an explicit lower bound, you need to include it as part of the range function. See
the example below that prints the first 10 natural numbers:
>>> for count in range(1,11):
... print(count,end=" ")
...
1 2 3 4 5 6 7 8 9 10

The for loops we have seen till now count through consecutive numbers in each iteration. Python
provides a third variant of the for loop that allows to count in a non-consecutive fashion. For this,
you need to explicitly mention the step size in the range function. The following code prints the
even numbers between 4 and 20 (both inclusive).
>>> for i in range(4,21,2):
... print(i,end=" ")
...
4 6 8 10 12 14 16 18 20

When the step size is negative, the loop variable is decremented by that amount in each iteration.
The following code displays numbers from 10 down to 1.
>>> for count in range(10,0,-1):
... print(count,end=" ")
...
10 9 8 7 6 5 4 3 2 1
Notice above that, to count from 10 down to 1, we write for count in range(10,0,-1) and not for
count in range(10,1,-1). Thus, to count from a down to b, we write for count in range(a,b-1,s) with
a > b and s < 0.

Conditional Iteration: The while loop


Conditional iteration requires that a condition be tested within the loop todetermine whether the
loop should continue. Python’s while loop is tailormade for this type of control logic. Here is the
code to print the first 10 natural numbers, but this time with while.
>>> i=1
>>> while i<=10:
... print(i,end=" ")
... i=i+1
...
1 2 3 4 5 6 7 8 9 10
Nested loops
It is possible to have a loop inside another loop. We can have a for loop inside a while loop or
inside another for loop. The same is possible for while loops too. The enclosing loop is called the
outer loop, and the other loop is called the inner loop. The inner loop will be executed once for
each iteration of the outer loop: Consider the following code:
>>> for i in range(1,5): #This is the outer loop
... for j in range(1,5): #This is the inner loop
... print(j, end=" ")
... print()
...
1234
1234
1234
1234
The loop variable i varies from 1 to 4 (not 5). For each value of i, the variable j varies from 1 to 4.
The statement print(j, end=" ") prints the j values on a single line, and the statement print() takes
the control to the next line after every iteration of the outer loop.

SEQUENCE DATA TYPES IN PYTHON


Lists
A list is an ordered set of data values. The values that make up a list are called its elements or
items. The logical structure of a list is similar to that of a string. Each item in the list has a unique
index that specifies its position and the items are ordered by their positions. Unlike strings, where
each element is a character, the items in a list can be of different data types.
Creating lists
To create a new list; you simply enclose the elements in a pair of square brackets ([ ]). Following
are some examples:
>>> newlist=[10,"hi",3.14]
>>> print(newlist)
[10, 'hi', 3.14]
>>> mynest=[2,4,[1,3]]
>>> print(mynest)
[2, 4, [1, 3]]
As in mynest above, a list can have another list as its element. Such a list is called nested list. The
list [1,3] which is an element of mynest is called a member list. A list that contains no elements is
called an empty list, denoted as [ ].

Lists of integers can also be built using the range() and list() functions. See some examples now:
>>> digits=list(range(10))
>>> print(digits)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Once a list is created, you can determine its length (number of elements in the list) using the len()
method. In the case of a nested list, the member list will be counted as a single element.
>>> languages=["C","Java","Python","C++"]
>>> print(len(languages))
4
Accessing list elements and traversal
List indices work the same way as string indices. Thus, you can use the subscript operator to access
the list elements. See some examples:
>>> newnest=[10, 20, [25, 30], 40]
>>> print(newnest[0])
10
>>> print(newnest[2])
[25, 30]
>>> print(newnest[-1])
40
>>> print(newnest[4])
Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list index out of
range
>>> print(newnest[5-4])
20
>>> print(newnest[1.0])
Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: list indices must
be integers or slices, not float
>>> print(newnest[-5])
Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list index out of
range
You could also use an expression as an index in the subscript operator, as in newnest[5-4] in the
example above. The interpreter will evaluate the expression to determine the index. Printing the
list by specifying its name in the print method displays the list elements enclosed in a pair of
brackets. To display the entire list without the brackets themselves, you should traverse the list
printing the elements one by one. See below:
>>> prime=[2,3,5,7,11]
>>> for p in prime:
... print(p,end=" ")
...
2 3 5 7 11
List Comprehension
Creating a new list from an existing list is called list comprehension. Assume you have a list of
numbers from which you want to create another list consisting only of odd numbers in the original
list. This can be done by using a for loop to traverse the original list and copying the odd numbers
to a second list.
>>> numbers = [1, 28, 73, 4, 100, 358, 75, 208]
>>> odd = [num for num in numbers if num%2 != 0]
>>> print(odd)
[1, 73, 75]
The statement
odd = [num for num in numbers if num%2 != 0]
essentially directs the interpreter to iterate through the list numbers and copy all odd numbers to
another list odd.
Suppose, now you need to create a list of two-digit even numbers, you could use comprehension:
>>> even2dig = [num for num in range(10,100) if num%2 == 0]
>>> print(even2dig)
[10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38,
40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68,
70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98]
Here, range(10,100) generates two digit numbers and the condition if num%2 == 0 makes sure
that only the even numbers are included in the constructed list. Finally, assume you want to replace
all multiples of 5 (less than 30) with -1, you just resort to comprehension:
>>> nomul5 = [num if num%5 != 0 else -1 for num in range(20)]
>>> print(nomul5)
[-1, 1, 2, 3, 4, -1, 6, 7, 8, 9, -1, 11, 12, 13, 14, -1, 16,
17, 18, 19]
The interpreter generates two-digit numbers less than 20 and then puts the generated number into
the constructed list if it is not a multiple of 5, otherwise puts a -1 into the list.
List operations
The + operator concatenates lists:
>>> prime=[2,3,5,7]
>>> composite=[4,6,8,10]
>>> numbers=prime+composite
>>> print(numbers)
[2, 3, 5, 7, 4, 6, 8, 10]
The * operator repeats a list a given number of times:
>>> binary=[0,1]
>>> bytesequence=binary*4
>>> print(bytesequence)
[0, 1, 0, 1, 0, 1, 0, 1]
Equality operator works well on lists. It checks if two lists have the same elements. See an
example:
>>> even=[2,4,6,8]
>>> mul2=[2,4,6,8]
>>> print(even==mul2)
True
>>> composite=[4,6,8]
>>> print(even==composite)
False

Other relational operators also work with lists. Consider:


>>> prime=[2,3,5]
>>> even=[2,4,6]
>>> print(prime>even)
False
Python starts by comparing the first element from each list. If they are equal, it goes on to the next
element, and so on, until it finds the first pair of elements that are different and determines the
relation between them. In the above example, prime[0] == even[0]. Next, prime[1] and even[1]
are compared. Thus the resulting relation is even is thus False. Note that once the result is
determined, the subsequent elements are skipped.
Membership operators can be applied to a list as well. See below:
>>> even=[2,4,6,8]
>>> composite=[4,6,8]
>>> print(2 in even)
True
>>> print(2 in composite)
False
>>> print(3 not in composite)
True
List slices
The slice operator on a list follows the same rules as applied to strings.
>>>sequence=["strings","lists","tuples","bytearrays","bytesequences","range objects"]
>>> print(sequence[:])
['strings', 'lists', 'tuples', 'bytearrays', 'bytesequences','range objects']
>>> print(sequence[1:4])
['lists', 'tuples', 'bytearrays']
>>> print(sequence[:3])
['strings', 'lists', 'tuples']
>>> print(sequence[3:])
['bytearrays', 'bytesequences', 'range objects']

List mutations
Unlike strings, lists are mutable. In other words, a list is updatable – elements can be inserted,
removed, or replaced. You use the subscript operator to replace an element at a given position:
>>> even=[2,4,5,8]
>>> even[2]=6
>>> print(even)
[2, 4, 6, 8]
You can also replace a single list item with a new list.
>>> even=[2,4,6,8]
>>> even[3]=[8,10]
>>> print(even)
[2, 4, 6, [8, 10]]
Slice operator and mutations
The slice operator is a nice tool to mutate lists in terms of replacing, inserting, or removing list
elements.
Replacing elements
You can use the slice operator to replace a single element or multiple elements in a list. See an
example below:
>>> composite=[13,17,19,23,25,27,37,41]
>>> composite[4:6]=[29,31]
>>> print(composite)
[13, 17, 19, 23, 29, 31, 37, 41]
>>> odd=[1,3,5,8]
>>> odd[3:4]=[7]
>>> print(odd)
[1, 3, 5, 7]
Inserting elements
The slice operator is useful for inserting new elements into a list at the desired location:
>>> prime=[2,3,5,7,13,17,19]
>>> prime[4:4]=[11]
>>> print(prime)
[2, 3, 5, 7, 11, 13, 17, 19]
>>> composite=[2,10,12]
>>> composite[1:1]=[4,6,8]
>>> print(composite)
[2, 4, 6, 8, 10, 12]
Removing elements
The slice operator can also be used to remove elements from a list by assigning the empty list to
them. Examples follow:
>>> composite=[3,5,7,9,11]
>>> composite[3:4]=[]
>>> print(composite)
[3, 5, 7, 11]
>>> composite=[29,31,33,35,37]
>>> composite[2:4]=[]
>>> print(composite)
[29, 31, 37]
Using del keyword for deletion
Assigning a list element to an empty list for deletion is cumbersome. As an alternative, Python
provides the del keyword exclusively for deletion
>>> composite=[3,5,7,9,11]
>>> del composite[3]
>>> print(composite)
[3, 5, 7, 11]
>>> composite=[29,31,33,35,37]
>>> del composite[2:4]
>>> print(composite)
[29, 31, 37]
>>> del composite[6]
Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list assignment
index out of range
As illustrated above, del causes a runtime error if the index is out of range
List methods
The list type includes several methods for inserting and removing elements.
Lists and functions
A function can be made to return multiple values using lists. This is done by defining the function
to return a list of values instead of a single value as in the normal case. The following code
illustrates this:
>>> def printTop3(list):
... list.sort()
... list.reverse()
... top3=[list[i] for i in range(3)]
... return(top3)
...
>>> mylist=[23,1,78,50,100,-5]
>>> printTop3(mylist)
[100, 78, 50]
Strings and lists
To construct a list out of the characters from a string, you can use list() method as follows:
>>> str="two words"
>>> strlist=list(str)
>>> print(strlist)
['t', 'w', 'o', ' ', 'w', 'o', 'r', 'd', 's']
If you want to split a multi-word string into its constituent words and construct a list out of those
words, you should use the split() method:
>>> str="two words"
>>> strwords=str.split()
>>> print(strwords)
['two', 'words']
In the above example, the white space where you split the string is known as the delimiter. If you
want to specify a different delimiter, you can pass that character (or even a string) as an argument
to the split() method. The following example illustrates this:
>>> str="two words"
>>> wsplit=str.split('w')
>>> print(wsplit)
['t', 'o ', 'ords']
>>> wosplit=str.split('wo')
>>> print(wosplit)
['t', ' ', 'rds']
The join() method works in the opposite sense of split(). It concatenates a list of strings by inserting
a “separator” between them. The following code uses a blank space as the separator.
>>> sep=" "
>>> wordlist=["three", "word", "string"]
>>> str=sep.join(wordlist)
>>> print(str)
three word string
The following code uses a hyphen as a separator.
>>> sep="-"
>>> wordlist=["three", "word", "string"]
>>> str=sep.join(wordlist)
>>> print(str)
three-word-string
List of lists
All the elements in a list may be again lists. Such a list is called list of lists. Here is one example:
lol=[[1], [15, 9], [108, 778]]
Since the constituent elements themselves are lists, to access the items in these constituents, you
need to use the subscript operator twice as illustrated below:
>>> lol=[[1], [15, 9], [108, 778]]
>>> celt=lol[1]
>>> elt=celt[1]
>>> print(elt)
9
A list of lists is often used to represent matrices. For example, the matrix:
123
456
789
might be represented as: matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
To print a row, you use the subscript operator:
>>> print(matrix[2])
[7, 8, 9]
To extract an individual element from the matrix, you should use the double index form:
>>> print(matrix[2][1])
8
List aliasing and cloning
If you assign a list variable to another, both variables refer to the same list. Because the same list
has two different names now, we say that the list is aliased. Changes made with one list affect the
other. See below:
>>> list = [1, 2, 3]
>>> alias=list
>>> print(alias)
[1, 2, 3]
>>> alias[1]=4
>>> print(list)
[1, 4, 3]
If you want to modify a list and also keep a copy of the original, you need to use a technique called
cloning. The easiest way to clone a list is to use the slice operator. See the example below:
>>> list = [1, 2, 3]
>>> clone=list[:]
>>> print(clone)
[1, 2, 3]
>>> clone[1]=4
>>> print(list)
[1, 2, 3]
>>> print(clone)
[1, 4, 3]
>>> list[1]=5
>>> print(clone)
[1, 4, 3]
>>> print(list)
[1, 5, 3]
Taking a slice of any list creates a new list. Thus you are free to make changes to the new list
without modifying the original or vice versa.
Equality of lists
A list and its alias refer to the same list. On the other hand, a list and its clone refer to two different
lists although both have the same elements. The equality between a list and its alias is object
identity and the equality between a list and its clone is known as structural equivalence. The is
operator can be used to test for object identity. It returns True if the two operands refer to the same
list, and it returns False if the operands refer to distinct lists (even if they are structurally
equivalent). The following code illustrates this: Consider the following code:
>>> mylist=[1,2,3]
>>> alias=mylist
>>> clone=mylist[:]
>>> print(alias is list)
True
>>> print(clone is list)
False
Tuples
A tuple is a sequence of values that resembles a list, except that a tuple is immutable. You create
a tuple by enclosing its elements in parentheses instead of square brackets. The elements are to be
separated by commas. Following are some examples:
t = ('a', 'b', 'c', 'd', 'e')
s = ('g', 2, 7, 8.978)
To create a tuple with a single element, we have to include a comma at the end, even though there
is only one value. Without the comma, Python treats the element as a string in parentheses.
>>> t1=('a',)
>>> t2=('a')
>>> print(type(t1))
<class 'tuple'>
>>> print(type(t2))
<class 'str'>
Tuple creation
As discussed above, a tuple can be created by enclosing its elements in parentheses. Another way
to create a tuple is to use the tuple() function.
>>> t1=tuple("string")
>>> print(t1)
('s', 't', 'r', 'i', 'n', 'g')
>>> t2=tuple([1,2,3])
>>> print(t2)
(1, 2, 3)
>>> t3=tuple()
>>> print(t3)
()
>>> t4=tuple([4])
>>> print(t4)
(4,)
Tuple operations
Most of the operators and functions used with lists can be used similarly with tuples. A few
examples follow:
>>> lan=tuple("python")
>>> print(lan[0])
p
>>> print(lan[1:4])
('y', 't', 'h')
But, unlike lists, tuples are immutable – you are bound to get error when you try to modify the
tuple contents. See below:
>>> lan=tuple("python")
>>> lan[0]='P
Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object
does not support item assignment
Python provides a quick way to swap two variables a and b without using any third temporary
variable:
>>> a,b=5,10
>>> print(a,b)
5 10
>>> a,b=b,a
>>> print(a,b)
10 5
This is known as tuple assignment.
Sets
A set is a collection of items just as tuples or lists but unlike the other two, a set is unordered; the
items in a set do not have a defined order.
Creating a set and accessing its elements
A set is created by enclosing the items in a pair of braces.
>>> directions={'east','west','south','north'}
>>> print(directions)
{'east', 'south', 'north', 'west'}
You can also use the set method to create a set from a list or a tuple as follows:
>>> numbers=set([1,2,3,4])
>>> print(numbers)
{1, 2, 3, 4}
>>> vowels=set(('a','e','i','o,','u'))
>>> print(vowels)
{'o', 'u', 'a', 'e', 'i'}
The items in a set aren’t associated with index or position. This means it is impossible to print an
arbitrary element of the set.
>>> vowels=set(('a','e','i','o,','u'))
>>> vowels[2]
Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'set' object is not
subscriptable
To traverse the set, use a for loop: The code
>>> for char in vowels:
... print(char,end=" ")
...
ouaei
The duplicates are automatically removed even if you supply non-unique values.
>>> fibonacci={0,1,1,2,3,5}
>>> print(fibonacci)
{0, 1, 2, 3, 5}
Adding and removing set elements
To add a single element to a set, you use the add() method:
>>> even={2,4,8,10}
>>> even.add(6)
>>> print(even)
{2, 4, 6, 8, 10}
To add multiple items to a set, the update() method is to be used:
>>> odd={1,3,7,9,13,15}
>>> odd.update({5,11})
>>> print(odd)
{1, 3, 5, 7, 9, 11, 13, 15}
To remove an item from a set, you should use the remove() method:
>>> primes={3,5,7,9,11}
>>> primes.remove(9)
>>> print(primes)
{3, 5, 7,11}
If the item to be removed is not present in the set, remove() will raise an error:
>>> primes={3,5,7,11}
>>> primes.remove(2)
Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 2
A second way to remove an item from a set is to use the discard() method which works the same
way as remove. The difference between these two methods is that discard() will not raise an error
if the item to be removed doesn’t exist in the set unlike remove().
>>> primes={3,5,7,9,11}
>>> primes.discard(9)
>>> print(primes)
{3, 5, 7, 11}
>>> primes.discard(2)
>>> print(primes)
{3, 5, 7, 11}
To remove all the set items at once, you can use the clear() method as illustrated below:
>>> primes={3,5,7,9,11}
>>> primes.clear()
>>> print(primes)
set()
The keyword del can be used to delete the set itself, that is, after the del operation, the set will not
exist anymore. Any subsequent access to the set will throw an error. See below:
>>> primes={3,5,7,9,11}
>>> del primes
>>> print(primes)
Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'primes'
is not defined
Sets and relational operators
The relational operators can be used to check the relationship (subset, superset, etc) between two
sets
>>> digits={0,1,2,3,4,5,6,7,8,9}
>>> natural={1,2,3,4,5,6,7,8,9,10}
>>> even={0,2,4,6,8}
>>> odd={1,3,5,7,9}
>>> prime={3,5,7}
>>> composite={4,6,8}
>>> print(even<natural)
False
>>> print(odd<digits)
True
>>> print(prime<=odd)
True
>>> print(digits>=even)
True
>>> print(composite==even)
False
>>> print(prime!=odd)
True
Mathematical set operations on Python sets
Python supports all the mathematical set operations (union, intersection, etc.).
>>> positive={1,2,3,4,5}
>>> negative={-5,-4,-3,-2,-1}
>>> numbers=positive.union(negative)
>>> print(numbers)
{1, 2, 3, 4, 5, -2, -5, -4, -3, -1}
>>> even={2,4,6,8,10}
>>> mul3={3,6,9,12}
>>> even={2,4,6,8,10,12}
>>> mul6=even.intersection(mul3)
>>> print(mul6)
{12, 6}
>>> mul15={0,15,30,45}
>>> mul5={0,5,10,15,20,25,30,25,40,45}
>>> mul5.intersection_update(mul15)
>>> print(mul5)
{0, 45, 30, 15}
>>> mul4={4,8,12,16,-4,-12,-20}
>>> mul8={8,16,-40,-120}
>>> mul4only=mul4.difference(mul8)
>>> print(mul4only)
{4, 12, -20, -12, -4}
11.4. DICTIONARIES 175
>>> mul4.difference_update(mul8)
>>> print(mul4)
{-4, 4, -12, -20, 12}
>>> numbers={-3,-2,-1,0,1,2,3}
>>> natural={1,2,3,4,5}
>>> integers=numbers.symmetric_difference(natural)
>>> print(integers)
{0, 4, 5, -1, -3, -2}
>>> numbers.symmetric_difference(natural)
>>> print(numbers)
{0, 4, 5, -1, -3, -2}
Dictionaries
A dictionary associates a set of keys with data values. For example, the words in a standard English
Dictionary comprise a set of keys, whereas their associated meanings are the data values. A Python
dictionary is written as a sequence of key/value pairs separated by commas and the entire sequence
is enclosed in a pair of braces. Each key is separated from its value by a colon (:). Such a list of
key-value pairs enclosed in a pair of braces is known as a dictionary literal. Following is an
example dictionary:
>>> stud = {'Name': 'Ram', 'Age': 18, 'Class': 'S1'}
>>> print(stud)
{'Name': 'Ram', 'Age': 18, 'Class': 'S1'}
Here Name, Age and Class are keys whereas Ram, 18 and S1 are the corresponding values. An
empty dictionary without any items is written as {}.
Dictionary operations
1. Accessing values
The subscript operator is used to obtain the value associated with a key.
>>> stud = {'Name': 'Ram', 'Age': 18, 'Class': 'S1'}
>>> print("I am",stud['Name'],"aged",stud['Age'],"and I am studying in",stud['Class'])
I am Ram aged 18 and I am studying in S1.
However, if the key is not present in the dictionary, Python raises an error.
2.Traversing a dictionary
A simple for loop can be used to traverse a dictionary as follows:
>>> stud = {'Name': 'Ram', 'Age': 18, 'Class': 'S1'}
>>> for key in stud:
... print(key,"-",stud[key])
...
Name - Ram
Age - 18
Class - S1
3.Inserting keys and updating key values
You should use the subscript operator to add a new key/value pair to the dictionary. The following
code illustrates this:
>>> stud['College']="Engineering college"
>>> print(stud)
{'Name': 'Ram', 'Age': 18, 'Class': 'S1', 'College':'Engineering college'}
The subscript is also used to replace the value of an existing key:
stud = {'Name': 'Ram', 'Age': 18, 'Class': 'S1'}
>>> stud['Age']=19
>>> print(stud)
{'Name': 'Ram', 'Age': 19, 'Class': 'S1'}
4.Removing keys
To remove a key from a dictionary, use the del operator. The corresponding key-value pair will be
removed. This is illustrated below:
stud = {'Name': 'Ram', 'Age': 18, 'Class': 'S1'}
>>> del stud['Age']
>>> print(stud)
{'Name': 'Ram', 'Class': 'S1'}
5.Miscellaneous operations
The len() function returns the number of key-value pairs in a dictionary:
>>> stud = {'Name': 'Ram', 'Age': 18, 'Class': 'S1'}
>>> print(len(stud))
3
The in operator can be used to know if a key exists in the dictionary. See below:
>>> stud = {'Name': 'Ram', 'Age': 18, 'Class': 'S1'}
>>> print('Age' in stud)
True
>>> print('College' in stud)
False
Dictionary methods

Dictionary aliasing and copying


Assigning a dictionary literal to another creates an alias of the original dictionary. In this case, the
changes made to one will affect the other. If you want to modify a dictionary and keep a copy of
the original, you need to use the copy() method.
>>> directions={'N':'North','E':'East','S':'South','W':'West'}
>>> alias=directions
>>> copy=directions.copy()
>>> copy['S']='Sit down students!'
>>> print(copy)
{'N': 'North', 'E': 'East', 'S': 'Sit down students!', 'W':'West'}
>>> print(directions)
{'N': 'North', 'E': 'East', 'S': 'South', 'W': 'West'}
>>> alias['S']='Sit down students!'
>>> print(directions)
{'N': 'North', 'E': 'East', 'S': 'Sit down students!', 'W':'West'}
>>> print(alias)
{'N': 'North', 'E': 'East', 'S': 'Sit down students!', 'W':'West'}
CREATING AND USING ARRAYS IN PYTHON (USING NUMPY LIBRARY)
NumPy stands for Numerical Python. This package also provides extensive support for arrays. An
array is a homogeneous collection of data items, unlike lists and tuples that are heterogeneous. As
always, if you want to use the components in NumPy, you should first import the package. NumPy
is usually imported with the np alias name:
import numpy as np
Now the NumPy package can be referred to as np instead of numpy.
Creating arrays
To create an array, you use the array() method of numpy package as shown below:
>>> import numpy as np
>>> arr = np.array([1, 2, 3, 4, 5, 6])
>>> print(arr)
[1 2 3 4 5 6]
You can also create matrices (two-dimensional arrays) with array() method:
>>> mat = np.array([[1, 2, 3], [4, 5, 6]])
>>> print(mat)
[[1 2 3]
[4 5 6]]
To know the dimensions of an array, you use the ndim attribute in NumPy. See below:
>>> arr = np.array([1, 2, 3, 4, 5])
>>> print(arr.ndim)
1
To know the size across each dimension, you use the shape attribute:
>>> arr = np.array([1, 2, 3, 4, 5])
>>> mat = np.array([[1, 2, 3], [4, 5, 6]])
>>> print(mat.shape)
(2, 3)
>>> print(arr.shape)
(6,)
The first output (2, 3) means that the array mat has 2 elements in the first dimension (2 rows) and
3 elements in the second dimension (3 columns). The second output (6,) means that the array arr
has 6 elements in the first dimension and no elements in the second dimension).
Accessing array elements
Indexing and slicing work the same way with arrays as with other sequences like lists, and tuples.
Some examples follow:
>>> arr = np.array([1,2,3,4,5,6])
>>> mat = np.array([[1, 2, 3], [4, 5, 6]])
>>> print(arr[3])
4
>>> print(arr[2:5])
[3 4 5]
>>> print(mat[1])
[4 5 6]
>>> print(mat[0,2])
3
>>> print(mat[0][2])
3
>>> print(arr[-2])
5
>>> print(mat[-1,2])
6
>>> print(mat[-2,-3])
1
>>> print(arr[-2:])
[5 6]
>>> print(arr[1:5:2])
[2 4]
>>> print(arr[:5:2])
[1 3 5]
>>> print(mat[:2:2])
[[1 2 3]]
for loop is used to traverse the array:
>>> arr = np.array([1,2,3,4,5,6])
>>> mat = np.array([[1, 2, 3], [4, 5, 6]])
>>> for elt in arr:
... print(elt,end=" ")
...
123456
>>> for row in mat:
... print(row)
...
[1 2 3]
[4 5 6]
>>> for row in mat:
... for elt in row:
... print(elt,end=" ")
...
123456
>>> for row in mat:
... for elt in row:
... print(elt,end=" ")
... print("")
...
123
456
Changing array dimensions
Changing the array dimensions means to increase or decrease the number of dimensions. For this,
you use the reshape() method. See some illustrations:
>>> arr = np.array([1,2,3,4,5,6])
>>> mat = np.array([[1, 2, 3], [4, 5, 6]])
>>> mat.reshape(1,6)
array([[1, 2, 3, 4, 5, 6]])
>>> arr.reshape(3,2)
array([[1, 2],
[3, 4],
[5, 6]])
When you convert a one-dimensional array into a two-dimensional array, there should be enough
number of elements to fill all the rows. Otherwise, you will end up with an error.
>>> arr=np.array([1,2,3,4,5])
>>> arr.reshape(2,3)
Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: cannot reshape
array of size 5 into shape (2,3)
As the error itself mentions, you cannot convert a one-dimensional array with 5 elements to a two-
dimensional array of 6 elements (2 rows and 3 columns).
Matrix operations
NumPy has excellent support for doing most of the matrix operations. Some of the matrix
operations and their Python implementation are listed in Table.

>>> mat1=np.array([[1,2],[3,4]])
>>> mat2=np.array([[5,6],[7,8]])
>>> print(np.add(mat1,mat2))
[[ 6 8]
[10 12]]
>>> print(np.subtract(mat1,mat2))
[[-4 -4]
[-4 -4]]
>>> print(np.matmul(mat1,mat2))
[[19 22]
[43 50]]
>>> print(mat1.T)
[[1 3]
[2 4]]
>>> print(mat2.T)
[[5 7]
[6 8]]
PROBLEM DECOMPOSITION
In the example of traveling from Trivandrum to Kashmir, breaking down the journey into smaller
segments helped you come up with an effective plan for the completion of the trip. This is the key
to solving complex problems. When the problem to be solved is too complex to manage, break it
into manageable parts known as sub-problems. This process is known as problem decomposition.
Here are the steps in solving a problem using the decomposition approach:
1. Understand the problem: Develop a thorough understanding of the problem.
2. Identify the sub-problems: Decompose the problems into smaller parts.
3. Solving the sub-problems: Once decomposition is done, you proceed to solve the
individual sub-problems. You may have to decide upon the order in which the various sub-
problems are to be solved.
4. Combine the solution: Once all the sub-problems have been solved, you should combine
all those solutions to form the solution for the original problem.
5. Test the combined solution: Finally you ensure that the combined solution indeed solves
the problem effectively.
MODULARIZATION
Solutions for complex real-world problems are nevertheless going to be simple. Essentially,
you should divide your proposed solution into subtasks. This is the idea of decomposition.
Modularization takes it one step further by keeping the subtasks as independent as possible.
These subtasks are known as modules. A module is a named, self-contained block of
statements that carries out some specific, well-defined task.
Motivations for modularization
The modular approach offers several advantages to program development:
1. Promotes re-use: If the system functionality is divided into modules, other systems can
reuse them. Thus, a programmer can build on what others have already done, instead of
starting from scratch. This also eliminates redundancy (duplication of code) to a great
extent.
2. Hides complexity: Since a module is self-contained, it does whatever it is intended to. If
you want the functionality provided by a module, you access the module and get it done,
without worrying about how the functionality is achieved. This mechanism is called
abstraction.
3. Supports division of labour: Ideally, each module achieves a single goal. A complex task
could be divided into simpler sub-tasks, each of which can be performed by individual
modules. This promotes parallel operation with different teams working on separate
modules, speeding up the entire process with improved collaboration among the teams.
4. Improves debugging and testing: Since modules have minimal interaction among
themselves, you can isolate a module and investigate it separately to pinpoint errors, if any.
Each of the modules can be individually tested for correctness and efficiency. This reduces
the risk when the modules are integrated to build the big picture.
5. Contributes to scalability: New modules can be added to an existing system to enhance
its functionality. This makes the system scalable without having to redesign the entire
thing.
6. Reduces development costs: The reuse of existing modules contributes to cost savings in
development. Also, the self-containment aspect of modules makes the system maintenance
cost-effective and productive.
7.
The anatomy of functions
A function should be “defined” before its first use. To define a function, you specify its name
and then write the logic for implementing its functionality. A function definition looks like
this:
def function-name(parameter-list):
statement-1
statement-2
.
.
statement-n
return-statement
Function definition begins with the keyword def followed by the function name. Then you
have the parameter list – a comma-separated list of arguments enclosed in a pair of parentheses.
This line of the function definition is called header.
• The block of statements that constitute the function logic (statement-1 to return-statement)
is called function body.
• The final statement return exits a function.
• The header has to end with a colon, and the body has to be indented. The parameters and
the return statement are optional, i.e., you could write a function without these.
Defining a function is not the end of the game. To use its functionality, you need to call it. The
function will not get executed unless it is called. You call a function by its name, passing
(supplying) the value for each parameter separated by commas and the entire list enclosed in
parentheses. If the function call does not require any arguments, an empty pair of parentheses
must follow the name of the function. The following example illustrates how a Python function
is defined and called:
>>> def printName(): #This is how you define a function
... name=input("Enter your name")
... print("Hi",name,"! Welcome to the world of functions!")
...
>>> printName() #This is how you call a function
Enter your nameSam
Hi Sam ! Welcome to the world of functions!
Arguments and return value
Arguments are the way you supply data to a function for processing. The function performs
the operations on the received data and produces the result which is given back through the
return statement. The value that is given back is known as the return value. Technically, you
say that a function “receives” one or more arguments and “returns” a result. The parameters in
the function definitions are called formal parameters, and those in the function call are called
actual parameters.
1. Function with no arguments and no return value

>>> def sum():


... a=int(input("Enter an integer"))
... b=int(input("Enter another integer"))
... s=a+b
... print("The sum of the entered integers is",s)
...
>>> sum()
Enter an integer6
Enter another integer8
The sum of the entered integers is 14
2. Function with arguments but no return value

>>> def sum(c,d):


... s=c+d
... print("The sum of the entered integers is",s)
...
>>> a=int(input("Enter an integer"))
Enter an integer3
>>> b=int(input("Enter another integer"))
Enter another integer5
>>> sum(a,b)
The sum of the entered integers is 8
3. Function with return value but no arguments

>>> def sum():


... a=int(input("Enter an integer"))
... b=int(input("Enter another integer"))
... s=a+b
... return s
...
>>> print("The sum of the entered integers is",sum())
Enter an integer4
Enter another integer7
The sum of the entered integers is 11
4. Function with arguments and return value

>>> def sum(c,d):


... s=c+d
... return s
...
>>> a=int(input("Enter an integer"))
Enter an integer10
>>> b=int(input("Enter another integer"))
Enter another integer15
>>> print("The sum of the entered integers is",sum(a,b))
The sum of the entered integers is 25
The arguments for a function can be specified in two ways:
1. Positional arguments
2. Named arguments (aka keyword arguments)
Positional arguments are the default way in which arguments are supplied to a function. Here
the values are passed to arguments in the same order in which they occur in the function
definition. When the positions of the arguments are changed, the values assigned also change.
See an example:
>>> def myfun(a,b):
... print("The values of a and b are respectively",a,b)
...
>>> myfun(3,4)
The values of a and b are respectively 3 4
>>> myfun(4,3)
The values of a and b are respectively 4 3
In order not to bother with the order of the parameters, Python has the mechanism of named
arguments or keyword arguments. This allows you to specify the argument names in the
function call along with their values.
This is illustrated below:
>>> def myfun(a,b):
... print("The values of a and b are respectively",a,b)
...
>>> myfun(a=3,b=4)
The values of a and b are respectively 3 4
>>> myfun(b=4,a=3)
The values of a and b are respectively 3 4
Default arguments or optional arguments allow you to assign default values to named
arguments. The default values for the arguments are included while defining the function.
When the function is called without passing value for the default arguments, their default
values will be taken.
>>> def sample(a,b=7):
... print("The values of a and b are respectively",a,b)
...
>>> sample(3)
The values of a and b are respectively 3 7
>>> sample(3,4)
The values of a and b are respectively 3 4
You can pass values to default arguments in two ways:
1. by position: Here the values are passed in the order in which the arguments occur in the
function header.
2. by name: Here the values are passed using the argument names in the function call.
3.

>>> def sample(a,b=7,c=9):


... print("The values of a, b and c are respectively",a,b,c)
...
>>> sample(3)
The values of a, b and c are respectively 3 7 9
>>> sample(3,4) # default values taken by position
The values of a, b and c are respectively 3 4 9
>>> sample(3,c=5) # default values taken by name
The values of a, b and c are respectively 3 7 5
Return Statement
In Python, the return statement is used inside a function to send a result back to the caller. It
allows a function to produce a value that can be used later in the program. Without the return
statement, a function doesn't return anything by default, and the value returned is None.
Returning Multiple Values
1. Returning Multiple Values as a Tuple (Most Common Method)
The simplest and most common way to return multiple values from a function is by using
a tuple. Python automatically creates a tuple when you return more than one value
separated by commas.
def get_dimensions():
width = 5
height = 10
# Returning a tuple implicitly
return width, height
result = get_dimensions()
print(result) # Output: (5, 10)

When you return multiple values like width, height, Python packs them into a tuple (5, 10)
behind the scenes. Accessing Values: You can unpack the tuple later when you call the function
width, height = get_dimensions()
print(width) # Output: 5
print(height) # Output: 10

2. Returning Multiple Values as a List. A function can return a list to hold multiple values.
Lists are mutable, meaning you can modify them after they are returned.
def get_fruits():
fruits = ['apple', 'banana', 'cherry']
# Returning a list
return fruits
result = get_fruits()
print(result)
# Output: ['apple', 'banana', 'cherry']
3. Returning Multiple Values as a Dictionary.
A dictionary can be used to return multiple values, especially when the data has a
meaningful key-value structure. This is particularly useful when you want to return named
values (e.g.,"name"and"age").
def get_person_info():
info = {
'name': 'Alice',
'age': 30,
'is_student': False
}
return info # Returning a dictionary
result = get_person_info()
print(result)
# Output: {'name': 'Alice', 'age': 30, 'is_student': False}
RECURSION
Recursion involves a function calling itself to solve smaller or simpler instances of the same
problem. The recursive approach is built on the idea of solving a problem by solving instances of
the same problem. Imagine searching for a name in a phone book by first opening it to the middle.
If the name is on that page, you have found it. If not, you decide where to search next: if the name
should be in the earlier part of the book, you focus on the first half; if it should be in the later part,
you focus on the second half. You continue this process, repeatedly narrowing down your search
to progressively smaller sections of the phone book until you locate the name.
Reasons for using Recursion

Recursion is used in programming for several important reasons. Here are the key ones:

1. Simplifies Complex Problems: Recursion breaks a problem into smaller sub-problems


of the same type. This is especially useful for problems that have a natural recursive
structure, such as: Tree traversals, Graph algorithms, Divide and conquer algorithms
(e.g., Merge Sort, Quick Sort)
2. Cleaner and More Readable Code: Recursive solutions are often more concise and
closer to the problem’s mathematical definition. For example: Calculating factorial,
Computing Fibonacci numbers, Solving Tower of Hanoi
3. Effective for Data Structures: Recursion is ideal for working with recursive data
structures like: Trees (binary trees, N-ary trees), Linked lists, Graphs
4. Easier to Implement Certain Algorithms: Some problems are more intuitively and
efficiently expressed with recursion than with iteration. Examples: Backtracking
problems (e.g., Sudoku solver, N-Queens), Dynamic programming with memoization
5. Natural Fit for Divide and Conquer: Many efficient algorithms use a divide and
conquer strategy, which is inherently recursive. These include: Merge Sort, Binary
Search, Quick Sort
6. Facilitates Mathematical Computations: Recursion is directly aligned with many
mathematical definitions and formulas, such as:
Factorials (n! = n × (n-1)!)
Power (aⁿ = a × aⁿ⁻¹)

Recursion involves a function calling itself with modified arguments. A recursive function
typically has two key components:
• Base Case: The simplest version of the problem that can be solved directly without further
recursion.
• Recursive Case: The part of the problem that involves calling the function itself to handle
a smaller or simpler instance.
The Call Stack
The Call Stack The call stack is a crucial data structure used in programming to manage function
calls and control flow. It operates on a last-in, first-out (LIFO) principle, meaning that the most
recent function call is processed first when returning control to previous calls. Each time a function
is invoked, a stack frame is created, storing information such as the function’s parameters, local
variables, and the return address. As functions call other functions, new frames are pushed onto
the stack, and as functions return, their frames are popped off. This mechanism ensures that each
function executes in the correct context and allows for the orderly execution and return of function
calls, especially important in recursive programming where functions call themselves.
How the Call Stack Works
1. Function Call: When a function is called, an activation record (also known as a stack frame)
is created and pushed onto the top of the call stack. This frame contains information such
as:
1. The return address (where to return control after the function execution completes)
2. The parameters of the function
3. Local variables of the function
4. Saved registers
2. Function Execution: The CPU executes the function. If the function calls another function,
a new frame is pushed onto the stack.
3. Function Return: When a function finishes executing, its frame is popped from the stack.
Control is then transferred back to the return address stored in the popped frame, and
execution resumes from there.
Importance of the Call Stack
• Function Management: The call stack manages function calls and returns in a structured
manner, ensuring that each function’s local variables and return address are properly
maintained.
• Recursion: The call stack is crucial for handling recursive functions, where a function calls
itself. Each recursive call adds a new frame to the stack.
• Error Handling: The call stack helps in debugging and error handling. A stack trace (a
report of the active stack frames at a certain point in time) is often used to diagnose the
sequence of function calls leading to an error or exception.
Let us look at an example. Consider the following simple example of a recursive
function:
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n - 1)
#Calling the function
print(factorial(5))
Here is how the call stack would look during the execution of factorial(5):
• Initial Call: factorial(5)
– A frame for factorial(5) is pushed onto the stack.
• First Recursive Call: factorial(4)
– A frame for factorial(4) is pushed onto the stack.
• Second Recursive Call: factorial(3)
– A frame for factorial(3) is pushed onto the stack.
• Third Recursive Call: factorial(2)
– A frame for factorial(2) is pushed onto the stack.
• Fourth Recursive Call: factorial(1)
– A frame for factorial(1) is pushed onto the stack.
• Base Case: factorial(0)
– A frame for factorial(0) is pushed onto the stack. Since n == 0, it returns 1 and
this frame is popped.
As the base case returns, each function call completes and returns control to its caller, popping
frames off the stack in the reverse order of their addition.
Explanation
• factorial(5) calls factorial(4) and waits for the result.
• factorial(4) calls factorial(3) and waits for the result.
• factorial(3) calls factorial(2) and waits for the result.
• factorial(2) calls factorial(1) and waits for the result.
• factorial(1) calls factorial(0) and waits for the result.
• factorial(0) returns 1 (base case).
• factorial(1) receives the result 1 and returns 1 * 1 = 1.
• factorial(2) receives the result 1 and returns 2 * 1 = 2.
• factorial(3) receives the result 2 and returns 3 * 2 = 6.
• factorial(4) receives the result 6 and returns 4 * 6 = 24.
• factorial(5) receives the result 24 and returns 5 * 24 = 120

Implications of Recursion and the Call Stack


• Memory Usage: Each recursive call adds a new frame to the stack. Deep recursion can
lead to high memory usage and even stack overflow if the recursion depth is too large.
• Stack Overflow: This occurs when the call stack exceeds its limit due to too many
recursive calls. It’s often a sign of either too deep recursion or an infinite recursion due to
missing base cases.
• Efficiency: Recursion can be elegant and easy to understand but might be less efficient
than iterative solutions in terms of both time and space. Tail recursion optimization can
help mitigate some inefficiencies in languages that support it.
• Debugging: Debugging recursive functions involves tracking the state of each frame on
the call stack, which can be complex. Stack traces are often used to trace the function calls
leading up to an error. Understanding the call stack is essential for debugging, optimizing,
and writing efficient and error-free code, especially in languages that support recursion and
have complex function call structures.
Steps for Solving Computational Problems using Recursion
The recursive problem-solving technique involves solving a problem by breaking it down into
smaller, more manageable subproblems of the same type. This method relies on a function
calling itself to handle these subproblems, which can lead to elegant and efficient solutions.
To effectively use recursion for computational problem-solving, one must follow a structured
approach. This includes understanding the problem thoroughly, identifying the base case to
prevent infinite recursion, defining the recursive case to break the problem into simpler
instances, implementing the recursive function by combining the base and recursive cases, and
rigorously testing the function to ensure its correctness across various inputs.
1. Understand the Problem
Problem: Given a list of numbers, calculate the sum of all its elements.
Example Input: [1, 2, 3, 4, 5]
Example Output: 15

To solve this, we need to add up all the elements in the list. Recursion will help us simplify
this task by handling one element at a time and summing it with the result of the sum of
the remaining elements.
2. Identify the Base Case
Base Case: The simplest instance of the problem that can be solved directly without
recursion. For summing elements in a list, the base case is when the list is empty, and so
the sum is 0.
Base Case Condition: sum([]) = 0
3. Define the Recursive Case
Recursive Case: Break down the problem into smaller subproblems and express the
solution in terms of these subproblems. For a non-empty list, the sum can be calculated by
adding the first element of the list to the sum of the remaining elements. This reduces the
problem size by removing the first element and applying the same sum operation to the rest
of the list.
Recursive Case Formula: sum(lst) = lst[0] + sum(lst[1:])
4. Implement the Recursive Function
Combine the base case and recursive case into a single function.
Implementation in Python:
def sum_list(lst):
# Base case: if the list is empty, return 0
if len(lst) == 0:
return 0
# Recursive case: add the first element to the sum of
# the rest of the list
else:
return lst[0] + sum_list(lst[1:])
Explanation:
• If the list is empty (len(lst) == 0), return 0 (base case).
• Otherwise, return the first element (lst[0]) plus the result of calling sum_list on the
rest of the list (lst[1 :]).

5. Test the Recursive Function
Testing ensures that the function handles different scenarios correctly.
Test Cases:
#Test case 1: Empty list
print(sum_list([])) # Expected output: 0
# Test case 2: List with one element
print(sum_list([5])) # Expected output: 5
# Test case 3: List with multiple elements
print(sum_list([1, 2, 3, 4, 5])) # Expected output: 15
# Test case 4: List with negative and positive elements
print(sum_list([-1, 2, -3, 4])) # Expected output: 2

Explanation of Tests:
• The function should correctly return 0 for an empty list.
• It should return the element itself if the list has only one element.
• For a typical list with multiple elements, it should compute the sum of all elements.
• For a list with negative and positive numbers, the function should still correctly
compute the sum
Avoiding Circularity in Recursion
Avoiding circularity in recursion is crucial to ensure that your recursive functions terminate
correctly and do not fall into infinite loops. Circularity in recursion can occur if a function keeps
calling itself without a proper termination condition or if the termination conditions are not
properly defined.
1. Define a Clear Base Case
The base case is a condition that stops further recursive calls. Every recursive function
must have a base case to prevent infinite recursion.
Python Example: Sum of Natural Numbers
def sum_natural_numbers(n):
""" Base case: if n is 0 or negative, return 0 """
if n <= 0:
return 0
else: # Recursive call
return n + sum_natural_numbers(n - 1)
# Test print(sum_natural_numbers(5)) # Output: 15
# (5 + 4 + 3 + 2 + 1 = 15)
2. Ensure Progress Towards the Base Case
Each recursive call should make progress toward the base case. This means modifying
parameters so that the function eventually reaches the base case.
Python Example: Counting Down
def countdown(seconds):
""" Base case: If seconds is less than or equal to 0,
print 'Time's up!' and end recursion. """
if seconds <= 0:
print("Time's up!")
else:
print(seconds)
# Ensure progress by decrementing the number of seconds.
# Recursive call with the updated number of seconds.
countdown(seconds - 1)
# Test
countdown(5) # Counts down from 5 to 0

3. Avoid Unchanging Parameters


The parameters passed in each recursive call should be updated to ensure that they
eventually lead to the base case. Avoid passing parameters that would lead to redundant or
unchanged recursion.
Python Example: Computing Exponentiation
def power(base, exponent):
""" Base case: any number to the power 0 is 1 """
if exponent == 0:
return 1
else:
# Recursive call with decremented exponent
return base * power(base, exponent - 1)
# Test
print(power(2, 3)) # Output: 8 (2^3)

4. Handle Edge Cases Appropriately


Ensure that edge cases are handled properly so the function behaves correctly for all
possible inputs, including special or boundary instances.
Python Example: Printing Elements in a List
def print_list(lst, index=0):
""" Base case: index out of range of the list """
if index >= len(lst):
return
print(lst[index])
# Recursive call with incremented index
print_list(lst, index + 1)
# Test
print_list([10, 20, 30]) # Output: 10 20 30
5. Use Debugging Tools
If you suspect your recursive function might have issues with termination, use debugging
techniques to trace function calls and verify that recursion progresses toward the base case.
Python Example: Fibonacci Sequence with Debugging
def fibonacci(n):
print(f"Calling fibonacci({n})") # Debugging statement
if n <= 1: # Base case: fibonacci of 0 or 1
return n
else: # Recursive calls
return fibonacci(n - 1) + fibonacci(n - 2)
# Test
print(fibonacci(4))
# Output:
""" Calling fibonacci(4)
Calling fibonacci(3)
Calling fibonacci(2)
Calling fibonacci(1)
Calling fibonacci(0)
Calling fibonacci(1)
Calling fibonacci(2)
Calling fibonacci(1)
Calling fibonacci(0)
3 """
SAMPLE PROBLEMS
1. Finding the nth Fibonacci number
def fibonacci_recursive(n):
if n <= 1:
return n
return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2)

# Example usage:
n = 10
result = fibonacci_recursive(n)
print(f"The {n}th Fibonacci number is: {result}")

Explanation:
1. Base Case:
The function first checks if n is less than or equal to 1. If it is, it returns n directly
(as Fibonacci numbers for 0 and 1 are 0 and 1, respectively).
2. Recursive Step:
Otherwise, the function recursively calls itself with n-1 and n-2, and returns the
sum of the results. This implements the Fibonacci sequence's rule: each number is
the sum of the two preceding numbers.
2. Greatest common divisor of two positive integers
def gcd(a, b):
if b == 0:
return a
else:
return gcd(b, a % b)
# Example usage
a = 48
b = 18
print(f"The GCD of {a} and {b} is {gcd(a, b)}")
Explanation
1. Base Case: When b = 0, the GCD is a. This is because any number is divisible by itself,
and the remainder is zero.
2. Recursive Case: Compute gcd(b, a mod b). The modulo operation reduces the size of
the problem, and the recursion continues with the new values.
3. factorial of a positive integer
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n - 1)
4. Adding two positive integers
def recursive_add(num1, num2):
if num2 == 0:
return num1
else:
return recursive_add(num1 + 1, num2 - 1)
# Example usage:
a=5
b=3
sum_result = recursive_add(a, b)
print(f"The sum of {a} and {b} is: {sum_result}")
c = 10
d=0
sum_result2 = recursive_add(c, d)
print(f"The sum of {c} and {d} is: {sum_result2}")

Explanation:
• Base Case:
The if num2 == 0: statement defines the base case for the recursion. When
the second number (num2) becomes 0, it means that all additions have been
effectively transferred to num1, and num1 now holds the final sum.
• Recursive Step:
In the else: block, the function calls itself (recursive_add) with modified
arguments:
num1 + 1: Increments the first number.
num2 - 1: Decrements the second number.

This process continues until num2 reaches 0, at which point the base case
is met and the accumulated sum in num1 is returned.

5. The sum of digits of a positive number


• Base Case:
If the number is 0, the sum of its digits is 0. This serves as the termination
condition for the recursion.
• Recursive Step:
For any positive number, the sum of its digits can be found by adding its
last digit (obtained using the modulo operator % 10) to the sum of the
digits of the remaining number (obtained by integer division // 10).

def sum_digits_recursive(n):
if n == 0: # Base case
return 0
else: # Recursive step
return (n % 10) + sum_digits_recursive(n // 10)
# Example usage:
number = 12345
result = sum_digits_recursive(number)
print(f"The sum of the digits of {number} is: {result}")
number_two = 789
result_two = sum_digits_recursive(number_two)
print(f"The sum of the digits of {number_two} is: {result_two}")
6. Merge sort
Merge Sort, a divide and conquer algorithm, can be used to find the top three integers in a
list by first sorting the list using the algorithm and then extracting the first three
elements. Here's how it's done in Python:
Merge Sort Algorithm:
• Divide: Recursively divide the list into two halves until you have sublists
of size 1 (which are inherently sorted).
• Conquer: Repeatedly merge the sorted sublists until you have a single sorted
list.
• Merge: The core of the algorithm. It takes two sorted sublists and combines
them into a single sorted list.

Implementation in Python:
def merge_sort(arr):
if len(arr) <= 1:
return arr

mid = len(arr) // 2
left_half = merge_sort(arr[:mid])
right_half = merge_sort(arr[mid:])
return merge(left_half, right_half)
def merge(left, right):
merged = []
i=0
j=0

while i < len(left) and j < len(right):


if left[i] <= right[j]:
merged.append(left[i])
i += 1
else:
merged.append(right[j])
j += 1

merged.extend(left[i:])
merged.extend(right[j:])

return merged

Explanation:
• The merge_sort function recursively divides the input list until it's broken down
into single-element lists.
• The merge function then merges these sorted sublistss back together, ensuring that
each merged list remains sorted.
MODULE 4

COMPUTATIONAL APPROACHES TO PROBLEM- SOLVING

Computational problem-solving uses various strategies to tackle complex challenges across


different domains using computational power.

Brute-force approach exhaustively checks all possible solutions to find the best one; it's simple
but often inefficient for large problems.

Divide-and-conquer strategy breaks a problem into smaller sub-problems, solves them


independently, and combines their solutions (e.g., merge sort).This method improves efficiency
over brute-force but may require extra memory and processing due to recursion.

Dynamic programming solves problems by storing solutions to overlapping subproblems to


avoid redundant computation.It is more efficient than divide-and-conquer for certain problems,
trading off increased space for reduced time complexity.

Greedy algorithms make the best choice at each step in hopes of finding a global optimum (e.g.,
task scheduling problems).While fast and simple, greedy methods may miss optimal solutions
because they don't consider the bigger picture.

Randomized algorithms incorporate randomness to provide solutions to problems where


deterministic methods may be less effective or impractical.These approaches are useful for
understanding stochastic behavior and solving problems like coupon collection or random
matching.

Brute-Force Approach to Problem Solving

The brute-force approach, or exhaustive search, is a fundamental problem-solving method that


systematically evaluates all possible solutions to identify the optimal one. Common in applications
like chess engines, it leverages computational speed rather than sophisticated techniques. While
effective, brute-force methods can be computationally intensive, especially for large solution
spaces. However, clever analysis and minor optimizations—such as reducing the number of
possibilities from 240 to 220—can significantly improve efficiency. Despite its simplicity, the
brute-force approach often requires creativity to make it practical and efficient in real-world
scenarios.

Examples of Brute-Force Approach:

1. Padlock:
A brute-force solution involves trying all possible four-digit combinations from "0000" to
"9999" until the padlock opens. Though simple and guaranteed to succeed, it can be time-
consuming.

2. Password Guessing:
In cybersecurity, brute-force attacks systematically attempt every character combination
to crack passwords. For a six-character alphanumeric password, this could mean testing up
to 2.18 billion combinations.

3. Cryptography - Cracking Codes:


Brute-force techniques are used to break encryption by testing every possible key. For
instance, a substitution cipher can be cracked by trying all possible alphabet shifts until the
original message is revealed.

4. Sudoku Solving:
This method tries every number in each cell and backtracks on conflicts, ensuring a correct
solution. Although effective, it can be computationally intensive for difficult puzzles.

Characteristics of Brute-Force Solutions

1. Exhaustive Search: Every possible solution is examined without any optimization.

2. Simplicity: Easy to understand and implement.

3. Inefficiency: Often slow and resource-intensive due to the large number of possibilities.

4. Guaranteed Solution: If a solution exists, the brute-force method will eventually find
it.

Divide-and-conquer Approach to Problem Solving

The divide-and-conquer approach is a powerful problem-solving technique that simplifies


complex tasks by breaking them into smaller, manageable parts. For example, in a classroom
organizing books, students might first sort by genre, then by category, and finally alphabetically—
making the large task much easier. Similarly, in project management, building a high-rise is split
into tasks like foundation work, electrical setup, and interior finishing, each handled by specialized
teams to ensure efficiency. In software development, an e-commerce platform can be divided into
modules like authentication, product catalog, and payment processing, each developed and tested
separately before integration.
This approach is also widely applied in other fields. In healthcare, doctors use divide-and-conquer
to categorize and investigate symptoms separately, leading to accurate diagnoses. In logistics,
companies manage supply chains by dividing operations into regional centers, improving
efficiency and reducing costs. Overall, divide-and-conquer solves complex problems by dividing
them into similar sub-problems, solving them independently, and combining the results—
especially useful for problems with a recursive structure.

Steps of Divide-and-Conquer:

The divide-and-conquer approach involves three fundamental steps: Divide, Conquer, and
Combine.

1. Divide: The original problem is split into smaller, more manageable sub-problems that are
similar in nature. For example, organizing a large set of files can start with dividing them
by type (e.g., documents, images, videos) or by project. This step simplifies the complexity
by focusing on smaller segments of the overall problem.

2. Conquer: Each sub-problem is then solved individually. If a sub-problem is small enough,


it is solved directly; otherwise, the divide-and-conquer method is applied recursively. For
instance, documents can be further sorted into reports, presentations, and spreadsheets, then
categorized by date or project. This recursive handling ensures that even complex subsets
become manageable.

3. Combine: Finally, the solutions to the sub-problems are merged to form the solution to the
original problem. In the file organization example, this means integrating all organized
subsets into a coherent directory structure. The result is an efficient, organized system that
reflects the overall goal. This structured method makes large problems easier to tackle and
implement effectively.

Example 1: Merge Sort

Steps:

1. Divide: Split the array into two halves.

2. Conquer: Recursively sort each half.

3. Combine: Merge the two sorted halves into one sorted array.

Key Idea:

● Keep dividing until each part has a single element (which is naturally sorted).
● Then merge these parts while sorting them.

Advantages:

● Stable sort (preserves order of equal elements).

● Time Complexity: O(n log n)

● Good for large datasets.

Array = [38, 27, 43, 3, 9, 82, 10]


Example 2: Binary Search

Advantages and Disadvantages of Divide and Conquer Approach

Advantages of Divide and Conquer Approach

1. Simplicity in Problem Solving: By breaking a problem into smaller subproblems, each


subproblem is simpler to understand and solve, making the overall problem more manageable.

2. Efficiency: Many divide-and-conquer algorithms, such as merge sort and quicksort, have
optimal or near-optimal time complexities. These algorithms often have lower time complexities
compared to iterative approaches.

3. Modularity: Divide-and-conquer promotes a modular approach to problem-solving, where each


subproblem can be handled by a separate function or module. This makes the code easier to
maintain and extend.

4. Reduction in Complexity: By dividing the problem, the overall complexity is reduced, and
solving smaller subproblems can lead to simpler and more efficient solutions.
5. Parallelism: The divide-and-conquer approach can easily be parallelized because the
subproblems can be solved independently and simultaneously on different processors, leading to
potential performance improvements.

6. Better Use of Memory: Some divide-and-conquer algorithms use memory more efficiently. For
example, the merge sort algorithm works well with large data sets that do not fit into memory, as
it can process subsets of data in chunks.

Disadvantages of Divide and Conquer Approach

1. Overhead of Recursive Calls: The recursive nature can lead to significant overhead due to
function calls and maintaining the call stack. This can be a problem for algorithms with deep
recursion or large subproblem sizes.

2. Increased Memory Usage: Divide-and-conquer algorithms often require additional memory for
storing intermediate results, which can be a drawback for memory-constrained environments.

3. Complexity of Merging Results: The merging step can be complex and may not always be
straightforward. Efficient merging often requires dditional algorithms and can add to the
complexity of the overall solution,

4. Not Always the Most Efficient: For some problems, divide-and-conquer might not be the most
efficient approach compared to iterative or dynamic programming methods. The choice of strategy
depends on the specific problem and context.

5. Difficulty in Implementation: Implementing divide-and-conquer algorithms can be more


challenging, especially for beginners. The recursive nature and merging steps require careful
design to ensure correctness and efficiency.

6. Stack Overflow Risk: Deep recursion can lead to stack overflow errors if the recursion depth
exceeds the system’s stack capacity, particularly with large inputs or poorly designed algorithms.

Dynamic Problem Solving Approach

Dynamic Programming (DP) is a problem-solving method that breaks complex problems into
simpler overlapping subproblems, solves each subproblem once, and stores their solutions to avoid
redundant work. It is especially useful for optimization problems that have:

● Optimal Substructure: The optimal solution to a problem contains optimal solutions to its
subproblems.

● Overlapping Subproblems: Subproblems recur multiple times in the process of solving the
larger problem.
The principle behind DP is Bellman’s Principle of Optimality, which states that any optimal
solution can be composed of optimal sub-solutions. For example, finding the shortest route from
city A to city C via city B can be done by combining the shortest route from A to B and the shortest
route from B to C.

In essence, DP improves efficiency by storing intermediate results, making it a powerful technique


for tackling various optimization problems like shortest path, resource allocation, and sequence
alignment.

Key Concepts:

1. Problem Breakdown:

○ Smaller Subproblems: To find the shortest path to a cell (i, j) in a grid, consider
the shortest paths to its neighbors (i-1, j) and (i, j-1).

○ Optimal Substructure: The shortest path to (i, j) can be built from the optimal
paths to its neighbors.

2. How it Works:

○ Solve the smallest subproblems first.

○ Use these solutions to build up answers for larger subproblems incrementally.

○ Combine the results to solve the original problem efficiently.

3. Overlapping Subproblems:

○ Many subproblems recur multiple times.

○ DP stores these intermediate results (memoization or tabulation) to avoid


recomputation and improve performance.

Example: Fibonacci Sequence

● Without DP: Fibonacci(3) is recalculated multiple times when computing higher Fibonacci
numbers, causing redundant work.

● With DP: Fibonacci(3) is computed once, stored, and reused, greatly speeding up the
computation.
Without Dynamic Programming

With Dynamic Programming

Advantages of Dynamic Programming

1. Efficiency: DP significantly reduces computation time by storing solutions to overlapping


subproblems and avoiding redundant calculations.

2. Optimal Solutions:It guarantees finding the optimal solution when the problem exhibits
optimal substructure.

3. Systematic Approach:DP provides a clear, structured framework to solve complex


problems by breaking them down into manageable parts.
4. Versatility:It can be applied to a wide range of problems such as shortest paths, resource
allocation, sequence alignment, and more.

5. Simplifies Recursive Solutions:Converts exponential-time recursive problems into


polynomial-time solutions through memoization or tabulation.
Disadvantages of Dynamic Programming

1. High Memory Usage:Storing solutions for many subproblems can require large amounts
of memory, especially for problems with large state spaces.

2. Implementation Complexity:Designing DP algorithms can be challenging due to the need


to correctly identify subproblems, states, and transitions.

3. Not Always Intuitive: The approach requires a good understanding of the problem’s
structure; beginners may find it difficult to formulate the DP solution.

4. Overhead of Table Management: Maintaining and updating the storage table (memo or DP
table) adds overhead, which can be inefficient for simple problems.

5. Not Suitable for Non-overlapping Subproblems: If subproblems do not overlap, DP may


not provide efficiency benefits compared to other methods like divide-and-conquer.

Comparison of Dynamic Programming with Divide and Conquer and Greedy Algorithms

Aspect Dynamic Divide and Greedy Algorithms


Programming Conquer

Problem Breakdown Breaks into smaller Breaks into Makes local decisions at
overlapping sub independent sub each step
problems problems

Reuse of Sub Yes – stores and No – does not reuse No – does not reuse past
problem Solutions reuses solutions solutions, may computations
(memoization/tabulati recompute sub
on) problems
Optimality Yes – ensures global Sometimes – No – may miss global
Guarantee optimum if problem depends on the optimum
has optimal problem
substructure

Efficiency Highly efficient for May be inefficient Efficient, but only if


problems with due to repeated local choice leads to
overlapping calculations global optimum
subproblems

Implementation More complex to Easier compared to Simple and easy to


design and implement DP implement

Use Case Examples Shortest path, Merge sort, Activity selection, coin
knapsack, matrix quicksort, binary change (with greedy
chain multiplication search denominations),
Huffman coding

Greedy Approach to Problem Solving

The greedy approach is a straightforward and intuitive algorithmic strategy that solves problems
by making the locally optimal choice at each step, without reconsidering previous decisions or
evaluating future consequences. It simplifies complex problems by breaking them into smaller
decision-making steps, aiming to construct an overall solution through a sequence of the best
immediate choices. This method is especially effective for problems with the greedy-choice
property, where local optima leads to a global optimum.

However, the greedy approach does not always guarantee the best overall solution, especially in
problems that require global foresight or where early decisions affect later outcomes. In such cases,
more robust techniques like dynamic programming or brute-force may be needed to ensure
accuracy. Despite this limitation, the greedy method is favored for its simplicity and speed, making
it a suitable choice for problems where efficiency is crucial and the structure supports greedy
decisions, such as in Huffman coding, activity selection, and minimum spanning trees.
Example : Coin Change Problem Using Greedy Algorithm

The coin change problem asks for the minimum number of coins needed to make a certain amount
of money using a given set of coin denominations. A common way to solve this is by using a
greedy algorithm, which always picks the largest coin value that doesn’t go over the remaining
amount. This process repeats until the full amount is reached.

The greedy approach works well for some coin sets, like 1, 2, and 5, where it always gives the best
(minimum) number of coins. But it doesn’t always work for other coin sets. For example, with
coins 1, 3, and 4, to make 6, the greedy method picks 4 + 1 + 1 = 3 coins. But the better solution
is 3 + 3 = only 2 coins. So, greedy is fast and simple but not always correct for all coin systems.

Key Characteristics of the Greedy Approach

1. Local Optimization: At each step, the algorithm makes the best possible choice without
considering the overall problem. This choice is made with the hope that these local optimal
decisions will lead to a globally optimal solution.

2. Irrevocable Decisions: Once a choice is made, it cannot be changed.The algorithm


proceeds to the next step, making another locally optimal choice.

3. Efficiency: Greedy algorithms are typically easy to implement and run quickly, as they
make decisions based on local information and do not need to consider all possible
solutions.

Motivations for the Greedy Approach

● Simple Logic: Greedy algorithms follow a straightforward decision-making process based


on local choices.
● Easy Implementation: They are easy to code and understand, needing minimal setup.
● No Complex Structures: Greedy methods work without complex data structures or heavy
bookkeeping.
● Fast Execution: They usually run in linear or polynomial time, making them suitable for
large datasets.
● Low Memory Use: Greedy algorithms require little memory since they don’t store many
intermediate results.

Effective for Some Problems: They work well when the problem has the greedy-choice
property.
● Supports Optimal Substructure: They are efficient when a global solution can be built from
optimal sub-solutions.
● Real-World Use: Commonly used in real tasks like job scheduling, network routing, and
resource management.
● Quick Results: Ideal for scenarios where speed is more important than exactness.
● Good Approximations: Often deliver near-optimal solutions when perfect accuracy is not
critical.

Characteristics of the Greedy Algorithm

● Local Optimization: Makes the best decision at each step based only on the current
situation.
● No Global View: Ignores the overall problem and focuses on immediate benefits.
● Irrevocable Decisions: Once a decision is made, it cannot be changed; no backtracking is
allowed.
● No Reconsideration: The algorithm moves forward step by step without revisiting past
choices.
● Problem-Specific Heuristics: Uses tailored rules or logic specific to the problem to guide
decisions.
● Heuristic-Driven: Relies on knowledge of the problem’s structure to make efficient
decisions.
● Conditional Optimality: Produces optimal results only for problems that satisfy certain
properties.
● Not Universally Optimal: May fail to find the best solution for problems lacking the
greedy-choice property or optimal substructure.
● Time Efficiency: Typically runs fast—often in linear or polynomial time.
● Space Efficiency: Uses minimal memory as it doesn't store past states or multiple solutions.

Advantages and Disadvantages of the Greedy Approach

Advantages of the Greedy Approach

1. Simplicity: Greedy algorithms are generally easy to understand and implement.

2. Speed: These algorithms typically run quickly, making them suitable for large input sizes.

3. Optimal for Certain Problems: For some problems, like the Coin Change Problem with certain
denominations, greedy algorithms provide an optimal solution.

Disadvantages of the Greedy Approach

1. Suboptimal Solutions: Greedy algorithms do not always produce the optimal solution for every
problem. They are most effective when the problem has the greedy-choice property, meaning a
global optimum can be reached by making local optimal choices.
2. Irrevocable Decisions: Once a choice is made, it cannot be changed, which may lead to a
suboptimal solution in some cases.

3. Lack of Backtracking: Greedy algorithms do not explore all possible solutions or backtracks,
which means they can miss better solutions.

Randomized Approach to Problem Solving

Randomized approaches in computational problem solving use simulations and randomness to


tackle complex challenges that traditional deterministic methods find difficult. For example,
estimating the area of a circle inscribed in a square can be done by randomly placing points inside
the square and calculating the proportion that falls within the circle. This ratio helps approximate
the circle’s area, showcasing how randomness can simplify geometric problems. Such simulations
allow us to model uncertainty, analyze system behavior, and derive practical solutions in various
scenarios where exact calculations are complicated.

Classic examples like the birthday problem, random walks, and random coin flips illustrate the
power of randomized methods. By simulating these processes many times, we can estimate
probabilities and expected outcomes in an intuitive and hands-on way. Randomized approaches
provide a flexible and accessible way to explore problems involving uncertainty and probabilistic
elements, expanding our problem-solving toolkit beyond traditional algorithms and offering
valuable insights into complex systems.

Motivations of the Randomized Approach

Randomized approaches simplify complex problems by using probabilistic choices, which reduce
the need to examine every possible option. For example, instead of testing all combinations of
health screening station locations in a city, randomly selecting and evaluating a few setups can
reveal effective patterns efficiently. This reduces complexity and speeds up decision-making.

These methods are versatile and applicable across many fields, such as software testing or library
management, where exhaustive analysis is impractical. By sampling randomly—like testing a
subset of app users or monitoring a selection of books—randomized approaches provide valuable
insights while saving time and resources. In many cases, they offer better performance and
practicality compared to deterministic methods, especially with large or complex datasets.

Characteristics of the Randomized Approach

● Probabilistic Decisions: Uses random sampling or probabilistic events to make


choices, resulting in outcomes that vary but are statistically predictable.

● Variable Results: The output can differ between runs due to randomness, yet
overall performance remains reliable on average.
● Efficiency: Gains efficiency by accepting probabilistic correctness instead of strict
deterministic guarantees, saving time in complex scenarios.
● Practicality: Useful when exhaustive computation or analysis is too costly or
impossible.
● Average-Case Analysis: Performance is evaluated based on expected or average
behavior over many runs rather than worst-case scenarios.
● Adaptability: Can be applied across diverse problems and domains where
uncertainty or complexity is high.
● Reduced Complexity: Simplifies difficult problems by focusing on representative
samples instead of full problem spaces.
● Scalability: Handles large datasets or systems efficiently by processing smaller
randomized subsets.
● Trade-off: Balances between accuracy and computational resources, often
providing good-enough solutions quickly.
● Statistical Insights: Allows decision-making based on empirical data and
statistical patterns rather than deterministic certainty.

Example 1: Coupon Problem:

A company gives you one coupon for each pair of jeans you buy. There are n different types of
coupons. If you collect all n different coupons, you get a free pair of jeans. The question is: How
many pairs of jeans do you expect to buy before collecting all the different coupons?

Intuition and Approach:

● Every time you buy jeans, you get a random coupon from the n types.

● You want to keep buying jeans until you have at least one of each coupon type.

● Since coupons are given randomly, it may take more or fewer jeans each time.

● To find the average number of jeans you need to buy, you can simulate this process many
times and take the average.

Algorithm in Simple Steps:

1. Start with zero jeans bought and no coupons collected.

2. Buy one pair of jeans, get a random coupon, and add it to your collection.
3. Keep buying jeans until you have collected all n different coupons.

4. Repeat this entire process many times (like 100,000 times) to get a good estimate.

5. Calculate the average number of jeans bought across all simulations.

Example 2: Hat Problem

At a party, n people leave their hats with a hat-check person. When they leave, a different person
randomly returns the hats. The question is: On average, how many people will get back their own
hat?

Intuition and Approach:

● Each hat is given back randomly to one of the n people.

● We want to know the expected number of people who receive their own original hat.

● Instead of calculating this exactly (which involves permutations), we simulate the process
many times.

● By averaging the number of people who get their own hats in many simulations, we
estimate the expected value.

Algorithm in Simple Steps:

1. Set the total correct matches (people who get their own hats) to zero.

2. Repeat the following for many simulations (e.g., 100,000 times):

○ Create a list representing hats for each person (e.g., [1, 2, 3, ..., n]).

○ Shuffle the list to simulate random distribution of hats.

○ Count how many people have their own hat (positions where the hat number
matches the person number).

○ Add this count to the total correct matches.


3. Calculate the average number of correct matches by dividing the total by the number of
simulations.

4. This average is the expected number of people getting their own hats back.

Randomized Approach vs Deterministic Methods

Aspect Randomized Approach Deterministic Approach

Definition Uses random sampling or chance in Uses fixed, predictable logic and
decision-making rules

Nature of Results Varies with each run, but statistically Same result every time for the same
predictable over time input

UseCase Example Estimating customer satisfaction via Calculating total cost of items in a
random branch surveys shopping cart

Strengths - Handles large/complex datasets - High accuracy- Reliable and


efficiently- Adapts to uncertainty- consistent results- Suitable for
Faster in large-scale problems precise problems

Weaknesses - Results may slightly vary- - Can be computationally heavy on


Requires many runs for high large/uncertain data- Not adaptable
accuracy to randomness
Ideal Scenarios Surveys- Simulations- Estimations- - Engineering design- Financial
When exhaustive computation is calculations- Where precision is
impractical critical

Example Scenario Surveying sample customers to Calculating the strength of materials


estimate exercise habits in machinery design

Handling Well-suited to uncertainty and Not ideal for unpredictable or


Uncertainty variability incomplete data

Result Type Approximate but statistically valid Exact and consistent


QUESTION BANK

Module 1

1. Explain the difference between the input() and print() functions in Python. Write a program
that prompts the user for their name and age, and then displays a message including both.

2. Write a Python program that calculates the square root of a number entered by the user using
the math.sqrt() function. Also, explain how the math.sqrt() function works.

3. What are formatted strings (f-strings) in Python? Write a program that takes the radius of a
circle from the user and prints the area of the circle using a formatted string to display the
result rounded to 2 decimal places.

4. How does the math.pow() function work? Write a Python program that calculates and prints the
result of raising a number to a power (both provided by the user) using the math.pow() function.

5. Why is it necessary to convert user input from the input() function into numeric types such as
int or float when performing calculations? Illustrate this by writing a Python program that
takes two numbers from the user and prints their sum.

6. Write a Python program that calculates compound interest. The program should ask for the
principal amount, rate of interest, time (in years), and number of times the interest is
compounded per year.

7. Explain how the math.factorial() function is used. Write a program that takes a positive
integer from the user and prints its factorial.

8. Arithmetic Operators
✔ What is the difference between the division (/) and floor division (//) operators in

Python? Provide examples.


✔ Write a Python program to calculate the remainder of 17 divided by 5 using the
modulus operator.
9. Assignment Operators
✔ Explain how the += operator works with an example. How does it differ from the
simple = assignment operator?
✔ Write a Python program to multiply a variable by 4 using an assignment operator.

10. Comparison Operators


✔ How does the != comparison operator work? Provide an example where this operator
returns True.
✔ Write a Python program that checks whether two numbers are equal and print the
result.
11. Logical Operators
✔ Explain the difference between the and, or, and not logical operators in Python with
examples.
✔ Write a Python program that combines two conditions using the and operator and
prints the result.
12. Bitwise Operators
✔ What is the result of performing a left shift (<<) operation on the number 5 by 2 bits?
Explain with an example.
✔ Write a Python program to demonstrate the use of the bitwise AND (&) operator on
two numbers.
13. Membership Operators
✔ How does the in operator work with lists? Write a Python program to check if a
number is present in a list.
✔ Explain the result of the not in operator when applied to a string. Provide an
example.
14. Identity Operators
✔ What is the difference between the is and == operators in Python? Provide an
example to illustrate the difference.
✔ Write a Python program to check if two variables point to the same object using the
is an operator.

15. Define problem-solving strategies. Explain their significance in tackling real-


world problems.

16. Compare and contrast trial-and-error and heuristics. Provide an example for
each.
17. Illustrate how means-ends analysis can be used to plan a family vacation with
constraints such as budget and time.
18. Explain the concept of backtracking with an example of solving a Sudoku puzzle.
19. A chess player needs to make a critical move. How can the backtracking approach
assist in determining the best move?
20. Describe a scenario where heuristics would be more efficient than trial-and-
error. Justify your reasoning.
21. How would you use problem decomposition to design a program for calculating
electricity bills based on units consumed?
22. Discuss the role of trial-and-error in debugging programs. Provide a real-world
example.
23. Your friend is learning to solve Rubik’s Cube puzzles. Suggest and explain a suitable
problem-solving strategy they could use.

24 . Walk through the six problem-solving steps to find the second smallest number
in a list of integers. (Important)
25. Formulate a model to calculate the total cost of an e-commerce order, including
product prices, taxes, and delivery charges. Explain each step.
26. Design an algorithm to check if a given number is a palindrome. Explain how testing
ensures the algorithm's correctness.
27. How would you evaluate the efficiency of a solution to a scheduling problem such as
minimizing waiting times?
28 . Outline the steps involved in writing and testing a program to calculate the area of a
triangle given its three sides.
29. Explain why understanding the problem is crucial in designing a successful algorithm.
Provide an example.
30. Create an algorithm for a library management system that tracks book issues and
returns.
31. How can the evaluation of a solution help improve subsequent problem-solving
efforts?
31. Define variables in Python. Write a Python program to find the average of three
numbers using variables.
32. Discuss the importance of using meaningful variable names in Python. Provide
examples of valid and invalid names.
33. Differentiate between numeric and string data types in Python. Provide examples to
illustrate your explanation.
34. Discuss the role of type conversion in Python. Provide examples using the int(),
float(), and str() functions.
35. Explain the purpose of the math module in Python. Write a program to calculate
the factorial of a number using the math module.
36. Create a Python program to calculate the area and perimeter of a rectangle using user
input.
37. Demonstrate the use of formatted output in Python to display a multiplication table
from 1 to 10.
38. Explain the purpose of the input() function in Python. Write a program that
takes two numbers as input and prints their sum.
39. List and explain the different types of operators in Python with examples.
40. Write a Python program to evaluate the expression (15 + 5) * 2 - 10 / 5 and
explain the role of operator precedence in the result.
41. What are membership operators in Python? Write a program to check if a specific
item exists in a list.
42. Write a Python program that demonstrates the use of bitwise operators by performing
AND, OR, and XOR operations on two integers.

Module 2

1. Write a case statement that will examine the value of the flag and print one of the following
messages, based on the value assigned to the flag.
2. Draw a flowchart to print the numbers that are divisible by 4 but not by 3 in a list of n positive
numbers.
3. Draw a flowchart to find the average mileage of a car in kilometres per litre after six fill-ups at
petrol pumps. Input data include the number of litres of diesel, the starting odometer reading,
and the odometer reading at each fill-up.
4. Light travels at 3 × 108 meters per second. A light-year is the distance a light beam travels is
one year. Write an algorithm that inputs a large distance value (in meters) and displays it in
light-years.

5.Draw a flowchart to find the sum of digits of a number

6.Explain what pseudocode is and how it is used in the development of algorithms.


7. Discuss the advantages of using pseudocode over actual programming code during the algorithm
design phase.

8.What are some key reasons why software engineers and programmers prefer using pseudocode
during the initial stages of software development?
9. How does using pseudocode contribute to better problem-solving and more effective algorithm
design?
10. Describe the concept of sequencing in pseudocode and give an example of how it is used in a
simple algorithm.
11. Explain the role of selection structures, such as the &quot;if-else &quot; and &quot;case &
quot; structures, in pseudocode and how they allow for decision-making.
12. Provide a detailed example of how an &quot;if-else &quot; structure can be used in pseudocode
to determine if a person is eligible to vote.
13. Explain the concept of a &quot;r&quot; loop in pseudocode and provide an example of how it
can be used to perform repetitive tasks.
14. Describe the function of a &quot;while&quot; loop in pseudocode and compare it to a
&quot;r&quot; loop.Provide an example to illustrate.
15. What is the repeat until; loop in pseudocode, and how does it differ from the while loop?
Provide an example.
16. Explain the significance and use of the start and end symbols in a flowchart. Provide an
example.
17. What does the &quot;Arithmetic Calculations symbol represent in a flowchart, and how is it
typically used?
18.Describe the purpose and usage of the &quot;Input/Output Operation symbol in a flowchart.
19.What is the function of the Decision (Selection) symbol in a flowchart, and how does it work?
20. In flowcharts, what is the purpose of the &quot;Module Name (Call)&quot; symbol, and when
would it be used?
21. What is the role of the &quot;For Loop (Hexagon) symbol in a flowchart? Explain how it
operates.
22. How do &quot;Flow-Lines work in a flowchart, and why are they essential for understanding
the process?
23. Describe the function of an &quot;On-Page Connector; symbol in a flowchart. How does it
improve readability?
24. What is an &quot;Off-Page Connector; in a flowchart, and when is it used?
25. How do symbols like ;Start/End, Decision; and Input/Output; differ in terms of their usage in
flowcharts, and why is it important to use the correct symbol?
26.Can you provide an example of how a flowchart might use both the &quot;Input/Output;symbol
and the ;Decision symbol together?
27. How does the use of the For Loop ; symbol (Hexagon) enhance the representation of repetitive
28. In a flowchart, what might happen if you incorrectly use a Decision symbol where a Start/End
symbol is required?
29. How can the use of On-Page and &quot;Off-Page Connectors improve the flowchart structure
in larger processes?
Module 3
1) Explain the functionality and syntax of the if-else statement in Python. Provide an example
where you check whether a number is even or odd.

2) What is the difference between if-else and elif in Python? Provide an example using if-elif-
else to categorize age into child, teenager, or adult.

3) How does the for loop work in Python? Explain how it can be used to iterate over a list.

4) What is the role of the range() function in Python? Provide an example where range() is used
in a for loop to print numbers from 1 to 5.

5) Describe how the while loop works in Python. Provide an example of using a while loop to
print numbers from 1 to 5.

6) What is a list in Python? Describe its characteristics and provide an example of how to create
and manipulate a list.
7) How is a tuple different from a list in Python? Explain its characteristics and provide an
example of creating a tuple.

8) What is a set in Python? Describe its properties and provide an example of creating and
modifying a set.

9) How are strings in Python treated as a sequence data type? Provide an example of string
manipulation, such as slicing or concatenation.

10) What is a dictionary in Python? Explain its characteristics and how to create and access
elements in a dictionary.

11) What is the purpose of using the Numpy library in Python, and how do you create an array
using Numpy?

12) How does Numpy's arange() function work? Provide an example of creating an array with
a specific range of numbers.

13) What is the difference between a Python list and a Numpy array in terms of performance
and functionality?

14) How can you perform element-wise addition of two Numpy arrays? Provide an example.

15) What are some common Numpy array methods for reshaping and accessing array elements?

16) What is problem decomposition, and why is it an effective strategy for solving complex
problems in programming?

17) Explain the concept of modularization in programming and how it helps in organizing
complex software systems.

18) What are some motivations for modularization in software development? Discuss at least
three reasons why modularization is beneficial.

19) How can you define a function in Python? Provide an example that defines a function to
calculate the square of a number.

20) What is the purpose of using functions in Python, and how do they contribute to
decomposition and modularization?
21) How do you define and return multiple values from a function in Python? Provide an
example where a function returns both the sum and the product of two numbers.

22) When returning multiple values from a function, how can you access the individual values?
Provide an example where the returned tuple is unpacked into separate variables.

23) What is the advantage of using functions that return multiple values in Python? How does
this support the concepts of modularization and decomposition?

24) How would you design a function to calculate the area and perimeter of a rectangle, and
return both values?

25) Can functions that return multiple values be used to simplify complex programs? Give an
example where this approach is beneficial in a real- world problem.

26) What is recursion in programming? Define it and provide an example to demonstrate how
it works. Recursion in programming is a technique where a function calls itself in order to solve
a

27) What are the key components of a recursive function? Explain the base
case and recursive case with an example.

28) Why is recursion an important concept in programming? What are the benefits of using
recursion to solve certain types of problems?

29) How does recursion work with the call stack in a programming language like Python?
Explain the role of the call stack in managing recursive function calls.

30) What is the "call stack" and how does it relate to recursion? What happens if the recursion
goes too deep?

32) How can we avoid stack overflow in recursion? Provide suggestions to prevent infinite
recursion or excessive depth.

33) What is tail recursion? How does it differ from regular recursion, and why can it be more
efficient in some programming languages?

34) What does "avoiding circularity" in recursion mean, and how can circular recursion be
prevented?
35) Provide a real-world scenario where recursion is a useful approach, and explain how the
recursive solution is implemented.

36) Write a recursive function to find an array's minimum and maximum 4 elements. Your method
should return a tuple (a, b), where a is the minimum element and b is the maximum.

37) Write a program to input a matrix and determine its type: lower triangular, 4 upper triangular,
or diagonal.

38) Write a program to read N words and display them in the increasing order of 4 their lengths.
The length of each word is also to be displayed.

39) There are 500 light bulbs (numbered 1 to 500) arranged in a row. Initially, they are all OFF.
Starting with bulb 2, all even numbered bulbs are turned ON. Next, starting with bulb 3, and
visiting every third bulb, it is turned ON if it is OFF, and it is turned OFF if it is ON. This procedure
is repeated for every fourth bulb, then every fifth bulb, and so on up to the 500th bulb. Devise an
algorithm to determine which bulbs glow at the end of the above exercise.

Module 4
1) What is the Brute-force approach in problem-solving? Explain its characteristics, advantages,
and disadvantages.

2) Discuss the Divide-and-conquer approach in problem-solving. How does it work, and provide
an example of a problem that can be solved using this technique.

3) What is Dynamic Programming (DP), and how does it differ from other approaches like
Divide-and-conquer?

4) What is a Greedy Algorithm? Explain the core idea behind the greedy approach and give an
example where this approach works well.

5) What is the Randomized Approach in problem-solving? Explain how randomness is


incorporated into the solution process and provide an example of a problem that uses this
technique.
6) Compare the Brute-force approach and the Greedy Algorithm approach in terms of efficiency
and suitability for different types of problems.

7) When should you use the Divide-and-conquer approach over the Dynamic Programming
8) How does Dynamic Programming optimize solutions compared to the Brute-force approach?
Provide an example.

9) Discuss how Randomized algorithms can help in reducing the complexity of certain problems.
Provide an example of a problem where a randomized approach can significantly improve
performance.

10) What is the main difference between Divide-and-conquer and Dynamic Programming in
terms of problem-solving approach?

11) Write a python program to create a 3x3 matrix named A with random integer values between
1 and 10.

12) Write a Python program for a random walk simulation in a 2D grid starting from the origin (0,
0).At each step, randomly move up, down, left, or right. Print the final position after 10 steps.

MODEL QUESTION BANK

1) Draw a flowchart to find the sum of digits of a number

2) Write a python program to find the sum of elements in list.

3) Write a python program Create a list named vegetables with the elements” Tomato”, ”Potato” ,
”Onion”, and ”Cucumber”. Print the second element of the list. Update the last element in the list
to “Cabbage”. Print the modified list.

4) Briefly explain the steps in divide and conquer approach.

5) Write a python program to check whether the given number is prime or not.

6) Give the characteristics of Set.

7) Explain brute force approach with a real life example.

8) Use divide and conquer approach to find the smallest element in the array given.
[23,78,56,34,10,97,5]
10) A mad scientist wishes to make chain out of plutonium and lead pieces. There is a problem,
however. If the scientist places 2 pieces of plutonium next to each other, BOOM! The question is,
in how many ways can the scientist safely construct a chain of length n?

11) Write a python program that takes a number as input from the user and finds the factorial of
that number using a while loop.

12) For the given figure, find the shortest path from the source to destination using greedy
algorithmic approach with detailed explanation

13) How do you use a decomposition strategy to design a menu-driven calculator that supports
four basic arithmetic operators - addition, subtraction, multiplication, and division?

14)Draw a flowchart to print the numbers that are divisible by 4 but not by 3 in a list of n positive
numbers.
15) Give the pseudocode for brute force technique to find the mode of elements in an array. Mode
is the value that appears most frequently in the array.

16) Your professor has given you an assignment on “Algorithmic thinking” to be submitted by this
Wednesday. How do you employ means-end analysis to devise a strategy for completing your
assignment before the deadline?
17) Mr. Shyam, a history professor, would like to know the percentage increase in the population
of our country per decade given the first decade and the last decade. Other given data include the
population at the beginning of each decade. Draw a flowchart for determining the percentage
increase in the population.

18) Draw a flowchart to find the average mileage of a car in kilometers per litre after six fill-ups
at petrol pumps. Input data include the number of litres of diesel, the starting odometer reading,
and the odometer reading at each fillup.

19) Light travels at 3 × 108 meters per second. A light-year is the distance a light beam travels in
one year. Write an algorithm that inputs a large distance value (in meters) and displays it in light-
years.

20) Use divide and conquer to find the majority element in an array, where the majority element
appears more than n/2 times. Divide the array into two halves, find the majority element in each
half, and combine the results to identify if there is a majority element in the entire array.

21) Write a program to read N words and display them in the increasing order of their lengths. The
length of each word is also to be displayed.

22) There are 500 light bulbs (numbered 1 to 500) arranged in a row. Initially, they are all OFF.
Starting with bulb 2, all even numbered bulbs are turned ON. Next, starting with bulb 3, and
visiting every third bulb, it is turned ON if it is OFF, and it is turned OFF if it is ON. This procedure
is repeated for every fourth bulb, then every fifth bulb, and so on up to the 500th bulb. Devise an
algorithm to determine which bulbs glow at the end of the above exercise.

23) Walk through the six problem-solving steps to find the largest number out of three numbers.

24)Name two current problems in your life that might be solved through a heuristic approach.
Explain why each of these problems can be solved using heuristics.
25) A standard science experiment is to drop a ball and see how high it bounces. Once the
“bounciness” of the ball has been determined, the ratio gives a bounciness index. For example, if
a ball dropped from a height of 10 feet bounces 6 feet high, the index is 0.6, and the total distance
travelled by the ball is 16 feet after one bounce. If the ball were to continue bouncing, the distance
after two bounces would be 10 ft + 6 ft + 6 ft + 3.6 ft = 25.6 ft. Note that the distance travelled for
each successive bounce is the distance to the floor plus 0.6 of that distance as the ball comes back
up. Write an algorithm that lets the user enter the initial height of the ball, bounciness index and
the number of times the ball is allowed to continue bouncing. Output should be the total distance
traveled by the ball.

You might also like