0%

73- Advanced Dictionaries

Master dictionary operations for complex data structures. defaultdict, Counter, OrderedDict, and advanced techniques. Go beyond key-value pairs.

Dictionaries are the Swiss Army knife of Python data structures. You have used them for simple key-value storage. But dictionaries can do much more.
The collections module provides specialized dictionary types: defaultdict for automatic default values, Counter for counting occurrences, OrderedDict for preserving insertion order (though regular dicts now do this too), and ChainMap for combining multiple dictionaries. You can also create dictionaries with dictionary comprehensions, merge them with the | operator, and use advanced lookup techniques.
This lesson explores these advanced dictionary capabilities. You will learn to count items efficiently, create nested dictionaries automatically, combine multiple dictionaries, and use dictionaries for sophisticated data processing.

🕯️ Magic Note

Starting from Python 3.7, regular dictionaries preserve insertion order. This was an implementation detail in 3.6 and became a language guarantee in 3.7. OrderedDict is still useful for methods like move_to_end() that regular dicts do not have.

defaultdict: Automatic Default Values
defaultdict provides a default value for missing keys, eliminating KeyError checks.

Python

from collections import defaultdict

# Without defaultdict (manual checking)

word_count = {}

words = [“apple”, “banana”, “apple”, “cherry”, “banana”, “apple”]

for word in words:

if word not in word_count:

word_count[word] = 0

word_count[word] += 1

# With defaultdict (automatic default)

word_count = defaultdict(int) # int() returns 0

for word in words:

word_count[word] += 1

print(dict(word_count)) # {‘apple’: 3, ‘banana’: 2, ‘cherry’: 1}

# defaultdict with list (grouping items)

groups = defaultdict(list)

students = [(“A”, “Ali”), (“B”, “Sara”), (“A”, “Reza”), (“C”, “Mina”), (“B”, “Hassan”)]

for grade, name in students:

groups[grade].append(name)

print(dict(groups)) # {‘A’: [‘Ali’, ‘Reza’], ‘B’: [‘Sara’, ‘Hassan’], ‘C’: [‘Mina’]}

# defaultdict with set (unique grouping)

unique_groups = defaultdict(set)

for grade, name in students:

unique_groups[grade].add(name)

print(dict(unique_groups)) # {‘A’: {‘Ali’, ‘Reza’}, ‘B’: {‘Sara’, ‘Hassan’}, ‘C’: {‘Mina’}}

💡 defaultdict takes a callable that returns the default value. Common choices: int (0), list ([]), set (set()), dict ({}), or your own function.
Counter: Counting Made Easy
Counter is a dictionary subclass for counting hashable objects.

Python

from collections import Counter

# Create counter from iterable

words = [“apple”, “banana”, “apple”, “cherry”, “banana”, “apple”]

counter = Counter(words)

print(counter) # Counter({‘apple’: 3, ‘banana’: 2, ‘cherry’: 1})

# Create counter from string

char_counter = Counter(“mississippi”)

print(char_counter) # Counter({‘i’: 4, ‘s’: 4, ‘p’: 2, ‘m’: 1})

# Most common elements

print(counter.most_common(2)) # [(‘apple’, 3), (‘banana’, 2)]

# Counter arithmetic

c1 = Counter(a=3, b=2, c=1)

c2 = Counter(a=1, b=2, d=1)

print(c1 + c2) # Counter({‘a’: 4, ‘b’: 4, ‘c’: 1, ‘d’: 1})

print(c1 – c2) # Counter({‘a’: 2, ‘c’: 1}) (keeps only positive counts)

print(c1 & c2) # Counter({‘b’: 2, ‘a’: 1}) (intersection – min)

print(c1 | c2) # Counter({‘a’: 3, ‘b’: 2, ‘c’: 1, ‘d’: 1}) (union – max)

# Update counter

counter.update([“apple”, “banana”])

print(counter) # Counter({‘apple’: 4, ‘banana’: 3, ‘cherry’: 1})

🕯️ Magic Note

Counter is perfect for frequency analysis, word counting, inventory management, and any situation where you need to count occurrences. It is also very fast because it is implemented in C.

OrderedDict: Dictionary with Order Methods
Although regular dicts preserve order from Python 3.7+, OrderedDict offers additional methods.

Python

from collections import OrderedDict

# Create OrderedDict (order preserved)

od = OrderedDict()

od[“a”] = 1

od[“b”] = 2

od[“c”] = 3

print(od) # OrderedDict([(‘a’, 1), (‘b’, 2), (‘c’, 3)])

# move_to_end() – move key to end (or beginning)

od.move_to_end(“a”)

print(od) # OrderedDict([(‘b’, 2), (‘c’, 3), (‘a’, 1)])

od.move_to_end(“b”, last=False) # Move to beginning

print(od) # OrderedDict([(‘b’, 2), (‘c’, 3), (‘a’, 1)])

# popitem() – pop last (or first) item

last = od.popitem(last=True) # Remove and return last item

first = od.popitem(last=False) # Remove and return first item

print(last, first)

# Equality is order-sensitive (unlike regular dicts)

d1 = OrderedDict([(“a”, 1), (“b”, 2)])

d2 = OrderedDict([(“b”, 2), (“a”, 1)])

print(d1 == d2) # False (order matters)

d3 = {“a”: 1, “b”: 2}

d4 = {“b”: 2, “a”: 1}

print(d3 == d4) # True (order does not matter for regular dicts)

🕯️ Magic Note

Use OrderedDict when you need order-sensitive equality or when you need to move items to the beginning or end frequently. For most other use cases, regular dicts are sufficient.

ChainMap: Combining Multiple Dictionaries
ChainMap groups multiple dictionaries into a single view for lookup.

Python

from collections import ChainMap

defaults = {“color”: “black”, “font”: “Arial”, “size”: 12}

user_prefs = {“color”: “blue”, “size”: 14}

# ChainMap searches in order (first match wins)

settings = ChainMap(user_prefs, defaults)

print(settings[“color”]) # ‘blue’ (from user_prefs)

print(settings[“font”]) # ‘Arial’ (from defaults)

print(settings[“size”]) # 14 (from user_prefs)

# Update ChainMap (only affects first dictionary)

settings[“size”] = 16

print(user_prefs[“size”]) # 16

print(defaults[“size”]) # 12 (unchanged)

# Add new dictionary to the front

cmd_args = {“color”: “red”}

settings = settings.new_child(cmd_args)

print(settings[“color”]) # ‘red’ (from cmd_args)

# Get all maps

print(settings.maps) # [{‘color’: ‘red’}, {‘color’: ‘blue’, ‘size’: 16}, {‘color’: ‘black’, ‘font’: ‘Arial’, ‘size’: 12}]

🕯️ Magic Note

ChainMap is perfect for managing configuration with fallbacks: command line arguments → user settings → system defaults. It avoids copying dictionaries, making it memory efficient for large nested configurations.

Dictionary Comprehensions
Create dictionaries dynamically using comprehensions.

Python

# Basic dictionary comprehension

squares = {x: x**2 for x in range(1, 6)}

print(squares) # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# Dictionary comprehension with condition

even_squares = {x: x**2 for x in range(1, 11) if x % 2 == 0}

print(even_squares) # {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}

# Transform existing dictionary

original = {“a”: 1, “b”: 2, “c”: 3}

doubled = {k: v * 2 for k, v in original.items()}

print(doubled) # {‘a’: 2, ‘b’: 4, ‘c’: 6}

# Swap keys and values

swapped = {v: k for k, v in original.items()}

print(swapped) # {1: ‘a’, 2: ‘b’, 3: ‘c’}

# Nested dictionary comprehension

matrix = {i: {j: i * j for j in range(1, 4)} for i in range(1, 4)}

print(matrix) # {1: {1: 1, 2: 2, 3: 3}, 2: {1: 2, 2: 4, 3: 6}, 3: {1: 3, 2: 6, 3: 9}}

# Dictionary comprehension with conditional expression

numbers = [1, 2, 3, 4, 5]

labels = {x: “even” if x % 2 == 0 else “odd” for x in numbers}

print(labels) # {1: ‘odd’, 2: ‘even’, 3: ‘odd’, 4: ‘even’, 5: ‘odd’}

Merging Dictionaries (Python 3.9+)
Python 3.9 introduced the | and |= operators for merging dictionaries.

Python

dict1 = {“a”: 1, “b”: 2}

dict2 = {“c”: 3, “d”: 4}

dict3 = {“a”: 10, “e”: 5}

# Merge with | (creates new dictionary)

merged = dict1 | dict2

print(merged) # {‘a’: 1, ‘b’: 2, ‘c’: 3, ‘d’: 4}

# Later values override earlier ones

merged = dict1 | dict3

print(merged) # {‘a’: 10, ‘b’: 2, ‘e’: 5}

# In-place merge (updates dict1)

dict1 |= dict2

print(dict1) # {‘a’: 1, ‘b’: 2, ‘c’: 3, ‘d’: 4}

# For older Python versions, use {**dict1, **dict2}

merged_old = {**dict1, **dict2}

print(merged_old) # {‘a’: 1, ‘b’: 2, ‘c’: 3, ‘d’: 4}

Dictionary Views: keys(), values(), items()
Dictionary views are dynamic and reflect changes to the dictionary.

Python

d = {“a”: 1, “b”: 2, “c”: 3}

keys = d.keys()

values = d.values()

items = d.items()

print(f”Keys: {keys}”) # dict_keys([‘a’, ‘b’, ‘c’])

print(f”Values: {values}”) # dict_values([1, 2, 3])

print(f”Items: {items}”) # dict_items([(‘a’, 1), (‘b’, 2), (‘c’, 3)])

# Views are dynamic (reflect changes)

d[“d”] = 4

print(f”After adding: {keys}”) # dict_keys([‘a’, ‘b’, ‘c’, ‘d’])

# Convert to list if needed

keys_list = list(keys)

# Set-like operations on keys view

d1 = {“a”: 1, “b”: 2, “c”: 3}

d2 = {“b”: 20, “c”: 30, “d”: 40}

print(d1.keys() & d2.keys()) # {‘b’, ‘c’}

print(d1.keys() – d2.keys()) # {‘a’}

print(d1.keys() | d2.keys()) # {‘a’, ‘b’, ‘c’, ‘d’}

Using get() and setdefault() for Safe Access
Advanced techniques for accessing and setting dictionary values safely.

Python

d = {“a”: 1, “b”: 2}

# get() with default value

print(d.get(“a”, 0)) # 1

print(d.get(“c”, 0)) # 0

# setdefault() – get value if key exists, otherwise set and return default

counts = {}

words = [“a”, “b”, “a”, “c”, “b”, “a”]

for word in words:

counts[word] = counts.get(word, 0) + 1

print(counts) # {‘a’: 3, ‘b’: 2, ‘c’: 1}

# setdefault for nested dictionaries

nested = {}

nested.setdefault(“a”, {})[“b”] = 1

print(nested) # {‘a’: {‘b’: 1}}

# Better: use defaultdict for nested structures

from collections import defaultdict

nested = defaultdict(dict)

nested[“a”][“b”] = 1

print(dict(nested)) # {‘a’: {‘b’: 1}}

Practical Example: Word Frequency Analyzer
Combine Counter and defaultdict for text analysis.

Python

from collections import Counter, defaultdict

import re

def analyze_text(text):

“””Analyze word frequency and patterns in text.”””

# Clean and split text

words = re.findall(r”\b\w+\b”, text.lower())

# Word frequency

word_freq = Counter(words)

# Words by length

by_length = defaultdict(list)

for word in set(words):

by_length[len(word)].append(word)

# First letter frequency

first_letter = Counter(word[0] for word in words if word)

# Most common words

most_common = word_freq.most_common(10)

return {

“total_words”: len(words),

“unique_words”: len(word_freq),

“word_frequency”: word_freq,

“most_common”: most_common,

“words_by_length”: dict(by_length),

“first_letter_freq”: first_letter

}

sample = “The quick brown fox jumps over the lazy dog. The dog barks quickly.”

results = analyze_text(sample)

print(f”Total words: {results[‘total_words’]}”)

print(f”Unique words: {results[‘unique_words’]}”)

print(f”Most common: {results[‘most_common’]}”)

print(f”Words by length: {results[‘words_by_length’]}”)

Practical Example: Nested Configuration Manager
Use ChainMap and defaultdict for configuration management.

Python

from collections import ChainMap, defaultdict

class ConfigManager:

def __init__(self):

self._system_defaults = {

“host”: “localhost”,

“port”: 8080,

“debug”: False,

“timeout”: 30

}

self._user_config = {}

self._env_config = {}

self._cli_args = {}

self._config = ChainMap(self._cli_args, self._env_config, self._user_config, self._system_defaults)

def set_user_config(self, key, value):

self._user_config[key] = value

def set_env_config(self, key, value):

self._env_config[key] = value

def set_cli_args(self, key, value):

self._cli_args[key] = value

def get(self, key, default=None):

return self._config.get(key, default)

def get_all(self):

return dict(self._config)

config = ConfigManager()

config.set_user_config(“port”, 3000)

config.set_env_config(“debug”, True)

config.set_cli_args(“host”, “192.168.1.100”)

print(f”Host: {config.get(‘host’)}”) # 192.168.1.100 (CLI overrides)

print(f”Port: {config.get(‘port’)}”) # 3000 (user config)

print(f”Debug: {config.get(‘debug’)}”) # True (env)

print(f”Timeout: {config.get(‘timeout’)}”) # 30 (default)

print(f”All: {config.get_all()}”)

Common Mistakes with Advanced Dictionaries
  • Assuming defaultdict works with keys that do not exist for reading (d[“missing”] still raises KeyError; only assignment works)
  • Forgetting that Counter.most_common() returns a list of tuples, not a Counter
  • Using regular dict when order matters across Python versions (use OrderedDict for pre-3.7 compatibility)
  • Modifying a dictionary while iterating over its items (can cause RuntimeError)
  • Expecting ChainMap to copy dictionaries (it does not; it references them)
Check Your Understanding
  • Write a function that groups a list of words by their first letter using defaultdict.
  • What is the difference between Counter and defaultdict(int)?
  • How do you merge two dictionaries in Python 3.9+?
  • Write a dictionary comprehension that creates a mapping from numbers to their cubes for numbers 1 to 10.
  • What is the purpose of ChainMap? Give a use case.
  • How do you get the first item from an OrderedDict?

⚡ Whisper

Dictionaries are the memory of Python. Every key points to a value. defaultdict ensures the key always has a home. Counter tallies the world. OrderedDict remembers the order of arrival. ChainMap layers configuration. Each tool is a specialized dictionary. Each solves a specific problem. Use defaultdict for grouping and counting. Use Counter for frequency analysis. Use OrderedDict when order-sensitive equality matters. Use ChainMap for cascading lookups. Master them, and your dictionaries become not just storage, but logic. They compute. They organize. They prioritize. The data is in the keys and values. The intelligence is in choosing the right dictionary. Choose wisely. Your code will be shorter, faster, and clearer.

Related posts