Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 82 additions & 15 deletions src/main/java/com/thealgorithms/maths/Means.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,118 @@
import org.apache.commons.collections4.IterableUtils;

/**
* https://en.wikipedia.org/wiki/Mean
* Utility class for computing various types of statistical means.
* <p>
* 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}.
* </p>
*
* <p>
* Supported means:
* <ul>
* <li><b>Arithmetic Mean</b>: The sum of all values divided by the count</li>
* <li><b>Geometric Mean</b>: The nth root of the product of n values</li>
* <li><b>Harmonic Mean</b>: The reciprocal of the arithmetic mean of
* reciprocals</li>
* </ul>
* </p>
*
* @see <a href="https://en.wikipedia.org/wiki/Mean">Mean (Wikipedia)</a>
* @author Punit Patel
*/
public final class Means {

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.
* <p>
* The arithmetic mean is calculated as: (x₁ + x₂ + ... + xₙ) / n
* </p>
* <p>
* Example: For numbers [2, 4, 6], the arithmetic mean is (2+4+6)/3 = 4.0
* </p>
*
* @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 <a href="https://en.wikipedia.org/wiki/Arithmetic_mean">Arithmetic
* Mean</a>
*/
public static Double arithmetic(final Iterable<Double> 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.
* <p>
* The geometric mean is calculated as: ⁿ√(x₁ × x₂ × ... × xₙ)
* </p>
* <p>
* Example: For numbers [2, 8], the geometric mean is √(2×8) = √16 = 4.0
* </p>
* <p>
* 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.
* </p>
*
* @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 <a href="https://en.wikipedia.org/wiki/Geometric_mean">Geometric
* Mean</a>
*/
public static Double geometric(final Iterable<Double> 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.
* <p>
* The harmonic mean is calculated as: n / (1/x₁ + 1/x₂ + ... + 1/xₙ)
* </p>
* <p>
* Example: For numbers [1, 2, 4], the harmonic mean is 3/(1/1 + 1/2 + 1/4) =
* 3/1.75 ≈ 1.714
* </p>
* <p>
* Note: This method will produce unexpected results if any input number is
* zero,
* as it involves computing reciprocals.
* </p>
*
* @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 <a href="https://en.wikipedia.org/wiki/Harmonic_mean">Harmonic Mean</a>
*/
public static Double harmonic(final Iterable<Double> 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<Double> numbers) {
if (!numbers.iterator().hasNext()) {
throw new IllegalArgumentException("Emtpy list given for Mean computation.");
throw new IllegalArgumentException("Empty list given for Mean computation.");
}
}
}
199 changes: 173 additions & 26 deletions src/test/java/com/thealgorithms/maths/MeansTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
* <p>
* This class provides comprehensive test coverage for all mean calculation
* methods,
* including edge cases, various collection types, and error conditions.
* </p>
*/
class MeansTest {

private static final double EPSILON = 1e-9;

// ========== Arithmetic Mean Tests ==========

@Test
void arithmeticMeanZeroNumbers() throws IllegalArgumentException {
void testArithmeticMeanThrowsExceptionForEmptyList() {
List<Double> 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<Double> numbers = Arrays.asList(2.5);
assertEquals(2.5, Means.arithmetic(numbers), EPSILON);
}

@Test
void testArithmeticMeanTwoNumbers() {
List<Double> numbers = Arrays.asList(2.0, 4.0);
assertEquals(3.0, Means.arithmetic(numbers), EPSILON);
}

@Test
void geometricMeanZeroNumbers() throws IllegalArgumentException {
void testArithmeticMeanMultipleNumbers() {
List<Double> numbers = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0);
assertEquals(3.0, Means.arithmetic(numbers), EPSILON);
}

@Test
void testArithmeticMeanWithTreeSet() {
Set<Double> 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<Double> 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<Double> numbers = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);
assertEquals(3.3, Means.arithmetic(numbers), EPSILON);
}

@Test
void testArithmeticMeanWithVector() {
Vector<Double> 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<Double> 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<Double> numbers = new LinkedHashSet<>(Arrays.asList(2.5));
assertEquals(2.5, Means.geometric(numbers), EPSILON);
}

@Test
void testGeometricMeanTwoNumbers() {
List<Double> numbers = Arrays.asList(2.0, 8.0);
assertEquals(4.0, Means.geometric(numbers), EPSILON);
}

@Test
void testGeometricMeanMultipleNumbers() {
LinkedList<Double> 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<Double> 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<Double> numbers = Arrays.asList(5.0, 5.0, 5.0, 5.0);
assertEquals(5.0, Means.geometric(numbers), EPSILON);
}

@Test
void testGeometricMeanWithLinkedHashSet() {
LinkedHashSet<Double> 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<Double> 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<Double> numbers = new LinkedHashSet<>(Arrays.asList(2.5));
assertEquals(2.5, Means.harmonic(numbers), EPSILON);
}

@Test
void arithmeticMeanSingleNumber() {
List<Double> numbers = Lists.newArrayList(2.5);
assertEquals(2.5, Means.arithmetic(numbers));
void testHarmonicMeanTwoNumbers() {
List<Double> 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<Double> numbers = Sets.newHashSet(Lists.newArrayList(2.5));
assertEquals(2.5, Means.geometric(numbers));
void testHarmonicMeanMultipleNumbers() {
Vector<Double> 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<Double> numbers = Sets.newLinkedHashSet(2.5);
assertEquals(2.5, Means.harmonic(numbers));
void testHarmonicMeanThreeNumbers() {
List<Double> 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<Double> numbers = Sets.newTreeSet(1d, 2.5, 83.3, 25.9999, 46.0001, 74.7, 74.5);
assertEquals(44, Means.arithmetic(numbers));
void testHarmonicMeanIdenticalNumbers() {
List<Double> numbers = Arrays.asList(6.0, 6.0, 6.0);
assertEquals(6.0, Means.harmonic(numbers), EPSILON);
}

@Test
void geometricMeanMultipleNumbers() {
LinkedList<Double> numbers = new LinkedList<>(Lists.newArrayList(1d, 2d, 3d, 4d, 5d, 6d, 1.25));
assertEquals(2.6426195539300585, Means.geometric(numbers));
void testHarmonicMeanWithLinkedList() {
LinkedList<Double> 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<Double> numbers = Arrays.asList(1e100, 2e100, 3e100);
assertEquals(2e100, Means.arithmetic(numbers), 1e90);
}

@Test
void harmonicMeanMultipleNumbers() {
Vector<Double> 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<Double> numbers = Arrays.asList(1e-100, 2e-100, 3e-100);
assertEquals(2e-100, Means.arithmetic(numbers), 1e-110);
}

@Test
void testGeometricMeanWithOnes() {
List<Double> numbers = Arrays.asList(1.0, 1.0, 1.0, 1.0);
assertEquals(1.0, Means.geometric(numbers), EPSILON);
}

@Test
void testAllMeansConsistencyForIdenticalValues() {
List<Double> 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<Double> 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");
}
}