Session-2-2-Examples

This page shows the source code for Session-2-2-Examples.py with teaching notes, PURPOSE comments, and VS Code instructions.

Source File Session-2-2-Examples.py
Folder Chapter-2-Basic-Sessions

import sys
sys.stdout.reconfigure(encoding='utf-8')

"""
Session 2: Control Flow & Logic - Code Examples
Introduction to Python Course
"""

# ============================================
# PART 1: Conditional Statements
# ============================================
print("=" * 50)
print("PART 1: Conditional Statements")
print("=" * 50)
# ======================================================
# WHAT THIS SECTION DOES:
# 1) PURPOSE: Conditional statements let your program make
#    DECISIONS. Instead of running every line top-to-bottom,
#    the program checks a condition (True or False) and
#    chooses which block of code to run. This is the core
#    of all intelligent program behaviour.
#
# 2) VS CODE: Run each numbered Example block separately -
#    select its lines and press Shift+Enter. Change the
#    values (age, score, day) between runs to see how the
#    output changes based on different conditions.
# ======================================================

# ------------------------------------------------------
# HOW if / elif / else WORKS
# ------------------------------------------------------
#
#   STRUCTURE:
#       if <condition>:          <- checked first
#           <code block A>       <- runs if condition is True
#       elif <condition2>:       <- checked only if first was False
#           <code block B>       <- runs if condition2 is True
#       else:                    <- catches everything remaining
#           <code block C>       <- runs if ALL above were False
#
#   CRITICAL RULE - INDENTATION:
#   Python uses spaces (indentation) to know which lines
#   belong inside an if block. Every line inside MUST be
#   indented by the same amount (usually 4 spaces).
#
#       if age >= 18:
#           print("Adult")   <- indented = INSIDE the if
#       print("Done")        <- NOT indented = OUTSIDE the if
#
#   A condition is any expression that evaluates to True/False:
#       age >= 18            True if age is 18 or more
#       number % 2 == 0      True if number divides evenly by 2
#       name == "Alice"      True if name equals "Alice"
#
#   LOGICAL OPERATORS combine conditions:
#       and  ->  BOTH sides must be True
#       or   ->  AT LEAST ONE side must be True
#       not  ->  flips True to False and False to True
# ------------------------------------------------------

# Example 1: Simple if statement
print("\n--- Example 1: Age Check ---")
# ======================================================
# WHAT: The simplest form - only runs the indented block
#       when the condition is True. If False, nothing happens.
# TRY:  Change age to 15 and run again - no output from the if.
# ======================================================
age = 20
if age >= 18:
    print(f"Age {age}: You are an adult")

# Example 2: if-else statement
print("\n--- Example 2: Even or Odd ---")
# ======================================================
# WHAT: if-else covers BOTH possibilities - one branch runs
#       when True, the other when False. Always one or the other.
# TRY:  Change number to 8 - the "even" branch will run.
#       The % operator gives the remainder: 7 % 2 = 1 (odd),
#       8 % 2 = 0 (even). If remainder is 0, it divides evenly.
# ======================================================
number = 7
if number % 2 == 0:
    print(f"{number} is even")
else:
    print(f"{number} is odd")

# Example 3: if-elif-else statement
print("\n--- Example 3: Grade Calculator ---")
# ======================================================
# WHAT: elif (short for "else if") lets you check multiple
#       conditions in sequence. Python checks them top-to-bottom
#       and runs ONLY the first one that is True, then skips
#       all the rest. The else at the bottom is the fallback
#       if nothing above matched.
# TRY:  Change score to 92, then 65, then 45 to see each
#       branch execute in turn.
# ======================================================
score = 85

if score >= 90:
    grade = "A"
    message = "Excellent!"
elif score >= 80:          # only checked if score < 90
    grade = "B"
    message = "Great job!"
elif score >= 70:          # only checked if score < 80
    grade = "C"
    message = "Good work!"
elif score >= 60:          # only checked if score < 70
    grade = "D"
    message = "Needs improvement"
else:                      # catches score < 60
    grade = "F"
    message = "Please study more"

print(f"Score: {score}")
print(f"Grade: {grade}")
print(f"Message: {message}")

# Example 4: Logical operators - and
print("\n--- Example 4: Login Check (and operator) ---")
# ======================================================
# WHAT: 'and' requires BOTH conditions to be True.
#       If either username OR password is wrong, the
#       whole condition becomes False -> login fails.
# TRY:  Change password to "wrong" - login will fail even
#       though the username is correct.
# ======================================================
username = "admin"
password = "pass123"

if username == "admin" and password == "pass123":
    print("Login successful!")
else:
    print("Login failed!")

# Example 5: Logical operators - or
print("\n--- Example 5: Weekend Check (or operator) ---")
# ======================================================
# WHAT: 'or' only needs ONE condition to be True.
#       Saturday OR Sunday -> either one triggers the weekend message.
# TRY:  Change day to "Sunday" -> still weekend.
#       Change to "Monday" -> weekday branch runs.
# ======================================================
day = "Saturday"

if day == "Saturday" or day == "Sunday":
    print(f"{day}: It's the weekend!")
else:
    print(f"{day}: It's a weekday")

# Example 6: Logical operators - not
print("\n--- Example 6: Weather Check (not operator) ---")
# ======================================================
# WHAT: 'not' flips a Boolean value.
#       is_raining = False
#       not is_raining  ->  not False  ->  True
#       So the "Great day for a walk!" branch runs.
# TRY:  Change is_raining to True - the 'not' flips it to
#       False and the else branch runs instead.
# ======================================================
is_raining = False

if not is_raining:         # not False -> True -> runs this block
    print("Great day for a walk!")
else:
    print("Better stay inside")

# Example 7: Complex conditions
print("\n--- Example 7: Movie Ticket Price ---")
# ======================================================
# WHAT: Combines multiple conditions and logical operators
#       to model a real-world pricing rule. Notice how
#       'and' is used inside an elif to check a range:
#       age >= 12 and age < 18  means "between 12 and 17".
# TRY:  Change age to 10, then 15, then 30 and watch which
#       price is selected each time.
# ======================================================
age = 25
is_student = True

if age < 12:
    price = 5
elif age >= 12 and age < 18:   # range check using and
    price = 8
elif is_student:               # True/False variable used directly
    price = 10
else:
    price = 12

print(f"Age: {age}, Student: {is_student}")
print(f"Ticket price: ${price}")

# Example 8: Nested if statements
print("\n--- Example 8: Grade with Bonus ---")
# ======================================================
# WHAT: An if inside another if - called "nested if".
#       The inner if only runs when the outer if is True.
#       Indentation shows the nesting level:
#           if score >= 80:          <- outer (4 spaces in)
#               if attendance >= 90: <- inner (8 spaces in)
#                   ...              <- inner body (12 spaces in)
# TRY:  Change attendance to 75 - the outer if still passes
#       (score 88 >= 80) but the inner if fails -> Grade B.
# ======================================================
score = 88
attendance = 95

if score >= 80:                 # outer if
    if attendance >= 90:        # inner if (only reached if score >= 80)
        print(f"Score: {score}, Attendance: {attendance}%")
        print("Grade: A (with perfect attendance bonus!)")
    else:
        print(f"Score: {score}, Attendance: {attendance}%")
        print("Grade: B")
else:
    print("Grade: C or below")


# ============================================
# PART 2: Loops - for Loop
# ============================================
print("\n" + "=" * 50)
print("PART 2: For Loops")
print("=" * 50)
# ======================================================
# WHAT THIS SECTION DOES:
# 1) PURPOSE: A for loop repeats a block of code once for
#    each item in a sequence (a range of numbers, a list,
#    a string, etc.). Instead of copy-pasting the same
#    print line 10 times, you write it once inside a loop.
#
# 2) VS CODE: Select each Example block and press Shift+Enter.
#    Pay attention to the range() arguments - changing them
#    is the most important skill to practice here.
# ======================================================

# ------------------------------------------------------
# HOW for LOOPS AND range() WORK
# ------------------------------------------------------
#
#   STRUCTURE:
#       for <variable> in <sequence>:
#           <code block>        <- runs once per item
#
#   range() generates a sequence of numbers:
#       range(5)        ->  0, 1, 2, 3, 4        (start=0, stop=5)
#       range(1, 6)     ->  1, 2, 3, 4, 5        (start=1, stop=6)
#       range(0, 11, 2) ->  0, 2, 4, 6, 8, 10    (start, stop, step)
#
#   IMPORTANT: range(stop) STOPS BEFORE the stop value.
#       range(5) gives 0-4, NOT 0-5.
#       range(1, 6) gives 1-5, NOT 1-6.
#
#   The loop variable (i, fruit, letter) takes the value of
#   each item one at a time:
#       for fruit in ["apple", "banana"]:
#           print(fruit)    <- prints "apple", then "banana"
#
#   INDENTATION applies here too: the indented block runs
#   each iteration; the non-indented line after the loop
#   runs only once when the loop is finished.
# ------------------------------------------------------

# Example 1: Basic for loop with range
print("\n--- Example 1: Count to 5 ---")
# ======================================================
# WHAT: range(5) produces 0,1,2,3,4 - five numbers.
#       The loop runs 5 times; i holds each value in turn.
# TRY:  Change range(5) to range(10) to count to 9.
# ======================================================
for i in range(5):             # i = 0, then 1, then 2, then 3, then 4
    print(f"Count: {i}")

# Example 2: Range with start and end
print("\n--- Example 2: Range 1 to 5 ---")
# ======================================================
# WHAT: range(1, 6) starts at 1 and stops BEFORE 6 -> 1,2,3,4,5.
#       end=" " in print() puts a space instead of a newline,
#       keeping all numbers on one line.
#       The bare print() after the loop adds the final newline.
# ======================================================
for i in range(1, 6):
    print(i, end=" ")          # end=" " -> prints on same line with space
print()                        # moves to the next line after the loop

# Example 3: Range with step
print("\n--- Example 3: Even Numbers (step by 2) ---")
# ======================================================
# WHAT: The third argument to range() is the STEP - how much
#       to add each time. range(0, 11, 2) -> 0,2,4,6,8,10.
# TRY:  Change to range(1, 11, 2) to get odd numbers instead.
# ======================================================
for i in range(0, 11, 2):     # step=2 -> skips every other number
    print(i, end=" ")
print()

# Example 4: Loop through a list
print("\n--- Example 4: Fruits List ---")
# ======================================================
# WHAT: You can loop over any list directly - no range() needed.
#       fruit takes the value of each list item one at a time:
#       first "apple", then "banana", then "cherry", then "date".
# TRY:  Add "elderberry" to the list and run again.
# ======================================================
fruits = ["apple", "banana", "cherry", "date"]
for fruit in fruits:           # fruit = each item in the list
    print(f"I like {fruit}")

# Example 5: Loop through a string
print("\n--- Example 5: Loop Through String ---")
# ======================================================
# WHAT: Strings are sequences of characters, so you can loop
#       over them just like lists. Each iteration gives one letter.
# TRY:  Change word to your own name and run again.
# ======================================================
word = "PYTHON"
for letter in word:            # letter = "P", then "Y", then "T", ...
    print(letter, end="-")
print()

# Example 6: Multiplication table
print("\n--- Example 6: Multiplication Table (5) ---")
# ======================================================
# WHAT: range(1, 11) gives 1 through 10. Inside the loop,
#       the result is calculated fresh each iteration.
#       This is the for-loop pattern for building a table.
# TRY:  Change 5 * i to 7 * i to get the 7-times table.
# ======================================================
for i in range(1, 11):
    result = 5 * i             # calculated anew each loop iteration
    print(f"5 x {i} = {result}")

# Example 7: Sum of numbers
print("\n--- Example 7: Sum of 1 to 10 ---")
# ======================================================
# WHAT: total starts at 0 OUTSIDE the loop. Each iteration
#       adds the current i to it. This is the ACCUMULATOR pattern -
#       the most common use of loops in real programs.
#       total += i  is shorthand for  total = total + i
# ======================================================
total = 0                      # accumulator - starts at 0
for i in range(1, 11):
    total += i                 # add current i to running total
print(f"Sum of 1 to 10: {total}")

# Example 8: Counting specific items
print("\n--- Example 8: Count Even Numbers ---")
# ======================================================
# WHAT: Combines a loop with an if inside it - the if filters
#       which iterations actually do something (the counter).
#       This is the FILTER + COUNT pattern.
# TRY:  Change the condition to i % 3 == 0 to count
#       multiples of 3 between 1 and 20 instead.
# ======================================================
even_count = 0
for i in range(1, 21):
    if i % 2 == 0:             # filter: only count even numbers
        even_count += 1
print(f"Even numbers between 1-20: {even_count}")


# ============================================
# PART 3: While Loops
# ============================================
print("\n" + "=" * 50)
print("PART 3: While Loops")
print("=" * 50)
# ======================================================
# WHAT THIS SECTION DOES:
# 1) PURPOSE: A while loop keeps repeating AS LONG AS its
#    condition is True. Use while when you do NOT know in
#    advance how many times to loop (e.g. "keep asking until
#    the user enters a valid password"). Use for when you DO
#    know the count or are iterating over a fixed sequence.
#
# 2) VS CODE: Select each Example block and press Shift+Enter.
#    WARNING: if you accidentally create an infinite loop
#    (condition never becomes False), press Ctrl+C in the
#    terminal to stop it.
# ======================================================

# ------------------------------------------------------
# HOW while LOOPS WORK
# ------------------------------------------------------
#
#   STRUCTURE:
#       while <condition>:
#           <code block>    <- repeats as long as condition is True
#
#   EXECUTION FLOW:
#       1. Check condition
#       2. If True  -> run the block, then go back to step 1
#       3. If False -> exit the loop, continue with next line
#
#   CRITICAL: something inside the loop MUST eventually make
#   the condition False, otherwise it loops forever!
#
#       count = 5
#       while count > 0:
#           print(count)
#           count -= 1      <- THIS makes the condition eventually False
#
#   COMMON MISTAKE - infinite loop (missing the update):
#       count = 5
#       while count > 0:
#           print(count)    <- count never changes -> loops forever!
# ------------------------------------------------------

# Example 1: Basic while loop
print("\n--- Example 1: Countdown ---")
# ======================================================
# WHAT: count starts at 5. Each iteration prints count,
#       then count -= 1 decreases it by 1. When count
#       reaches 0, "count > 0" becomes False and the loop ends.
#       "Blastoff!" is outside the loop so it prints once after.
# TRY:  Change count to 10 for a longer countdown.
# ======================================================
count = 5
while count > 0:               # loop runs while this is True
    print(f"Countdown: {count}")
    count -= 1                 # MUST decrease or loop runs forever!
print("Blastoff!")             # outside loop - runs once after loop ends

# Example 2: User input with while loop (demo)
print("\n--- Example 2: Password Validator (demo) ---")
# ======================================================
# WHAT: This is the classic "try again" pattern. The loop
#       keeps running as long as attempts > 0. When the
#       correct password is entered, 'break' exits immediately.
#       The 'else' on a while loop runs ONLY if the loop
#       finished naturally (condition became False), NOT via break.
# HOW TO TRY: Remove the # from all lines below this comment
#       block, select them, and press Shift+Enter.
#       Type wrong passwords, then "secret123" to see both paths.
# ======================================================
# attempts = 3
# while attempts > 0:
#     password = input(f"Enter password ({attempts} attempts left): ")
#     if password == "secret123":
#         print("Access granted!")
#         break               # exits the loop immediately
#     attempts -= 1
#     print("Wrong password!")
# else:
#     print("Account locked!") # runs only if loop ended without break

# Example 3: Sum until negative number
print("\n--- Example 3: Sum Numbers (demo) ---")
# ======================================================
# WHAT: The while loop walks through a list using an index.
#       When a negative number is found, 'break' stops the loop
#       immediately - the 20 after -1 is never processed.
#       index < len(numbers) is the boundary guard that also
#       prevents going past the end of the list.
# ======================================================
numbers = [5, 10, 15, -1, 20]
total = 0
index = 0
print("Enter numbers (negative to stop):")
while index < len(numbers):   # len(numbers) = 5, so index can be 0-4
    num = numbers[index]
    print(f"Number entered: {num}")
    if num < 0:
        break                  # stop immediately when negative seen
    total += num
    index += 1
print(f"Total sum: {total}")


# ============================================
# PART 4: Loop Control (break, continue)
# ============================================
print("\n" + "=" * 50)
print("PART 4: Break and Continue")
print("=" * 50)
# ======================================================
# WHAT THIS SECTION DOES:
# 1) PURPOSE: break and continue give you fine-grained control
#    INSIDE a loop without changing the loop condition itself.
#    break  = "stop the loop RIGHT NOW, exit completely"
#    continue = "skip the rest of THIS iteration, start the next one"
#
# 2) VS CODE: Run each Example separately. Trace through
#    the output and match each printed line to the code
#    to understand exactly when break/continue fires.
# ======================================================

# ------------------------------------------------------
# break vs continue - VISUAL SUMMARY
# ------------------------------------------------------
#
#   break exits the ENTIRE loop:
#       for i in range(5):
#           if i == 3:
#               break         <- loop stops here
#           print(i)          <- prints 0, 1, 2 then stops
#
#   continue skips only the CURRENT iteration:
#       for i in range(5):
#           if i == 3:
#               continue      <- skip i=3, keep going
#           print(i)          <- prints 0, 1, 2, 4 (3 is skipped)
#
#   Both work in for loops and while loops.
# ------------------------------------------------------

# Example 1: break statement
print("\n--- Example 1: Finding a Number (break) ---")
# ======================================================
# WHAT: The loop searches for 7. When found, it prints a
#       message and immediately exits with 'break'.
#       Lines after the break (i=8,9,10) are never reached.
# ======================================================
for i in range(1, 11):
    if i == 7:
        print(f"Found {i}! Stopping search.")
        break                  # exits loop immediately
    print(f"Checking {i}...")  # only reached when i != 7

# Example 2: continue statement
print("\n--- Example 2: Skip Multiples of 3 (continue) ---")
# ======================================================
# WHAT: When i is a multiple of 3 (3, 6, 9), 'continue'
#       jumps straight to the next iteration - the print
#       below it is skipped for those values only.
#       Output: 1 2 4 5 7 8 10
# ======================================================
for i in range(1, 11):
    if i % 3 == 0:
        continue               # skip this iteration, go to next i
    print(i, end=" ")          # only reached when i is NOT a multiple of 3
print()

# Example 3: break in while loop
print("\n--- Example 3: Search in List (break) ---")
# ======================================================
# WHAT: Manual list search using while + break. The 'found'
#       flag is set to True before breaking so the code after
#       the loop knows whether the search succeeded.
#       This is the SEARCH PATTERN - common in real programs.
# ======================================================
fruits = ["apple", "banana", "cherry", "date"]
search = "cherry"
index = 0
found = False

while index < len(fruits):
    if fruits[index] == search:
        print(f"Found '{search}' at position {index}")
        found = True
        break                  # no need to keep searching
    index += 1

if not found:
    print(f"'{search}' not found")


# ============================================
# PART 5: Nested Loops
# ============================================
print("\n" + "=" * 50)
print("PART 5: Nested Loops")
print("=" * 50)
# ======================================================
# WHAT THIS SECTION DOES:
# 1) PURPOSE: A nested loop is a loop INSIDE another loop.
#    The inner loop runs completely for EACH iteration of
#    the outer loop. Total iterations = outer count x inner count.
#    Common uses: tables, grids, 2D patterns, comparing
#    every item in one list against every item in another.
#
# 2) VS CODE: Run each Example separately. Count the output
#    lines and verify: outer_range x inner_range = total lines.
# ======================================================

# ------------------------------------------------------
# HOW NESTED LOOPS WORK
# ------------------------------------------------------
#
#   for i in range(3):          <- outer loop (runs 3 times)
#       for j in range(4):      <- inner loop (runs 4 times PER outer)
#           print(i, j)         <- runs 3 x 4 = 12 times total
#       print("---")            <- runs 3 times (once per outer loop)
#
#   Execution order for range(2) x range(3):
#       i=0, j=0  ->  i=0, j=1  ->  i=0, j=2
#       i=1, j=0  ->  i=1, j=1  ->  i=1, j=2
#
#   The OUTER loop variable (i) changes SLOWLY.
#   The INNER loop variable (j) changes FAST.
# ------------------------------------------------------

# Example 1: Simple nested loop
print("\n--- Example 1: Nested Loop Pattern ---")
# ======================================================
# WHAT: Outer loop i goes 0,1,2. For each i, inner loop
#       j goes 0,1,2,3. The print() at the end of the outer
#       body (not indented inside j) moves to the next line
#       after each inner loop finishes.
# ======================================================
for i in range(3):             # outer: 0, 1, 2
    for j in range(4):         # inner: 0, 1, 2, 3 (resets each outer)
        print(f"({i},{j})", end=" ")
    print()                    # newline after each full inner loop

# Example 2: Multiplication table
print("\n--- Example 2: Multiplication Table ---")
# ======================================================
# WHAT: Classic nested loop table. i is the row, j is the
#       column. end="\t" uses a tab character to line up columns.
# TRY:  Change range(1, 4) to range(1, 6) for a 5x5 table.
# ======================================================
for i in range(1, 4):
    for j in range(1, 4):
        print(f"{i}x{j}={i*j}", end="\t")  # \t = tab for alignment
    print()

# Example 3: Pattern printing
print("\n--- Example 3: Star Pattern ---")
# ======================================================
# WHAT: The inner loop runs i times (not a fixed count).
#       When i=1 -> 1 star, i=2 -> 2 stars, ..., i=5 -> 5 stars.
#       This creates a triangle because the inner range grows
#       each time the outer loop increments.
# TRY:  Change range(1, 6) to range(5, 0, -1) for
#       an upside-down triangle (decreasing stars).
# ======================================================
for i in range(1, 6):
    for j in range(i):         # inner range grows with i
        print("*", end=" ")
    print()


# ============================================
# PART 6: Practical Examples
# ============================================
print("\n" + "=" * 50)
print("PART 6: Practical Examples")
print("=" * 50)
# ======================================================
# WHAT THIS SECTION DOES:
# 1) PURPOSE: These three programs combine everything from
#    Parts 1-5 into realistic mini-applications. Reading
#    and understanding them is excellent practice for seeing
#    how if/elif, for, while, break, and continue work
#    together in real code.
#
# 2) VS CODE: Run each example, then try changing one value
#    at a time (secret_number, number to test, password string)
#    and predict the output BEFORE running to test your
#    understanding of the control flow.
# ======================================================

# Example 1: Number guessing game (simulated)
print("\n--- Example 1: Number Guessing Game (Demo) ---")
# ======================================================
# WHAT: for loop with break + elif chain. The 'else' on
#       the for loop only runs if the loop finished WITHOUT
#       a break (i.e. all guesses were wrong). This is
#       Python's unique "for...else" pattern.
# TRY:  Change guesses = [5, 8, 7] to [1, 2, 3] to trigger
#       the "Game over!" else branch.
# ======================================================
secret_number = 7
guesses = [5, 8, 7]
max_attempts = 3

for attempt in range(1, max_attempts + 1):
    guess = guesses[attempt - 1]
    print(f"Attempt {attempt}: You guessed {guess}")

    if guess == secret_number:
        print(f"Correct! You won in {attempt} attempts!")
        break                   # exit loop on correct guess
    elif guess < secret_number:
        print("Too low!")
    else:
        print("Too high!")
else:
    # 'else' on for loop: runs ONLY if loop ended without break
    print(f"Game over! The number was {secret_number}")

# Example 2: Prime number checker
print("\n--- Example 2: Prime Number Checker ---")
# ======================================================
# WHAT: A prime has no divisors except 1 and itself.
#       We only need to check up to the square root of number
#       (int(number ** 0.5) + 1) - a math shortcut that avoids
#       unnecessary checks. If ANY divisor is found, we set
#       is_prime = False and break immediately.
# TRY:  Change number to 15 (not prime) then 19 (prime).
# ======================================================
number = 17
is_prime = True

if number < 2:
    is_prime = False
else:
    for i in range(2, int(number ** 0.5) + 1):  # only check up to sqrt
        if number % i == 0:
            is_prime = False
            break              # no need to check further

if is_prime:
    print(f"{number} is a prime number")
else:
    print(f"{number} is not a prime number")

# Example 3: Password strength checker
print("\n--- Example 3: Password Strength Checker ---")
# ======================================================
# WHAT: Loops over each character in the password string
#       and uses built-in string methods to classify it:
#           char.isdigit() -> True if character is 0-9
#           char.isupper() -> True if character is A-Z
#           char.islower() -> True if character is a-z
#       Three boolean flags are set to True as soon as their
#       character type is found. Then the strength is determined
#       by combining the flags with 'and'/'or' logic.
# TRY:  Change password to "hello" (weak), "Hello1" (medium),
#       "SecurePass99" (strong) to test all three branches.
# ======================================================
password = "MyPass123"
has_digit = False
has_upper = False
has_lower = False

for char in password:          # check each character one by one
    if char.isdigit():
        has_digit = True       # found at least one digit
    elif char.isupper():
        has_upper = True       # found at least one uppercase letter
    elif char.islower():
        has_lower = True       # found at least one lowercase letter

strength = "Weak"
if len(password) >= 8 and has_digit and has_upper and has_lower:
    strength = "Strong"
elif len(password) >= 6 and (has_digit or has_upper):
    strength = "Medium"

print(f"Password: {password}")
print(f"Length: {len(password)}")
print(f"Strength: {strength}")

print("\nSession 2 completed successfully!")
print("Practice these control flow concepts!")