Skip to content

Latest commit

 

History

History
6601 lines (3412 loc) · 93.9 KB

File metadata and controls

6601 lines (3412 loc) · 93.9 KB

Weclome to Learning Python with me

Table of Contents


1-basics

Python is case-sensitive.

print("Hello World")

print() is used to display output. like print("Tea") or print(10)

print("Hello", "World") // Multiple values can be printed together.

Comments

Use # for comments

and to selected multiple block of code just click Ctrl + /

in terminal to use the previous command just press upwards arrow (↑)

  • python is case senstive meaning print() will work but not Print()

2-variables

A variable stores a value.

For ex name = "Sarbesh" age = 26 cgpa = 8.5

Here: name → variable "Sarbesh" → value = → assignment operator

The assignment operator stores the value on the right-hand side in the variable on the left-hand side.

**Variables can be updated and the new variable will get printed **
age = 26
age = 25.5

print(age)

Output: 25.5

  • to print varibales just type print(variable name) and no need of " " even its a string

variable code-

name = "Sarbesh"

first_name = "Sarbesh"
last_name = "Mallick"
age = 26 
gender = "male"
eye = "brown"
age = 100


print(first_name)
print(age)

print(gender , eye)

print(30)

Output- Sarbesh 100 male brown 30


3-data-types

Python hv 4 primitive data types

Type Meaning Example
str String / text "Sarbesh"
int Integer / whole number 25
float Decimal number 8.5
bool Boolean True / False

Examples:

name = "Sarbesh" # str age = 25 # int cgpa = 8.5 # float isStudent = True # bool

Boolean values must use capital letters: True False

Not: true and false. Python is case-sensitive.

Checking the data type-

Use the built-in type() function:

print(type(variable name))

type check code-

name = "Sarbesh"
age = 26
balance = 0.5 
gender = "Male"


print (type(name))

print (type(age))

print (type(balance))

Output- <class 'str'> <class 'int'> <class 'float'>

  • type() tells us what type of value a variable currently contains.
  • in c++ we need to define our variable values like for numbers int num = 5; but in python we can just declare it as it is without typing int
  • to check what the type is we do print(type(variable name))

4-input

Use input() to take input from the user.

name = input("Enter your name: ") print(name)

Eg code-

name = input("What is ur name: ")
profession = input(" what is ur job: ")
age = input("what is ur age: ")

print(name)
print(profession)
print(age)

Input What is ur name: Sarbesh what is ur job: Dev what is ur age: 16

Output Sarbesh Dev 16

6. Input code-

name = input("Enter your name: ")

print("Namaste" , name)

Input Enter your name: Sarbesh

Output- Namaste Sarbesh

Tip-

suppose my python filename is 6. Input.py

  • if we run this program using python filename.py then remember as I have used spaces so use " " in terminal
  • like for example- python "6. Input.py"

Important-

input() always returns a string.

Even if the user enters: 23 Python initially stores it as: "23"
not: 23

input returns a string even it is a number(int)


Concatenation

concatenation means joining strings.

for eg:

name = "Sarbesh" print("Hello " + name)

Output- Hello Sarbesh

Remember Be careful about spaces:

"Hello" + name produces: HelloSarbesh

"Hello " + name produces: Hello Sarbesh

  • The + operator can concatenate strings.

7. concatenation code-

name = input("Enter your name: ")

print("Hello Namaste " + name)

Input Enter your name: Sarbesh

Output- Hello Namaste Sarbesh


5-First-Exercise

Problem stat- Add a person with first name as Tony and last name as Stark. Tony's age is 53. Tony's height is 1.85m. Tony is secretly a superhero. Take his superhero name as input & print it. -->

first_name = "Tony"
last_name = "Stark"

age = 53
height = 1.85 

name = input("What is his superhero name: ")
print(name)

Input What is his superhero name: Ironman

Output Ironman


6-Type-Casting-or-Type-Conversion

Because input() returns a string, we often need to convert it before performing calculations.

type casting -> when coders changes type conversion -> python interpreter automatically does it

Common conversion functions: int() float() str() bool()

type casting code-

age = input("Enter your age: ")

print (type(age))

print (age)

Input Enter your age: 23

Output <class 'str'> 23

Note-

  • print (age + 1) ❌ // we cannot do this as age varibale value i.e 23 is passed as string
  • see the age varibale is passed as string and not integer although 23 is an integer
  • if we have to do print (age + 1) we cannot do that
  • it will show TypeError: can only concatenate str (not "int") to str

Solution for this

age = input("Enter your age: ")

age = int(age)

print (type(age))

print (age)
print (age + 1)

Input Enter your age: 23

Output <class 'int'> 23 24

  • Now age is an integer

Temporary Conversion vs Reassignment

Let's understand this with code-

old_age = input("Enter ur age: ")

new_age = int(old_age) + 2

print(new_age)

print (float(new_age))                              # here we converted int to float. It is not conversion but Reassignment 

print (type(new_age))

Input- Enter ur age: 23

Output- 25 25.0 <class 'int'>

Note-

  • if u wondering after converting from int to float why class is int and not float because its a temporaray expression
  • new_age = float(new_age) // to convert it permanently

type casting -> when coders changes type conversion -> python interpreter automatically does it

Conversion examples- print (1 + 1.5) // python converts 1 into 1.0 and will give you answer in float (decimal)

Temporary Conversion-

age = 23

print(float(age)) print(type(age))

Output: 23.0 <class 'str'>

  • float(age) converted the value for that expression, but age itself remained a string.

Permanent conversion / reassignment-

age = 23

age = float(age)

print(age) print(type(age))

Output 23.0 <class 'float'>

  • The converted value was assigned back to age.

Easy rule

print(float(age)) → temporary conversion

  • converts the value for that expression; original variable remains unchanged.

age = float(age) print(age) → conversion + reassignment

  • converts the value and stores the converted value back in the variable.

You do not always need a new variable.

For example:

print(float(age) + 1.2)

is perfectly valid when you only need the converted value temporarily.

Another code eg-

age = input("enter age: ")

print(float(age) + 1.2)

print(type(age))

Input enter age: 23

Output 24.2 <class 'str>

Temp conversion- print(float(age)) // age is still a string

Permanent Conversion- age = float(age) // age is now float

OR

age = input("enter age: ") age = print(float(age) + 1.2) print(type(age))

Input enter age: 23

output 24.2 <class 'NoneType'>

  • variable e like for example age = ... amra bina brackets chara use korte pari if we add smthng + Eg- age = age(...) + 1
  • but print e puro print statement ta bracket under e mane print(... + 1 )

Explicit vs Implicit Conversion

Implicit Conversion-

Python automatically performs a compatible conversion.

Example:

print(1 + 1.5)

Result: 2.5

  • the above code is implicit is automatically converted by python intepreter, 1 (int) is converted into 1.0(float)
  • Python promotes the integer to a float during the operation.

Explicit Conversion / Type Casting-

The programmer tells Python to convert the type.

example:

print(1 + int(2.9999))

// Python is explicitly told to convert 2.9999 to an integer.

Result: 3

another example: print (1 + int(1.5))

  • this is type casting
  • here in the above code we forced (type casting) 1.5 to become int and it got casted into 1 , so 1 + 1 = 2
  • Result: 2

Summary

print (1 + 2.9999) // Type Conversion (implicit) // Result: 3.9999 print (1 + int(2.9999)) // Type Casting (explicit) // Result: 3


7-Second-Exercise

Problem stat- Sum Program where a , b are integers nos & we hv to take input and calc the sum of a & b and print it

a = input("Enter first number: ")

b = input("Enter second number: ")

total = int(a) + int(b)                                                         

print("the sum is" , total)           

print(type(a))
print(type(total))

Input Enter first number: 10 Enter second number: 20

Output the sum is 30 <class 'str'> <class 'int'>

Alt a = int(input("Enter first number: "))
b = int(input("Enter second number: ")) total = (a + b) print(total)

OR

a = int(input("Enter 1st number: "))
b = int(input("Enter 2nd number: "))

num_sum = a + b                                     

print("The sum of 2 integers is" , num_sum)

print(type(num_sum))
print(type(a))

Input Enter 1st number: 10 Enter 2nd number: 20

Output The sum of 2 integers is 30 <class 'int'> <class 'int'>


8-string-operations

  • Strings have useful methods
  • Strings are immutable.
  • String operations do not modify the original string rather a new string is produced.

Summary

  1. .upper() and .lower()

name = "Sarbesh Mallick" print(name.upper()) print(name.lower())

Output SARBESH MALLICK sarbesh mallick

  1. find()

name = "Sarbesh Mallick" print(name.find("h")) // only works for one character or substring. Gives result in index or position

output 7

  1. replace()

name = "Sarbesh Mallick" print (name.replace("S" , "Z")) print (name.replace("Mallick" , "Batman")) print (name.replace("Sarbesh Mallick" , "Muthu")) print(name.replace("besh" , "M"))

output Zarbesh Mallick Sarbesh Batman Muthu SarM Mallick

  1. in

name = "Sarbesh" print("b" in name)

output True

number = [10, 20, 30] print(40 in number)

output False

  1. not in

number = [10, 20, 30] print(40 not in number)

output True

let's understand from code-

name = "Sarbesh Mallick "

print (name)

print (name.upper())

print (name.lower())

print (name)                                              //  the original value is intact 

Output- Sarbesh Mallick SARBESH MALLICK sarbesh mallick Sarbesh Mallick // the original value is intact cuz strings are immutable


find() method

  • find() searches for a substring/character and returns its index/position.
  • returns no of the index and not boolean (true/false)

example:

name = "Sarbesh" print(name.find("b"))

Output: 3

  • If the character exists, Python returns its index.

  • If it doesn't exist: name.find("z")

    returns:
    -1
    

Summary: find() → tells you where something occurs. It does not simply return True or False.

Code-

name = "Sarbesh Mallick"

print (name.find('M'))                                          // if that string exists then we will get the index of that thing
                                               

Output- 8

Remember- find function returns index which is position if we search something that is not present we will get -1 as value which is invalid

replace() method

  • replace() is a string method, not a standalone function.

for eg:

name = "Sarbesh Mallick"
print(name.replace("Mallick", "Muthu"))

Result: Sarbesh Muthu

  • You call it using .replace()
  • variable_name.replace(...)
  • .replace()

example code-

name = "Sarbesh Mallick"

print (name.replace("Sarbesh Mallick" , "Muthu"))
print (name.replace("Mallick" , "Muthu"))                           
print (name.replace("S" , "D"))                                         // when we need to replace smthng partial 

Output- Muthu Sarbesh Muthu Darbesh Mallick

in operator

  • It is a check method.
  • it checks whether something exists or not and results comes in boolean (True/False)

for eg 1

name = "Sarbesh" print("S" in name)

Result: True

for eg 2 numbers = [10, 20, 30] print(20 in numbers)

Result: True

not in

for eg 3

numbers = [10, 20, 30] print(40 not in numbers)

Result: True

  • in is a keyword in python dictionary, we cannot use in as a varibale name, in operator job is to search

Note-

'S' in name → checks whether S exists anywhere

Whereas:

name.startswith('S') → checks whether the string starts with S.

Function vs Method

Functions are generally called independently:

print() input() int() float()

Methods are called on an object:

name.upper() name.lower() name.replace()

  • Functions → generally called independently: print(), input(), int()
  • Methods → called using . on an object: "hello".upper(), "hello".replace()

9-Third Exercise

Problem stat- Take price of 3 products as input (eg - 99.5, 23.75, 16.15) print the total Bill amount print the average price Take a superhero name as input & check if it starts with 'S' / 's' or not.

first_product = 101.55
second_product = 99.95
third_product = 15.15 


total = int(first_product) + int(second_product) + int(third_product)

print(total)

print ("the average price is: " , total/3)

name = input("What's your supehero name: ")

print (name.startswith('s') or name.startswith('S'))                            // Alt-    print(name.startswith(('s', 'S')))     OR       print(name.lower().startswith('s'))

Output- 215 the average price is: 71.66666666666667 What's your supehero name: Sarbesh True

  • if I want to check if S or s in the whole name is there or not then: print ('S' in name or 's' in name)

10-Operator

  • Arithmetic Operator
  • Comparison Operator
  • Logical Operator

Arithmetic Operator

  1. Arithmetic Operators-
Operator Name Example
+ Addition 5 + 2
- Subtraction 5 - 2
* Multiplication 5 * 2
/ Division 5 / 2
// Floor Division 5 // 2
% Modulus / Remainder 5 % 2
** Exponentiation / Power 5 ** 2

print (5 + 3) 8 // + is addition operator and 5,3 are operands print (5 - 3) 2 print (5 * 3) 15 print (5 / 3) 1.6667 // / is division operator

print (5 // 3) 1 // is floor division and completely removes the decimal part, only int part is left

print (5 % 3) 2 // % is modulus or remainder , very helpful to check even & odd

print (5 ** 3) 125 // ** is exponent of or power of 5³ = 555 = 125

  1. 5/2 -> 2.5

  2. 5 // 2 -> 2

    Very useful for checking even/odd: number % 2 == 0 → even number % 2 != 0 → odd

  3. 5 ** 2 -> 25 (5² = 25)


Operator Precedence

  • Python follows operator precedence rules when multiple operators appear in an expression.
  • just like in real life BODMAS is followed
  • operator precedence are rules that defines which operator has higher priority compared to other

For example:

2 + 5 * 3
= 17 Multiplication happens first

But parentheses have higher priority: (2 + 5) * 3 Result: 21

Basic rule to remember

  1. ()
  2. **
    • / // %
  • For operators with the same precedence, evaluation generally proceeds from left to right.

  • When in doubt, use parentheses to make the intended order clear.

  • if * and / both are present then our operations will start from Left -> Right

  • parantheses() have highest priority.


Operator comparison

  1. Comparison Operator

Comparison operators compare values and produce a Boolean result: TRUE or FALSE

Operator Meaning
> Greater than
< Less than
>= Greater than or equal to
<= Less than or equal to
== Equal to
!= Not equal to

Examples:

3 > 2 True

2 < 5 True

2 == 5 False

2 != 5 // != is NOT operator True

Remember-

= vs ==

= is the assignment operator: age = 25 It assigns a value.

== is the comparison operator: age == 25 It checks whether the values are equal.

= → assign == → compare

This distinction is extremely important in if statements.


Logical Operator

  1. Logical Operator
  • Python has three main logical operators: and or not
  1. and Both conditions must be true. (3 < 5) and (3 < 12) → True

Conceptually: True AND True → True True AND False → False False AND True → False False AND False → False

  1. or At least one condition must be true. (3 > 5) or (3 > 2) → True

Conceptually: True OR True → True True OR False → True False OR True → True False OR False → False

  1. not Reverses a Boolean value.

not True → False

not False → True

  • these operators work in statement or expression . for eg- () or ()

or -> (atleast one statement is true) and -> (both are true) not -> (reverses any value)

example

stt1 = 3 > 5 // False stt2 = 3 > 2 // True

print (stt1 or stt2) // Ans True (cuz one statement stt2 is True )

print ((3 > 5) or (3 > 2)) // we can write directly also

print ((3 < 5) and (3 < 12)) // Ans True (both the statements are true )

not operator always does the reverse

print (not(3 > 2)) // Ans False , although it is true but as it is not so False

print (not True) // Ans False


11-Conditional-statements

Conditional statements allow Python to make decisions.

  1. if
  2. elif
  3. else

example code1-

age = 24 

if age >= 18:                                  # in cpp we use {} where if statement is true execute everything inside it. in py it is :
    print("you are adult")                      # 4 spaces . this is called identation which is proper spacing 
    print("you can vote")

elif age < 18:                                  # elif is else if . after if everything we can write in elif 
    print ("you can't vote / drive")

output you are an adult you can vote

example code 2-

Problm stat- Marks are given out of 100. Assign a grade based on the marks: 80–100 → A 60–80 → B 60 → C write a Python program using if, elif, and else to determine and print the grade.

marks = int(input("Enter marks: "))

if marks >= 80:
    print('A')

elif marks >= 60:
    print('B')

else:
    print('C')

If you want to explicitly add into a range then follow this-

if marks >= 60 and marks <= 80: print("B")

  • marks should be greater than or equal to 60 AND less than or equal to 80.

Cleaner way- if 60 <= marks <= 80: print("B")

few more examples combing if/esle with operators

age = 25

if age >= 18 and age <= 60: print("Eligible")

name = input("Enter name: ")

if "S" in name or "s" in name: print("S exists") else: print("S does not exist")

This combines: in → membership operator or → logical operator if/else → conditional statement


12-mini-project-calculator

Problem stat- Build a calculator that can perform the following operations: a + b a - b a * b a % b a ** b

a = float(input("Enter first number: "))
b = float(input("Enter second number: "))

op = input("Enter operator (+, -, *, /, %, **): ")


if op == '+':
    print("Result:" , a+b)


elif op == '-':
    print("Result:" , a-b)


elif op == '*':
    print("Result:" , a*b)


elif op == '/':
    print("Result:" , a/b)


elif op == '%':
    print("Result:" , a%b)


elif op == '**':
    print("Result:" , a**b )


else:
    print("INVALID OPERATION")


13-loops

Range

  • range() function returns a range object that is a sequence of numbers. starts from 0

Structure for Range

range(start, stop, step)

range (start=0, stop, step=1) // default value if nothing specified. but we have to write a stop value everywhere

1 value -> stop // range(5)

2 value -> start & stop // range(2,6)

3 value -> start,stop,step // range(2,6,2)

Example 1-

num = range(5)
print(num) // 0,1,2,3,4

output of eg 1 range(0, 5)

Example 2- num = range(2, 6) print(num) // 2,3,4,5

output range(2, 6)

Example 3-

num = range(10, 0, -2) print(num) // 10, 8, 6, 4 , 2

output range(10, 0, -2)

we use range in loops like for i in range(5): print(i)


while-loop

while loop

Example 1

counter = 1 while counter <= 5: print("Sarbesh win") counter = counter + 1

Output: Sarbesh win Sarbesh win Sarbesh win Sarbesh win Sarbesh win

Example 2

counter = 1 // for counter we give variable name as i while counter <= 5: print(counter) counter = counter + 1

Output: 1 2 3 4 5

Example 3

i = 0

while i < 5: print(i) i = i + 1

Output: 0 1 2 3 4

  • if we encounter infinite loop just press Ctrl + C to stop it

production

  • A while loop is useful when you don't necessarily know beforehand how many iterations you'll need.
password = ""

while password != "python123":
    password = input("Enter password: ")

  • Here we don't say: Run this 5 times.

  • Instead: Keep running while the condition is true.

  • for loop -> Iterate over a known sequence/range.

  • while loop -> Continue until some condition changes.

Danger of while loop

i = 0

while i < 5:
    print(i)

  • This never changes i.

  • So i = 0 0 < 5 → True print 0 < 5 → True print 0 < 5 → True print

  • That's an infinite loop.

  • we normally need state change

    i = 0 while i < 5: print(i) i = i + 1

Example 1 of pattern printing using while

i = 1 while i <= 5: print(i * '*') i = i + 1

Output

**




Explaination-

whenever an integer number is multiplied by a string, that no of times the strings gets repeated. Multiplication * here is used as concatenation

like for eg i = 1 while i <= 5: print(i * 'hello') i = i + 1

Output: hello hellohello hellohellohello hellohellohellohello hellohellohellohellohello

Reverse pattern printing using while

i = 5 while i > 0: print(i * '*') i = i - 1

Output




** *


for-loop

for loop

Example 1

for i in range(5): print(i)

Output: 0 1 2 3 4

Another example

nums = range(5)

for i in nums: print(i)

Output 0 1 2 3 4

Example 2

for i in range(2,6): print(i)

Output: 2 3 4 5

  • if its i in range(2,6,2): print(i) // 2 4

  • if its i in range(2,6,5) print(i) // 2

Example 3: Another way of finding even numbers

for i in range(2, 11, 2):
print(i)

Output: 2 4 6 8 10

Example 4: Cleaner approach for finding even numbers

for i in range(1, 11): // to check even no if i % 2 == 0: print(i)

Output: 2 4 6 8 10

Example 5: printing multiples of 3 from (1 to 30)

for i in range(1,31): if i % 3 == 0: print(i)

Output 3 6 9 12 15 18 21 24 27 30

Default structure of range

range (start, stop, step ) range (optional, must, optional) range (0,must,0)

Concept of for loop

in c++ we use for loops like this

for (int i = 0; i < 5; i++) { cout << i; }

Python doesn't require you to manually write:

  • initialization
  • condition
  • increment

Instead for i in range(5):

means "Take each value produced by range(5) and assign it to i, one at a time."

for loop dosen't require range() always

  • Python's for loop can directly iterate over collections.

names = ["Sarbesh", "Rahul", "Amit"]

for name in names:
    print(name)

Output Sarbesh Rahul Amit

Why this matters in production

Imagine you're processing data from an API:

users = get_users()

for user in users:
    process_user(user)

You don't care whether there are 10 users or 10,000 users.

test example

tuple = [1, 2, 3]

print(tuple)                                                  // range print hoina but tuple print hoi 

for i in tuple:
  print(i)

output [1, 2, 3] 1 2 3


break

  • It means Immediately terminate the current loop.

Example 1

for i in range(10): if i == 5: break

print(i)

Output 0 1 2 3 4

  • when i == 5 becomes true , Python executes break. The Loop ends immediately

Example 2: printing multiples of 3 from (1 to 30) but stop when number reaches 21

for i in range(1,31): if i == 21: break if i % 3 == 0: print(i)

print("out of loop")

Output 3 6 9 12 15 18 out of loop

Trivia-

  • remember to write break logic before main logic if you want to break before print
  • if u want to print the the breaking number and then after that loop should end then write break after logic i.e after print(i)

Production : when break is uselful?

Imagine searching for something:

numbers = [4, 7, 2, 9, 15, 3]

for number in numbers: if number == 9: print("Found!") break

  • Once you've found what you're looking for, there's no reason to continue searching.

continue

  • It means Stop the current iteration right here and immediately move to the next iteration.
  • skiping some particular iteration

example 1

for i in range(1, 6):

if i == 3:
    continue

print(i)

Output 1 2 4 5

Trace:

Iteration 1 i = 1 i == 3? No print(1)

Iteration 2 i = 2 i == 3? No print(2)

Iteration 3 i = 3 i == 3? Yes continue // python dosent execute print(i) . It jumps back to the loop and starts the next iteration.

Iteration 4 i = 4 print(4)

Iteration 5 i = 5 print(5)

Remember

break ↓ EXIT LOOP COMPLETELY

continue ↓ SKIP THIS ITERATION ↓ NEXT ITERATION

Production : A realistic use of continue

Suppose you're processing numbers and only want to work with positive numbers:

numbers = [10, -5, 20, -3, 30]

for number in numbers:

    if number < 0:
        continue

    print(number)

Output 10 20 30

  • here continue means Negative numbers aren't relevant to this processing, so skip them. Filtering Logic

Example: printing multiples of 3 from (1 to 30) but skip the number 21

for i in range(1,31): if (i == 21): continue if (i % 3 == 0): print(i)

Output 3 6 9 12 15 18 24 27 30


Nested loop

  • A loop inside another loop

Example

for i in range(3): for j in range(3): print(i, j)

trace:

Outer loop starts: i = 0 Inner loop: j = 0 → print(0, 0) j = 1 → print(0, 1) j = 2 → print(0, 2)

Outer loop: i = 1 Inner loop starts again from begining j = 0 → print(1, 0) j = 1 → print(1, 1) j = 2 → print(1, 2)

Outer loop: i = 2 and again inner loop: j = 0 j = 1 j = 2

Output: 0 0 0 1 0 2 1 0 1 1 1 2 2 0 2 1 2 2

Mental model

The inner loop completes all its iterations for every single iteration of the outer loop.

Why nested loops matter for interviews?

Suppose:

for i in range(n): for j in range(n): print(i, j)

  • The outer loop runs n times.
  • For each outer iteration, the inner loop runs n times.
  • therefore, n × n = n²
  • time complexity -> O(n²)

You'll encounter them in: matrix problems 2D arrays brute-force solutions pair comparisons sorting algorithms graph algorithms pattern problems

nested loops + break

Consider:

for i in range(3):

for j in range(5):

    if j == 2:
        break

    print(i, j)
  • break breaks the inner loop only and not outer loop

for i = 0 inner loop: j = 0 → print j = 1 → print j = 2 → break

Output 0 0 0 1 1 0 1 1 2 0 2 1


14-multiple-exercises

  1. Problem stat- Print all odd numbers from 1 to 20
for i in range(1,21):
  if (i % 2 != 0):
    print(i)

Output 1 3 5 7 9 11 13 15 17 19

ALternative way

for i in range(1,21,2): print(i)


  1. Problem stat- Print the table of 57
for i in range(1,11):
  print(57 * i)

Output 57 114 171 228 285 342 399 456 513 570

OR

for i in range(571):

  if i % 57 == 0:                                          // multiples of 57 
    print(i)

refined way

for i in range(1, 11):
    print(57, "x", i, "=", 57 * i)

Result: 57 x 1 = 57 57 x 2 = 114 57 x 3 = 171 .. .. ....

same thing with while loop

i = 1

while i <= 10:
    print(57 * i)
    i = i + 1

Alt way of writing in while

i = 1

while i in range(1, 11):
    print(57 * i)
    i = i + 1

  • while expects condition in True or False unlike for loop but this thing can also work
  • but in real production code, you would usually use while with a condition that expresses the actual stopping rule rather than i in range(...).

Real usage of while

while password != correct_password: password = input("Enter password: ")

while not connected: connect_to_server()

while queue: item = queue.pop(0) process(item)

for → "Go through these things / repeat this known number of times." while → "Keep doing this until this condition changes."


  1. Problem stat- Print all multiples of 3 from 1 to 50 but skip 15
for i in range(1,51):
  if (i == 15):
    continue 
  if (i % 3 == 0):
    print(i)

Output 3 6 9 12 18 21 24 27 30 33 36 39 42 45 48

in while loop

i = 1

while i <= 50:

  if i == 15:
    i = i + 1
    continue 


  if i % 3 == 0:
    print(i)
  i = i + 1


  1. Take two integers a and b as input. Find and print the first number between 1 and 1000 that is divisible by both numbers.
a = int(input("Enter first number: "))
b = int(input("Enter second number: "))


for i in range(1,1001):
  if (i % a == 0) and (i % b == 0):
    print("The first no to be divisible by both" , i)
    break

Output Enter first number: 4 Enter second number: 5 The first no to be divisible by both 20

Enter first number: 4 Enter second number: 6 The first no to be divisible by both 12

Remember

% -> "What is the remainder when the LEFT number is divided by the RIGHT number?"

X % Y == 0

20 is divisible by 5 20 % 5 == 0

36 is divisible by 9 36 % 9 == 0

i is divisible by a i % a == 0


Note loop

Multiples of 3 — Loop Practice

Goal: print 3, 6, 9, ... 30

  1. Multiplication — Generate multiples

for i in range(1, 11): print(3 * i)

3 × 1 → 3, 3 × 2 → 6, ... 3 × 10 → 30

  1. % — Find/check multiples

for i in range(1, 31): if i % 3 == 0: print(i)

i % 3 == 0 → i is exactly divisible by 3.

  1. range() with step — Generate directly

for i in range(3, 31, 3): print(i)

range(start, stop, step) → 3, 6, 9, ... 30

Important: stop is excluded, so use 31 to include 30.

  1. while + multiplication

i = 1 while i <= 10: print(3 * i) i = i + 1

  1. while + % i = 1 while i <= 30: if i % 3 == 0: print(i) i = i + 1

  2. while + adding 3 i = 3 while i <= 30: print(i) i = i + 3

Quick Logic

Approach Think 3 * i Generate multiples i % 3 == 0 Find/check multiples range(3, 31, 3) Generate with step while + 3 * i Generate using while while + % Find/check using while i = i + 3 Generate by repeatedly adding

Edge cases range(1, 11) # 1 to 10 range(3, 31, 3) # 3 to 30

  • range() excludes the stop value.
  • 0 % 3 == 0 → 0 is technically a multiple of 3.
  • % checks divisibility; it does not generate multiples by itself.
  • For reverse multiples, use a negative step:

for i in range(30, 0, -3): print(i)

Output:

30 27 24 21 18 15 12 9 6 3

Best 3 to remember:

Generate

for i in range(1, 11): print(3 * i)

Find/check

for i in range(1, 31): if i % 3 == 0: print(i)

Step

for i in range(3, 31, 3): print(i)


15-Data-Structure

Lists- [] tuple= () set = {} dict = {}

Lists

  • A list stores multiple values in one variable.
  • It is written using square brackets []
  • A list can contain values of different data types.
  • Lists are Mutuable. You can change the contents of the list after creating it.

Let's say I want to type marks of different students and I need to type everytime seperately like: marks1 = 99 marks2 = 90 marks3 = 50

List solves it by grouping related values into one variable like: marks = [99, 90, 50]

marks = [99, 90, 50] ↑ ↑ ↑
0 1 2 // indexes

  • A list can contain different data types data = [10, "Python", 3.14, True]

  • Python allows this, although in production code you'll usually have logically related data in a list.

Summary

  1. len(variable)

print(len(variable)) -> show length

  1. Access elements index print(variable[0]) -> Index

  2. Adding elemets .append()

variable.append("xxx") -> inserting new element but at last position & in set .add() is used print(variable)

example names = ["Sarbesh", "Rahul"] names.append("Amit") print(names) // ['Sarbesh', 'Rahul', 'Amit']

  1. Adding elements .insert()

variable.insert(index, "xxx") -> inserting new element at particular position by writing the index number at left print(variable)

example names = ["Sarbesh" , "Amit"] names.insert(1, "Trilok") print(names) // ['Sarbesh', 'Trilok', 'Amit']

  1. Adding multiple elements .extend()

variable.extend([50, 60]) -> inserting multiple elements at once and in set .update() is used print(variable)

  1. Removing elements .remove() , .pop() , .clear()

variable.remove("xxx") -> removes particular element like for here it is xxx
variable.pop() -> removes last element unless sepcified variable.pop(index) -> removes element mentioned in the index variable.clear() -> removes whole list

example names = ["Google", "Micro", "Sarbesh", "Apple", "Meta"] names.remove("Meta") names.pop(1) print(names) // ['Google', 'Sarbesh', 'Apple']

  1. in

variable = ["xxx", "yyy", 12]

print("xxx" in variable) // True

store = 13 in variable print(store) // False

  1. Looping

example 1

variable = ["Sarbesh", 100, "Google"] for i in variable: print(i)

output- Sarbesh 100 Google

example 2: range, len method

variable = ["Sarbesh", 100, "Google"] for i in range(len(variable)): print(variable[i])

Output- Sarbesh 100 Google

example 3: use case of range, len method

variable = [10,20,30,40]

for i in range(len(variable)): variable[i] = variable[i] * 2 print(variable[i])

output- 20 40 60 80

example 4: another usecase of range, len method

variable = [10, 20, 30, 40]

for i in range(len(variable)): print("Index of", i, "is", variable[i])

output- Index of 0 is 10 Index of 1 is 20 Index of 2 is 30 Index of 3 is 40

example 5:

variable = [10, 20, "Sarbesh", 30]

for i in range(len(variable)): print("Index of", i, "is", variable[i])

output- Index of 0 is 10 Index of 1 is 20 Index of 2 is Sarbesh Index of 3 is 30

  1. Slicing
  • start is included but stop is excluded

variable[start:stop] -> structure variable[start:stop:update]

print(variable[start:stop]) -> printing the sliced part

print(variable[:3]) -> from 0 index (start) to index 2

print(variable[3:]) -> from 3rd index till last index

print(variable[0:5:2]) -> adding a step value

variable([:]) -> print the whole list

varaible([::2]) -> print every 2nd element

variable(::-1) -> reverses a list

example numbers = [10, 20, 30, 40, 50] print(numbers[0:5:2])

output- [10, 30, 50]

  1. Reversing a list

variable.reverse() // modifies the original list print(variable)

variable(::-1) // returns new list, original list unmodified

numbers = [10, 20, 30, 40, 50] for i in range(len(numbers) - 1, -1, -1): // range(4, -1, -1) print(numbers[i])


example 1

marks = [96, 98, 67, 'S']

print(marks) print(len(marks)) // calculating the length of the list

Output: [96, 98, 67, 'S'] 4

Accessing elements- indexes

Example 2

names = ["Sarbesh", "Rahul", "Amit"] print(name[0])

Output Sarbesh

  • Python, like C++, uses zero-based indexing.

  • Index: 0 1 2 ↓ ↓ ↓ Sarbesh Rahul Amit

names[0] // Sarbesh names[1] // Rahul names[2] // Amit

Example 3: Negative Indexing

names = ["Sarbesh", "Rahul", "Amit"] print(names[-1])

Output: Amit

  • Because -1 means last element.

  • Index: 0 1 2 -3 -2 -1 ↓ ↓ ↓ Sarbesh Rahul Amit

names[-1] # last // Amit names[-2] # second last // Rahul


Lists are mutuable

Lists are mutable, meaning their elements can be added, removed or changed.

alt text alt text

example 4

marks = [98, 97, 95] marks[0] = 100

print(marks)

Output: [100, 97, 95]

example 5 marks = [85, 72, 91] marks[1] = 80

print(marks)

Output: [85, 80, 91]

  • The list itself was modified.

List → mutable Tuple → immutable


Adding elements — append()

  • append() adds an element at the end of the list

Example 6

names = ["Sarbesh", "Rahul"] names.append("Amit")

print(names)

Output: ["Sarbesh", "Rahul", "Amit"]

Adding at a particular position — insert()

  • insert() adds an element at a particular position.

Example 7: Suppose you want name Rahul between 2 names that is in 2nd postion

names = ["Sarbesh", "Amit"]

names.insert(1, "Rahul")

print(names)

Output: ["Sarbesh", "Rahul", "Amit"]

  • The first argument is the position, and the second is the value.

Syntax- list.insert(index, value)


Removing elements

remove()

  • remove() removes the value you specify.

Example 8 names = ["Sarbesh", "Rahul", "Amit"] names.remove("Rahul")

print(names)

Output ["Sarbesh", "Amit"]

pop()

Example 9 names = ["Sarbesh", "Rahul", "Amit"] names.pop() // pop() removes the last element unless specified

print(names)

Output: ["Sarbesh", "Rahul"]

Example 10 names = ["Sarbesh", "Rahul", "Amit"] names.pop(1) // pop(1) means remove the element at index 1

print(names)

Output ['Sarbesh', 'Amit']

pop() returns the removed element

removed = names.pop() print(removed)

clear()

Example 11

marks = [98, 97, 95, 93.5, "A"] marks.clear()

print(marks) print(len(marks))

Output [] 0


checking for an element

Example 12

marks = [98, 97, 95, 93.5, "A"] print(95 in marks) print(99 in marks)

Output True False


Remember-

num = range(5) print(num)

Output- range(0, 5)

❌ I will not get 0,1,2,3,4 . For that I need to convert them into list

Code:

num = range(5) print(list(num))

Output- [0, 1, 2, 3, 4]

code: num = range(5) num = list(num) // permanently reassigned num from range to list print(num)

output [0, 1, 2, 3, 4]


looping directly over a list

Example 13: just see this , its not looping

numbers = [10, 20, 30, 40] print(numbers)

Output [10, 20, 30, 40]

Example 14: Looping directly over a list (pythonic)

numbers = [10, 20, 30, 40] for number in numbers: print(number)

Output 10 20 30 40

Example 15: by using range and len. I can use Eg 14 and I don't need this process

numbers = [10, 20, 30, 40] for i in range(len(numbers)): print(numbers[i])

Output 10 20 30 40

  • Both are valid. (Eg 14 & 15). The first is usually cleaner when the index isn't needed. First is more pythonic

Concept

Eg 14: First approach — iterate over the values

numbers = [10, 20, 30, 40]

for number in numbers:
    print(number)

  • Python directly takes each element/value from the list.
  • So number is actually holding the value.
  • You don't care where the value is located.
  • This is usually what you use in production

number → VALUE

For example, suppose you get users from a database/API:

users = ["Alice", "Bob", "Charlie"]

for user in users:
    send_email(user)

  • For every user, send an email. You don't care whether Alice is at index 0 or index 500.

Eg 15: Second approach — iterate over indexes

numbers = [10, 20, 30, 40]

for i in range(len(numbers)):
    print(numbers[i])

  • 1st, len(numbers) gives 4
  • 2nd, range(4) gives 0 1 2 3

So, the loop does: i = 0 → numbers[0] → 10 i = 1 → numbers[1] → 20 i = 2 → numbers[2] → 30 i = 3 → numbers[3] → 40

  • Here i is not the value. i is the index/location.

i → INDEX

Why we need index then?

  1. Suppose you want to modify elements based on their position. numbers = [10, 20, 30, 40]
  • u want to double very element
numbers = [10, 20, 30, 40]
for i in range(len(numbers)):
    numbers[i] = numbers[i] * 2
    print(numbers[i])

Output 20 40 60 80

  1. and there's another use case: Print the position of every number.
numbers = [10, 20, 30, 40]

for i in range(len(numbers)):
    print("Index:", i, "Value:", numbers[i])

Output Index: 0 Value: 10 Index: 1 Value: 20 Index: 2 Value: 30 Index: 3 Value: 40

Wrong version of Example 15: ❌

numbers = [10,20,30,40] for i in range(numbers): print(numbers[i])

Error-> TypeError: 'list' object cannot be interpreted as an integer

  • python can read what's there in numbers in list format
  • but range() needs integer values whereas in list integer, char, bool everything can get stored
  • python don't assume range([]) , it needs range(5) or range(2,6) or something integer bound
  • so len(numbers) solves this. if numbers = [10,20,30,30] then len(numbers) produces 4
  • len produces 4, so range(4)
  • range(4) means 0,1,2,3

Example 16: When we need index

numbers = [10, 20, 30]

for i in range(len(numbers)): print(i, numbers[i])

Output Output-> 0 10 1 20 2 30

example

variable = [10, 20, "Sarbesh", 30]

for i in range(len(variable)):
  print("Index of", i, "is", variable[i])

output Index of 0 is 10 Index of 1 is 20 Index of 2 is Sarbesh Index of 3 is 30

python list vs C++ vector comparison

  1. Python:

numbers = [10, 20, 30] numbers.append(40)

  1. C++

vector numbers = {10, 20, 30}; numbers.push_back(40);

Python C++
list vector
list[index] vector[index]
append() push_back()
len(list) vector.size()
for x in list for (auto x : vector)

So: list → collection of VALUES range → generates NUMBERS len → tells me HOW MANY values are in the list


slicing

  • Slicing extracts part of a list
  • it means Taking a portion of a list without changing the original list.

numbers = [10, 20, 30, 40, 50] Indexes:

Value: 10 20 30 40 50 Index: 0 1 2 3 4 -5 -4 -3 -2 -1

  1. Basic syntax

list[start:stop]

start is included, stop is excluded.

Eg: numbers = [10, 20, 30, 40, 50] print(numbers[1:4])

means: start at index 1 ↓ 20 30 40 ↑ stop at 4 (not included)

Output: [20, 30, 40]

  1. Leaving start or stop empty

Eg: from the beginning numbers = [10, 20, 30, 40, 50] print(numbers[:3]) // Start from the beginning and stop before index 3.

Output [10, 20, 30]

Eg: Until the end numbers = [10, 20, 30, 40, 50] print(numbers[2:])

Output [30, 40, 50]

  1. So,

numbers[:3] # beginning → index 2 numbers[2:] # index 2 → end

  1. Adding a step

list[start:stop:step]

Eg numbers = [10, 20, 30, 40, 50] print(numbers[0:5:2])

Start at 0, stop before 5, jump by 2:

Output [10, 30, 50]

Trivia

print(numbers[0:5:2]) // [10, 30, 50]
print(numbers[0:8:2]) // [10, 30, 50]

  • here stoping number is not mandatory so both the output is coming same although last index is 4. so stoping number here is the boundary
  1. Reverse a list

numbers = [10, 20, 30, 40, 50] numbers[::-1]

Output [50, 40, 30, 20, 10]

  1. printing every nth element: here for eg every 2nd element

numbers = [10, 20, 30, 40, 50] print(numbers[::2])

output [10, 30, 50]

concept

[start : stop : step]

[::2] ↑ ↑ ↑ | | └── step = 2 | └──── stop = omitted └────── start = omitted

  • Start from the beginning, go to the end, and take every 2nd element.

variable[::2] -> technically it selects elements at even indexes numbers[1::2] -> selects elements at odd indexes

Remember

  1. numbers[start:stop:step]

  2. numbers[1:4] 1, 2, 3

  3. numbers[:3] # beginning → 2 numbers[2:] # 2 → end

  4. numbers[::2] # every 2nd element numbers[::-1] # reverse

Reversing a list ways

  1. variable(::-1)

  2. variable.reverse()
    print(variable)

  3. numbers = [10, 20, 30, 40, 50] for i in range(len(numbers) - 1, -1, -1): // range(4, -1, -1) print(numbers[i])

Helpful in interview / prod

first_three = numbers[:3] last_three = numbers[-3:]


Tuple

  • A tuple stores multiple values and is written using parentheses ()
  • Tuples are immutable, so their elements cannot be changed
  • we use Tuple, when we want fixed values and not something changeable like GPS coordinates

Example 1

numbers = (10, 20, 30, 40) print(numbers)

Output (10, 20, 30, 40)

Summary

  1. we can access the element just like the list.

numbers = (10, 20, 30, 40) print(numbers[0])

Output 10

  1. Indexing and slicing works in tuple also numbers[-1] numbers[1:3]

  2. I cannot modify

numbers = [10, 20, 30] // List can modify numbers[0] = 100 print(numbers) // [100, 20, 30]

numbers = (10, 20, 30) ❌❌❌ numbers[0] = 100

  • Because tuples are immutable.
  • Once the tuple is created, you cannot modify its elements.
  1. Different data types supported just like List person = ("Sarbesh", 24, True)

  2. I can loop through tuple

numbers = (10, 20, 30, 40) for number in numbers: print(number)

  1. append() ❌ remove() ❌ pop() ❌ insert() ❌

  2. count() ✔ index() ✔

Example 1: Counts how many times a value appears

numbers = (10, 20, 20, 30) print(numbers.count(20))

Output 2

Example 2: Returns the index of the first occurrence of a value

numbers = (10, 20, 20, 30, 30) print(numbers.index(30))

Output 3

  1. Single element tuple needs a comma

x = (10, 20, 30) ✔ x = (10,) ✔ x = 10 ❌ // this is an integer

  1. In Python code, you'll commonly encounter tuples when:

a function returns multiple values representing fixed groups of values working with dictionary keys working with database/query results unpacking values

Tuple Unpacking code-

person = ("Sarbesh", 24)

name, age = person

print(name)
print(age)

Output Sarbesh 24

  • Here the tuple contains two related values, and Python unpacks them into two variables.
  • but remember no of variables should match no of values

Set

  • A set stores unique values and is written using curly brackets {}
  • Repeated values are automatically removed.
  • Sets are unordered, so their display order is not guaranteed.
  • They also do not support indexing.
  • looping is allowed

Summary

  1. No element access like variable[0]

  2. looping over a set can be done but it is not ordered so be careful

  3. Adding elements --- .add() // which is .append() in lists

variable.add("xxx") print(variable)

  1. Adding multiple elements --- .update() // which is .extend() in lists

variable.update([20, 30, "yyy"])

  1. Removing elements --- .remove() , .discard()

variable = {10,20,30,20} variable.remove(20) print(variable)

Output {10, 30}

  • .remove() checks element exists or not before removing but .discard() dosen't checks . So .discard() dosen't throw any error if requested element dosen't exists
  1. Set Operations

    1. Union (A | B) // All Unique elemnets in both the sets
    2. Intersection (A & B) // Elements present in both the sets get printed
    3. Difference (A - B) // Last set that is B is the base where experimentation happens & the elements present in Higher Set A but not in B gets printed

Example 1 numbers = {10, 20, 10, 30, 20, 40} print(numbers)

Output {10, 20, 30, 40} // removing the duplicates

3 characteristics of Set-

  1. Unique elements
  2. Unordered
  3. Mutable
  4. No duplicates + no indexing

alt text

Sets dont hv indexes

  • within a list: numbers = [10, 20, 30] print(numbers[0])

Output- 10

  • in set: numbers = {10, 20, 30} print(numbers[0])

Output- ❌error Because a set doesn't maintain elements in a meaningful positional order.

Real life-

  1. checking permissions

permissions = {"read", "write", "delete"} if "write" in permissions: print("User can write")

  • You don't care whether "read" is conceptually first or "delete" is third.
  • You care about: Does this permission exist?
  1. Membership checking

we can use- if value in my_set:

eg-

allowed_roles = {"admin", "manager", "developer"} if "developer" in allowed_roles: print("Access granted")

Adding elements .add()

  • Because sets are mutable, you can add elements.

Example 2

numbers = {10, 20, 30} numbers.add(40) print(numbers)

Output {10, 20, 30, 40}

but if u want to add numbers.add(20) nothing changes cuz no duplicates in sets

Removing elements .remove() , .discard()

example 3

numbers = {10, 20, 30, 20} numbers.remove(20) print(numbers)

Output {10, 30}

  • we can also use discard instead of remove. remove checks if element exists or not before removing but discard dosen't checks

exmaple 4 numbers = {10, 20, 30} numbers.discard(50) // 50 dosent exists print(numbers)

Output {10, 20, 30}

looping over a set can be done but it is not ordered so be careful

Set Operations

  1. Union

Example 5: Union

A = {1, 2, 3, 4} B = {3, 4, 5, 6}

print(A | B) // Union (|) - Values present in either set // combine both sets and remove duplicates

Output {1, 2, 3, 4, 5, 6}

  1. Intersection

Example 6: Intersection

A = {1, 2, 3, 4} B = {3, 4, 5, 6}

print(A & B) // Intersection (&) - Values present in both // What do they have in common? // useful in data processing

Output {3, 4}

  1. Difference

Example 7: Difference

A = {1, 2, 3, 4} B = {3, 4, 5, 6}

print(A - B) // Difference (-) - What does A have that B doesn't?

Output {1, 2}

  • A holo supreme and B holo base. A te jeigulo ache but B te nei seigulo list kora hbe

Real world example of Set operations

Example 8

frontend = {"React", "JavaScript", "HTML", "CSS"}

backend = {"Python", "SQL", "JavaScript", "Docker"}

print(frontend & backend) -> {"Javascript"} // Technologies known by both

print(frontend | backend) -> {"React", "JavaScript", "HTML", "CSS", "Python", "SQL", "Docker"} // All technologies

print (frontend - backend) -> {"React", "HTML", "CSS"} // Frontend technologies not in backend

Converting a list into Set

Example 9

numbers = [10, 20, 10, 30, 20, 40] unique_set = set(numbers)

print(unique_set)

Output {40, 10, 20, 30} // as usual not ordered

Syntax Trap

  • An empty set is not {}
  • it is set()
  • {} -> Dictionary

Empty Dictionary: x = {}

Empty Set: x = set()

example code

numbers = {10, 20, 10, 30, 10}

print(numbers, len(numbers))

for value in numbers: print(value)

Output {10, 20, 30} 3 10 20 30


Dictionary

  • A dictionary is a set of key value & pairs
  • key & its pair is denoted by :
  • key : pair
  • key and its pair is sperated from another key and its pair by comma ,
  • key1 : pair1 , key2 : pair 2
  • we create dict by {}
  • syntax- {}
  • dictonaries are mutable just like lists and sets

Summary

  1. What is index in list is key in dictionary

  2. Accessing elements

person = { "name": "Sarbesh", "age": 24, "role": "Developer" }

print(person["name"]) // Sarbesh

  1. Dictionaries are mutable and we can add a key:value pair or can update a key:value pair. We don't need .add() or .append() to add elements.

person = { "name": "Sarbesh", "age": 24, "role": "Developer" }

person = { "name": "Sarbesh", "age": 24, "role": "Developer" }

person["age"] = 25 person["city"] = "Bengaluru"

print(person)

output {'name': 'Sarbesh', 'age': 25, 'role': 'Developer', 'city': 'Bengaluru'}

  1. Removing elements - .pop() , del

person = { "name": "Sarbesh", "age": 24, "role": "Dev" }

person.pop("age") del person ["role"]

print(person)

Output {'name': 'Sarbesh'}

  1. checking

    1. in method

person = { "name": "Sarbesh", "age": 24, "role": "Developer" }

print("age" in person) print("city" in person)

output True False

  1. if : checks both keys and values

person = { "name": "Sarbesh", "age": 24, "role": "Developer" }

if "age" in person: print(person["age"])

output 24

  1. .get() method

variable.get("key") -> structure

person = { "name": "Sarbesh", "age": 24 }

print(person.get("email")) // very simple, if key dosen't exists then None is returned

print(person.get("city", "Not Provided")) // a good response i.e Not provided is given as output if no key exists

print(person.get("name", "Not Provided")) // here the key exists so it printed it value

output None Not Provided Sarbesh

  1. Looping
  • Iteration is over Keys and not indexes
  1. Looping over Keys only

person = { "name": "Sarbesh", "age": 24 }

for key in person: print(key)

Output name age

  • we can replace the key keyword with anything like xxx or value or i
  • and then also it will iterate over key

person = { "name": "Sarbesh", "age": 24 } for i in person: print(i)

output name age

  1. Looping over Keys & Values both

person = { "name": "Sarbesh", "age": 24 }

for key in person: print(key, person[key])

output name Sarbesh age 24

OR

person = { "name": "Sarbesh", "age": 24 }

for key, value in person.items(): print(key, value)

  1. Looping over values only

person = { "name": "Sarbesh", "age": 24 }

for value in person.values(): print(value)

output Sarbesh 24

Now imagine a situation

  • with a list we can access postion/index like for eg- numbers[0]
  • but we have to remember the the index right like 0 here which is not so readable

example

Name → Sarbesh Age → 24 Role → Developer

Using a list: person = ["Sarbesh", 24, "Developer"]

now u need to remember: 0 → name 1 → age 2 → role

  • A dictionary lets us associate a key with a value

Using dict:

person = { "name": "Sarbesh", "age": 24, "role": "Developer" }

print(person["name"]) // now we can directly say person("[name]") and get Sarbesh so print(variable["key"])

Output Sarbesh

print(person)

Output {'name': 'Sarbesh', 'age': 24, 'role': 'Developer'}

So

List: index → value

Dictionary: key → value

example 2

marks = {"maths" : 99, "Physics" : 80, "Chemistry" : "Fail"} print(marks, "&&&" , type(marks), "&&&" , len(marks))

Output {'maths': 99, 'Physics': 80, 'Chemistry': 'Fail'} &&& <class 'dict'> &&& 3

we access value in dictionary using key

person = { "name": "Sarbesh", "age": 24, "role": "Developer" }

print(person["name"]) print(person["age"])

Output- Sarbesh 24

print(person[0]) -> ❌ // dictonaries expect a key

useful scenarios in API call

user = { "id": 101, "name": "Sarbesh", "email": "sarbesh@example.com", "active": True }

  • I can directly acess email by user["email"]

Dictonaries are mutable

example

person = { "name": "Sarbesh", "age": 24 }

person["age"] = 25 print(person)

Output {"name": "Sarbesh", "age": 25}


Adding a key value pair : we dont need add()

example

person = { "name": "Sarbesh", "age": 24 }

person["city"] = "Bengaluru"

print(person)

Output {'name': 'Sarbesh', 'age': 24, 'city': 'Bengaluru'}

  • if city dosen't exists create it , if it exists update its value

removing data : .pop() or del

example

person = { "name": "Sarbesh", "age": 24, "role": "Dev" }

person.pop("age") del person ["role"]

print(person)

Output {'name': 'Sarbesh'}


checking whether key exists or not

  • u can't search for pair, u hv to search key

eg

person = { "name": "Sarbesh", "age": 24, "email": "sarbeshmk@gmail.com" }

if "email" in person: print(person["email"])

if "name" in person: print(person["name"])

Output sarbeshmk@gmail.com Sarbesh

if "Sarbesh" in person -> ❌ // cuz "Sarbesh" is a key and not value if "name" in person -> ✔

OR

person = { "name": "Sarbesh", "age": 24, "email": "sarbeshmk@gmail.com" }

print("name" in person) print("role" in person)

Output True False


.get() method

  • very frequently used with API/JSON data where some fields are missing

eg person = { "name": "Sarbesh", "age": 24 }

person["email"] -> KeyError // email dosen't exists

To mitigate this:

person.get("email") returns none

OR

person.get("email", "Not provided") returns Not provided

example

person = { "name": "Sarbesh", "age": 24 }

print(person.get("email"))

Output None

print(person.get("email", "Not provided")) -> Not provided

example

person = { "name": "Sarbesh", "age": 24 }

print(person.get("email", "Not provided")) print(person.get("name", "Not provided"))

output Not provided Sarbesh


Looping through dictionary

Just Keys

  • Python's default dictionary iteration is over keys

example

person = { "name": "Sarbesh", "age": 24 }

for key in person: print(key)

Output name age

Keys and Value

  • we can use .items()

example

person = { "name": "Sarbesh", "age": 24 }

for key, value in person.items(): print(key, value) // print(key, ":", value) we can add colon to make it more natural

Ouput name Sarbesh age 24

OR

person = { "name": "Sarbesh", "age": 24 }

for key in person: print(key, person[key])

Output name Sarbesh age 24

If you wan to print only values

eg

person = { "name": "Sarbesh", "age": 24 }

for value in person.values(): print(value)

output Sarbesh 24

  1. python dosent care about variable name here.
  2. like for keys i cam literally do: person = { "name": "Sarbesh", "age": 24 } for value in person: print(value)

it will output- // here i want values but got keys so keys looping is default so here store the key in varibale named value name // if u need values then for x in person.values() age

  1. even with keys and values

    person = { "name": "Sarbesh", "age": "24 } for x,y in person.items(): print(x,y)

output- name Sarbesh age 24


Dictionarypluslist

Dictionary + Lists

example

users = [ {"name": "Alice", "age": 25}, {"name": "Bob", "age": 30}, {"name": "Charlie", "age": 28} ]

for user in users: print(user["name"])

output Alice Bob Charlie

model-

List ├── Dictionary ├── Dictionary └── Dictionary

interesting example 1

users = [ {"name": "Alice", "age": 25}, {"name": "Bob", "age": 30}, {"name": "Charlie", "age": 28} ]

for user in users: print(user)

output {'name': 'Alice', 'age': 25} {'name': 'Bob', 'age': 30} {'name': 'Charlie', 'age': 28}

interesting example 2

users = [ {"name": "Alice", "age": 25}, {"name": "Bob", "age": 30}, {"name": "Charlie", "age": 28} ]

for user in users: print(user) print(user["name"])

output {'name': 'Alice', 'age': 25} Alice {'name': 'Bob', 'age': 30} Bob {'name': 'Charlie', 'age': 28} Charlie

interesting example 3

users = [ {"name": "Alice", "age": 25}, {"name": "Bob", "age": 30}, {"name": "Charlie", "age": 28} ]

for user in users: print(users)

output [{'name': 'Alice', 'age': 25}, {'name': 'Bob', 'age': 30}, {'name': 'Charlie', 'age': 28}] [{'name': 'Alice', 'age': 25}, {'name': 'Bob', 'age': 30}, {'name': 'Charlie', 'age': 28}] [{'name': 'Alice', 'age': 25}, {'name': 'Bob', 'age': 30}, {'name': 'Charlie', 'age': 28}]

example 4

users = [ {"name": "Alice", "age": 25}, {"name": "Bob", "age": 30}, {"name": "Charlie", "age": 28} ]

print(users)

output [{'name': 'Alice', 'age': 25}, {'name': 'Bob', 'age': 30}, {'name': 'Charlie', 'age': 28}]


DictionaryplusDictionary

Dictionary + Dictionary

example

user = { "name": "Sarbesh", "address": { "city": "Delhi", "country": "India" } }

print(user["address"]["city"])

output Delhi

example

user = { "name": "Sarbesh", "address": { "city": "Delhi", "country": "India" } }

print(user["address"])

output {'city': 'Delhi', 'country': 'India'}


dictionary vs set

  1. Set numbers = {10, 20, 30}

  2. dict person = {"name": "Sarbesh", "age": 24}

  3. Remember Set: {value, value, value}

Dictionary: {key: value, key: value}

  1. empty dictionary

{}

  1. empty set

set()


why dict matter in interviews

You want: 1 → 1 2 → 2 3 → 3

example

numbers = [1, 2, 2, 3, 3, 3] frequency = {}

for number in numbers: frequency[number] = frequency.get(number, 0) + 1

print(frequency)

output {1: 1, 2: 2, 3: 3}


🧠 Your final mental model

LIST → Ordered → Duplicates allowed → Mutable → Access by index

TUPLE → Ordered → Duplicates allowed → Immutable → Access by index

SET → Unique values → No meaningful indexing/order → Mutable → Fast membership checking

DICTIONARY → Key → Value → Keys are unique → Mutable → Access by key

  • Mutable data types are slower compared to immutable data types
  • Tuple is faster

16-Fifth-Exercise

Problem statement-

A. Given a list of roll numbers: [101, 105, 102, 101, 108, 105, 110]. Print all unique roll nums in the list.

B. Given Employee records in the form of a list of tuples where each tuple contains: (Employee ID, Employee Name, Salary) Example - [ (101, "Alice", 50000), (102, "Bob", 65000), (103, "Charlie", 45000) ] Ask user to enter Employee ID & search it inside records.

roll_number = [101, 105, 102, 101, 108, 105, 110]
unique_number = set(roll_number)
print("unique roll numbers" , unique_number)



records = [
  (101, "Alice", 50000),
  (102, "Bob", 65000),
  (103, "Charlie", 45000)
]


employee_id = int(input("Enter your employee id: "))


for record in records:
  if record[0] == employee_id:
      print(record)
      break

Input Enter your employee id: 102

Output unique roll numbers {101, 102, 105, 108, 110} (102, 'Bob', 65000)

Understand the code

  1. There's a list and inside a list there are 3 tuples

records ↓ ┌─────────────────────────────┐ │ (101, "Alice", 50000) │ ← tuple 1, index 0 or records[0] for records │ (102, "Bob", 65000) │ ← tuple 2 index 1 or records[1] for records │ (103, "Charlie", 45000) │ ← tuple 3 index 2 or records[2] for records └─────────────────────────────┘

  1. Each tuple represents one employee.

And inside each tuple: (101, "Alice", 50000) ↑ ↑ ↑ ID Name Salary index[0]

  1. Records is our entire list. The loop takes one element from the list at a time and puts it into the variable record.

    • Record varibale always gets updated.
    • First iteration -> (101, "Alice", 50000)
    • Second iteration -> (102, "Bob", 65000) ...
    • The variable record is not the entire list. It is one tuple at a time.
  2. for record in record means

    for number in numbers: number → one value from the list

    for record in records: record → one tuple from the list

    record = (101, "Alice", 50000) record = (102, "Bob", 65000) record = (103, "Charlie", 45000)

    Then because record is a tuple, you can access its contents using: record[0] # ID record[1] # Name record[2] # Salary

  3. Now lets talk about record[0]

    • record is a tuple

During 1st iteration- record = (101, "Alice", 50000)

Tuples use indexes just like lists:

  index
    ↓

(101, "Alice", 50000) ↑ ↑ ↑ 0 1 2

record[0] -> Give me the value at index 0 of this tuple. -> 101 record[1] -> "Alice" record[2] -> 50000

1st iteration: record[0] = 101

2nd iteration: record[1] = 102

3rd iteration: record[2] = 103

  1. if condition

if record[0] == employee_id:

Suppose the user entered: 102 So, employee_id = 102

First iteration

record = (101, "Alice", 50000)

Therefore, record[0] = 101

The condition becomes: 101 == 102 // that's false

That's false, So Python moves to the next iteration.

Second iteration

Now, record = (102, "Bob", 65000)

Therefore, record[0] = 102

The condition becomes: 102 == 102 // true

True. So, python enters if block

Mental model

Suppose the user enters: 102

The program effectively does:

employee_id = 102

record = (101, "Alice", 50000) ↓ record[0] = 101 ↓ 101 == 102 → False ↓ next iteration

record = (102, "Bob", 65000) ↓ record[0] = 102 ↓ 102 == 102 → True ↓ print(record) ↓ break ↓ STOP

Output: (102, "Bob", 65000)

alt text

Alternatives: if we want to show Invalid when user enters a wrong employee id

  1. using found variable
records = [
    (101, "Alice", 50000),
    (102, "Bob", 65000),
    (103, "Charlie", 45000)
]

employee_id = int(input("Enter your employee id: "))

found = False

for record in records:
    if record[0] == employee_id:
        print(record)
        found = True
        break

if not found:
    print("Employee ID not found")

What is found?

found is simply a variable name found = False

I could write it as x x = False

True/False are booleaans here

Initially, found = False

Mental concept: if user enters 102

Now imagine the user enters 102 employee_id = 102

Initially, found = False

So our state is: employee_id = 102 found = False

First loop iteration

record = (101, "Alice", 50000) if record[0] == employee_id:

becomes, if 101 == 102:

False, So nothing inside the if runs. found is still: False

Second iteration

record = (102, "Bob", 65000)

The condition becomes: if 102 == 102:

True! ✅

So Python executes: print(record)

found = True // we found the employee break // stop the loop

What happens after the loop?

if not found: print("Employee ID not found")

Since: found = True

then: not found means not True which is False

Therefore the print("Employee ID not found") doesn't execute.

Now suppose the user enters 105

Initially: found = False

The loop checks: 101 == 105 → False 102 == 105 → False 103 == 105 → False

We never execute: found = True

So after the loop:

found = False

Then: if not found:

becomes: if not False:

which is: if True:

Employee ID not found gets printed

There are only two possible final states:

Match occurs: found = False → True if not True → False → don't print "Not found"

No match: found remains False if not False → True → print "Not found"


  1. using else
records = [
  (101, "Alice", 50000),
  (102, "Bob", 65000),
  (103, "Charlie", 45000)
]


employee_id = int(input("Enter your employee id: "))


for record in records:
  if record[0] == employee_id:
      print(record)
      break
  
else:
  print("No employee ID found")

Note

  • write else seperately
  • do not write else just next line to break for eg is user enters 103 No employee ID found No employee ID found 103, "Charlie", 45000 // it will check every id and then execute the else statement but that's not we want

Notice where the else is:

for ├── if │ ├── print │ └── break │ └── else └── not found

Special Rule

A for loop's else executes only if the loop finishes normally without hitting break.

User enters 103:

101 → no match 102 → no match 103 → match ↓ print ↓ break ↓ loop's else is SKIPPED

User enters 105:

101 → no match 102 → no match 103 → no match ↓ loop finishes normally ↓ else executes ↓ "No employee ID found"

  • break is necessary

Did the loop hit break? ↓ YES → don't execute else NO → execute else


  1. Even better: return from a function

In production code, if you're searching inside a function, you can often simply: This is often much cleaner because finding the employee is the function's job.

def find_employee(employee_id):
    for record in records:
        if record[0] == employee_id:
            return record

    return None

employee = find_employee(102)

if employee:
    print(employee)
else:
    print("Not found")


Functions

  • A function is a reusable block of code that performs a particular task.

  • There are 3 types of function-

    1. User Defined functions
    2. Built-in functions
    3. Module Functions
  1. User defined function

Suppose you have to caluclate gst everytime on new different prices and the gst calc formula is same for every item. So, just make gst calculation as a function

  • A function is created using the def keyword

example

def add_gst(price):
  new_price = price + (0.18 * price)
  print(new_price)

add_gst(100)                                                      # 118.0
add_gst(200)                                                      # 236.0

OR

def add_gst(price):
  print(price + 0.18 * price)

Parameter & Arguments

price -> parameter

add_gst(100) -> (100) is argument

Returning Function

A returned value can be stored or used in another calculation:

Example: GST using returning function

def add_gst(price):
  price = price + (0.18 * price)
  return price

print(add_gst(100))                                           // in returning functions just by calling the function we will not get output, we have to use print() 

new = add_gst(112)

print(new)

Example: Squares Using Returning functions

def square(number): return number * number

print(square(5)) print(square(10)) print(square(7))

output 25 100 49

Syntax breakdown:

def → tells Python we're defining a function square → function name (number) → parameter : → start of function body return → sends a result back

Example: cretaing a function where sum is calculated

def add(a, b):
  return a + b


print(add(10,20))


store = add(100,200)
store = store * 2
print(store)


new = store % 2
print(new)


another = add(200, 200) * 2 + 100
print(another)


print(add(10,20) + 10)

output 30 600 0 900 40

Note-

print() sends information to the screen. return sends a value back to the code that called the function.

Function with no parameters

def greet(): print("Welcome")

greet()

output welcome

Default parameters

  • Sometimes you want a function parameter to have a default value if the caller doesn't provide one.
def greet(name, message="Hello"):
    print(message, name)

greet("Sarbesh")
greet("Anu", "Good Morning")

output Hello Sarbesh Good Morning Anu

Mental model

def greet(name, message="Hello"):

name → required parameter message="Hello" → optional parameter with default value

Required parameters must come before default parameters like def greet(name, message="Hello"):

2nd example

probelm stat- the default tax percent is 10 but if user can enter dynamic rate to know final prices

def calculate_price(price, tax=10):
  gst = price + (price * tax/100)
  return gst 

print(calculate_price(100))

print(calculate_price(100, 20))

output 110 120

Function arguments: resusable

probelm stat- Create a function introduce(name, age) that accepts a person's name and age, and returns a formatted introduction containing both pieces of information.

def introduce(name, age):
  return (f"My name is {name} and I am {age} years old")

print(introduce("Sarbesh", 24))

Output My name is Sarbesh and I am 24 years old

note

f → f-string {name} and {age} → placeholders for variables

f is a formatted string. Evaluate anything inside {} and put its value here. f"....." means formatted string.

alt text

Return multiple values: but using reusable function arguments

Problem stat- Exercise: Return Multiple Values

Write a function called calculate(a, b) that: Calculates the sum of a and b. Calculates the difference (a - b). Returns both results from the function. Then store the returned values in two separate variables and print them.

def calculate(a, b):
  return (f"The sum of 2 numbers is {a + b} and difference is {a-b}")
    
print(calculate(20, 8))

List comprehensions

  • List comprehensions are just a shorter way of writing a for loop that creates a list.

Example: Squaring each element

  • We can do this without list comprehensions but it will just print it without storing result into a new list
numbers = [1, 2, 3, 4, 5]

for number in numbers:
    number = number * number
    print(number)

output 1 4 9 16 25

  • But we wanted to create a new list containing all the squares
numbers = [1,2,3,4,5]

squares = []

for number in numbers:
  squares.append(number * number)

print(squares)

Output [1, 4, 9, 16, 25]

ANother example using double

  • Identation matters so much in python
numbers = [1, 2, 3, 4, 5]

double = []

for i in numbers:
  double.append(i * 2)

print(i)

print(double)

output 5 [2, 4, 6, 8, 10]

now I want to show you if you write print statements inside the loop then at each loop it will print before reaching final element

numbers = [1, 2, 3, 4, 5]

double = []

for i in numbers:
  double.append(i * 2)
  print(double)

output [2] [2, 4] [2, 4, 6] [2, 4, 6, 8] [2, 4, 6, 8, 10]

numbers = [1, 2, 3, 4, 5]

double = []

for i in numbers:
  double.append(i * 2)
  print(i)

output 1 2 3 4 5

practise problem

probelm stat- Given a list of numbers, create a new list containing only the even numbers using a list comprehension.

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]                                        // Alt-    numbers = range(1,11)

even_number = []

for number in numbers:
  if number % 2 == 0:
    even_number.append(number)

print(even_number)

output [2, 4, 6, 8, 10]


  1. Built-in Functions

Built-in functions are available directly in Python and do not require an import.

numbers = [1, 2, 3, 4, 5]

print(len(numbers)) # 5 print(max(numbers)) # 5 print(min(numbers)) # 1 print(sum(numbers)) # 15

input() , print(), int(), float(), str() all are Built in functions


Module-Functions

  1. Module functions

A module is a file containing useful functions and other Python code.

Python's math module provides mathematical functions Importing an entire module-

import math
print(math.sqrt(16))                                  //  4.0
print(math.log2(64))                                 //  6.0

When the complete module is imported, use the module name before its function- math.sqrt(16)

Importing selected functions-

from math import sqrt, log2
print(sqrt(16))                                          # 4.0
print(log2(64))                                          # 6.0

another module is random

import random 
print(random.random())                      // 0 to 1 where 1 is excluded and every time a new number gets generated 
import random 
print(random.randint(1, 10))                // from range 1 to 10 a random number will get generated and where 1 & 10 both are included 

Viewing the contents of a module-

dir() displays the names available inside a module:

import math print(dir(math))


Exception-Handling

Situation

Imagine a situation where user enters a string instead of integers

age = int(input("Enter age: ")) and user enters abc Python crashes with a ValueError

Exception handling lets us handle that error instead of letting the program terminate.

Basic structure

try: age = int(input("Enter age: ")) except ValueError: print("Please enter a valid number.")

Mnetal Model

try → attempt this code

except → if a specific error happens, handle it here

Problem Exercise

Write a program that asks the user to enter a number and prints: You entered: 25 if the input is valid.

If they enter something like: hello it should print: Invalid input. Please enter a number.

try:
  number = int(input("Please enter a number: "))                                   
  print("You entered:", number)                                                        // Alt-    print(f"You entered: {number}")

except ValueError:
  print("Invalid input. Please enter a number")

Input Please enter a number: 23

Output You entered: 23

Trvia-

Remember whenever Error is rasied and Expect is triggered then python doesn't continue executing the remaining lines inside try.

Problem Exercise- Multiple except to catch multiple errors

Probelem stat- Asks the user for two numbers. Divides the first number by the second. Handles: non-numeric input with ValueError division by zero with ZeroDivisionError

try:
  a = int(input("Enter first number: "))
  b = int(input("Enter second number: "))
  result = a/b
  print(result)


except ValueError:
  print("Enter a valid number")


except ZeroDivisionError:
  print("Cannot divide by zero")

else and finally

else -> else runs only when the try block succeeds without an exception. finally -> Run this code no matter whether an exception happened or not.

try:
    number = int(input("Enter a number: "))
    print(number)

except ValueError:
    print("Invalid input")

finally:
    print("Program finished")

See

If input is valid:

Enter a number: 10 Program finished

If input is invalid:

Enter a number: abc Invalid input Program finished

  • The finally block runs in both cases.

File-Handling

So far, our programs mostly work with data that exists while the program is running: Once the program ends, that data is gone. A file lets us store data permanently so another program run can access it later.

Programs often need to read from and write to files:

  1. logs
  2. configuration
  3. CSV/text data (.csv or .txt)
  4. reports
  5. saved application data
  • Python's basic mechanism is open()

example: to read a file

file = open("data.txt", "r")

content = file.read() print(content)

file.close()

The important pieces:

open() → opens the file "data.txt" → file name , file we want to access "r" → read mode file → variable referring to the opened file .read() → reads the contents .close() → closes the file

Basic flow- open → read/write → close

  • However, in modern Python, we normally use a with statement so Python handles closing the file automatically

modern example using with

with open("data.txt", "r") as file: content = file.read()

print(content)

Traditional method-

file = open("data.txt", "r") content = file.read() print(content) file.close()

with method-

with open("data.txt", "r") as file: content = file.read()

print(content)

  • Python automatically handles closing the file when you're finished with the with block.

Problem Exercise

  1. Prblm stat- create a filed called numbers.txt. Put some numbers in it like 10,20,...,50 Then write Python code that opens the file and prints its contents.
  1. Problem statement

Created a file called numbers.txt where I stored 10, 20, 30, 40, 50

Then created a python file where I wrote the logic to read its contents. So it is File -> Python

with open("numbers.txt", "r") as file:

  content = file.read()
  print(content)

output 10, 20, 30, 40, 50

converting raw data into list

  • .read() gives you raw data and usually the output is in string format. We can convert string into List and later integers
with open("numbers.txt", "r") as file:
    content = file.read()

numbers = content.split(",")
print(numbers)

output ['10', ' 20', ' 30', ' 40', ' 50']

  • Now we have a list, but they're still strings. We can convert it into strings

Converting raw data -> list -> Integer

with open("numbers.txt", "r") as file:
  content = file.read()

numbers = []

for number in content.split(","):
  numbers.append(int(number))


print(numbers)
print(sum(numbers))

Output [10, 20, 30, 40, 50] 150

Alt

with open("numbers.txt", "r") as file: content = file.read()

numbers = []

values = content.split(",")

for number in values: numbers.append(int(number))

shortend version numbers = [int(number) for number in content.split(",")]


We did File -> Python

now, Python -> File

Writing with "w"

example

with open("output.txt", "w") as file:
    file.write("Hello Python")

  • This creates output.txt if it doesn't exist.

  • If it already exists, "w" overwrites its existing contents.

  • we can write multiple lines using \n

with open("output.txt", "w") as file:
    file.write("Python\n")
    file.write("Java\n")
    file.write("C++\n")

The file will contain: Python Java C++

Practise Exercise

Prblm stat- Create output.txt and write: Apple Banana Orange Mango using with open() and "w".

here python -> File so my end result is a file named output.txt where my result will get stored

with open("output.txt", "w") as file:

  file.write("Apple\n")
  file.write("Banana\n")
  file.write("Orange\n")
  file.write("Mango\n")

output in a new file output.txt

Mode Meaning "r" Read "w" Write / overwrite "a" Append

Follow up exercise: Append (Update exisiting content)

Prblm stat- Using your existing output.txt, write Python code that adds: Pineapple Watermelon to the end of the file without deleting Apple, Banana, Orange and Mango.

with open("output.txt", "a") as file:
  file.write("Pineapple\n")
  file.write("Watermelon\n")

One more useful file concept: reading line-by-line

Since files often contain many lines, you don't always want to load the entire file with: content = file.read()

You can iterate over the file directly:

with open("output.txt", "r") as file:
    for line in file:
        print(line)

  • but new line character \n produces extra spacing. So we need to strip it down
with open("output.txt", "r") as file:
    for line in file:
        print(line.strip())

  • we don't need for loop always to print indivual components as .read() already gives you the entire file as one string & .strip() removes spacing
with open("output.txt", "r") as file:

  content = file.read()
  print(content.strip())