Nested data structures are data structures that contain other data structures as elements. They allow you to store data in a hierarchical and logically structured way, making them very useful for modeling real-world data in programs. This article will provide an overview of nested data structures in Python and examine some common real-world applications that utilize them.
Table of Contents
Open Table of Contents
Overview of Nested Data Structures
In Python, lists, tuples, dictionaries, and custom classes can all contain nested elements. Some key types of nested data structures include:
Nested Lists
Lists in Python can contain other lists as elements. For example:
nested_list = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
This creates a list containing 3 inner lists. Nested lists are useful for grouping related data.
Nested Tuples
Like lists, tuples can also be nested:
nested_tuple = (
(1, 2, 3),
(4, 5, 6),
(7, 8, 9)
)
Tuples are immutable, so nested tuples provide a way to group related constant data.
Nested Dictionaries
Dictionaries can contain other dictionaries:
nested_dict = {
'dict1': {
'key1': 'value1',
'key2': 'value2'
},
'dict2': {
'key3': 'value3',
'key4': 'value4'
}
}
This allows you to create a complex nested structure of key-value pairs.
Nested Classes
You can define classes that have attributes which are other custom classes:
class NestedClass:
def __init__(self):
self.inner_class = InnerClass()
class InnerClass:
def __init__(self):
self.inner_value = 5
This enables composition relationships between classes.
Real-World Applications
Now let’s explore some common real-world use cases where nested data structures are applied.
Modeling Hierarchical Data
Nested data structures naturally model hierarchical relationships very well. For example, directories on a file system have a tree-like structure. We can model this using nested dictionaries:
file_system = {
'usr': {
'bin': {
'python': None,
'git': None,
},
'lib': {
'python3.8': None,
'numpy': None,
}
},
'etc': {
'nginx': None,
'mysql': None
}
}
The nested dictionaries create parent-child relationships mimicking the tree structure of the file system.
We can also model a company’s organizational hierarchy using nested dictionaries:
company = {
'CEO': {
'CTO': {
'Engineering': None,
'Product': None
},
'CFO': {
'Accounting': None,
'Finance': None
}
}
}
This maps the reporting structure of the company into a nested dictionary format.
Storing Multi-Dimensional Data
Nested lists are very useful for working with multi-dimensional data like matrices or tables.
For example, we can represent a matrix as a nested list of rows, which are also lists:
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
We can easily perform matrix operations like transpose using nested lists:
transpose = [[row[i] for row in matrix] for i in range(len(matrix[0]))]
Multi-dimensional numerical data from scientific applications like machine learning is often represented using nested lists.
Nested lists also provide a convenient way to store tabular data, like CSV files:
table = [
['Name', 'Age', 'Job'],
['Alice', 30, 'Engineer'],
['Bob', 25, 'Scientist'],
]
Composite Data Structures
We can use nested data structures to create composite data structures that combine other data structures.
For example, we can have a dictionary where each value is a list:
users = {
'Alice': [30, 'Engineer'],
'Bob': [25, 'Scientist']
}
This allows us to associate multiple related properties with each key.
We can also have nested data structures with both lists and dictionaries:
company = [
{
'name': 'Acme Co',
'employees': [
'John', 'Jane'
]
},
{
'name': 'Globex Corp',
'employees': [
'Rick', 'Morty'
]
}
]
This nested structure combines a list of companies, each having a dictionary with properties and a list of employees.
Data Serialization
Nested data structures are commonly used in serializing data for storage or transmission.
For example, JSON is a popular serialization format that consists entirely of nested lists and dictionaries.
Here is how we would serialize a simple Python object to JSON using nested dicts and lists:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person('Alice', 30)
json_data = {
'person': {
'name': person.name,
'age': person.age
}
}
import json
print(json.dumps(json_data))
# Output: {"person": {"name": "Alice", "age": 30}}
The json library automatically converts the nested dict into a nested JSON string.
Similarly, YAML, XML, and other serialization formats use nested structures extensively.
Graph Data Structures
Graphs can be represented in Python using nested dictionaries to implement adjacency lists:
graph = {
'A': ['B', 'C'],
'B': ['A', 'D'],
'C': ['A'],
'D': ['B']
}
Here the keys represent nodes and the nested lists contain adjacent nodes.
We can also represent weighted graphs using nested dicts:
graph = {
'A': {'B': 1, 'C': 3},
'B': {'A': 1, 'D': 2},
'C': {'A': 3},
'D': {'B': 2}
}
Now the weights are stored in the nested dictionaries. This makes it easy to lookup adjacent nodes and weights in O(1) time.
Game Trees
Game trees like those used in chess or go can be implemented recursively using nested data structures.
For instance, a simple game tree for tic-tac-toe could be defined as:
class Node:
def __init__(self, state, children=[]):
self.state = state
self.children = children
tree = Node('X', [
Node('O', [
Node('X'),
Node('O')
]),
Node('O', [
Node('X')
])
])
Here each node contains a game state and a list of possible child nodes representing valid moves. Complex game AIs utilize much larger nested game trees.
Property Lookup Chains
Nested dictionaries and classes provide a way to model chains of property lookups.
For example:
person = {
'address': {
'street': {
'number': 515,
'name': 'Main St'
}
}
}
print(person['address']['street']['number']) # 515
This allows looking up specific nested properties by traversing the chain of dictionaries.
Similarly, we can perform chained attribute access on nested class instances:
class Address:
def __init__(self, street):
self.street = street
class Street:
def __init__(self, number, name):
self.number = number
self.name = name
person = Person()
person.address = Address(Street(515, 'Main St'))
print(person.address.street.number) # 515
This provides an easy way to access deeply nested properties on objects.
When to Avoid Nesting
While nested data structures are very useful, you should also avoid overusing them when a flat data structure would work better. Some cases where you may want to avoid nesting include:
- The data has no inherent hierarchical structure.
- You need to frequently access specific elements. Lookup time increases for deeply nested structures.
- You plan to serialize the data and nested formats like JSON or XML have too much overhead for your use case.
- Shallow flat structures suffice and nesting would only complicate the code.
- Nesting makes your data structure excessively complex and hard to understand.
Evaluate whether nesting adds value before applying it. Also, try to limit nesting depth where possible.
Conclusion
This article covered the basics of nested data structures in Python and some real-world applications where they excel. Key benefits of nesting include the ability to model hierarchical relationships, group composite data, serialize data, and lookup chained properties. Nested lists, dictionaries, tuples, and custom classes provide flexible options for building complex nested structures. Just be mindful of cases where nesting may add unneeded complexity. The examples and guidelines provided here should help you leverage the power of nested data structures effectively in your own Python programs.