CODE: Python Data Structures

Python data structures, covering when and why to use lists, tuples, sets, dictionaries, stacks, queues, trees, and graphs for clean and efficient code, and designed to help developers choose the right structure for performance, memory, and scalability.

Strings and String Methods

Strings are one of the most important and commonly used data structures in Python. A string is a sequence of characters, such as letters, numbers, or symbols, enclosed in single ('), double (") or triple quotes (''' / """). Strings are widely used for text processing, user input, file handling, and much more.


Creating Strings

Strings can be defined in multiple ways:

python
str1 = 'Hello'
str2 = "World"
str3 = """This is 
a multi-line string"""

  • Single or double quotes work the same.
  • Triple quotes allow multi-line text.

Basic String Operations

Python strings support many useful operations.

  • Concatenation (joining):
  • python
    greeting = "Hello" + " " + "Python"
    print(greeting)   # Output: Hello Python

  • Repetition:
  • python
    laugh = "ha" * 3
    print(laugh)      # Output: hahaha

  • Indexing:
  • python
    word = "Python"
    print(word[0])    # Output: P
    print(word[-1])   # Output: n

  • Slicing:
  • python
    print(word[0:3])  # Output: Pyt
    print(word[2:])   # Output: thon


Common String Methods

Python provides many built-in methods to work with strings.

  • Changing case:
  • python
    text = "python"
    print(text.upper())  # Output: PYTHON
    print(text.capitalize())  # Output: Python

  • Searching:
  • python
    sentence = "I love Python"
    print(sentence.find("love"))  # Output: 2
    print("Python" in sentence)   # Output: True

  • Replacing:
  • python
    print(sentence.replace("Python", "coding"))  
    # Output: I love coding

  • Splitting and joining:
  • python
    words = sentence.split()  
    print(words)  # ['I', 'love', 'Python']
    
    joined = "-".join(words)
    print(joined) # I-love-Python

  • Stripping whitespace:
  • python
    name = "  Amr  "
    print(name.strip())  # Output: Amr


String Formatting

Python provides powerful ways to insert variables into strings.

  • f-strings (modern way):
  • python
    name = "Amr"
    age = 30
    print(f"My name is {name} and I am {age} years old.")

  • format method:
  • python
    print("My name is {} and I am {} years old.".format(name, age))

These make output dynamic and user-friendly.


Lists and List Operations

list in Python is an ordered collection that can store multiple items in a single variable. Unlike strings, lists are mutable, meaning you can change, add, or remove elements after creating them. Lists are one of the most flexible and widely used data structures in Python.


Creating Lists

Lists are defined using square brackets [].

python
numbers = [1, 2, 3, 4, 5]
fruits = ["apple", "banana", "cherry"]
mixed = [1, "hello", 3.14, True]
empty = []

  • A list can contain elements of different data types.
  • Lists can also be nested, meaning one list inside another.

Accessing List Elements

Lists support indexing and slicing, similar to strings.

python
fruits = ["apple", "banana", "cherry"]
print(fruits[0])   # Output: apple
print(fruits[-1])  # Output: cherry
print(fruits[0:2]) # Output: ['apple', 'banana']

  • Indexing starts at 0.
  • Negative indices count from the end.

Modifying Lists

Since lists are mutable, you can change elements directly.

python
fruits[1] = "blueberry"
print(fruits)  # ['apple', 'blueberry', 'cherry']

  • You can also add or remove elements dynamically.

Adding Elements

Python provides multiple ways to add items to a list.

python
fruits.append("orange")         # Add to the end
fruits.insert(1, "mango")       # Insert at position 1
fruits.extend(["kiwi", "melon"]) # Add multiple items
print(fruits)


Removing Elements

Lists allow flexible removal of items.

python
fruits.remove("apple")   # Removes first occurrence
popped = fruits.pop()    # Removes last element
del fruits[0]            # Deletes element at index 0
print(fruits)

  • pop() can also remove by index: fruits.pop(1).

Iterating Over Lists

You can loop through list elements easily.

python
for fruit in fruits:
    print(fruit)

  • Useful for processing items in bulk.

Useful List Methods

Python lists come with many built-in methods.

python
numbers = [5, 2, 9, 1]

print(len(numbers))   # Length → 4
print(max(numbers))   # Maximum → 9
print(min(numbers))   # Minimum → 1

numbers.sort()
print(numbers)        # Sorted → [1, 2, 5, 9]

numbers.reverse()
print(numbers)        # Reversed → [9, 5, 2, 1]


Tuples and Immutability

tuple in Python is an ordered collection of elements, similar to a list. However, unlike lists, tuples are immutable, meaning once created, their elements cannot be changed, added, or removed. This immutability makes tuples useful for storing fixed collections of data that should remain constant throughout the program.


Creating Tuples

Tuples are created using parentheses ().

python
numbers = (1, 2, 3)
fruits = ("apple", "banana", "cherry")
mixed = (1, "hello", 3.14, True)
single = (5,)   # Note the comma for a single-element tuple
empty = ()

  • Without the comma, (5) would be treated as an integer, not a tuple.

Accessing Tuple Elements

Tuples support indexing and slicing, just like lists.

python
fruits = ("apple", "banana", "cherry")
print(fruits[0])    # Output: apple
print(fruits[-1])   # Output: cherry
print(fruits[0:2])  # Output: ('apple', 'banana')

  • Elements can be accessed but not modified.

Immutability of Tuples

Tuples cannot be changed after creation.

python
fruits = ("apple", "banana", "cherry")
# fruits[1] = "mango"   # This will raise a TypeError

  • This immutability makes tuples safe and reliable for storing data that should remain constant.
  • However, if a tuple contains a mutable object (like a list), that object can still be modified.

python
nested = (1, [2, 3], 4)
nested[1].append(5)
print(nested)  # Output: (1, [2, 3, 5], 4)


Tuple Operations

Although immutable, tuples still support useful operations.

python
colors = ("red", "green", "blue")

print(len(colors))       # Length → 3
print("green" in colors) # Membership → True
print(colors + ("yellow",)) # Concatenation → ('red', 'green', 'blue', 'yellow')
print(colors * 2)        # Repetition → ('red', 'green', 'blue', 'red', 'green', 'blue')


Tuple Unpacking

Tuples can be unpacked directly into variables, making assignments concise.

python
person = ("Amr", 30, "Engineer")
name, age, job = person
print(name)  # Amr
print(age)   # 30
print(job)   # Engineer

  • This feature is often used in returning multiple values from functions.

Sets and Set Operations

set in Python is an unordered collection of unique elements. Unlike lists and tuples, sets do not allow duplicates, and the order of elements is not preserved. Sets are very useful for mathematical operations like unions and intersections, as well as tasks such as removing duplicates from data.


Creating Sets

Sets are created using curly braces {} or the set() function.

python
numbers = {1, 2, 3, 4}
fruits = {"apple", "banana", "cherry"}
empty = set()   # Correct way to create an empty set

  • Writing {} creates an empty dictionary, not a set.
  • Duplicate values are automatically removed:
  • python
    nums = {1, 2, 2, 3}
    print(nums)  # Output: {1, 2, 3}


Accessing Elements in Sets

Since sets are unordered, elements cannot be accessed by index.

  • You can loop through a set:
  • python
    for fruit in fruits:
        print(fruit)

  • Membership testing is very fast:
  • python
    print("apple" in fruits)   # True
    print("mango" not in fruits) # True


    Adding and Removing Elements

Sets are mutable, so you can add or remove items.

python
fruits.add("orange")   # Add a single element
fruits.update(["kiwi", "melon"])  # Add multiple elements
fruits.remove("banana")  # Removes "banana" (raises error if not found)
fruits.discard("pear")   # Safe remove (no error if not found)
print(fruits)

  • pop() removes and returns a random element (since sets are unordered).

Set Operations

Sets support powerful mathematical-style operations.

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

print(A | B)   # Union → {1, 2, 3, 4, 5, 6}
print(A & B)   # Intersection → {3, 4}
print(A - B)   # Difference → {1, 2}
print(A ^ B)   # Symmetric difference → {1, 2, 5, 6}

  • Union (|) combines all elements.
  • Intersection (&) finds common elements.
  • Difference (-) finds items in one set but not the other.
  • Symmetric difference (^) finds items in either set but not both.

Useful Set Methods

Sets include additional helpful methods.

python
nums = {1, 2, 3}
nums.clear()       # Removes all elements
print(len(nums))   # Length of the set

  • These methods make sets excellent for managing collections of unique values.

Dictionaries (key–value pairs)

Dictionaries are one of the most important and flexible data structures in Python. They allow you to store and organize data as key–value pairs, where each key acts like a label that maps to a value. Unlike lists, which are ordered by index, dictionaries are optimized for fast lookups based on keys.


What are Dictionaries?

  • dictionary is an unorderedmutable collection of items stored as key–value pairs.
  • Keys: must be unique and immutable (e.g., strings, numbers, tuples).
  • Values: can be of any type (string, number, list, dict, etc.), and may be repeated.
  • Since Python 3.7, dictionaries maintain the insertion order of keys (in practice, they are ordered).

python
student = {
    "id": 101,
    "name": "Amr",
    "age": 22,
    "courses": ["Math", "Physics"],
    "is_active": True
}


Creating Dictionaries

Dictionaries can be created in several ways.

  • **Using curly braces `{}
  • python
    person = {"name": "Sara", "age": 25}

  • Using the dict() constructor
  • python
    employee = dict(name="Ali", position="Engineer")

  • From a list of tuples
  • python
    pairs = [("x", 10), ("y", 20)]
    coordinates = dict(pairs)  # {"x": 10, "y": 20}

  • Empty dictionary
  • python
    empty = {}


Accessing and Updating Values

You retrieve or modify values using their keys.

  • Accessing values
  • python
    student = {"name": "Amr", "age": 22}
    print(student["name"])      # Output: Amr

  • **Safe access with `.get()
  • python
    print(student.get("grade", "Not Assigned"))  # Output: Not Assigned

  • Updating values
  • python
    student["age"] = 23

  • Adding new key–value pairs
  • python
    student["grade"] = "A"


Dictionary Methods

Dictionaries come with many useful methods:

  • .keys() → returns all keys
  • .values() → returns all values
  • .items() → returns key–value pairs
  • .update({...}) → merges another dictionary
  • .pop(key) → removes a key and returns its value
  • .popitem() → removes the last inserted pair
  • .clear() → removes everything

python
info = {"a": 1, "b": 2}
print(info.keys())     # dict_keys(['a', 'b'])
print(info.values())   # dict_values([1, 2])
print(info.items())    # dict_items([('a', 1), ('b', 2)])


Iterating over Dictionaries

You can loop through keys, values, or both.

python
user = {"id": 1, "name": "Sara", "role": "Admin"}

for key in user:               # keys
    print(key)

for value in user.values():    # values
    print(value)

for key, value in user.items():  # pairs
    print(key, ":", value)


Nested Dictionaries

Dictionaries can contain other dictionaries, allowing complex data structures.

python
company = {
    "IT": {"manager": "Amr", "employees": 25},
    "HR": {"manager": "Sara", "employees": 10}
}

print(company["IT"]["manager"])  # Output: Amr


Dictionary Comprehensions

You can build dictionaries in one line using comprehensions.

python
squares = {x: x**2 for x in range(5)}
print(squares)  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

Example with conditions:

python
prices = {"apple": 2, "banana": 1, "pear": 3}
discounted = {item: price*0.9 for item, price in prices.items() if price > 1}
print(discounted)  # {'apple': 1.8, 'pear': 2.7}


Common Pitfalls

  • Unhashable keys: Lists and dicts cannot be used as keys. Tuples are fine if they only contain hashable types.
  • Duplicate keys: Later assignments overwrite earlier ones.
  • python
    d = {"a": 1, "a": 2}
    print(d)  # {"a": 2}

  • Shared mutable values: Avoid reusing the same list for multiple keys.

Real-world Uses

  • JSON-like data: APIs and configs often return data as dicts.
  • Counting frequencies:

python
text = "banana"
count = {}
for ch in text:
    count[ch] = count.get(ch, 0) + 1
print(count)  # {'b':1, 'a':3, 'n':2}

  • Fast lookup tables:
  • python
    countries = {"NL": "Netherlands", "EG": "Egypt"}
    print(countries["EG"])  # Output: Egypt

  • Grouping data:
  • python
    records = [("EU","NL"), ("EU","DE"), ("US","CA")]
    groups = {}
    for region, country in records:
        groups.setdefault(region, []).append(country)
    print(groups)  # {'EU': ['NL', 'DE'], 'US': ['CA']}


Nesting and Comprehensions (List, Dict, Set)

Comprehensions are one of Python’s most elegant features. They let you build new collections (lists, dicts, sets) in a single, readable line, often replacing multiple loops. Combined with nesting, they become powerful tools for data transformation.


1. List Comprehensions

list comprehension creates a new list by applying an expression to each item of an iterable.

python
# Basic form
[expression for item in iterable if condition]

  • Without comprehension
  • python
    squares = []
    for x in range(5):
        squares.append(x**2)

  • With comprehension
  • python
    squares = [x**2 for x in range(5)]
    print(squares)  # [0, 1, 4, 9, 16]

  • With condition
  • python
    even_squares = [x**2 for x in range(10) if x % 2 == 0]
    print(even_squares)  # [0, 4, 16, 36, 64]


2. Dictionary Comprehensions

dictionary comprehension builds dictionaries concisely.

python
# Create mapping: number → square
squares = {x: x**2 for x in range(5)}
print(squares)  # {0:0, 1:1, 2:4, 3:9, 4:16}

  • With condition
  • python
    prices = {"apple": 2, "banana": 1, "pear": 3}
    discounted = {k: v*0.9 for k, v in prices.items() if v > 1}
    print(discounted)  # {'apple': 1.8, 'pear': 2.7}


3. Set Comprehensions

set comprehension builds sets, automatically removing duplicates.

python
nums = [1, 2, 2, 3, 4, 4, 5]
unique_squares = {x**2 for x in nums}
print(unique_squares)  # {1, 4, 9, 16, 25}


4. Nesting with Comprehensions

Comprehensions can be nested, replacing multiple loops.

  • Nested loops in list comprehension
  • python
    pairs = [(x, y) for x in [1, 2] for y in [3, 4]]
    print(pairs)  # [(1,3), (1,4), (2,3), (2,4)]

  • Flattening nested lists
  • python
    matrix = [[1,2,3], [4,5,6], [7,8,9]]
    flat = [num for row in matrix for num in row]
    print(flat)  # [1,2,3,4,5,6,7,8,9]

  • Nested condition
  • python
    even_pairs = [(x, y) for x in range(3) for y in range(3) if (x+y) % 2 == 0]
    print(even_pairs)  # [(0,0), (0,2), (1,1), (2,0), (2,2)]


5. Common Use Cases

  • Transforming data
  • python
    names = ["Amr", "Sara", "Ali"]
    lower = [n.lower() for n in names]

  • Filtering
  • python
    nums = [10, 15, 20, 25]
    even = [n for n in nums if n % 2 == 0]

  • Building dict from two lists
  • python
    keys = ["a", "b", "c"]
    values = [1, 2, 3]
    mapping = {k: v for k, v in zip(keys, values)}

  • Set of vowels in a word
  • python
    word = "comprehension"
    vowels = {ch for ch in word if ch in "aeiou"}
    print(vowels)  # {'o', 'e', 'i'}


6. Pitfalls

  • Readability: Don’t overuse nesting; complex comprehensions can be harder to understand.
  • Memory use: For very large ranges, prefer generator expressions with () instead of [].

python
# Generator (lazy evaluation)
squares = (x**2 for x in range(1000000))