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:
+ *
+ * - Arithmetic Mean: The sum of all values divided by the count
+ * - Geometric Mean: The nth root of the product of n values
+ * - Harmonic Mean: The reciprocal of the arithmetic mean of
+ * reciprocals
+ *
+ *
+ *
+ * @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");
}
}