From 31278918d0a1ba40662e40ae507b0849ce440389 Mon Sep 17 00:00:00 2001 From: poorva0405 Date: Thu, 6 Aug 2026 19:22:57 +0530 Subject: [PATCH 1/4] Add K-Nearest Neighbors classifier --- .../machinelearning/KNearestNeighbors.java | 206 ++++++++++++++++ .../KNearestNeighborsTest.java | 220 ++++++++++++++++++ 2 files changed, 426 insertions(+) create mode 100644 src/main/java/com/thealgorithms/machinelearning/KNearestNeighbors.java create mode 100644 src/test/java/com/thealgorithms/machinelearning/KNearestNeighborsTest.java diff --git a/src/main/java/com/thealgorithms/machinelearning/KNearestNeighbors.java b/src/main/java/com/thealgorithms/machinelearning/KNearestNeighbors.java new file mode 100644 index 000000000000..8b2355a00308 --- /dev/null +++ b/src/main/java/com/thealgorithms/machinelearning/KNearestNeighbors.java @@ -0,0 +1,206 @@ +package com.thealgorithms.machinelearning; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * K-Nearest Neighbors (KNN) classifier. + * + *

K-Nearest Neighbors is a supervised machine learning algorithm that + * classifies a sample based on the majority class among its {@code k} + * nearest training samples using the Euclidean distance metric. + * + *

The classifier stores the training dataset during the fitting phase and + * predicts class labels for new samples without building an explicit model. + * + * @see + * K-Nearest Neighbors + */ +public final class KNearestNeighbors { + private final int k; + + /** + * Constructs a K-Nearest Neighbors classifier with the specified number + * of neighbors. + * + * @param k the number of nearest neighbors to consider during prediction + */ + public KNearestNeighbors(int k) { + + if (k <= 0) { + throw new IllegalArgumentException("k must be greater than 0."); + } + + this.k = k; + } + + /** + * Represents a neighboring training sample and its distance from the test sample. + */ + private static final class Neighbor { + + private final double distance; + + private final int label; + + Neighbor(double distance, int label) { + this.distance = distance; + this.label = label; + } + } + + private double[][] trainingFeatures; + private int[] trainingLabels; + private int numFeatures; + + /** + * Fits the classifier using the provided training dataset. + * + *

The training feature vectors and their corresponding class labels are + * stored for use during prediction. + * + * @param features the training feature vectors + * @param labels the corresponding class labels + */ + public void fit(double[][] features, int[] labels) { + + if (features == null || labels == null) { + throw new IllegalArgumentException("Features and labels cannot be null."); + } + + if (features.length == 0 || labels.length == 0) { + throw new IllegalArgumentException("Features and labels cannot be empty."); + } + + if (features.length != labels.length) { + throw new IllegalArgumentException("Features and labels must have the same length."); + } + + if (features[0] == null) { + throw new IllegalArgumentException("Feature vectors cannot be null."); + } + + numFeatures = features[0].length; + + if (numFeatures == 0) { + throw new IllegalArgumentException("Feature vectors cannot be empty."); + } + + for (double[] sample : features) { + if (sample == null) { + throw new IllegalArgumentException("Feature vectors cannot be null."); + } + + if (sample.length != numFeatures) { + throw new IllegalArgumentException("All feature vectors must have the same dimension."); + } + } + + this.trainingFeatures = features; + this.trainingLabels = labels; + } + + /** + * Computes the Euclidean distance between two feature vectors. + * + * @param first the first feature vector + * @param second the second feature vector + * @return the Euclidean distance between the two vectors + */ + private static double euclideanDistance(double[] first, double[] second) { + double sum = 0.0; + + for (int i = 0; i < first.length; i++) { + double difference = first[i] - second[i]; + sum += difference * difference; + } + + return Math.sqrt(sum); + } + + /** + * Predicts the class label for a single sample. + * + *

The prediction is made by finding the {@code k} nearest neighbors + * among the training samples and selecting the class with the highest + * number of votes. In the event of a tie, the smaller class label is + * returned. + * + * @param testPoint the sample to classify + * @return the predicted class label + */ + public int predict(double[] testPoint) { + if (trainingFeatures == null || trainingLabels == null) { + throw new IllegalStateException("Classifier has not been fitted."); + } + + if(testPoint == null) { + throw new IllegalArgumentException("Sample cannot be null."); + } + + if (testPoint.length != numFeatures) { + throw new IllegalArgumentException("Sample length must match training feature count."); + } + + List neighbors = new ArrayList<>(trainingFeatures.length); + + for (int i = 0; i < trainingFeatures.length; i++) { + double distance = euclideanDistance(trainingFeatures[i], testPoint); + neighbors.add(new Neighbor(distance, trainingLabels[i])); + } + + + neighbors.sort(Comparator.comparingDouble(neighbor -> neighbor.distance)); + + Map votes = new HashMap<>(); + + if (k > trainingFeatures.length) { + throw new IllegalArgumentException("k cannot be greater than the number of training samples."); + } + + for (int i = 0; i < k; i++) { + int label = neighbors.get(i).label; + votes.merge(label, 1, Integer::sum); + } + + int predictedLabel = -1; + int maxVotes = -1; + + for (Map.Entry entry : votes.entrySet()) { + int label = entry.getKey(); + int count = entry.getValue(); + + if (count > maxVotes || (count == maxVotes && label < predictedLabel)) { + maxVotes = count; + predictedLabel = label; + } + } + + return predictedLabel; + } + + + /** + * Predicts class labels for multiple samples. + * + * @param samples the samples to classify + * @return an array containing the predicted class label for each sample + */ + public int[] predict(double[][] samples) { + + if (samples == null) { + throw new IllegalArgumentException("Samples cannot be null."); + } + + int[] predictions = new int[samples.length]; + + for (int i = 0; i < samples.length; i++) { + predictions[i] = predict(samples[i]); + } + + return predictions; + } +} diff --git a/src/test/java/com/thealgorithms/machinelearning/KNearestNeighborsTest.java b/src/test/java/com/thealgorithms/machinelearning/KNearestNeighborsTest.java new file mode 100644 index 000000000000..503a15d45b1e --- /dev/null +++ b/src/test/java/com/thealgorithms/machinelearning/KNearestNeighborsTest.java @@ -0,0 +1,220 @@ +package com.thealgorithms.machinelearning; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class KNearestNeighborsTest { + + @Test + void predictsCorrectClassOnSeparableDataset() { + double[][] features = { + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, + }; + + int[] labels = {0, 0, 1, 1}; + + KNearestNeighbors knn = new KNearestNeighbors(3); + knn.fit(features, labels); + + assertEquals(0, knn.predict(new double[] {1.5, 1.5})); + assertEquals(1, knn.predict(new double[] {8.5, 8.5})); + } + + @Test + void predictsBatchClassesOnSeparableDataset() { + double[][] features = { + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, + }; + + int[] labels = {0, 0, 1, 1}; + + KNearestNeighbors knn = new KNearestNeighbors(3); + knn.fit(features, labels); + + double[][] samples = {{1.5, 1.5}, {8.5, 8.5}}; + int[] predictions = knn.predict(samples); + + assertArrayEquals(new int[] {0, 1}, predictions); + } + + @Test + void throwsExceptionWhenKIsNotPositive() { + assertThrows(IllegalArgumentException.class, () -> new KNearestNeighbors(-4)); + assertThrows(IllegalArgumentException.class, () -> new KNearestNeighbors(0)); + } + + @Test + void throwsExceptionWhenKIsGreaterThanNumberOfTrainingSamples() { + double[][] features = { + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, + }; + + int[] labels = {0, 0, 1, 1}; + KNearestNeighbors knn = new KNearestNeighbors(7); + knn.fit(features, labels); + + assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[] {1.5, 1.5})); + } + + @Test + void nullFeaturesArrayThrowsIllegalArgumentException() { + KNearestNeighbors knn = new KNearestNeighbors(1); + + assertThrows(IllegalArgumentException.class, () -> knn.fit(null, new int[] {0, 1})); + } + + @Test + void emptyFeaturesArrayThrowsIllegalArgumentException() { + KNearestNeighbors knn = new KNearestNeighbors(1); + + assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {}, new int[] {})); + } + + @Test + void nullLabelsArrayThrowsIllegalArgumentException() { + KNearestNeighbors knn = new KNearestNeighbors(1); + + assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {{1, 1}, {2, 2}}, null)); + } + + @Test + void emptyLabelsArrayThrowsIllegalArgumentException() { + KNearestNeighbors knn = new KNearestNeighbors(1); + + assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {{1, 1}, {2, 2}}, new int[] {})); + } + + @Test + void mismatchedFeatureAndLabelLengthsThrowsIllegalArgumentException() { + KNearestNeighbors knn = new KNearestNeighbors(3); + + assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {{0, 0}, {1, 1}, {8, 8}, {9, 9}}, new int[] {0, 0, 1})); + } + + @Test + void emptyFeatureVectorThrowsIllegalArgumentException() { + KNearestNeighbors knn = new KNearestNeighbors(1); + + assertThrows(IllegalArgumentException.class,() -> knn.fit(new double[][] {{}}, new int[] {0})); + } + + @Test + void nullFeatureSampleThrowsIllegalArgumentException() { + KNearestNeighbors knn = new KNearestNeighbors(2); + + assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {{0, 0}, null, {8, 8}}, new int[] {0, 0, 1})); + } + + @Test + void mismatchedDimensionThrowsIllegalArgumentException() { + KNearestNeighbors knn = new KNearestNeighbors(2); + + assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {{0, 0}, {1, 1, 2}, {8, 8}}, new int[] {0, 0, 1})); + } + + @Test + void predictBeforeFitThrowsIllegalStateException() { + KNearestNeighbors knn = new KNearestNeighbors(3); + + assertThrows(IllegalStateException.class, () -> knn.predict(new double[] {1, 1})); + } + + @Test + void nullTestPointThrowsIllegalArgumentException() { + double[][] features = { + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, + }; + + int[] labels = {0, 0, 1, 1}; + + KNearestNeighbors knn = new KNearestNeighbors(3); + knn.fit(features, labels); + + assertThrows(IllegalArgumentException.class, () -> knn.predict((double[]) null)); + assertThrows(IllegalArgumentException.class, () -> knn.predict((double[][]) null)); + } + + @Test + void mismatchedTestPointLengthThrowsIllegalArgumentException() { + double[][] features = { + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, + }; + + int[] labels = {0, 0, 1, 1}; + + KNearestNeighbors knn = new KNearestNeighbors(3); + knn.fit(features, labels); + + assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[] {1.5, 1.5, 1.5})); + } + + @Test + void tieBreakReturnsSmallerLabel() { + double[][] features = { + {0, 0}, + {0, 2}, + {2, 0}, + {2, 2} + }; + + int[] labels = {0, 1, 0, 1}; + + KNearestNeighbors knn = new KNearestNeighbors(4); + knn.fit(features, labels); + + assertEquals(0, knn.predict(new double[] {1, 1})); + } + + @Test + void nullBatchSampleThrowsIllegalArgumentException() { + double[][] features = { + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, + }; + + int[] labels = {0, 0, 1, 1}; + + KNearestNeighbors knn = new KNearestNeighbors(3); + knn.fit(features, labels); + + assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[][] {{1.5, 1.5}, null})); + } + + @Test + void invalidBatchSampleThrowsIllegalArgumentException() { + double[][] features = { + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, + }; + + int[] labels = {0, 0, 1, 1}; + + KNearestNeighbors knn = new KNearestNeighbors(3); + knn.fit(features, labels); + + assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[][] {{1.5, 1.5}, {8.5, 8.5, 8.5}})); + } + +} \ No newline at end of file From f0ce829f133f1bd918ef90f7f63fa400bf4c2737 Mon Sep 17 00:00:00 2001 From: poorva0405 Date: Fri, 7 Aug 2026 11:56:45 +0530 Subject: [PATCH 2/4] Fix checkstyle issues --- .../machinelearning/KNearestNeighbors.java | 33 +++-- .../KNearestNeighborsTest.java | 139 +++++++++--------- 2 files changed, 90 insertions(+), 82 deletions(-) diff --git a/src/main/java/com/thealgorithms/machinelearning/KNearestNeighbors.java b/src/main/java/com/thealgorithms/machinelearning/KNearestNeighbors.java index 8b2355a00308..c398d6482c9a 100644 --- a/src/main/java/com/thealgorithms/machinelearning/KNearestNeighbors.java +++ b/src/main/java/com/thealgorithms/machinelearning/KNearestNeighbors.java @@ -9,15 +9,17 @@ /** * K-Nearest Neighbors (KNN) classifier. * - *

K-Nearest Neighbors is a supervised machine learning algorithm that + *

+ * K-Nearest Neighbors is a supervised machine learning algorithm that * classifies a sample based on the majority class among its {@code k} * nearest training samples using the Euclidean distance metric. * - *

The classifier stores the training dataset during the fitting phase and + *

+ * The classifier stores the training dataset during the fitting phase and * predicts class labels for new samples without building an explicit model. * * @see - * K-Nearest Neighbors + * K-Nearest Neighbors */ public final class KNearestNeighbors { private final int k; @@ -38,7 +40,8 @@ public KNearestNeighbors(int k) { } /** - * Represents a neighboring training sample and its distance from the test sample. + * Represents a neighboring training sample and its distance from the test + * sample. */ private static final class Neighbor { @@ -59,11 +62,12 @@ private static final class Neighbor { /** * Fits the classifier using the provided training dataset. * - *

The training feature vectors and their corresponding class labels are + *

+ * The training feature vectors and their corresponding class labels are * stored for use during prediction. * * @param features the training feature vectors - * @param labels the corresponding class labels + * @param labels the corresponding class labels */ public void fit(double[][] features, int[] labels) { @@ -106,7 +110,7 @@ public void fit(double[][] features, int[] labels) { /** * Computes the Euclidean distance between two feature vectors. * - * @param first the first feature vector + * @param first the first feature vector * @param second the second feature vector * @return the Euclidean distance between the two vectors */ @@ -124,7 +128,8 @@ private static double euclideanDistance(double[] first, double[] second) { /** * Predicts the class label for a single sample. * - *

The prediction is made by finding the {@code k} nearest neighbors + *

+ * The prediction is made by finding the {@code k} nearest neighbors * among the training samples and selecting the class with the highest * number of votes. In the event of a tie, the smaller class label is * returned. @@ -137,7 +142,7 @@ public int predict(double[] testPoint) { throw new IllegalStateException("Classifier has not been fitted."); } - if(testPoint == null) { + if (testPoint == null) { throw new IllegalArgumentException("Sample cannot be null."); } @@ -151,19 +156,18 @@ public int predict(double[] testPoint) { double distance = euclideanDistance(trainingFeatures[i], testPoint); neighbors.add(new Neighbor(distance, trainingLabels[i])); } - neighbors.sort(Comparator.comparingDouble(neighbor -> neighbor.distance)); - Map votes = new HashMap<>(); + Map votes = new HashMap<>(); if (k > trainingFeatures.length) { throw new IllegalArgumentException("k cannot be greater than the number of training samples."); } - for (int i = 0; i < k; i++) { - int label = neighbors.get(i).label; - votes.merge(label, 1, Integer::sum); + for (int i = 0; i < k; i++) { + int label = neighbors.get(i).label; + votes.merge(label, 1, Integer::sum); } int predictedLabel = -1; @@ -182,7 +186,6 @@ public int predict(double[] testPoint) { return predictedLabel; } - /** * Predicts class labels for multiple samples. * diff --git a/src/test/java/com/thealgorithms/machinelearning/KNearestNeighborsTest.java b/src/test/java/com/thealgorithms/machinelearning/KNearestNeighborsTest.java index 503a15d45b1e..bba682d0741c 100644 --- a/src/test/java/com/thealgorithms/machinelearning/KNearestNeighborsTest.java +++ b/src/test/java/com/thealgorithms/machinelearning/KNearestNeighborsTest.java @@ -6,48 +6,48 @@ import org.junit.jupiter.api.Test; -public class KNearestNeighborsTest { - +class KNearestNeighborsTest { + @Test void predictsCorrectClassOnSeparableDataset() { double[][] features = { - {0, 0}, - {1, 1}, - {8, 8}, - {9, 9}, + { 0, 0 }, + { 1, 1 }, + { 8, 8 }, + { 9, 9 }, }; - int[] labels = {0, 0, 1, 1}; + int[] labels = { 0, 0, 1, 1 }; KNearestNeighbors knn = new KNearestNeighbors(3); knn.fit(features, labels); - assertEquals(0, knn.predict(new double[] {1.5, 1.5})); - assertEquals(1, knn.predict(new double[] {8.5, 8.5})); + assertEquals(0, knn.predict(new double[] { 1.5, 1.5 })); + assertEquals(1, knn.predict(new double[] { 8.5, 8.5 })); } @Test void predictsBatchClassesOnSeparableDataset() { double[][] features = { - {0, 0}, - {1, 1}, - {8, 8}, - {9, 9}, + { 0, 0 }, + { 1, 1 }, + { 8, 8 }, + { 9, 9 }, }; - int[] labels = {0, 0, 1, 1}; + int[] labels = { 0, 0, 1, 1 }; KNearestNeighbors knn = new KNearestNeighbors(3); knn.fit(features, labels); - double[][] samples = {{1.5, 1.5}, {8.5, 8.5}}; + double[][] samples = { { 1.5, 1.5 }, { 8.5, 8.5 } }; int[] predictions = knn.predict(samples); - assertArrayEquals(new int[] {0, 1}, predictions); + assertArrayEquals(new int[] { 0, 1 }, predictions); } @Test - void throwsExceptionWhenKIsNotPositive() { + void throwsExceptionWhenKIsNotPositive() { assertThrows(IllegalArgumentException.class, () -> new KNearestNeighbors(-4)); assertThrows(IllegalArgumentException.class, () -> new KNearestNeighbors(0)); } @@ -55,24 +55,24 @@ void throwsExceptionWhenKIsNotPositive() { @Test void throwsExceptionWhenKIsGreaterThanNumberOfTrainingSamples() { double[][] features = { - {0, 0}, - {1, 1}, - {8, 8}, - {9, 9}, + { 0, 0 }, + { 1, 1 }, + { 8, 8 }, + { 9, 9 }, }; - int[] labels = {0, 0, 1, 1}; + int[] labels = { 0, 0, 1, 1 }; KNearestNeighbors knn = new KNearestNeighbors(7); knn.fit(features, labels); - assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[] {1.5, 1.5})); + assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[] { 1.5, 1.5 })); } @Test void nullFeaturesArrayThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(1); - - assertThrows(IllegalArgumentException.class, () -> knn.fit(null, new int[] {0, 1})); + + assertThrows(IllegalArgumentException.class, () -> knn.fit(null, new int[] { 0, 1 })); } @Test @@ -85,62 +85,66 @@ void emptyFeaturesArrayThrowsIllegalArgumentException() { @Test void nullLabelsArrayThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(1); - - assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {{1, 1}, {2, 2}}, null)); + + assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] { { 1, 1 }, { 2, 2 } }, null)); } @Test void emptyLabelsArrayThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(1); - - assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {{1, 1}, {2, 2}}, new int[] {})); + + assertThrows(IllegalArgumentException.class, + () -> knn.fit(new double[][] { { 1, 1 }, { 2, 2 } }, new int[] {})); } @Test void mismatchedFeatureAndLabelLengthsThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(3); - - assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {{0, 0}, {1, 1}, {8, 8}, {9, 9}}, new int[] {0, 0, 1})); + + assertThrows(IllegalArgumentException.class, + () -> knn.fit(new double[][] { { 0, 0 }, { 1, 1 }, { 8, 8 }, { 9, 9 } }, new int[] { 0, 0, 1 })); } @Test void emptyFeatureVectorThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(1); - assertThrows(IllegalArgumentException.class,() -> knn.fit(new double[][] {{}}, new int[] {0})); + assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] { {} }, new int[] { 0 })); } @Test void nullFeatureSampleThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(2); - - assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {{0, 0}, null, {8, 8}}, new int[] {0, 0, 1})); + + assertThrows(IllegalArgumentException.class, + () -> knn.fit(new double[][] { { 0, 0 }, null, { 8, 8 } }, new int[] { 0, 0, 1 })); } @Test void mismatchedDimensionThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(2); - - assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {{0, 0}, {1, 1, 2}, {8, 8}}, new int[] {0, 0, 1})); + + assertThrows(IllegalArgumentException.class, + () -> knn.fit(new double[][] { { 0, 0 }, { 1, 1, 2 }, { 8, 8 } }, new int[] { 0, 0, 1 })); } @Test void predictBeforeFitThrowsIllegalStateException() { KNearestNeighbors knn = new KNearestNeighbors(3); - assertThrows(IllegalStateException.class, () -> knn.predict(new double[] {1, 1})); + assertThrows(IllegalStateException.class, () -> knn.predict(new double[] { 1, 1 })); } @Test void nullTestPointThrowsIllegalArgumentException() { double[][] features = { - {0, 0}, - {1, 1}, - {8, 8}, - {9, 9}, + { 0, 0 }, + { 1, 1 }, + { 8, 8 }, + { 9, 9 }, }; - int[] labels = {0, 0, 1, 1}; + int[] labels = { 0, 0, 1, 1 }; KNearestNeighbors knn = new KNearestNeighbors(3); knn.fit(features, labels); @@ -152,69 +156,70 @@ void nullTestPointThrowsIllegalArgumentException() { @Test void mismatchedTestPointLengthThrowsIllegalArgumentException() { double[][] features = { - {0, 0}, - {1, 1}, - {8, 8}, - {9, 9}, + { 0, 0 }, + { 1, 1 }, + { 8, 8 }, + { 9, 9 }, }; - int[] labels = {0, 0, 1, 1}; + int[] labels = { 0, 0, 1, 1 }; KNearestNeighbors knn = new KNearestNeighbors(3); knn.fit(features, labels); - assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[] {1.5, 1.5, 1.5})); + assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[] { 1.5, 1.5, 1.5 })); } @Test void tieBreakReturnsSmallerLabel() { double[][] features = { - {0, 0}, - {0, 2}, - {2, 0}, - {2, 2} + { 0, 0 }, + { 0, 2 }, + { 2, 0 }, + { 2, 2 } }; - int[] labels = {0, 1, 0, 1}; + int[] labels = { 0, 1, 0, 1 }; KNearestNeighbors knn = new KNearestNeighbors(4); knn.fit(features, labels); - assertEquals(0, knn.predict(new double[] {1, 1})); + assertEquals(0, knn.predict(new double[] { 1, 1 })); } @Test void nullBatchSampleThrowsIllegalArgumentException() { double[][] features = { - {0, 0}, - {1, 1}, - {8, 8}, - {9, 9}, + { 0, 0 }, + { 1, 1 }, + { 8, 8 }, + { 9, 9 }, }; - int[] labels = {0, 0, 1, 1}; + int[] labels = { 0, 0, 1, 1 }; KNearestNeighbors knn = new KNearestNeighbors(3); knn.fit(features, labels); - assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[][] {{1.5, 1.5}, null})); + assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[][] { { 1.5, 1.5 }, null })); } @Test void invalidBatchSampleThrowsIllegalArgumentException() { double[][] features = { - {0, 0}, - {1, 1}, - {8, 8}, - {9, 9}, + { 0, 0 }, + { 1, 1 }, + { 8, 8 }, + { 9, 9 }, }; - int[] labels = {0, 0, 1, 1}; + int[] labels = { 0, 0, 1, 1 }; KNearestNeighbors knn = new KNearestNeighbors(3); knn.fit(features, labels); - assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[][] {{1.5, 1.5}, {8.5, 8.5, 8.5}})); + assertThrows(IllegalArgumentException.class, + () -> knn.predict(new double[][] { { 1.5, 1.5 }, { 8.5, 8.5, 8.5 } })); } -} \ No newline at end of file +} From a8c385dda0a9386f6f9336550292e7a9f21ba51b Mon Sep 17 00:00:00 2001 From: poorva0405 Date: Fri, 7 Aug 2026 13:03:46 +0530 Subject: [PATCH 3/4] Improve test coverage --- .../machinelearning/KNearestNeighbors.java | 6 +- .../KNearestNeighborsTest.java | 138 ++++++++++-------- 2 files changed, 86 insertions(+), 58 deletions(-) diff --git a/src/main/java/com/thealgorithms/machinelearning/KNearestNeighbors.java b/src/main/java/com/thealgorithms/machinelearning/KNearestNeighbors.java index c398d6482c9a..bac1505dd9bc 100644 --- a/src/main/java/com/thealgorithms/machinelearning/KNearestNeighbors.java +++ b/src/main/java/com/thealgorithms/machinelearning/KNearestNeighbors.java @@ -138,7 +138,11 @@ private static double euclideanDistance(double[] first, double[] second) { * @return the predicted class label */ public int predict(double[] testPoint) { - if (trainingFeatures == null || trainingLabels == null) { + if (trainingFeatures == null) { + throw new IllegalStateException("Classifier has not been fitted."); + } + + if (trainingLabels == null) { throw new IllegalStateException("Classifier has not been fitted."); } diff --git a/src/test/java/com/thealgorithms/machinelearning/KNearestNeighborsTest.java b/src/test/java/com/thealgorithms/machinelearning/KNearestNeighborsTest.java index bba682d0741c..8a6924f035dd 100644 --- a/src/test/java/com/thealgorithms/machinelearning/KNearestNeighborsTest.java +++ b/src/test/java/com/thealgorithms/machinelearning/KNearestNeighborsTest.java @@ -11,39 +11,39 @@ class KNearestNeighborsTest { @Test void predictsCorrectClassOnSeparableDataset() { double[][] features = { - { 0, 0 }, - { 1, 1 }, - { 8, 8 }, - { 9, 9 }, + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, }; - int[] labels = { 0, 0, 1, 1 }; + int[] labels = {0, 0, 1, 1}; KNearestNeighbors knn = new KNearestNeighbors(3); knn.fit(features, labels); - assertEquals(0, knn.predict(new double[] { 1.5, 1.5 })); - assertEquals(1, knn.predict(new double[] { 8.5, 8.5 })); + assertEquals(0, knn.predict(new double[] {1.5, 1.5})); + assertEquals(1, knn.predict(new double[] {8.5, 8.5})); } @Test void predictsBatchClassesOnSeparableDataset() { double[][] features = { - { 0, 0 }, - { 1, 1 }, - { 8, 8 }, - { 9, 9 }, + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, }; - int[] labels = { 0, 0, 1, 1 }; + int[] labels = {0, 0, 1, 1}; KNearestNeighbors knn = new KNearestNeighbors(3); knn.fit(features, labels); - double[][] samples = { { 1.5, 1.5 }, { 8.5, 8.5 } }; + double[][] samples = {{1.5, 1.5}, {8.5, 8.5}}; int[] predictions = knn.predict(samples); - assertArrayEquals(new int[] { 0, 1 }, predictions); + assertArrayEquals(new int[] {0, 1}, predictions); } @Test @@ -55,24 +55,24 @@ void throwsExceptionWhenKIsNotPositive() { @Test void throwsExceptionWhenKIsGreaterThanNumberOfTrainingSamples() { double[][] features = { - { 0, 0 }, - { 1, 1 }, - { 8, 8 }, - { 9, 9 }, + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, }; - int[] labels = { 0, 0, 1, 1 }; + int[] labels = {0, 0, 1, 1}; KNearestNeighbors knn = new KNearestNeighbors(7); knn.fit(features, labels); - assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[] { 1.5, 1.5 })); + assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[] {1.5, 1.5})); } @Test void nullFeaturesArrayThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(1); - assertThrows(IllegalArgumentException.class, () -> knn.fit(null, new int[] { 0, 1 })); + assertThrows(IllegalArgumentException.class, () -> knn.fit(null, new int[] {0, 1})); } @Test @@ -86,7 +86,7 @@ void emptyFeaturesArrayThrowsIllegalArgumentException() { void nullLabelsArrayThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(1); - assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] { { 1, 1 }, { 2, 2 } }, null)); + assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {{1, 1}, {2, 2}}, null)); } @Test @@ -94,7 +94,7 @@ void emptyLabelsArrayThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(1); assertThrows(IllegalArgumentException.class, - () -> knn.fit(new double[][] { { 1, 1 }, { 2, 2 } }, new int[] {})); + () -> knn.fit(new double[][] {{1, 1}, {2, 2}}, new int[] {})); } @Test @@ -102,14 +102,21 @@ void mismatchedFeatureAndLabelLengthsThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(3); assertThrows(IllegalArgumentException.class, - () -> knn.fit(new double[][] { { 0, 0 }, { 1, 1 }, { 8, 8 }, { 9, 9 } }, new int[] { 0, 0, 1 })); + () -> knn.fit(new double[][] {{0, 0}, {1, 1}, {8, 8}, {9, 9}}, new int[] {0, 0, 1})); + } + + @Test + void firstFeatureVectorNullThrowsIllegalArgumentException() { + KNearestNeighbors knn = new KNearestNeighbors(1); + + assertThrows(IllegalArgumentException.class,() -> knn.fit(new double[][] {null, {1, 1}}, new int[] {0, 1})); } @Test void emptyFeatureVectorThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(1); - assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] { {} }, new int[] { 0 })); + assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {{}}, new int[] {0})); } @Test @@ -117,7 +124,7 @@ void nullFeatureSampleThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(2); assertThrows(IllegalArgumentException.class, - () -> knn.fit(new double[][] { { 0, 0 }, null, { 8, 8 } }, new int[] { 0, 0, 1 })); + () -> knn.fit(new double[][] {{0, 0}, null, {8, 8}}, new int[] {0, 0, 1})); } @Test @@ -125,26 +132,26 @@ void mismatchedDimensionThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(2); assertThrows(IllegalArgumentException.class, - () -> knn.fit(new double[][] { { 0, 0 }, { 1, 1, 2 }, { 8, 8 } }, new int[] { 0, 0, 1 })); + () -> knn.fit(new double[][] {{0, 0}, {1, 1, 2}, {8, 8}}, new int[] {0, 0, 1})); } @Test void predictBeforeFitThrowsIllegalStateException() { KNearestNeighbors knn = new KNearestNeighbors(3); - assertThrows(IllegalStateException.class, () -> knn.predict(new double[] { 1, 1 })); + assertThrows(IllegalStateException.class, () -> knn.predict(new double[] {1, 1})); } @Test void nullTestPointThrowsIllegalArgumentException() { double[][] features = { - { 0, 0 }, - { 1, 1 }, - { 8, 8 }, - { 9, 9 }, + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, }; - int[] labels = { 0, 0, 1, 1 }; + int[] labels = {0, 0, 1, 1}; KNearestNeighbors knn = new KNearestNeighbors(3); knn.fit(features, labels); @@ -156,70 +163,87 @@ void nullTestPointThrowsIllegalArgumentException() { @Test void mismatchedTestPointLengthThrowsIllegalArgumentException() { double[][] features = { - { 0, 0 }, - { 1, 1 }, - { 8, 8 }, - { 9, 9 }, + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, }; - int[] labels = { 0, 0, 1, 1 }; + int[] labels = {0, 0, 1, 1}; KNearestNeighbors knn = new KNearestNeighbors(3); knn.fit(features, labels); - assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[] { 1.5, 1.5, 1.5 })); + assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[] {1.5, 1.5, 1.5})); } @Test void tieBreakReturnsSmallerLabel() { double[][] features = { - { 0, 0 }, - { 0, 2 }, - { 2, 0 }, - { 2, 2 } + {0, 0}, + {0, 2}, + {2, 0}, + {2, 2} + }; + + int[] labels = {0, 1, 0, 1}; + + KNearestNeighbors knn = new KNearestNeighbors(4); + knn.fit(features, labels); + + assertEquals(0, knn.predict(new double[] {1, 1})); + } + + @Test + void tieBreakKeepsCurrentWinnerWhenLabelIsGreater() { + double[][] features = { + {0, 0}, + {0, 2}, + {2, 0}, + {2, 2} }; - int[] labels = { 0, 1, 0, 1 }; + int[] labels = {1, 0, 1, 0}; KNearestNeighbors knn = new KNearestNeighbors(4); knn.fit(features, labels); - assertEquals(0, knn.predict(new double[] { 1, 1 })); + assertEquals(0, knn.predict(new double[] {1, 1})); } @Test void nullBatchSampleThrowsIllegalArgumentException() { double[][] features = { - { 0, 0 }, - { 1, 1 }, - { 8, 8 }, - { 9, 9 }, + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, }; - int[] labels = { 0, 0, 1, 1 }; + int[] labels = {0, 0, 1, 1}; KNearestNeighbors knn = new KNearestNeighbors(3); knn.fit(features, labels); - assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[][] { { 1.5, 1.5 }, null })); + assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[][] {{1.5, 1.5}, null})); } @Test void invalidBatchSampleThrowsIllegalArgumentException() { double[][] features = { - { 0, 0 }, - { 1, 1 }, - { 8, 8 }, - { 9, 9 }, + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, }; - int[] labels = { 0, 0, 1, 1 }; + int[] labels = {0, 0, 1, 1}; KNearestNeighbors knn = new KNearestNeighbors(3); knn.fit(features, labels); assertThrows(IllegalArgumentException.class, - () -> knn.predict(new double[][] { { 1.5, 1.5 }, { 8.5, 8.5, 8.5 } })); + () -> knn.predict(new double[][] {{1.5, 1.5}, {8.5, 8.5, 8.5}})); } } From 37d77ef72f347a50be26f2e779d727b8eb0d239c Mon Sep 17 00:00:00 2001 From: poorva0405 Date: Fri, 7 Aug 2026 14:31:09 +0530 Subject: [PATCH 4/4] Apply clang-format formatting --- .../KNearestNeighborsTest.java | 88 ++++++++----------- 1 file changed, 36 insertions(+), 52 deletions(-) diff --git a/src/test/java/com/thealgorithms/machinelearning/KNearestNeighborsTest.java b/src/test/java/com/thealgorithms/machinelearning/KNearestNeighborsTest.java index 8a6924f035dd..855b008e81c6 100644 --- a/src/test/java/com/thealgorithms/machinelearning/KNearestNeighborsTest.java +++ b/src/test/java/com/thealgorithms/machinelearning/KNearestNeighborsTest.java @@ -11,10 +11,10 @@ class KNearestNeighborsTest { @Test void predictsCorrectClassOnSeparableDataset() { double[][] features = { - {0, 0}, - {1, 1}, - {8, 8}, - {9, 9}, + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, }; int[] labels = {0, 0, 1, 1}; @@ -29,10 +29,10 @@ void predictsCorrectClassOnSeparableDataset() { @Test void predictsBatchClassesOnSeparableDataset() { double[][] features = { - {0, 0}, - {1, 1}, - {8, 8}, - {9, 9}, + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, }; int[] labels = {0, 0, 1, 1}; @@ -55,10 +55,10 @@ void throwsExceptionWhenKIsNotPositive() { @Test void throwsExceptionWhenKIsGreaterThanNumberOfTrainingSamples() { double[][] features = { - {0, 0}, - {1, 1}, - {8, 8}, - {9, 9}, + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, }; int[] labels = {0, 0, 1, 1}; @@ -93,23 +93,21 @@ void nullLabelsArrayThrowsIllegalArgumentException() { void emptyLabelsArrayThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(1); - assertThrows(IllegalArgumentException.class, - () -> knn.fit(new double[][] {{1, 1}, {2, 2}}, new int[] {})); + assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {{1, 1}, {2, 2}}, new int[] {})); } @Test void mismatchedFeatureAndLabelLengthsThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(3); - assertThrows(IllegalArgumentException.class, - () -> knn.fit(new double[][] {{0, 0}, {1, 1}, {8, 8}, {9, 9}}, new int[] {0, 0, 1})); + assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {{0, 0}, {1, 1}, {8, 8}, {9, 9}}, new int[] {0, 0, 1})); } @Test void firstFeatureVectorNullThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(1); - assertThrows(IllegalArgumentException.class,() -> knn.fit(new double[][] {null, {1, 1}}, new int[] {0, 1})); + assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {null, {1, 1}}, new int[] {0, 1})); } @Test @@ -123,16 +121,14 @@ void emptyFeatureVectorThrowsIllegalArgumentException() { void nullFeatureSampleThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(2); - assertThrows(IllegalArgumentException.class, - () -> knn.fit(new double[][] {{0, 0}, null, {8, 8}}, new int[] {0, 0, 1})); + assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {{0, 0}, null, {8, 8}}, new int[] {0, 0, 1})); } @Test void mismatchedDimensionThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(2); - assertThrows(IllegalArgumentException.class, - () -> knn.fit(new double[][] {{0, 0}, {1, 1, 2}, {8, 8}}, new int[] {0, 0, 1})); + assertThrows(IllegalArgumentException.class, () -> knn.fit(new double[][] {{0, 0}, {1, 1, 2}, {8, 8}}, new int[] {0, 0, 1})); } @Test @@ -145,10 +141,10 @@ void predictBeforeFitThrowsIllegalStateException() { @Test void nullTestPointThrowsIllegalArgumentException() { double[][] features = { - {0, 0}, - {1, 1}, - {8, 8}, - {9, 9}, + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, }; int[] labels = {0, 0, 1, 1}; @@ -163,10 +159,10 @@ void nullTestPointThrowsIllegalArgumentException() { @Test void mismatchedTestPointLengthThrowsIllegalArgumentException() { double[][] features = { - {0, 0}, - {1, 1}, - {8, 8}, - {9, 9}, + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, }; int[] labels = {0, 0, 1, 1}; @@ -179,12 +175,7 @@ void mismatchedTestPointLengthThrowsIllegalArgumentException() { @Test void tieBreakReturnsSmallerLabel() { - double[][] features = { - {0, 0}, - {0, 2}, - {2, 0}, - {2, 2} - }; + double[][] features = {{0, 0}, {0, 2}, {2, 0}, {2, 2}}; int[] labels = {0, 1, 0, 1}; @@ -196,12 +187,7 @@ void tieBreakReturnsSmallerLabel() { @Test void tieBreakKeepsCurrentWinnerWhenLabelIsGreater() { - double[][] features = { - {0, 0}, - {0, 2}, - {2, 0}, - {2, 2} - }; + double[][] features = {{0, 0}, {0, 2}, {2, 0}, {2, 2}}; int[] labels = {1, 0, 1, 0}; @@ -214,10 +200,10 @@ void tieBreakKeepsCurrentWinnerWhenLabelIsGreater() { @Test void nullBatchSampleThrowsIllegalArgumentException() { double[][] features = { - {0, 0}, - {1, 1}, - {8, 8}, - {9, 9}, + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, }; int[] labels = {0, 0, 1, 1}; @@ -231,10 +217,10 @@ void nullBatchSampleThrowsIllegalArgumentException() { @Test void invalidBatchSampleThrowsIllegalArgumentException() { double[][] features = { - {0, 0}, - {1, 1}, - {8, 8}, - {9, 9}, + {0, 0}, + {1, 1}, + {8, 8}, + {9, 9}, }; int[] labels = {0, 0, 1, 1}; @@ -242,8 +228,6 @@ void invalidBatchSampleThrowsIllegalArgumentException() { KNearestNeighbors knn = new KNearestNeighbors(3); knn.fit(features, labels); - assertThrows(IllegalArgumentException.class, - () -> knn.predict(new double[][] {{1.5, 1.5}, {8.5, 8.5, 8.5}})); + assertThrows(IllegalArgumentException.class, () -> knn.predict(new double[][] {{1.5, 1.5}, {8.5, 8.5, 8.5}})); } - }