🚀 Alguni Start learning

Unit 23: Hash Tables

How dictionaries really work.

Unit 23 of 31 in Python for kids. Its 4 lessons are Turning Words Into Numbers, Buckets, When Two Land Together and Hash Master — below is everything each one explains, and a question or two from it to try.

Every sample on this page was run through real Python before it shipped, and prints exactly what it says it prints.

This unit opens with a fortnight’s trial of everything — no card needed — or with a family plan, bought in the iPhone app. The first two units of every track are free for ever. Try it in the app.

🔢 Turning Words Into Numbers

The problem a dictionary solves

Finding a name in a list means checking each one in turn. A dictionary finds it instantly, however many there are. The trick is turning the key itself into a *position*.

Python

ages = {"Ada": 9, "Sam": 11}
print(ages["Sam"])

It prints

11

A hash is a word squashed into a number

A hash function turns anything into a number. Here we add up the letter codes — ord gives each letter its number.

Python

def hash_text(text):
    return sum(ord(c) for c in text)

print(ord("a"))
print(hash_text("cat"))

It prints

97
312

Then squeeze it into the shelf you have

The number is far too big for a small table, so % folds it into range. With 10 buckets you always get a slot from 0 to 9.

Python

def slot(text, size):
    return sum(ord(c) for c in text) % size

print(slot("cat", 10))
print(slot("dog", 10))

It prints

2
4

Python’s own hash is scrambled on purpose

Python has a built-in hash(), but for text it mixes in a different random number every time your program starts — so the same word gives a different hash each run. That is a safety measure, and it is why we use our own predictable one for learning.

Python

print(hash("cat") == hash("cat"))
print(hash(42))

It prints

True
42

Try it yourself

Why use % on the hash number?

  • To make it smaller to read
  • To fold it into the number of buckets the table actually has
  • To make it positive
  • It is not needed

Answer it in the app

🪣 Buckets

A shelf of buckets

A hash table is a list of buckets. To store something you work out its slot and drop it in that bucket. To find it again you work out the same slot and look there — no searching.

Python

size = 5
buckets = [[] for _ in range(size)]

def slot(text):
    return sum(ord(c) for c in text) % size

buckets[slot("cat")].append(("cat", 9))
print(slot("cat"))
print(buckets[slot("cat")])

It prints

2
[('cat', 9)]

Looking one up

Go to the right bucket, then check the few things in it. Because each bucket holds hardly anything, this is almost instant no matter how big the table gets.

Python

size = 5
buckets = [[] for _ in range(size)]

def slot(text):
    return sum(ord(c) for c in text) % size

buckets[slot("cat")].append(("cat", 9))

def get(key):
    for k, v in buckets[slot(key)]:
        if k == key:
            return v
    return None

print(get("cat"))
print(get("cow"))

It prints

9
None

Try it yourself

Why is looking something up in a hash table so fast?

  • The list is sorted
  • The key itself says which bucket to look in, so almost nothing is searched
  • Computers are fast
  • It checks every bucket quickly

What does this print?

Python

size = 4
buckets = [[] for _ in range(size)]
buckets[1].append(("a", 1))
buckets[1].append(("b", 2))
print(len(buckets))
print(len(buckets[1]))

Answer them in the app

💥 When Two Land Together

Collisions are normal

Two different keys can easily land in the same slot — that is a collision. It is not a bug, and with more keys than buckets it is unavoidable.

Python

def slot(text, size):
    return sum(ord(c) for c in text) % size

print(slot("ab", 5))
print(slot("ba", 5))

It prints

0
0

Keep both in the same bucket

The usual fix is chaining: the bucket holds a little list, so both fit. That is why the lookup still checks the key after finding the bucket.

Python

size = 5
buckets = [[] for _ in range(size)]

def slot(text):
    return sum(ord(c) for c in text) % size

buckets[slot("ab")].append(("ab", 1))
buckets[slot("ba")].append(("ba", 2))
print(buckets[0])

It prints

[('ab', 1), ('ba', 2)]

Too few buckets makes it slow again

With only one bucket every key collides, and the "instant" lookup becomes a plain search through everything. Real dictionaries grow themselves a bigger table when they fill up, to keep buckets short.

Python

size = 1
buckets = [[] for _ in range(size)]
for word in ["cat", "dog", "hen"]:
    buckets[0].append(word)
print(len(buckets[0]))

It prints

3

Try it yourself

Why must a lookup still compare the key after finding the bucket?

  • To be safe
  • Because something else may have collided into the same bucket
  • To count the items
  • It does not have to

Answer it in the app

🏆 Hash Master

Try it yourself

What does this print?

Python

def slot(text, size):
    return sum(ord(c) for c in text) % size

print(slot("a", 10))
print(slot("b", 10))

Answer it in the app