Hash tables — solutions
Solutions to the practice problems at the end of Hash tables.
Problem 1: Hash by hand
Section titled “Problem 1: Hash by hand”string_hash("ab") is , and string_hash("ba") is . In a table with 8 slots, "ab" belongs in slot and "ba" in slot . Under sum_hash, both strings have the hash code , so they collide: every rearrangement of a string’s characters has the same sum.
Problem 2: Chaining by hand
Section titled “Problem 2: Chaining by hand”The slots are , , , , , and :
| Slot | Bucket |
|---|---|
| 0 | 10 |
| 1 | |
| 2 | 12, 7, 22, 17 |
| 3 | 3 |
| 4 |
The load factor is . The key 27 belongs in slot 2, so an unsuccessful search for 27 compares it with all four keys in that bucket. The keys happen to share a remainder by 5, so the table works much worse than its load factor suggests; a hash function that ignored this pattern in the keys would spread them more evenly.
Problem 3: Linear probing by hand
Section titled “Problem 3: Linear probing by hand”12 goes into slot 5, 7 into slot 0, 22 into slot 1, and 3 into slot 3. 17 belongs in slot 3, which is taken, so it goes into slot 4. 10 belongs in slot 3; slots 3, 4, and 5 are taken, so it goes into slot 6:
| Slot | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| Key | 7 | 22 | 3 | 17 | 12 | 10 |
A search for 24 starts at slot and probes slots 3, 4, 5, 6, 0, 1, and 2, stopping at the empty slot 2: seven probes, to learn that 24 is not in the table. The six keys form one long cluster, from slot 3 around to slot 1.
Problem 4: Phone numbers as keys
Section titled “Problem 4: Phone numbers as keys”The last two digits of a phone number vary almost at random from customer to customer, so each of the 100 pigeonholes gets about the same share. The first two digits are part of the area code, which most customers of one store share, so nearly every customer would land in one or two slots.
With 1000 slots and the whole phone number as the hash code, the slot is the last three digits of the number. If every number ends in 0, only the slots 0, 10, 20, …, 990 are ever used: 100 slots out of 1000. The table behaves like a table with 100 slots, and its buckets are ten times longer than the load factor predicts.
Problem 5: A broken __hash__
Section titled “Problem 5: A broken __hash__”return 0is correct, since equal points have equal hash codes, but every point collides with every other point. The table becomes one long bucket, and every operation takes time.return hash(self.x)is correct, but all points with the samexcollide. Points on a vertical line, which are common in grid problems, all land in one bucket.return id(self)breaks the rule. Two differentPoint(3, 4)objects are equal but have different ids, so after storing one, a search with the other looks in the wrong slot and reports that the point is missing.return hash(self.x) + hash(self.y)is correct, but symmetric:Point(1, 2)andPoint(2, 1)collide, and so do all the points on each line . Hashing the tuple(self.x, self.y)avoids this, since a tuple’s hash code depends on the order of its elements.
Problem 6: Counting collisions
Section titled “Problem 6: Counting collisions”def count_collisions(keys, m, hash_function=string_hash): used = set() collisions = 0 for key in keys: i = hash_function(key) % m if i in used: collisions += 1 used.add(i) return collisionsOn the 1138 distinct words of the United States Constitution, the counts are:
m | string_hash | sum_hash |
|---|---|---|
| 100 | 1038 | 1038 |
| 1000 | 441 | 645 |
| 10000 | 61 | 619 |
| 100000 | 4 | 619 |
With 100 slots, both functions must collide at least times. As m grows, the collisions under string_hash fall off roughly as the birthday paradox predicts for random slots. The collisions under sum_hash stop falling: the largest sum for any word in the text is 1765, so no table larger than that helps, and anagrams collide whatever the size.
Problem 7: Rehashing cost
Section titled “Problem 7: Rehashing cost”The table grows when the number of keys passes three-quarters of the capacity: on the 7th key (from 8 to 16 slots), the 13th (to 32), the 25th (to 64), the 49th (to 128), the 97th (to 256), the 193rd (to 512), the 385th (to 1024), and the 769th (to 2048). That is 8 growths, which rehash
keys in total, about 1.5 rehashes per key inserted. Each growth rehashes about twice as many keys as the one before, so the last growth accounts for about half of the total, and the total stays below a constant times the number of keys.
Problem 8: Removing with chaining
Section titled “Problem 8: Removing with chaining”A search in a table with chaining looks only in the bucket for its key’s slot, and a bucket holds exactly the keys that hash to that slot. Removing a key from its bucket leaves every other key in its own bucket, where a search for it will look. Under linear probing, a key may sit in a slot other than its own, and a search reaches it by walking through the slots in between. Emptying one of those slots cuts the walk short, which is the problem that a tombstone solves.