diff --git a/src/main/java/com/thealgorithms/randomized/ReservoirSampling.java b/src/main/java/com/thealgorithms/randomized/ReservoirSampling.java new file mode 100644 index 000000000000..05e70f635055 --- /dev/null +++ b/src/main/java/com/thealgorithms/randomized/ReservoirSampling.java @@ -0,0 +1,55 @@ +package com.thealgorithms.randomized; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * Reservoir Sampling Algorithm + * + * Use Case: + * - Efficient for selecting k random items from a stream of unknown size + * - Used in streaming systems, big data, and memory-limited environments + * + * Time Complexity: O(n) + * Space Complexity: O(k) + * + * @author Michael Alexander Montoya (@cureprotocols) + * @see Reservoir Sampling - Wikipedia + */ +public final class ReservoirSampling { + + // Prevent instantiation of utility class + private ReservoirSampling() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Selects k random elements from a stream using reservoir sampling. + * + * @param stream The input stream as an array of integers. + * @param sampleSize The number of elements to sample. + * @return A list containing k randomly selected elements. + */ + public static List sample(int[] stream, int sampleSize) { + if (sampleSize > stream.length) { + throw new IllegalArgumentException("Sample size cannot exceed stream size."); + } + + List reservoir = new ArrayList<>(sampleSize); + Random rand = new Random(); + + for (int i = 0; i < stream.length; i++) { + if (i < sampleSize) { + reservoir.add(stream[i]); + } else { + int j = rand.nextInt(i + 1); + if (j < sampleSize) { + reservoir.set(j, stream[i]); + } + } + } + + return reservoir; + } +} diff --git a/src/test/java/com/thealgorithms/randomized/ReservoirSamplingTest.java b/src/test/java/com/thealgorithms/randomized/ReservoirSamplingTest.java new file mode 100644 index 000000000000..0c6061fcde2a --- /dev/null +++ b/src/test/java/com/thealgorithms/randomized/ReservoirSamplingTest.java @@ -0,0 +1,45 @@ +package com.thealgorithms.randomized; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class ReservoirSamplingTest { + + @Test + public void testSampleSizeEqualsStreamLength() { + int[] stream = {1, 2, 3, 4, 5}; + int sampleSize = 5; + + List result = ReservoirSampling.sample(stream, sampleSize); + + assertEquals(sampleSize, result.size()); + assertTrue(Arrays.stream(stream).allMatch(result::contains)); + } + + @Test + public void testSampleSizeLessThanStreamLength() { + int[] stream = {10, 20, 30, 40, 50, 60}; + int sampleSize = 3; + + List result = ReservoirSampling.sample(stream, sampleSize); + + assertEquals(sampleSize, result.size()); + for (int value : result) { + assertTrue(Arrays.stream(stream).anyMatch(x -> x == value)); + } + } + + @Test + public void testSampleSizeGreaterThanStreamLengthThrowsException() { + int[] stream = {1, 2, 3}; + + Exception exception = assertThrows(IllegalArgumentException.class, () -> ReservoirSampling.sample(stream, 5)); + + assertEquals("Sample size cannot exceed stream size.", exception.getMessage()); + } +}