Implement 1NN with NumPy
OpenAIMachine Learning Engineer · Technical ScreenLast updated
Tests your ability to implement a fast, vectorized 1-Nearest Neighbor (1NN) classifier using NumPy, focusing on memory-efficient distance computations and tie-breaking.
Problem Statement
Implement a 1-Nearest Neighbor (OneNearestNeighbor) classifier in Python using NumPy. Your implementation must be fully vectorized—avoid explicit Python loops over test or training samples during prediction.
Implement the following class interface:
import numpy as np
class OneNearestNeighbor:
def __init__(self, metric: str = "euclidean"):
"""
metric: Supports 'euclidean' and 'cosine'.
"""
pass
def fit(self, X: np.ndarray, y: np.ndarray) -> None:
"""
Store training features and labels.
X: shape (N, D), float64 array of training vectors.
y: shape (N,), int64 array of training labels.
"""
pass
def predict(self, X: np.ndarray) -> np.ndarray:
"""
Predict labels for query samples.
X: shape (M, D), float64 array of query vectors.
Returns: shape (M,), int64 array of predicted labels.
"""
pass
Metric Definitions
- Euclidean distance: For query vector
uand training vectorv,dist(u, v) = sqrt(sum((u - v)^2)). Note: minimizing squared Euclidean distance is equivalent to minimizing Euclidean distance. - Cosine distance:
dist(u, v) = 1 - (u . v) / (||u||_2 * ||v||_2). Assume vectors have non-zero norms.
Tie-breaking Rule
If a query point has equal minimum distance to multiple training points with differing labels, break ties by selecting the label associated with the smallest training index (i.e., the first point encountered in X_train).
Constraints
1 <= N, M <= 10_0001 <= D <= 1_000N * M <= 50_000_000(fits in memory if distance matrix is(M, N)float64, ~400 MB max).- Do not use 3D broadcasting of shape
(M, N, D)as it can trigger Out-Of-Memory errors for largeD. - Do not use
scikit-learnorscipy.spatial.
Examples
Example 1: Euclidean Distance
X_train = np.array([[1.0, 1.0], [2.0, 2.0], [5.0, 5.0]])
y_train = np.array([0, 1, 2])
X_test = np.array([[1.1, 0.9], [4.5, 4.8]])
clf = OneNearestNeighbor(metric="euclidean")
clf.fit(X_train, y_train)
preds = clf.predict(X_test)
# Output: array([0, 2])
# Explanation: [1.1, 0.9] is closest to [1.0, 1.0] (label 0);
# [4.5, 4.8] is closest to [5.0, 5.0] (label 2).
Example 2: Tie-Breaking
X_train = np.array([[1.0, 0.0], [0.0, 1.0]])
y_train = np.array([10, 20])
X_test = np.array([[0.5, 0.5]])
clf = OneNearestNeighbor(metric="euclidean")
clf.fit(X_train, y_train)
preds = clf.predict(X_test)
# Output: array([10])
# Explanation: Distances to both points are equal; training index 0 is chosen.
Edge Cases
M = 1orN = 1.- Large
Dwhere naive(M, 1, D) - (1, N, D)subtraction exceeds available RAM.
Hints
Hint 1
Expand squared Euclidean distance ||u - v||^2 into ||u||^2 - 2(u . v) + ||v||^2 so you can compute distances via matrix multiplication without materializing an (M, N, D) array.
Hint 2
NumPy's np.argmin returns the index of the first minimum value along an axis, which naturally implements the tie-breaking rule by index.
Hint 3
For cosine distance, normalize both query and training vectors to unit L2 norm upfront, reducing distance comparison to finding the maximum dot product.
Follow-up questions
- How would you modify this implementation to support batching if M * N exceeds available system memory?
- How does the time complexity change if we want k-Nearest Neighbors (k > 1), and which NumPy function avoids a full O(N log N) sort per query?
- At what dataset scale does exact 1NN become impractical, and what approximate nearest neighbor (ANN) indexing structures would you switch to in production?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.