No history yet

Advanced Data Structures

Writing Cleaner, Faster Code

You already know how to build lists and dictionaries using loops. But as your data gets bigger and your logic more complex, those loops can start to feel clunky. Python offers a more elegant and often faster way to create these structures: comprehensions.

Comprehensions are a concise way to create lists, dictionaries, and sets in a single, readable line.

Let's say we want to create a list of the first five perfect squares. The traditional way uses a for loop:

squares = []
for i in range(1, 6):
    squares.append(i * i)

# Result: [1, 4, 9, 16, 25]

A list comprehension does the same thing in one line. It reads almost like plain English: "make a list of i * i for each i in our range."

squares = [i * i for i in range(1, 6)]

# Result: [1, 4, 9, 16, 25]

You can also add a condition. What if we only want the squares of the odd numbers?

odd_squares = [i * i for i in range(1, 6) if i % 2 != 0]

# Result: [1, 9, 25]

The same logic applies to dictionaries. Imagine you have a list of words and want to create a dictionary mapping each word to its length. The old way is a bit verbose.

words = ['apple', 'banana', 'cherry']
word_lengths = {}
for word in words:
    word_lengths[word] = len(word)

# Result: {'apple': 5, 'banana': 6, 'cherry': 7}

A dictionary comprehension is much cleaner. This is considered more because it's both concise and highly readable once you're used to the syntax.

words = ['apple', 'banana', 'cherry']
word_lengths = {word: len(word) for word in words}

# Result: {'apple': 5, 'banana': 6, 'cherry': 7}

Data with Meaning

Tuples are great for storing fixed collections of items, but accessing data by index can make code hard to read. If you have a tuple representing a point, point[0] and point[1] don't tell you much. Is it (x, y) or (row, column)? You'd have to look it up every time.

A namedtuple solves this problem. It's part of Python's built-in collections module and lets you create tuple-like objects that have named fields, just like an object or a dictionary key.

from collections import namedtuple

# Define the blueprint for our namedtuple
Point = namedtuple('Point', ['x', 'y'])

# Create an instance
p1 = Point(10, 20)

# Access data by name (much clearer!)
print(f"The x-coordinate is {p1.x}") # Output: The x-coordinate is 10

# You can still access by index, too
print(f"The y-coordinate is {p1[1]}") # Output: The y-coordinate is 20

Namedtuples give you the readability of a dictionary with the low memory footprint and immutability of a tuple. They are perfect for simple, fixed data structures where you don't need the full power of a custom class.

Uniqueness and Speed

Lists are flexible, but they have two potential drawbacks: they allow duplicate items, and checking if an item exists in a large list can be slow. For tasks that require uniqueness and fast lookups, Python gives us sets.

A set is an unordered collection of unique elements. The most common use case is removing duplicates from a list.

numbers = [1, 2, 2, 3, 4, 4, 4, 5]
unique_numbers = set(numbers)

# Result: {1, 2, 3, 4, 5}

The real power of sets comes from their speed. Checking for membership (item in my_set) is incredibly fast, even for millions of items. This is because sets are implemented using a internally.

Sets also provide powerful mathematical operations for comparing collections.

OperationSymbolExampleDescription
Union```set1
Intersection&set1 & set2Items that exist in both sets.
Difference-set1 - set2Items in set1 but not in set2.
Symmetric Difference^set1 ^ set2Items in one set or the other, but not both.

Advanced Slicing and Nesting

You're likely familiar with basic list slicing like my_list[1:4]. But the full slicing syntax is [start:stop:step]. The step parameter lets you skip items. It's fantastic for tasks like getting every other element or reversing a list.

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# Get every second number
print(numbers[::2]) # [0, 2, 4, 6, 8]

# Get every third number, starting from index 1
print(numbers[1::3]) # [1, 4, 7]

# A common trick to reverse a list
print(numbers[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1]

Finally, let's talk about navigating nested data. In the real world, data is rarely flat. You'll often work with lists of dictionaries, or dictionaries where the values are other dictionaries or lists.

Imagine we have data about users and their favorite movies.

data = {
    'users': [
        {'id': 1, 'name': 'Alice', 'movies': ['Inception', 'The Matrix']},
        {'id': 2, 'name': 'Bob', 'movies': ['The Matrix', 'John Wick']}
    ]
}

How would we get a list of all unique movies that users like? We can combine our new skills. We'll need a loop to go through the users, and we can use a set to collect the unique movie titles.

# Using a for loop and a set
all_movies = set()
for user in data['users']:
    for movie in user['movies']:
        all_movies.add(movie)

# Result: {'Inception', 'The Matrix', 'John Wick'}

Can we do this with a comprehension? Yes, but it requires a nested loop inside the comprehension. This can be powerful, but be careful not to make it unreadable.

# Using a set comprehension with a nested loop
all_movies_comp = {movie for user in data['users'] for movie in user['movies']}

# Result: {'Inception', 'The Matrix', 'John Wick'}

Knowing how to use these advanced structures and techniques will make your Python code more efficient, readable, and professional.