Bookshop Blurb Match
A second-hand bookshop wants a "you might also like" shelf on its website. An embedding model has turned every book's back-cover blurb into a list of numbers. Blurbs about similar things point in similar directions, even if one blurb is long and gushing and the other is short and dry.
To compare two books, the shop wants a score that only cares about direction: 1 for the same direction, 0 for unrelated, -1 for opposite. Retrieval systems for language models rank documents with the same score.
Your task
Write blurbSimilarity(a, b). Return the cosine similarity of the two vectors: their dot product divided by the product of their lengths.
- Dot product:
a[0]·b[0] + a[1]·b[1] + … - Length of
a:√(a[0]² + a[1]² + …)
If either vector is all zeros it has no direction, so return 0.
blurbSimilarity(a: float[], b: float[]) → float Numbers are accepted within 1e-6.
Examples
a = [1, 2, 0] b = [2, 4, 0]
1
The dot product is 1·2 + 2·4 + 0·0 = 10. The lengths are √5 and √20, and √5 · √20 = √100 = 10. So the score is 10 / 10 = 1: the second vector is just the first one doubled, same direction.
a = [3, 0] b = [0, 5]
0
The dot product is 3·0 + 0·5 = 0, so the score is 0. One book is all about the first feature, the other all about the second: unrelated.
a = [1, 1] b = [-2, -2]
-1
The dot product is -4. The lengths are √2 and √8, whose product is 4. Score -4 / 4 = -1: exactly opposite directions.
Constraints
- 1 ≤ a.length = b.length ≤ 1,000
- -1,000 ≤ a[i], b[i] ≤ 1,000
- Answers within 0.000001 of the expected value are accepted.
Start with brute force
A natural first version normalises both vectors first: compute each length, build two new vectors divided by their lengths, then take the dot product of those. It is correct, but it makes three passes and two extra lists.
The tempting wrong shortcut is the raw dot product. It ranks long, wordy blurbs above short ones about the same topic, because longer vectors give bigger dot products. You get the same answer as the normalised version in one pass by keeping three sums at once.
Hints
0 of 4 shown- Hint 1
In the first example,
[2, 4, 0]is[1, 2, 0]doubled. Their plain dot product is 10. What would it be if you tripled the second vector instead? - Hint 2
The dot product grows when either vector gets longer. You want a score that ignores length and only measures direction, so you need to divide the length back out.
- Hint 3
Dividing the dot product by both lengths does exactly that. Each length is the square root of the sum of squares of that vector's entries.
- Hint 4
One loop: keep three running sums,
dot,a·aandb·b. At the end check for a zero length, then returndot / (√(a·a) · √(b·b)).
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.
How to solve it
Cosine similarity is the dot product with the lengths divided out. The dot product says "how much do these point the same way, times how long they are". Dividing by both lengths leaves only the direction part.
Trace a = [1, 2, 0], b = [2, 4, 0]:
- Position 0: dot += 2, a·a += 1, b·b += 4.
- Position 1: dot += 8, a·a += 4, b·b += 16.
- Position 2: nothing changes (both are 0).
- dot = 10, a·a = 5, b·b = 20. Neither length is zero. Score = 10 / (√5 · √20) = 10 / 10 = 1.
Check the zero case before dividing. A blurb the model could not embed (all zeros) would otherwise give 0 / 0, which is NaN in JavaScript and an error in Python.
function blurbSimilarity(a, b) {
let dot = 0, lenA = 0, lenB = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
lenA += a[i] * a[i];
lenB += b[i] * b[i];
}
if (lenA === 0 || lenB === 0) return 0; // no direction to compare
return dot / (Math.sqrt(lenA) * Math.sqrt(lenB));
}import math
def blurb_similarity(a, b):
dot = len_a = len_b = 0.0
for x, y in zip(a, b):
dot += x * y
len_a += x * x
len_b += y * y
if len_a == 0 or len_b == 0:
return 0.0 # no direction to compare
return dot / (math.sqrt(len_a) * math.sqrt(len_b))Run your code to see results here. Run checks the examples. Submit checks every test, including hidden edge cases.