Day 23 ยท Numbered shelves & straight-to-the-page

Arrays & Hashing

You will be able to
  • Explain how dynamic arrays resize and why append is amortized O(1)
  • Describe the hash function โ†’ bucket โ†’ collision chain that makes dict/set O(1) average
  • State why dict keys must be hashable and what load factor forces a resize
  • Apply the three hashing patterns: frequency counting, dedupe/seen-sets, and canonical keys
  • Solve Two Sum, Valid Anagram, and Group Anagrams in one pass each
Today's ~120 minutes
Spaced-rep warm-up: Day 22 ladder + Week 3 due cards10 min
ELI5 + tech read, watch the hash-buckets visualizer20 min
Guided: build the toy hash map + three classics40 min
Practice: two problems solo20 min
Project: start patterns.md (Hashing section)20 min
Quiz + flashcards10 min

Builds on: Day 4 โ€” Collections โ€” lists and dicts ยท Day 22 โ€” Big O & complexity

The analogy

A warehouse with numbered shelves is an array: tell the clerk "shelf 4,217" and she walks straight there โ€” one step, no searching โ€” because shelf positions are computed, not hunted for. But ask her "which shelf holds the red vase?" and she must walk every aisle: numbers give instant access by POSITION, and nothing by CONTENT.

Now the coat check at a theater. You hand over your coat; the attendant doesn't remember where every coat is โ€” she runs a little rule on your ticket number ("ticket 731 โ†’ hook 31") and walks straight to the hook. That rule is a hash function: it converts the thing you're looking up into a shelf number, so content-based lookup becomes position-based lookup โ€” straight to the page instead of reading the whole book. Occasionally two tickets map to the same hook (a collision); she hangs both coats there and checks the tags โ€” still fast, as long as no hook gets overloaded. When the rack gets crowded, the theater installs a bigger rack and re-hangs everything (a resize). Python's dict and set are exactly this coat check, and they are the single most useful object in your interview toolkit.

Why this matters on the job

Hashing is the workhorse of practical speed: the Day 22 fix ("replace the list scan with a set") IS this lesson, and it recurs everywhere โ€” deduplicating documents in a RAG ingest pipeline (Day 113), caching LLM responses by prompt hash (Day 156), counting token frequencies (Day 96). In interviews, "can I trade O(n) memory for O(1) lookup?" is the first question to ask of almost every array problem โ€” roughly a third of easy/medium problems fall to a hash map. Knowing WHY it's O(1) average (and when it isn't) is what separates users from engineers.

Watch it happen

Straight to the page โ€” how a dict finds keys in O(1)

step 1 / 7
[0]
[1]
[2]
[3]
[4]

Five empty buckets. A hash function turns any key into a number; key % 5 picks the bucket. No searching โ€” the key COMPUTES its own address.

Guided practice

guided 1

Build the coat check โ€” a toy hash map

25 min
  1. Create hashmap_lab.py and implement the ToyHashMap from the starter skeleton: 8 buckets, each a list of (key, value) pairs (chaining).
  2. Fill in _bucket_index, put, and get. Run the test block at the bottom โ€” all four asserts should pass.
  3. Add a stats() method printing each bucket's length, then insert 20 string keys and look at the distribution โ€” roughly even is the hash function doing its job.
  4. Sabotage: replace hash(key) with len(key) in _bucket_index and re-run stats with 20 words of similar length. Watch one bucket swell โ€” you have manufactured the O(n) worst case and now understand it forever. Restore hash.
  5. In a comment, answer: why must the table also store the KEY in the bucket, not just the value?
๐Ÿ python โ€” editable, runs in your browser
Ctrl/โŒ˜+Enter runs ยท Tab indents ยท numpy/pandas/sklearn auto-load on import (torch and network calls need a local run)
guided 2

The three patterns in anger

15 min

Solve the three classics in hashing_classics.py, each with a one-line pattern note before the function:

  1. Two Sum (seen-set/complement): given nums and target, return indices of two numbers summing to target โ€” one pass, store number โ†’ index, check for the complement BEFORE inserting.
  2. Valid Anagram (frequency map): are two strings anagrams? Compare Counters โ€” or two 26-slot tuples for the purist version.
  3. Group Anagrams (canonical key): group a list of words so all anagrams share a list. Key: tuple(sorted(word)).

For each: state time and space complexity in a comment, and test with the provided cases plus one edge case you invent (empty string, duplicate numbersโ€ฆ).

๐Ÿ python โ€” editable, runs in your browser
Ctrl/โŒ˜+Enter runs ยท Tab indents ยท numpy/pandas/sklearn auto-load on import (torch and network calls need a local run)

On your own

Two on your own

20 min

No scaffolding. Solve both, stating complexity before you code:

(1) First unique character: given a string, return the index of the first character that appears exactly once, or -1. Target O(n) โ€” two passes allowed.

(2) Longest consecutive sequence: given an unsorted list of ints, return the length of the longest run of consecutive values (e.g. [100, 4, 200, 1, 3, 2] โ†’ 4 for 1-2-3-4). Target O(n) โ€” sorting is the O(n log n) trap.

Hints (only if stuck): for (2), put everything in a set; only start counting from numbers whose predecessor is NOT in the set โ€” that guard is what keeps it linear.

Ship before you stop

Start the pattern log

Create patterns.md in your practice repo โ€” the living document that carries you to Day 179. Add today's first section, "Hashing," with: (1) a three-line summary of when to reach for a hash map (need content-based lookup / counting / grouping in O(1) per item); (2) your solved classics (Two Sum, Valid Anagram, Group Anagrams, plus today's practice pair) each with a one-line "the trick wasโ€ฆ" note; (3) the recognition cues โ€” phrases in a problem statement that smell like hashing ("appears once", "find pairs", "group by", "seen before"); (4) the honest costs: O(n) extra space, O(1) AVERAGE not worst, keys must be immutable. Commit it. Every pattern day this phase adds a section.

Rubric โ€” check what you completed (0/5)

Common mistakes & misconceptions

  • Using a list as a dict key and being surprised by TypeError: unhashable. Mutable objects can't promise a stable hash; convert to tuple (or frozenset) first.
  • Calling dict lookup O(1) unconditionally. It is O(1) average; collisions make it O(n) worst-case โ€” the sabotage step showed you exactly how.
  • Solving Two Sum by inserting first, then checking โ€” which matches a number with itself. Check for the complement before inserting the current number.
  • Reaching for sorting when a frequency map answers in O(n). "Compare compositions" (anagrams) needs counts, not order.
  • Forgetting that insert(0, x) and pop(0) on a list shift every element โ€” O(n). If you need cheap operations at both ends, that is tomorrow's deque.
  • Choosing a canonical key that isn't canonical: "".join(set(word)) loses counts and order โ€” "aab" and "ab" collide. sorted-tuple or count-tuple, nothing cleverer.
Knowledge check

Q1. Why is looking up a key in a dict O(1) on average, regardless of size?

Q2. my_dict[[1, 2]] = "x" raises TypeError. Why?

Q3. Best canonical key to group anagrams under one dict entry?

Go deeper โ€” curated resources

toolVisuAlgo โ€” hash table visualization (collisions & resizing live) โ†—15 minbookOpenDSA โ€” hashing chapter โ†—25 mincourseNeetCode Roadmap โ€” Arrays & Hashing section โ†—20 min
If you have a third hour
  • Python dict internals: compact dicts & insertion order โ€” Since 3.7, dicts preserve insertion order via a compact two-array layout. Skim how the entries array + sparse index table work โ€” it explains both the ordering guarantee and the memory win.
Done means
  • ToyHashMap works, sabotage experiment observed and explained
  • Five classics solved with stated complexities; longest-consecutive is O(n)
  • patterns.md committed with the Hashing section
  • Quiz โ‰ฅ 2/3
How this connects

โ† Back: Day 4 taught you to USE dict and set; today you know why they are fast and when they are not. Day 22's "swap the list scan for a set" now has a mechanism behind it.

Forward โ†’: Tomorrow's sliding window pairs with a hash map for its hardest problems. The hash-as-address idea returns as cache keys on Day 156 and โ€” transformed into geometry โ€” as the "meaning-based index" of vector databases on Day 115.

Unlocks: D24 Two Pointers & Sliding Window ยท D25 Stacks & Queues ยท D28 Week 4 Checkpoint: Pattern Drill ยท D30 Heaps, Priority Queues & Tries