Py Module 4
Py Module 4
JOIN WITH US
Program:
# Brute-force approach to unlock a padlock
def padlock(key):
for combination in range(1000): # 000 to 999
guess = f"{combination:03}" # Format to always have 3 digits
print(f"Trying combination: {guess}")
if guess == key:
print(f"Padlock opened with combination: {guess}")
break
# Example usage
padlock(“302”)
2. Password Guessing
Here, 95 is the total number of possible characters (26 lowercase + 26 uppercase + 10 digits +
33 special characters), and 956 gives the total number of possible combinations for a 6-
character password.
# Example usage
password = "CaT" # Set the correct password
check_password(password)
Divide and Conquer Algorithm
Divide and Conquer Algorithm is a problem-solving technique used to solve problems by dividing the
main problem into sub-problems, solving them individually and then merging them to find solution to
the original problem.
The Divide and Conquer algorithmic approach consists of three main steps: Divide,
Conquer, and Merge.
1. Divide
Goal: Break down the original problem into smaller, more manageable subproblems.
Each subproblem represents a part of the overall problem.
The division continues recursively until the subproblems are simple enough to solve
directly.
2. Conquer
Goal: Combine the solutions of the subproblems to form the solution to the original
problem.
Once the subproblems are solved, their solutions are recursively merged to resolve the
overall problem efficiently.
The Divide and Conquer algorithm can efficiently find the maximum element in an array by
following these steps:
Divide:
Conquer:
Once the subarrays have only one element, return that element as the maximum of
that subarray.
Merge:
Compare the maximums of the two halves and return the larger value as the maximum
for the combined array.
Python Program:
if left == right:
return arr[left]
Merge Sort
Ref: Notes/Video : Module 3 – Part – 8
1. Divide: The array is divided into two halves recursively until each subarray has one
element.
2. Sort: Each sub-array is sorted individually.
3. Merge: The sorted sub-arrays are merged to produce a single sorted array.
def fib(n):
if n <= 1:
return n
else:
return(fib(n-1) + fib(n-2))
n = int(input("Enter n :"))
if n <= 0:
else:
print("Fibonacci sequence:")
for i in range(n):
Fib(5)
Fib(4) Fib(3)
Fib(2) Fib(1)
Fib(3) Fib(2)
Fib(1) Fib(0)
Fib(2) Fib(1) Fib(1) Fib(0)
Fib(1) Fib(0)
Even though the recursive approach is intuitive, it often leads to increased time complexity.
This is because the same subproblems are repeatedly calculated, resulting in redundant
computations and inefficient performance.
Definition:
Memoization solves the problem recursively while storing the results of subproblems
in a table (often a dictionary or array).
Process:
o When a subproblem is solved, its result is saved.
o If the same subproblem is encountered again, the solution is retrieved from the
table instead of being recalculated.
2. Tabulation (Bottom-Up Approach)
Definition:
Tabulation solves the problem iteratively by filling a table (usually an array) in a
bottom-up manner.
Process:
o Starts by solving the smallest subproblems.
o Uses their solutions to iteratively build solutions for larger subproblems.
Advantages:
o Eliminates recursion overhead.
o Suitable for problems where all subproblems need to be solved.
def fib(n):
return array
Using a bottom-up
Recursion frequently employs
methodology, dynamic
a top-down method in which
programming starts by
the primary problem is broken
resolving the smallest
down into more manageable
subproblems before moving
subproblems.
Approach on to the primary issue.
Memory Usage
Features Recursion Dynamic Programming
Let’s consider the task of traveling from Thiruvananthapuram to Ernakulam with multiple
available modes of transport: bike, car, bus, train, airplane, or even walking. The goal is to
select the mode of transport based on a set of constraints or objectives:
At each step, the greedy algorithm makes the locally optimal choice by filtering options
based on the immediate constraint.
Available Options:
o Bike: 6 hours
o Car: 4.5 hours
o Bus: 5 hours
o Train: 3.5 hours
o Airplane: 1 hour
o Walking: 40 hours
Greedy Decision:
Select modes that satisfy the time constraint of reaching as quickly as possible. The airplane
(1 hour) is the fastest, followed by the train (3.5 hours).
Filter Outcome:
Airplane, Train.
Stage 2: Minimize Cost (Economical Constraint)
Greedy Decision:
Select the train as it is cheaper and satisfies the economical constraint.
Filter Outcome:
Train.
Final Decision
The algorithm selects the train as the final mode of transport based on:
Locally Optimal Choices: At each step, the algorithm optimizes for the immediate
constraint (time, cost, practicality) without revisiting earlier decisions.
Global Solution: The sequence of locally optimal decisions leads to an efficient and
practical final solution.
The Coin Changing Problem aims to find the minimum number of coins required to
make a specified amount using valid Indian Rupee coins. The greedy algorithm
repeatedly selects the largest denomination that fits into the remaining amount.
1. Start with ₹10: Take one ₹10 coin (₹18 - ₹10 = ₹8 left).
2. Next, ₹5: Take one ₹5 coin (₹8 - ₹5 = ₹3 left).
3. Next, ₹2: Take one ₹2 coin (₹3 - ₹2 = ₹1 left).
4. Finally, ₹1: Take one ₹1 coin (₹1 - ₹1 = ₹0 left).
1. Local Optimization
o Makes the best possible choice at each step using only current state
information.
2. Irrevocable Decisions
o Choices are final; no backtracking or revision of earlier decisions.
3. Problem-Specific Heuristics
o Relies on heuristics tailored to the problem's properties for decision-making.
4. Optimality
o
Guarantees optimal solutions for problems like coin change, Huffman coding,
and Kruskal's algorithm, but not universally applicable.
5. Efficiency
o High efficiency in time and space due to reliance on local information and
limited exploration of solutions.
Pr o b l e m ( T as k C o m p l eti o n Pr o bl e m )
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.
Steps:
• Iterating:
breaks.
total_time = 0
task_count = 0
total_time += time
task_count += 1
else:
break
return task_count
# Example usage
completion_times = [2, 3, 1, 4, 6]
available_time = 8
Advantages
Disadvantages
Not guaranteed to find the best solution: Greedy algorithms may not find the best
solution because they don't consider all the data.
Local optima: Greedy algorithms may get stuck in local optima and fail to find the
global optimum.
Dependence on problem structure: Greedy algorithms may not work well for
problems that don't fit the greedy paradigm.
Lack of rigorous proof: Greedy algorithms often lack a rigorous proof of correctness
Python's random module provides various functions to generate random numbers and perform
random operations. Here's an overview of the most commonly used functions:
import random
print(random.random()) # e.g., 0.7234
random.uniform(a, b)
o Returns a random float between aaa and bbb (inclusive).
o Example:
random.randint(a, b)
o Returns a random integer between aaa and bbb (both inclusive).
o Example:
random.choices(sequence, k=n)
o Returns a list of n random elements (with replacement).
o Example:
random.shuffle(sequence)
o Shuffles the sequence in place.
o Example:
nums = [1, 2, 3, 4, 5]
random.shuffle(nums)
print(nums) # e.g., [3, 1, 5, 2, 4]
Randomized Approach to Problem Solving
Introduction
Calculating the area of a unit circle using a randomized approach involves Monte Carlo
simulation, a technique often used in numerical methods to estimate values through random
sampling.
Steps to Calculate the Area of a Unit Circle Using Monte Carlo Simulation:
import random
num_points = 100000
inside_circle = 0
for _ in range(num_points):
x = random.uniform(-1,1)
y = random.uniform(-1,1)
inside_circle = inside_circle + 1
area_of_circle = 4 * (inside_circle/num_points)
import random
head=0
tail=0
for _ in range(n):
if random.randint(0,1)==1:
head=head+1
else:
tail=tail+1
Q. Toss the coin N=100,500,1000,5000 and 500000 times and compute the
probability (p) of head in each case.
import random
n = [100,500,1000,5000,500000]
prob=[]
count=0
while(count<len(n)):
head=0
tail=0
for i in range(n[count]):
if random.randint(0,1)==0:
head=head+1
else:
tail=tail+1
p=head/(head+tail)
prob.append(p)
count=count+1
print(prob)
1. Complexity Reduction
o Simplifies problems by introducing probabilistic decisions.
o Example: Optimizing screening station placements by sampling.
2. Versatility
o Adaptable across domains like optimization and simulations.
3. Performance
o Enhances efficiency in large datasets.
Characteristics of Randomized Approach
1. Probabilistic Choices
o Decisions are based on random sampling, leading to variable but statistically
predictable outcomes.
2. Efficiency
o Sacrifices deterministic guarantees for probabilistic correctness.
3. Average-Case Complexity
o Performance is evaluated over multiple iterations, focusing on typical behavior
rather than worst-case scenarios.
Randomized:
o Incorporates chance.
o Efficient for large, complex, or variable datasets.
o Example: Estimating customer satisfaction via random sampling.
Deterministic:
o Yields exact, repeatable results.
o Suitable for precision-critical problems.
o Example: Exact cost calculation in shopping.
Problem Details
Mr. Chottu has a bag with a capacity of 15 kg, and he plans to fill it with objects to maximize his
profit as he heads to the market for a sale. He has the following items with their respective weights
and profits:
Steps:
Weight
Object Profit (P) P/W
(W)
1 10 2 5.0
2 5 3 1.67
3 15 5 3.0
4 7 7 1.0
5 6 1 6.0
6 18 4 4.5
7 3 1 3.0
Sorted by P/W (Descending):
5 6 1 6
1 10 2 5
6 18 4 4.5
3 15 5 3
7 3 1 3
2 5 3 1.67
4 7 7 1
Execution: