diff --git a/src/main/java/com/thealgorithms/maths/Means.java b/src/main/java/com/thealgorithms/maths/Means.java index dccc820b172e..5445a3caebc7 100644 --- a/src/main/java/com/thealgorithms/maths/Means.java +++ b/src/main/java/com/thealgorithms/maths/Means.java @@ -4,9 +4,27 @@ import org.apache.commons.collections4.IterableUtils; /** - * https://en.wikipedia.org/wiki/Mean + * Utility class for computing various types of statistical means. *

- * by: Punit Patel + * This class provides static methods to calculate different types of means + * (averages) + * from a collection of numbers. All methods accept any {@link Iterable} + * collection of + * {@link Double} values and return the computed mean as a {@link Double}. + *

+ * + *

+ * Supported means: + *

+ *

+ * + * @see Mean (Wikipedia) + * @author Punit Patel */ public final class Means { @@ -14,41 +32,90 @@ private Means() { } /** - * @brief computes the [Arithmetic Mean](https://en.wikipedia.org/wiki/Arithmetic_mean) of the input - * @param numbers the input numbers - * @throws IllegalArgumentException empty input + * Computes the arithmetic mean (average) of the given numbers. + *

+ * The arithmetic mean is calculated as: (x₁ + x₂ + ... + xₙ) / n + *

+ *

+ * Example: For numbers [2, 4, 6], the arithmetic mean is (2+4+6)/3 = 4.0 + *

+ * + * @param numbers the input numbers (must not be empty) * @return the arithmetic mean of the input numbers + * @throws IllegalArgumentException if the input is empty + * @see Arithmetic + * Mean */ public static Double arithmetic(final Iterable numbers) { checkIfNotEmpty(numbers); - return StreamSupport.stream(numbers.spliterator(), false).reduce((x, y) -> x + y).get() / IterableUtils.size(numbers); + double sum = StreamSupport.stream(numbers.spliterator(), false).reduce(0d, (x, y) -> x + y); + int size = IterableUtils.size(numbers); + return sum / size; } /** - * @brief computes the [Geometric Mean](https://en.wikipedia.org/wiki/Geometric_mean) of the input - * @param numbers the input numbers - * @throws IllegalArgumentException empty input + * Computes the geometric mean of the given numbers. + *

+ * The geometric mean is calculated as: ⁿ√(x₁ × x₂ × ... × xₙ) + *

+ *

+ * Example: For numbers [2, 8], the geometric mean is √(2×8) = √16 = 4.0 + *

+ *

+ * Note: This method may produce unexpected results for negative numbers, + * as it computes the real-valued nth root which may not exist for negative + * products. + *

+ * + * @param numbers the input numbers (must not be empty) * @return the geometric mean of the input numbers + * @throws IllegalArgumentException if the input is empty + * @see Geometric + * Mean */ public static Double geometric(final Iterable numbers) { checkIfNotEmpty(numbers); - return Math.pow(StreamSupport.stream(numbers.spliterator(), false).reduce((x, y) -> x * y).get(), 1d / IterableUtils.size(numbers)); + double product = StreamSupport.stream(numbers.spliterator(), false).reduce(1d, (x, y) -> x * y); + int size = IterableUtils.size(numbers); + return Math.pow(product, 1.0 / size); } /** - * @brief computes the [Harmonic Mean](https://en.wikipedia.org/wiki/Harmonic_mean) of the input - * @param numbers the input numbers - * @throws IllegalArgumentException empty input + * Computes the harmonic mean of the given numbers. + *

+ * The harmonic mean is calculated as: n / (1/x₁ + 1/x₂ + ... + 1/xₙ) + *

+ *

+ * Example: For numbers [1, 2, 4], the harmonic mean is 3/(1/1 + 1/2 + 1/4) = + * 3/1.75 ≈ 1.714 + *

+ *

+ * Note: This method will produce unexpected results if any input number is + * zero, + * as it involves computing reciprocals. + *

+ * + * @param numbers the input numbers (must not be empty) * @return the harmonic mean of the input numbers + * @throws IllegalArgumentException if the input is empty + * @see Harmonic Mean */ public static Double harmonic(final Iterable numbers) { checkIfNotEmpty(numbers); - return IterableUtils.size(numbers) / StreamSupport.stream(numbers.spliterator(), false).reduce(0d, (x, y) -> x + 1d / y); + double sumOfReciprocals = StreamSupport.stream(numbers.spliterator(), false).reduce(0d, (x, y) -> x + 1d / y); + int size = IterableUtils.size(numbers); + return size / sumOfReciprocals; } + /** + * Validates that the input iterable is not empty. + * + * @param numbers the input numbers to validate + * @throws IllegalArgumentException if the input is empty + */ private static void checkIfNotEmpty(final Iterable numbers) { if (!numbers.iterator().hasNext()) { - throw new IllegalArgumentException("Emtpy list given for Mean computation."); + throw new IllegalArgumentException("Empty list given for Mean computation."); } } } diff --git a/src/test/java/com/thealgorithms/maths/MeansTest.java b/src/test/java/com/thealgorithms/maths/MeansTest.java index 4b3a5df44b34..deee0a931910 100644 --- a/src/test/java/com/thealgorithms/maths/MeansTest.java +++ b/src/test/java/com/thealgorithms/maths/MeansTest.java @@ -2,70 +2,217 @@ 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.ArrayList; +import java.util.Arrays; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; +import java.util.TreeSet; import java.util.Vector; -import org.assertj.core.util.Lists; -import org.assertj.core.util.Sets; import org.junit.jupiter.api.Test; +/** + * Test class for {@link Means}. + *

+ * This class provides comprehensive test coverage for all mean calculation + * methods, + * including edge cases, various collection types, and error conditions. + *

+ */ class MeansTest { + private static final double EPSILON = 1e-9; + + // ========== Arithmetic Mean Tests ========== + @Test - void arithmeticMeanZeroNumbers() throws IllegalArgumentException { + void testArithmeticMeanThrowsExceptionForEmptyList() { List numbers = new ArrayList<>(); - assertThrows(IllegalArgumentException.class, () -> Means.arithmetic(numbers)); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> Means.arithmetic(numbers)); + assertTrue(exception.getMessage().contains("Empty list")); + } + + @Test + void testArithmeticMeanSingleNumber() { + List numbers = Arrays.asList(2.5); + assertEquals(2.5, Means.arithmetic(numbers), EPSILON); + } + + @Test + void testArithmeticMeanTwoNumbers() { + List numbers = Arrays.asList(2.0, 4.0); + assertEquals(3.0, Means.arithmetic(numbers), EPSILON); } @Test - void geometricMeanZeroNumbers() throws IllegalArgumentException { + void testArithmeticMeanMultipleNumbers() { + List numbers = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0); + assertEquals(3.0, Means.arithmetic(numbers), EPSILON); + } + + @Test + void testArithmeticMeanWithTreeSet() { + Set numbers = new TreeSet<>(Arrays.asList(1.0, 2.5, 83.3, 25.9999, 46.0001, 74.7, 74.5)); + assertEquals(44.0, Means.arithmetic(numbers), EPSILON); + } + + @Test + void testArithmeticMeanWithNegativeNumbers() { + List numbers = Arrays.asList(-5.0, -3.0, -1.0, 1.0, 3.0, 5.0); + assertEquals(0.0, Means.arithmetic(numbers), EPSILON); + } + + @Test + void testArithmeticMeanWithDecimalNumbers() { + List numbers = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5); + assertEquals(3.3, Means.arithmetic(numbers), EPSILON); + } + + @Test + void testArithmeticMeanWithVector() { + Vector numbers = new Vector<>(Arrays.asList(10.0, 20.0, 30.0)); + assertEquals(20.0, Means.arithmetic(numbers), EPSILON); + } + + // ========== Geometric Mean Tests ========== + + @Test + void testGeometricMeanThrowsExceptionForEmptyList() { List numbers = new ArrayList<>(); - assertThrows(IllegalArgumentException.class, () -> Means.geometric(numbers)); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> Means.geometric(numbers)); + assertTrue(exception.getMessage().contains("Empty list")); } @Test - void harmonicMeanZeroNumbers() throws IllegalArgumentException { + void testGeometricMeanSingleNumber() { + Set numbers = new LinkedHashSet<>(Arrays.asList(2.5)); + assertEquals(2.5, Means.geometric(numbers), EPSILON); + } + + @Test + void testGeometricMeanTwoNumbers() { + List numbers = Arrays.asList(2.0, 8.0); + assertEquals(4.0, Means.geometric(numbers), EPSILON); + } + + @Test + void testGeometricMeanMultipleNumbers() { + LinkedList numbers = new LinkedList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 1.25)); + assertEquals(2.6426195539300585, Means.geometric(numbers), EPSILON); + } + + @Test + void testGeometricMeanPerfectSquares() { + List numbers = Arrays.asList(1.0, 4.0, 9.0, 16.0); + double expected = Math.pow(1.0 * 4.0 * 9.0 * 16.0, 1.0 / 4.0); + assertEquals(expected, Means.geometric(numbers), EPSILON); + } + + @Test + void testGeometricMeanIdenticalNumbers() { + List numbers = Arrays.asList(5.0, 5.0, 5.0, 5.0); + assertEquals(5.0, Means.geometric(numbers), EPSILON); + } + + @Test + void testGeometricMeanWithLinkedHashSet() { + LinkedHashSet numbers = new LinkedHashSet<>(Arrays.asList(2.0, 4.0, 8.0)); + double expected = Math.pow(2.0 * 4.0 * 8.0, 1.0 / 3.0); + assertEquals(expected, Means.geometric(numbers), EPSILON); + } + + // ========== Harmonic Mean Tests ========== + + @Test + void testHarmonicMeanThrowsExceptionForEmptyList() { List numbers = new ArrayList<>(); - assertThrows(IllegalArgumentException.class, () -> Means.harmonic(numbers)); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> Means.harmonic(numbers)); + assertTrue(exception.getMessage().contains("Empty list")); + } + + @Test + void testHarmonicMeanSingleNumber() { + LinkedHashSet numbers = new LinkedHashSet<>(Arrays.asList(2.5)); + assertEquals(2.5, Means.harmonic(numbers), EPSILON); } @Test - void arithmeticMeanSingleNumber() { - List numbers = Lists.newArrayList(2.5); - assertEquals(2.5, Means.arithmetic(numbers)); + void testHarmonicMeanTwoNumbers() { + List numbers = Arrays.asList(2.0, 4.0); + double expected = 2.0 / (1.0 / 2.0 + 1.0 / 4.0); + assertEquals(expected, Means.harmonic(numbers), EPSILON); } @Test - void geometricMeanSingleNumber() { - Set numbers = Sets.newHashSet(Lists.newArrayList(2.5)); - assertEquals(2.5, Means.geometric(numbers)); + void testHarmonicMeanMultipleNumbers() { + Vector numbers = new Vector<>(Arrays.asList(1.0, 2.5, 83.3, 25.9999, 46.0001, 74.7, 74.5)); + assertEquals(4.6697322801074135, Means.harmonic(numbers), EPSILON); } @Test - void harmonicMeanSingleNumber() { - LinkedHashSet numbers = Sets.newLinkedHashSet(2.5); - assertEquals(2.5, Means.harmonic(numbers)); + void testHarmonicMeanThreeNumbers() { + List numbers = Arrays.asList(1.0, 2.0, 4.0); + double expected = 3.0 / (1.0 / 1.0 + 1.0 / 2.0 + 1.0 / 4.0); + assertEquals(expected, Means.harmonic(numbers), EPSILON); } @Test - void arithmeticMeanMultipleNumbers() { - Set numbers = Sets.newTreeSet(1d, 2.5, 83.3, 25.9999, 46.0001, 74.7, 74.5); - assertEquals(44, Means.arithmetic(numbers)); + void testHarmonicMeanIdenticalNumbers() { + List numbers = Arrays.asList(6.0, 6.0, 6.0); + assertEquals(6.0, Means.harmonic(numbers), EPSILON); } @Test - void geometricMeanMultipleNumbers() { - LinkedList numbers = new LinkedList<>(Lists.newArrayList(1d, 2d, 3d, 4d, 5d, 6d, 1.25)); - assertEquals(2.6426195539300585, Means.geometric(numbers)); + void testHarmonicMeanWithLinkedList() { + LinkedList numbers = new LinkedList<>(Arrays.asList(3.0, 6.0, 9.0)); + double expected = 3.0 / (1.0 / 3.0 + 1.0 / 6.0 + 1.0 / 9.0); + assertEquals(expected, Means.harmonic(numbers), EPSILON); + } + + // ========== Additional Edge Case Tests ========== + + @Test + void testArithmeticMeanWithVeryLargeNumbers() { + List numbers = Arrays.asList(1e100, 2e100, 3e100); + assertEquals(2e100, Means.arithmetic(numbers), 1e90); } @Test - void harmonicMeanMultipleNumbers() { - Vector numbers = new Vector<>(Lists.newArrayList(1d, 2.5, 83.3, 25.9999, 46.0001, 74.7, 74.5)); - assertEquals(4.6697322801074135, Means.harmonic(numbers)); + void testArithmeticMeanWithVerySmallNumbers() { + List numbers = Arrays.asList(1e-100, 2e-100, 3e-100); + assertEquals(2e-100, Means.arithmetic(numbers), 1e-110); + } + + @Test + void testGeometricMeanWithOnes() { + List numbers = Arrays.asList(1.0, 1.0, 1.0, 1.0); + assertEquals(1.0, Means.geometric(numbers), EPSILON); + } + + @Test + void testAllMeansConsistencyForIdenticalValues() { + List numbers = Arrays.asList(7.5, 7.5, 7.5, 7.5); + double arithmetic = Means.arithmetic(numbers); + double geometric = Means.geometric(numbers); + double harmonic = Means.harmonic(numbers); + + assertEquals(7.5, arithmetic, EPSILON); + assertEquals(7.5, geometric, EPSILON); + assertEquals(7.5, harmonic, EPSILON); + } + + @Test + void testMeansRelationship() { + // For positive numbers, harmonic mean ≤ geometric mean ≤ arithmetic mean + List numbers = Arrays.asList(2.0, 4.0, 8.0); + double arithmetic = Means.arithmetic(numbers); + double geometric = Means.geometric(numbers); + double harmonic = Means.harmonic(numbers); + + assertTrue(harmonic <= geometric, "Harmonic mean should be ≤ geometric mean"); + assertTrue(geometric <= arithmetic, "Geometric mean should be ≤ arithmetic mean"); } }