No history yet

Advanced Data Structures

Specialized Data Tools

You already know that lists and dictionaries are the workhorses of Python. They're great for general-purpose data storage. But sometimes, a general tool isn't the best tool. For specific tasks, Python offers more specialized containers that can make your code cleaner, faster, and more readable.

Most of these tools live in the module, a part of Python's standard library. Think of it as a workshop full of specialized tools, ready for when a simple hammer isn't quite right.

Readable Data with namedtuple

Tuples are fast and memory-efficient, but they can be hard to read. Accessing data with point[0] and point[1] doesn't tell you much about what those values represent. A namedtuple solves this problem by giving each element in a tuple a name.

It creates a custom, lightweight object type that behaves like a tuple but allows you to access its values by name, just like an object attribute. This makes your code self-documenting.

from collections import namedtuple

# Define the structure of our namedtuple
Point = namedtuple('Point', ['x', 'y'])

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

# Access data by name
print(f"The x-coordinate is {p1.x}")

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

Another common headache is handling missing keys in a dictionary. You often have to write extra code to check if a key exists before you can add a value to it, especially when you're grouping items into lists.

The defaultdict is the perfect tool for this. It's a dictionary subclass that calls a factory function to supply missing values.

from collections import defaultdict

# Create a defaultdict where missing keys get an empty list
grouped_data = defaultdict(list)

fruits = [('apple', 'red'), ('banana', 'yellow'), ('cherry', 'red')]

for fruit, color in fruits:
    # No need to check if the key exists first!
    grouped_data[color].append(fruit)

# Output: {'red': ['apple', 'cherry'], 'yellow': ['banana']}
print(grouped_data)

Queues and Counters

Lists are fine for adding items to the end, but they are very slow if you need to add or remove items from the beginning. Every time you do, all the other elements have to be shifted. For situations where you need fast appends and pops from both ends, use a (pronounced "deck").

Think of a deque as a conveyor belt. You can add items to the front or back, and take them off from either end, all with the same efficiency.

from collections import deque

line = deque(['Alice', 'Bob', 'Charlie'])

# A new person gets in the back of the line
line.append('David')
# Result: deque(['Alice', 'Bob', 'Charlie', 'David'])

# The first person is served
line.popleft()
# Result: deque(['Bob', 'Charlie', 'David'])

# A VIP cuts to the front of the line
line.appendleft('Eve')
# Result: deque(['Eve', 'Bob', 'Charlie', 'David'])

What about counting things? You could use a dictionary and manually increment a counter, but there's a better way. The Counter object is designed specifically for this job. It's a dictionary subclass where keys are items and values are their counts.

from collections import Counter

votes = ['red', 'blue', 'red', 'green', 'blue', 'red']

vote_counts = Counter(votes)

# See the counts
# Output: Counter({'red': 3, 'blue': 2, 'green': 1})
print(vote_counts)

# Find the 2 most common votes
# Output: [('red', 3), ('blue', 2)]
print(vote_counts.most_common(2))

Pythonic Shortcuts

Beyond the collections module, Python has powerful syntax for manipulating sequences. Advanced slicing lets you extract parts of a list with a [start:stop:step] pattern. A negative step, for instance, is a common trick for reversing a list.

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

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

# Get the list in reverse order
# Output: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
print(numbers[::-1])

Finally, let's talk about comprehensions. They are a hallmark of idiomatic Python, allowing you to create lists and dictionaries concisely. Instead of writing a full for loop to build a new list, you can do it in one expressive line of code.

This is a list comprehension for creating a list of squares.

# Creates a list of squares for numbers 0 through 9
squares = [x**2 for x in range(10)]

# You can also add a condition
# Creates a list of squares only for even numbers
even_squares = [x**2 for x in range(10) if x % 2 == 0]

The same logic applies to dictionaries. A dictionary comprehension lets you build a new dictionary from any iterable, also in a single line.

# Creates a dictionary mapping numbers to their squares
square_map = {x: x**2 for x in range(5)}

# Output: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
print(square_map)

Let's check your understanding of these advanced tools.

Quiz Questions 1/6

What is the primary advantage of using a collections.deque over a standard Python list?

Quiz Questions 2/6

Which of the following problems is collections.namedtuple designed to solve?

Mastering these specialized data structures and syntactic shortcuts will help you write more efficient and expressive Python code.