# Counter, defaultdict, OrderedDict and ChainMap — Data Structures in Python

Source: https://www.skillbyai.com/en/data-structures-python/h-collections

> Use the collections module for common dictionary patterns.

## Specialised dictionaries

The **`collections`** module provides dictionary variants for common patterns. **`Counter`** counts hashable items: `Counter(words)`, `most_common(k)`, arithmetic between counters (`+`, `-`, `&`, `|`) and `update` to add more counts; missing keys return 0. **`defaultdict(factory)`** creates a default value the first time a missing key is accessed, ideal for **grouping** (`defaultdict(list)`) and nested counters; be aware that merely reading a missing key inserts it. **`OrderedDict`** remembers order (as plain dicts now do) but adds `move_to_end` and `popitem(last=False)`, which make it a convenient building block for **LRU caches**, and its equality comparison is order-sensitive. **`ChainMap`** searches several dictionaries in order without copying them, useful for layered configuration (command-line options over environment variables over defaults). Also useful: **`deque`** (next section) and **`namedtuple`**. For read-only views of a dict, use `types.MappingProxyType`. Choosing the right one replaces several lines of boilerplate and expresses intent clearly.

## Grouping, counting and layered configuration

defaultdict, Counter, OrderedDict and ChainMap.

```python
from collections import ChainMap, Counter, OrderedDict, defaultdict
import os

orders = [("Pune", "pen"), ("Delhi", "ink"), ("Pune", "ink"), ("Pune", "pen")]

items_by_city = defaultdict(list)
for city, sku in orders:
    items_by_city[city].append(sku)            # no need to check if the key exists
print(dict(items_by_city))                     # {'Pune': ['pen', 'ink', 'pen'], 'Delhi': ['ink']}

sku_counts = Counter(sku for _, sku in orders)
print(sku_counts.most_common(1))               # [('pen', 2)]
print(sku_counts["stapler"])                   # 0: missing keys count as zero

september = Counter(pen=10, ink=4)
october = Counter(pen=6, pad=3)
print(september + october)                     # Counter({'pen': 16, 'ink': 4, 'pad': 3})

recent = OrderedDict()
for page in ["home", "cart", "home", "checkout"]:
    recent[page] = True
    recent.move_to_end(page)                   # most recent at the end
print(list(recent))                            # ['cart', 'home', 'checkout']

defaults = {"theme": "light", "page_size": 20}
env = {k.lower(): v for k, v in os.environ.items() if k in {"THEME"}}
cli = {"page_size": 50}
config = ChainMap(cli, env, defaults)          # first mapping wins
print(config["page_size"], config["theme"])    # 50, and 'light' unless THEME is set
```

## defaultdict reads create keys

`if counts[key] > 0:` on a `defaultdict(int)` inserts `key` with 0. Use `key in counts` or `counts.get(key, 0)` when you only want to look.

**Quiz:** Which collections type returns 0 for missing keys and provides most_common()?

- [ ] OrderedDict
- [ ] ChainMap
- [x] Counter
- [ ] deque

*Answer:* Counter. Counter is a dict subclass designed for counting.
