Top-k by cosine similarity
Every vector database answers one question: which stored vectors point the same way as this one? Build the brute-force version that every index is measured against.
Write top_k(query, docs, k):
queryis a list of floats.docsis a list of vectors, each the same length asquery.- Return the indices of the
kdocs with the highest cosine similarity toquery, best first. - Break ties by the lower index.
- A zero vector has similarity
0with everything (don’t divide by zero). - If
kis larger thanlen(docs), return every index.
Cosine similarity is the dot product divided by the product of the lengths:
cos(a, b) = (a · b) / (|a| × |b|)
No numpy. Plain Python only.
>>> top_k([1, 0], [[0, 1], [1, 1], [2, 0]], 2)
[2, 1]
Hints get more specific as you go. Reveal one, try again, then come back.
Write a helper cos(a, b) first. sum(x * y for x, y in zip(a, b)) is the dot product, and
math.sqrt(sum(x * x for x in a)) is the length.
Guard the zero vector: if either length is 0, return 0.0 instead of dividing.
sorted(range(len(docs)), key=lambda i: (-score[i], i))[:k] sorts best-first and breaks ties by the
lower index in one go.
Try one more hint first? The solution is always here, but working it out is what makes it stick.
Approach
Score every document, then sort indices by score (descending) with the index as the tie-breaker.
import math
def top_k(query, docs, k):
def norm(v):
return math.sqrt(sum(x * x for x in v))
qn = norm(query)
scores = []
for d in docs:
dn = norm(d)
dot = sum(a * b for a, b in zip(query, d))
scores.append(0.0 if qn == 0 or dn == 0 else dot / (qn * dn))
return sorted(range(len(docs)), key=lambda i: (-scores[i], i))[:k]
Complexity
O(n·d) to score n documents of dimension d, plus O(n log n) to sort. Swap the sort for
heapq.nsmallest(k, …) to get O(n log k) when k is small.
Why this matters
This is exact search. Approximate indexes (HNSW, IVF) trade a little recall for a lot of speed, and
you measure that recall against exactly this function. Precompute qn once: it’s the same for every
document, and at a million documents that saves a million square roots.
Per-problem discussion threads are coming. Until then, questions and better solutions are welcome on LinkedIn or by email at hello@builtfrombits.com.
Press ⌘ ↵ or Run to check the visible tests. Submit runs the hidden ones too. Everything runs in your browser.