Bruised Apple Detector
An orchard's packing line has a camera that flags apples it thinks are bruised, so they can be pulled off the belt. Most apples are fine, so a lazy detector that never flags anything would be right most of the time and still useless.
The orchard checks a test batch by hand and wants two honest numbers. Of the apples the camera flagged, how many were really bruised? Of the apples that were really bruised, how many did the camera catch? Then one score that balances both. These are the standard scores for judging any yes/no classifier, from spam filters to medical screening.
Your task
Write detectorScores(flagged, bruised). flagged[i] is the camera's answer for apple i, and bruised[i] is the truth. Count:
- TP: flagged and bruised.
- FP: flagged but fine.
- FN: not flagged but bruised.
Return [precision, recall, f1] where precision = TP / (TP + FP), recall = TP / (TP + FN), and f1 = 2 · precision · recall / (precision + recall).
Whenever a denominator is 0, that score is 0. Empty lists give [0, 0, 0].
detectorScores(flagged: bool[], bruised: bool[]) → float[] Numbers are accepted within 1e-6.
Examples
flagged = [true, true, false, false, true, false] bruised = [true, false, false, true, true, false]
[0.6666666666666666, 0.6666666666666666, 0.6666666666666666]
Apples 0 and 4 are caught (TP = 2). Apple 1 is a false alarm (FP = 1). Apple 3 was missed (FN = 1). Precision 2 / 3, recall 2 / 3, so f1 is also 2 / 3.
flagged = [true, true, true, true] bruised = [true, false, false, false]
[0.25, 1, 0.4]
Flagging everything catches the one bruised apple, so recall is 1. But only 1 of 4 flags was right, so precision is 0.25. f1 = 2 · 0.25 · 1 / 1.25 = 0.4, much lower than the plain average 0.625. F1 drops sharply when either score is poor.
flagged = [false, false, false] bruised = [false, true, false]
[0, 0, 0]
The camera flagged nothing. TP + FP = 0, so precision is 0 by the rule. Recall is 0 / 1 = 0. Both are 0, so f1 is 0 too.
Constraints
- 0 ≤ flagged.length = bruised.length ≤ 10,000
- Each value is true or false.
- Answers within 0.000001 of the expected values are accepted.
Start with brute force
You could build lists of flagged positions and bruised positions, then intersect them to find TP. It works, but it is more machinery than needed.
The real trap is reaching for accuracy (correct answers divided by all apples). On a batch with few bruised apples, a camera that never flags anything scores very high accuracy while catching nothing. Counting the three boxes in one pass gives you all three honest scores.
Hints
0 of 4 shown- Hint 1
In the second example, the camera flagged every apple. It caught every bruised one, so is it a good detector? Which of the two questions in the story exposes it?
- Hint 2
Every apple lands in one of four boxes: caught, false alarm, missed, or correctly ignored. Precision and recall each use only some of these boxes. Which ones?
- Hint 3
Precision divides by everything that was flagged (TP + FP). Recall divides by everything that was really bruised (TP + FN). Correctly ignored apples are in neither.
- Hint 4
One loop to count TP, FP and FN. Then compute the three scores, checking each denominator for 0 before dividing. F1 is the harmonic mean, not the plain average.
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
Sort every apple into a box, then each score is one ratio of box counts. Correctly ignored apples (TN) never appear, which is exactly why these scores are not fooled by a batch that is mostly fine.
Trace flagged = [T, T, F, F, T, F], bruised = [T, F, F, T, T, F]:
- Apple 0: flagged, bruised. TP = 1.
- Apple 1: flagged, fine. FP = 1.
- Apple 2: not flagged, fine. No box needed.
- Apple 3: not flagged, bruised. FN = 1.
- Apple 4: flagged, bruised. TP = 2.
- Apple 5: not flagged, fine.
Precision = 2 / 3, recall = 2 / 3, f1 = 2 · (2/3) · (2/3) / (4/3) = 2 / 3. When precision and recall are equal, F1 equals them. When they differ, F1 sits closer to the smaller one.
function detectorScores(flagged, bruised) {
let tp = 0, fp = 0, fn = 0;
for (let i = 0; i < flagged.length; i++) {
if (flagged[i] && bruised[i]) tp++; // caught a bad apple
else if (flagged[i] && !bruised[i]) fp++; // false alarm
else if (!flagged[i] && bruised[i]) fn++; // missed a bad apple
}
const precision = tp + fp === 0 ? 0 : tp / (tp + fp);
const recall = tp + fn === 0 ? 0 : tp / (tp + fn);
const f1 = precision + recall === 0 ? 0 : (2 * precision * recall) / (precision + recall);
return [precision, recall, f1];
}def detector_scores(flagged, bruised):
tp = fp = fn = 0
for f, b in zip(flagged, bruised):
if f and b:
tp += 1 # caught a bad apple
elif f and not b:
fp += 1 # false alarm
elif not f and b:
fn += 1 # missed a bad apple
precision = 0.0 if tp + fp == 0 else tp / (tp + fp)
recall = 0.0 if tp + fn == 0 else tp / (tp + fn)
if precision + recall == 0:
return [precision, recall, 0.0]
f1 = 2 * precision * recall / (precision + recall)
return [precision, recall, f1]Run your code to see results here. Run checks the examples. Submit checks every test, including hidden edge cases.