Ucest105-Algorithmic Thinking With Python Notes
Ucest105-Algorithmic Thinking With Python Notes
SEMESTER S1
ALGORITHMIC THINKING WITH PYTHON
Teaching Hours/Week
3:0:2:0 ESE Marks 60
(L: T:P: R)
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).
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).
* 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
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
1 https://opentextbc.ca/h5ppsychology/chapter/problem-solving/
2 https://onlinecourses.nptel.ac.in/noc21_cs32/preview
1. Algorithm (2 Marks)
Algorithm Development: Correctness and efficiency of the algorithm related to the question.
2. Programming (3 Marks)
3. Result (3 Marks)
Proficiency in answering questions related to theoretical and practical aspects of the subject.
LAB Experiments:
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.
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.
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:
Increased Efficiency:
By understanding a wider range of strategies, individuals can identify the most efficient
approach, leading to faster and more effective problem resolution.
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.
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.
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:
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 :
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
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 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.
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.
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 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.
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:
Disadvantages:
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:
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
Python is a high-level, interpreted programming language known for its simplicity and versatility.
Its key features include:
● Simple and Readable Syntax: Code is easy to understand and resembles pseudocode or
algebraic notation.
$ python3
'Hello, Python!'
● 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.
$ python3 sample.py
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.
Keywords
● 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
Statements
● Types:
In Python, I/O (Input/Output) operations are handled primarily with built-in functions like
print() for output and input() for input.
Example 1:
Example 2:
>>> a = 7
Example 3:
>>> x=5
>>> y=3
Example 4:To receive input from the user, Python provides the input() method which accepts the
input as a string.
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
import math
print(math.e) # e: 2.71828…
math.tau τ = 2π ≈ 6.28318...
Operators in Python
• Arithmetic Operators
• Assignment Operators
• Logical Operators
• Bitwise Operators
• Membership Operators
• Identity 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
Chaining is allowed:
x < y <= z → True only if both conditions are True.
● Logical Operators
a. Logical AND
b. Logical OR
c. Logical NOT
Example:
>>> ~98
Output: -99
>>> ~102
Output :-103
>>> 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.
• 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 :
Output: True
Output:False
Output: True
MODULE 2
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
1. Start.
3. Multiply b by c.
6. Display d.
7. End.
Pseudocode:
START
INPUT a, b, c
temp ← b * c
d ← a + temp
PRINT d
STOP
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
Syntax:
IF (condition)
true_instructions
ELSE
false_instructions
ENDIF
Syntax:
IF (cond1)
block1
ELSE IF (cond2)
block2
ELSE
block_default
ENDIF
Checks conditions in order, executes the first matching block (or else case).
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
Syntax:
WHILE (condition)
loop_instructions
ENDWHILE
Syntax:
REPEAT
loop_instructions
UNTIL (condition)
Syntax Variants:
instructions
ENDFOR
instructions
ENDFOR
ENDFOR
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:
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.
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
READ A
READ B
SUM = A + B
DISPLAY "Sum is:", SUM
END
Algorithm:
1.Start
5.Stop
Flowchart:
Pseudocode:
BEGIN
READ A,B,C
SUM = A + B+C
END
Algorithm:
1. Start
4. Print SI
5. Stop
Flowchart:
Pseudocode:
Start
Input principal
Input rate
Input years
Stop
Algorithm:
1. Start
Flowchart:
Pseudocode:
Start
Read(a, b)
if (a > b)
large = a
Else
large = b
endif
Print(large)
Stop
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
Grade Message
R Red
G Green
B Blue
d = a + (b * c)
d=2+3*4
d = 2 + 12
d = 14
d=a+b^c
d=2+3^4
d=2+81
d=83
Invalid Expression
d=1+2*6/3-(4-2)
d=1+12/3-2
d=1+4-2
d=5-2
d=3
RAPTOR
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.
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
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
>>> 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
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:
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
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
# 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.
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
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
Brute-force approach exhaustively checks all possible solutions to find the best one; it's simple
but often inefficient for large problems.
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.
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.
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.
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.
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.
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.
Steps:
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:
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.
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.
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.
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 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.
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:
3. Overlapping Subproblems:
● 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
2. Optimal Solutions:It guarantees finding the optimal solution when the problem exhibits
optimal substructure.
1. High Memory Usage:Storing solutions for many subproblems can require large amounts
of memory, especially for problems with large state spaces.
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.
Comparison of Dynamic Programming with Divide and Conquer and Greedy Algorithms
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
Use Case Examples Shortest path, Merge sort, Activity selection, coin
knapsack, matrix quicksort, binary change (with greedy
chain multiplication search denominations),
Huffman coding
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.
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.
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.
● 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.
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.
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.
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.
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.
● 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.
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?
● 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.
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.
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?
● 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.
1. Set the total correct matches (people who get their own hats) to zero.
○ Create a list representing hats for each person (e.g., [1, 2, 3, ..., n]).
○ Count how many people have their own hat (positions where the hat number
matches the person number).
4. This average is the expected number of people getting their own hats back.
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
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
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.
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 "if-else " and "case &
quot; structures, in pseudocode and how they allow for decision-making.
12. Provide a detailed example of how an "if-else " structure can be used in pseudocode
to determine if a person is eligible to vote.
13. Explain the concept of a "r" loop in pseudocode and provide an example of how it
can be used to perform repetitive tasks.
14. Describe the function of a "while" loop in pseudocode and compare it to a
"r" 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 "Arithmetic Calculations symbol represent in a flowchart, and how is it
typically used?
18.Describe the purpose and usage of the "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 "Module Name (Call)" symbol, and when
would it be used?
21. What is the role of the "For Loop (Hexagon) symbol in a flowchart? Explain how it
operates.
22. How do "Flow-Lines work in a flowchart, and why are they essential for understanding
the process?
23. Describe the function of an "On-Page Connector; symbol in a flowchart. How does it
improve readability?
24. What is an "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 "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 "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.
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.
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.
5) Write a python program to check whether the given number is prime or not.
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.