Exercises — Hash function — properties - deterministic, uniform, fast
Before we start, one reminder of the three properties, because every problem tests one of them:
- Deterministic — same key ⟶ same bucket, always.
- Uniform — every bucket is equally likely. Write for a bucket index, i.e. names one of the buckets. Then for every such .
- Fast — computing costs (numbers) or (strings of length ).
And two symbols we reuse constantly:
- = number of buckets (table size).
- = number of keys currently stored.
- = the load factor — average keys per bucket.
Level 1 — Recognition
L1.1
Recall Solution
Broken property: Deterministic.
Because a fresh is drawn on every call, the same key gives a different bucket each time. Concretely: insert("cat") might compute and land in bucket say; a later lookup("cat") draws a new and computes bucket . The key sits in bucket but is searched for in bucket — retrieval fails. This directly breaks .
What about the other two? Fast: drawing one integer and doing one add + one is a fixed handful of operations, so it costs — no dependence on . Uniform: since is uniform on , the output is also uniform on (adding a fixed just cyclically shifts a uniform value, which stays uniform) — so on its own it would scatter keys evenly. The only failure is determinism — but a hash table lives or dies on put-then-find matching, so that single failure is fatal.
The fix: draw once when the table is created and store it as a fixed seed. Then is deterministic (same , same , same bucket) and still well-spread — this is exactly the legitimate "random salt fixed per table" idea from the parent note.
L1.2
Recall Solution
Compute each remainder:
Every key lands in bucket 0. All odd buckets stay empty. This wrecks Uniformity. Reason: is a power of two, so only looks at the lowest 3 bits of . Multiples of 8 have those bits all zero, so they all collide.
Level 2 — Application
L2.1
Recall Solution
| 18 | 7 |
| 41 | 8 |
| 22 | 0 |
| 59 | 4 |
| 32 | 10 |
| 31 | 9 |
| 73 | 7 |
Check a tricky one: , so remainder . And , remainder . Buckets used: 0, 4, 7 (twice), 8, 9, 10. Collision: and both land in bucket 7. Everything else is unique. With , load factor , so a single collision is entirely normal.
L2.2
Recall Solution
First, why Horner at all? We want the value of the polynomial (for a 3-char string). The naive way computes each power separately and adds — that repeats multiplications and builds huge numbers. Horner's method factors the powers out from the inside: Read that right-hand side: start with the first char, then each new step multiplies the running total by (shifting everything up one "place" in base ) and adds the next char. Every character passes through exactly one multiply and one add, so the whole thing is and never needs a separate power. That is precisely the update rule .
Now apply it. Start hash = 0. Process characters k, e, y.
k= 11: .e= 5: .y= 25: .- Take mod: .
Answer: . Why we may mod at every step (self-contained why). Taking inside the loop gives the same final answer because of one algebra fact: . In words, reducing a running total mod before the next multiply-and-add cannot change what it becomes mod afterwards — the quotient we throw away was already a multiple of , and multiples of stay multiples of . So we may keep the running number small at all times: it never exceeds about , which fits in a machine word. That is what keeps the cost and the hash Fast.
Level 3 — Analysis
L3.1
Recall Solution
Plain sum:
"abc": ."cab": .
Same value ⟶ collision. Addition is commutative, so it ignores order — every anagram maps identically. That is a systematic (non-uniform) failure.
Polynomial hash (Horner, left→right):
"abc"(a=1,b=2,c=3): ."cab"(c=3,a=1,b=2): .
— the weights give each position a different "place value", so order now matters. No more anagram collision.
L3.2
Recall Solution
(a) . (b) Self-contained why. Fix one bucket . For each key , define an indicator that is if key lands in bucket and otherwise. Under Simple Uniform Hashing , so . The number of keys in bucket is , and linearity of expectation (the expectation of a sum is the sum of expectations, even when the are dependent) gives So the expected keys in a fixed bucket is exactly . (Sanity: .) (c) Average successful-search probes .
Why , not ? The is fixed cost: you must compute the hash and step to the head of the chain — one unavoidable operation. The extra term measures how far down the chain you walk to find a key that is already stored. Here is the sketch: think of the keys inserted into one chain in order. When you later search for the -th key inserted into that chain, it sits behind the keys that were already there, so finding it costs about steps into the chain. Average that cost over all keys, and each key on average sits behind half of its chain-mates (some are near the front, some near the back, evening out to the middle). Since a full chain averages keys, half of that is . Add the fixed and you get . (Contrast an unsuccessful search, where you must walk the whole average chain, giving .)
With the chains are long — you're doing ~3 comparisons per lookup on average, no longer close to . This is the signal to rehash. See Load Factor and Rehashing.
Level 4 — Synthesis
L4.1
Recall Solution
(a) Each of the 3 positions has 26 choices, independent: . (b) Pigeonhole Principle: distributing items into buckets, the fullest bucket holds at least No hash function — however clever — can avoid this. Since , collisions are unavoidable. The best a good hash can do is make the distribution even (close to 176 everywhere) rather than lumpy. This is why a hash function's job is not "no collisions" but "few, evenly spread collisions."
L4.2
Recall Solution
Resize triggers when . With : threshold count is . So while , (OK). Inserting the 7th key gives ⟶ trigger. After doubling: . Now , comfortably below threshold. All 7 keys are rehashed into the 16-bucket table (each recomputed with ).
First resize at ; new .
Level 5 — Mastery
L5.1
Recall Solution
Start . Threshold count , so resize fires once (since ).
Insert 12: . Bucket 2 = [12]. . OK. Insert 22: . Collision with 12 — chain in bucket 2 = [12, 22]. . OK. Insert 9: . Bucket 4 = [9]. . OK. Insert 5: . Bucket 0 = [5]. ⟶ RESIZE.
Double to . Rehash all 4 keys with :
- (still collide! — bucket 2 = [12, 22])
Now . OK.
Insert 17: . Bucket 7 = [17]. . OK, no resize.
(d) Final table ():
| bucket | contents |
|---|---|
| 2 | 12, 22 |
| 5 | 5 |
| 7 | 17 |
| 9 | 9 |
| others | empty |
Note: and collide under both and , because is a multiple of both. Resizing doesn't magically separate keys whose difference is a multiple of the new — that's the honest limit of the division method.
L5.2
Recall Solution
Step 1 — anagrams are forced to collide. If two strings and are anagrams, they have the same multiset of characters, hence . Since reads only that sum, . So every anagram class maps to a single bucket, whatever is — never even sees the order, so it cannot separate what the sum has already merged.
Step 2 — counting shows this is a structural, not rare, failure. Fix strings of length over an alphabet of size with character values in . The number of distinct sums is bounded: ranges only from (all smallest) to (all largest), so there are at most possible sum-values, i.e. linear in . But the number of distinct strings is , exponential in . By pigeonhole (Pigeonhole Principle), the strings crush into at most groups, so some group has at least strings — an enormous pile forced onto one hash value before the table's buckets are even considered.
Step 3 — why cannot save it. Uniformity demands for a typical key drawn from the language. But real dictionaries contain heavy anagram families ("listen"/"silent"/"enlist", "stop"/"pots"/"tops"/"opts"). All members of each family land in one bucket, so those buckets are systematically overloaded while many others stay empty. can relabel the sums (send sum to bucket instead of ) but it can never un-merge two keys that already share a sum — relabelling is a function of the sum alone. Hence the distribution is inherently lumpy: not uniform.
The fix (ties back to L3.1). Make the hash depend on position, not just the multiset. The polynomial hash gives character position the distinct weight , so reordering characters changes the value — exactly what separated "abc" (26) from "cab" (16) in L3.1. Positional weighting is precisely the ingredient the commutative sum was missing.
One-line takeaway ::: A hash that reads only the character sum forces every anagram into one bucket, and there are exponentially many strings but only linearly many sums, so the overloaded buckets are structural, not bad luck — positional weights are the fix.
Recall One-figure recap: the whole grading ladder
The figure below shows the five rungs of this page stacked as a staircase: L1 Recognition (blue) at the bottom, then L2 Application (green), L3 Analysis (yellow), L4 Synthesis (red), and L5 Mastery (white) at the top. Each coloured box carries the one-line skill for that rung, and the white arrows show that every level reuses the one below it — you cannot analyse why a hash works (L3) until you can apply one (L2), and you cannot apply one until you can recognise the property in play (L1). Use it as a checklist: if a problem stumps you, drop one rung and make sure that skill is solid first.

Connections
- Parent: the three properties
- Modular Arithmetic — the used in division & polynomial hashing.
- Pigeonhole Principle — why collisions are unavoidable (L4.1, L5.2).
- Load Factor and Rehashing — the resize policies (L4.2, L5.1).
- Collision Resolution — Separate Chaining — how chains hold collided keys.
- Hash Table — the structure this all powers.
- Cryptographic Hash Functions — uniform but too slow for indexing (L5 trap).