# -*- coding: utf-8 -*-
"""
================================================================================
Comprehensive Python Guide: Data Structures
================================================================================
This program provides an in-depth demonstration of Python's core data
structures: Lists, Tuples, Dictionaries, and Sets.
It illustrates their creation, common operations, and key characteristics,
such as mutability (can be changed) vs. immutability (cannot be changed).
Sections:
1. Lists (Mutable, Ordered)
2. Tuples (Immutable, Ordered)
3. Dictionaries(Mutable, Key-Value Pairs, Ordered in Python 3.7+)
4. Sets (Mutable, Unordered, Unique Elements)
"""
# A function to print section headers for better readability.
def print_header(title):
"""Prints a formatted header to the console."""
print("\n" + "="*60)
print(f"| {title.center(56)} |")
print("="*60)
# ==============================================================================
# SECTION 1: LISTS (Mutable, Ordered)
# ==============================================================================
# A list is a collection which is ordered and changeable. Allows duplicate members.
print_header("Lists [Mutable, Ordered]")
# --- 1.1: Creation and Accessing ---
fruits = ["apple", "banana", "cherry", "orange", "kiwi"]
print(f"Initial list: {fruits}")
print(f"First element (index 0): {fruits[0]}")
print(f"Last element (index -1): {fruits[-1]}")
print(f"Slice from index 1 to 3: {fruits[1:3]}") # Elements at index 1 and 2
# --- 1.2: Modifying a List (Mutability) ---
print("\n--- Modifying the list (demonstrates mutability) ---")
fruits[1] = "blackberry" # Change an item
print(f"After changing index 1: {fruits}")
fruits.append("mango") # Add an item to the end
print(f"After appending 'mango': {fruits}")
fruits.insert(2, "strawberry") # Insert an item at a specific position
print(f"After inserting 'strawberry' at index 2: {fruits}")
removed_fruit = fruits.pop(3) # Remove and return item at index 3
print(f"After popping index 3 ('{removed_fruit}'): {fruits}")
fruits.remove("apple") # Remove the first occurrence of a value
print(f"After removing 'apple': {fruits}")
# --- 1.3: List Methods ---
print("\n--- Common list methods ---")
numbers = [5, 1, 9, 3, 7]
print(f"Original numbers list: {numbers}")
numbers.sort()
print(f"Sorted list (in-place): {numbers}")
numbers.reverse()
print(f"Reversed list (in-place): {numbers}")
print(f"Length of the fruits list: {len(fruits)}")
# ==============================================================================
# SECTION 2: TUPLES (Immutable, Ordered)
# ==============================================================================
# A tuple is a collection which is ordered and unchangeable. Allows duplicate members.
print_header("Tuples (Immutable, Ordered)")
# --- 2.1: Creation and Accessing ---
point = (10, 20, 30)
print(f"Initial tuple: {point}")
print(f"Element at index 0: {point[0]}")
print(f"Slice of the last two elements: {point[-2:]}")
# --- 2.2: Immutability ---
print("\n--- Demonstrating immutability ---")
print("Trying to change a tuple element will raise a TypeError.")
# The following line is commented out because it would stop the program.
# point[0] = 5 # <-- This would raise TypeError: 'tuple' object does not support item assignment
# --- 2.3: Tuple Methods & Unpacking ---
print("\n--- Tuple methods and unpacking ---")
mixed_tuple = ('a', 'b', 'c', 'a', 'd')
print(f"Tuple: {mixed_tuple}")
print(f"Count of 'a' in the tuple: {mixed_tuple.count('a')}")
print(f"Index of the first 'c': {mixed_tuple.index('c')}")
# Tuple unpacking
x, y, z = point
print(f"Unpacked tuple: x={x}, y={y}, z={z}")
# ==============================================================================
# SECTION 3: DICTIONARIES (Mutable, Key-Value Pairs)
# ==============================================================================
# A dictionary is a collection which is unordered (in Python < 3.7),
# changeable and indexed by a unique key.
print_header("Dictionaries [Mutable, Key-Value]")
# --- 3.1: Creation and Accessing ---
person = {
"first_name": "John",
"last_name": "Doe",
"age": 30
}
print(f"Initial dictionary: {person}")
print(f"Value for key 'first_name': {person['first_name']}")
# Using .get() is safer as it returns None instead of an error for missing keys
print(f"Value for key 'city' (using .get()): {person.get('city')}")
# --- 3.2: Modifying a Dictionary ---
print("\n--- Modifying the dictionary ---")
person['age'] = 31 # Update a value
print(f"After updating age: {person}")
person['city'] = "New York" # Add a new key-value pair
print(f"After adding city: {person}")
removed_value = person.pop('last_name') # Remove a key-value pair
print(f"After removing 'last_name' ('{removed_value}'): {person}")
# --- 3.3: Iterating over a Dictionary ---
print("\n--- Iterating over the dictionary ---")
print("Keys:")
for key in person.keys():
print(f"- {key}")
print("\nValues:")
for value in person.values():
print(f"- {value}")
print("\nItems (key-value pairs):")
for key, value in person.items():
print(f"- {key}: {value}")
# ==============================================================================
# SECTION 4: SETS (Mutable, Unordered, Unique)
# ==============================================================================
# A set is a collection which is unordered and unindexed. No duplicate members.
print_header("Sets [Mutable, Unordered, Unique]")
# --- 4.1: Creation and Basic Operations ---
# Duplicates are automatically removed
numbers_set = {1, 2, 3, 4, 4, 5, 2}
print(f"Initial set (duplicates removed): {numbers_set}")
numbers_set.add(6)
print(f"After adding 6: {numbers_set}")
numbers_set.remove(3) # Raises an error if the element is not found
print(f"After removing 3: {numbers_set}")
numbers_set.discard(10) # Does not raise an error if element is not found
print(f"After discarding 10 (no error): {numbers_set}")
print(f"Is 5 in the set? {5 in numbers_set}")
# --- 4.2: Set Operations ---
print("\n--- Set operations ---")
set_a = {1, 2, 3, 4}
set_b = {3, 4, 5, 6}
print(f"Set A: {set_a}")
print(f"Set B: {set_b}")
union_set = set_a.union(set_b) # or set_a | set_b
print(f"Union (all elements from both): {union_set}")
intersection_set = set_a.intersection(set_b) # or set_a & set_b
print(f"Intersection (elements in both): {intersection_set}")
difference_set = set_a.difference(set_b) # or set_a - set_b
print(f"Difference (elements in A but not B): {difference_set}")
symmetric_diff_set = set_a.symmetric_difference(set_b) # or set_a ^ set_b
print(f"Symmetric Difference (elements in A or B, but not both): {symmetric_diff_set}")
print("\n" + "="*60)
print("DEMONSTRATION COMPLETE".center(60))
print("="*60)