Crawler Seen Pages

MediumdesignAlso known as Bloom Filter

A small search startup crawls the web and must not fetch the same page twice. Keeping every URL it has seen in memory is too much: at about 100 bytes a URL, a billion URLs is 100 GB. A Bloom filter answers "have I seen this URL?" with about 10 bits per URL, so 1.25 GB for the same billion. The catch: it can say "maybe seen" for a URL it never saw (the crawler then skips a page it should have fetched), but it never says "not seen" for a URL that was added.

The same structure sits in front of disk lookups in key-value stores, to skip reading files that cannot hold a key, and in front of caches, to drop requests for keys that do not exist. See Design a Web Crawler and Design a Key-Value Store.

Your task

Write a class SeenFilter with a bit array of m bits, all 0 at the start, and k hash positions per URL:

  • add(url): set the bits at all k positions of url to 1.
  • mightContain(url): return true if all k positions of url are 1, otherwise false.
  • bitsSet(): return how many of the m bits are 1.

The positions come from two fixed hashes of the URL, spelled out here so every solution picks the same bits. For each character from left to right, with c its character code:

  • h1 = (h1 × 31 + c) mod 1,000,000,007, starting from h1 = 0
  • h2 = (h2 × 37 + c) mod 1,000,000,009, starting from h2 = 0

Position i, for i from 0 to k − 1, is (h1 + i × h2) mod m. Two positions of one URL may be the same bit.

class SeenFilter

Examples

Example 1
Input
new SeenFilter(16, 3)
.add("cats.io")
.add("dogs.io")
.add("owls.io")
.bitsSet()
.mightContain("dogs.io")
.mightContain("yaks.io")
.mightContain("bats.io")
Output
[null, null, null, null, 9, true, false, true]
Why

For "cats.io", h1 = 750,493,409 and h2 = 956,570,721, so the positions are 750,493,409 mod 16 = 1, then 2, then 3. "dogs.io" sets 9, 8, 7 and "owls.io" sets 11, 5, 15: 9 bits in all. "dogs.io" was added, so it is found. "yaks.io" needs bits 2, 15 and 12; bit 12 is 0, so it was certainly never added. "bats.io" needs bits 7, 1 and 11, which other URLs happened to set: a false positive. The filter says "maybe" for a URL it never saw.

Constraints

  • 1 ≤ m ≤ 100,000
  • 1 ≤ k ≤ 16
  • Each URL has 1 to 100 printable ASCII characters (codes 33 to 126)
  • At most 2,000 calls per test
Start with brute force

A hash set of every URL is exact, but it stores the URLs: about 100 GB for a billion of them, which does not fit in one machine's memory, so every check becomes a network or disk lookup.

The Bloom filter trades exactness for size. With about 10 bits per URL and 7 hash positions, the false positive rate is roughly (1 − e−0.7)7 ≈ 0.8%, for 1.25 GB instead of 100 GB.

Hints

0 of 4 shown

Walkthrough and solution

Unlocks when you pass every test. Stuck for a while? Looking is fine too, as long as you then close it and write the code yourself.

Ctrl+Enter runs

Run your code to see results here. Run checks the examples. Submit checks every test, including hidden edge cases.

Write code