Back to path
AdvancedLexi · Project 9 of 12 ~6h· 5 milestones

Suggest related terms with a graph search

Continues from the last build: Rung 8 gave you exact phrase matching, a sliding window and two pointers confirming that terms appear together in the right order. But it only answers 'does this phrase occur', never 'what else is this like'.

Your phrase matcher can now confirm that 'binary search tree' occurs as an exact run of tokens somewhere in the corpus, but it goes silent the moment a query has no exact match to find.

graph representation (adjacency list)breadth-first searchqueues and dequesvisited-set invariantsBig-O reasoning (O(V+E))priority queues (heapq) as contrastbenchmarking with perf_counteralgorithm complexity trade-offs

What you'll build

related_terms('recursion', max_hops=2) returns a ranked, finite list of genuinely co-occurring terms in well under a second on a multi-thousand-document graph, and bench_graph.py proves BFS's O(V+E) behavior against a naive O(V^2)-trending list frontier at n, 10n and 100n terms.

See how we teach, before you sign up

You don't just get code dumped on you. Every starter file and every solution is explained line-by-line, in plain English. Here's one real file from this project:

lexi/graph.pypython
from collections import defaultdict
from typing import Dict, List

def tokenize(text: str) -> List[str]:
    # TODO: split text into lowercase alphabetic tokens
    raise NotImplementedError

def build_term_graph(documents: Dict[str, str]) -> Dict[str, Dict[str, int]]:
    graph: Dict[str, Dict[str, int]] = defaultdict(dict)
    for doc_id, text in documents.items():
        terms = sorted(set(tokenize(text)))
        # TODO: for every pair of terms in this document, add or increment
        # an edge in both directions (the graph is undirected)
        pass
    return dict(graph)

Reading this file

  • graph: Dict[str, Dict[str, int]] = defaultdict(dict)adjacency list is a dict of dict, this is the shape you must fill in
  • terms = sorted(set(tokenize(text)))dedupe terms per document before pairing them up
  • # TODO: for every pair of terms in this document, add or incrementthis is where the co-occurrence counting logic goes
  • raise NotImplementedErrortokenize is the one helper you must write first

Builds the undirected, weighted co-occurrence graph. tokenize() and the pairing loop are yours to finish.

That's 1 of 8 explained code blocks in this single project.

The build, milestone by milestone

  1. 1

    Turn documents into a co-occurrence graph

    4 guided steps

    Graphs model relationships that hash tables and tries cannot: a hash index tells you where a term is, a graph tells you what a term is near. That is the only structure that can answer 'related to' queries.

  2. 2

    Traverse it with BFS, mind the visited set

    4 guided steps

    BFS explores a graph strictly level by level, so it is the natural fit for 'related within N hops', and it only stays correct (and fast) if you respect two invariants: a deque frontier and a visited set checked before enqueueing.

  3. 3

    Weight the edges, and see where BFS stops being enough

    5 guided steps

    BFS's hop count treats every edge as equally important, but real co-occurrence strength varies a lot. Once edges have different weights, the question changes from 'how many edges away' to 'how cheap is the cheapest path', and only a cost-aware search answers that.

  4. 4

    Wire related terms into the query pipeline

    4 guided steps

    A graph sitting off to the side is a toy. It only earns its place once the same query path the user already types into can call it: type 'trie' and see 'hash-tables' and 'heaps' come back as related, right next to any exact hits.

  5. 5

    Benchmark it: BFS's O(V+E) against a slow frontier

    4 guided steps

    A speedup claim without a timed, growing-input benchmark is just a guess. This milestone proves BFS's O(V+E) bound holds with a deque, and shows exactly how a list frontier's O(n) pop(0) degrades that bound in practice.

What's inside when you start

3 starter files, ready to clone
5 guided milestones
5 full reference solutions
8 code blocks explained line-by-line
5 "is it working?" checks
4 interview questions it prepares you for

You'll walk away with

lexi/graph.py building an undirected, weighted term co-occurrence graph from the same document store used since rung 2
lexi/related.py exposing related_terms (BFS, N-hop) and a Dijkstra-style strongest_path_cost for weighted comparison
A CLI/REPL 'related <term>' command returning ranked suggestions
bench_graph.py timing BFS at n, 10n, 100n graph sizes and comparing list vs deque frontiers
A short written note (comments or README) on when BFS's hop count answers the question and when it does not

This is portfolio-grade. Build it free.

Sign up to unlock every milestone step-by-step, the code skeletons, full reference solutions, and checkable tasks, with your progress saved as you build.

Start building