You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ah...@apache.org on 2024/01/01 10:06:21 UTC
(commons-statistics) 02/02: STATISTICS-81: Add aggregate statistics for int and long
This is an automated email from the ASF dual-hosted git repository.
aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-statistics.git
commit 3afb93d2b17182c5d4cf07839c6fe2e39eb3da54
Author: Alex Herbert <ah...@apache.org>
AuthorDate: Sun Dec 31 23:16:31 2023 +0000
STATISTICS-81: Add aggregate statistics for int and long
---
.../descriptive/BigIntegerStatisticResult.java | 0
.../statistics/descriptive/DoubleStatistics.java | 129 +-----
.../statistics/descriptive/IntStatisticResult.java | 0
.../{DoubleStatistics.java => IntStatistics.java} | 448 ++++++++++-----------
.../commons/statistics/descriptive/IntSum.java | 11 +
.../statistics/descriptive/IntSumOfSquares.java | 11 +
.../descriptive/LongStatisticResult.java | 0
.../{DoubleStatistics.java => LongStatistics.java} | 426 +++++++++-----------
.../commons/statistics/descriptive/LongSum.java | 11 +
.../statistics/descriptive/LongSumOfSquares.java | 11 +
.../commons/statistics/descriptive/Statistics.java | 218 ++++++++++
.../descriptive/DoubleStatisticsTest.java | 17 +-
...eStatisticsTest.java => IntStatisticsTest.java} | 394 ++++++++++--------
...StatisticsTest.java => LongStatisticsTest.java} | 398 ++++++++++--------
.../statistics/descriptive/StatisticsTest.java | 116 ++++++
.../commons/statistics/descriptive/TestHelper.java | 80 +++-
src/conf/checkstyle/checkstyle-suppressions.xml | 4 +-
src/conf/pmd/pmd-ruleset.xml | 3 +-
18 files changed, 1332 insertions(+), 945 deletions(-)
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/BigIntegerStatisticResult.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/BigIntegerStatisticResult.java
similarity index 100%
rename from commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/BigIntegerStatisticResult.java
rename to commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/BigIntegerStatisticResult.java
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java
index d77bc64..3bdd073 100644
--- a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java
@@ -34,25 +34,10 @@ import java.util.function.Function;
* @since 1.1
*/
public final class DoubleStatistics implements DoubleConsumer {
- /** A no-operation double consumer. This is exposed for testing. */
- static final DoubleConsumer NOOP = new DoubleConsumer() {
- @Override
- public void accept(double value) {
- // Do nothing
- }
-
- @Override
- public DoubleConsumer andThen(DoubleConsumer after) {
- // Delegate to the after consumer
- return after;
- }
- };
/** Error message for non configured statistics. */
private static final String NO_CONFIGURED_STATISTICS = "No configured statistics";
/** Error message for an unsupported statistic. */
private static final String UNSUPPORTED_STATISTIC = "Unsupported statistic: ";
- /** Error message for an incompatible statistics. */
- private static final String INCOMPATIBLE_STATISTICS = "Incompatible statistics";
/** Count of values recorded. */
private long count;
@@ -264,28 +249,7 @@ public final class DoubleStatistics implements DoubleConsumer {
this.sumOfSquares = sumOfSquares;
this.sumOfLogs = sumOfLogs;
this.config = config;
- consumer = compose(min, max, moment, sum, product, sumOfSquares, sumOfLogs);
- }
-
- /**
- * Chain the {@code consumers} into a single composite consumer. Ignore any {@code null}
- * consumer.
- *
- * @param consumers Consumers.
- * @return a composed consumer
- */
- private static DoubleConsumer compose(DoubleConsumer... consumers) {
- DoubleConsumer action = NOOP;
- for (final DoubleConsumer consumer : consumers) {
- if (consumer != null) {
- action = action.andThen(consumer);
- }
- }
- if (action == NOOP) {
- // This should not be possible
- throw new IllegalStateException(NO_CONFIGURED_STATISTICS + ": Please file a bug report");
- }
- return action;
+ consumer = Statistics.compose(min, max, moment, sum, product, sumOfSquares, sumOfLogs);
}
/**
@@ -453,7 +417,7 @@ public final class DoubleStatistics implements DoubleConsumer {
* Gets the value of the specified {@code statistic}.
*
* @param statistic Statistic.
- * @return the double
+ * @return the value
* @throws IllegalArgumentException if the {@code statistic} is not supported
* @see #isSupported(Statistic)
* @see #getSupplier(Statistic)
@@ -631,86 +595,25 @@ public final class DoubleStatistics implements DoubleConsumer {
*/
public DoubleStatistics combine(DoubleStatistics other) {
// Check compatibility
- checkNullOrElseOtherNonNull(min, other.min);
- checkNullOrElseOtherNonNull(max, other.max);
- checkNullOrElseOtherNonNull(sum, other.sum);
- checkNullOrElseOtherNonNull(product, other.product);
- checkNullOrElseOtherNonNull(sumOfSquares, other.sumOfSquares);
- checkNullOrElseOtherNonNull(sumOfLogs, other.sumOfLogs);
- checkNullOrElseOtherIsAssignable(moment, other.moment);
+ Statistics.checkCombineCompatible(min, other.min);
+ Statistics.checkCombineCompatible(max, other.max);
+ Statistics.checkCombineCompatible(sum, other.sum);
+ Statistics.checkCombineCompatible(product, other.product);
+ Statistics.checkCombineCompatible(sumOfSquares, other.sumOfSquares);
+ Statistics.checkCombineCompatible(sumOfLogs, other.sumOfLogs);
+ Statistics.checkCombineAssignable(moment, other.moment);
// Combine
count += other.count;
- combine(min, other.min);
- combine(max, other.max);
- combine(sum, other.sum);
- combine(product, other.product);
- combine(sumOfSquares, other.sumOfSquares);
- combine(sumOfLogs, other.sumOfLogs);
- combineMoment(moment, other.moment);
+ Statistics.combine(min, other.min);
+ Statistics.combine(max, other.max);
+ Statistics.combine(sum, other.sum);
+ Statistics.combine(product, other.product);
+ Statistics.combine(sumOfSquares, other.sumOfSquares);
+ Statistics.combine(sumOfLogs, other.sumOfLogs);
+ Statistics.combineMoment(moment, other.moment);
return this;
}
- /**
- * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
- * argument {@code b} must also be non-{@code null}.
- *
- * @param a LHS.
- * @param b RHS.
- */
- private static void checkNullOrElseOtherNonNull(Object a, Object b) {
- if (a != null && b == null) {
- throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
- }
- }
-
- /**
- * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
- * argument {@code b} must be run-time assignable to the same class as {@code a}.
- *
- * @param a LHS.
- * @param b RHS.
- */
- private static void checkNullOrElseOtherIsAssignable(Object a, Object b) {
- if (a != null && (b == null || !a.getClass().isAssignableFrom(b.getClass()))) {
- throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
- }
- }
-
- /**
- * If the left-hand side argument {@code a} is non-{@code null}, combined it with the
- * right-hand side argument {@code b}.
- *
- * @param <T> {@link DoubleStatistic} being accumulated.
- * @param a LHS.
- * @param b RHS.
- */
- private static <T extends DoubleStatistic & StatisticAccumulator<T>> void combine(T a, T b) {
- if (a != null) {
- a.combine(b);
- }
- }
-
- /**
- * If the left-hand side argument {@code a} is non-{@code null}, combined it with the
- * right-hand side argument {@code b}. Assumes that the RHS is run-time assignable
- * to the same class as LHS.
- *
- * @param a LHS.
- * @param b RHS.
- */
- private static void combineMoment(FirstMoment a, FirstMoment b) {
- // Avoid reflection and use the simpler instanceof
- if (a instanceof SumOfFourthDeviations) {
- ((SumOfFourthDeviations) a).combine((SumOfFourthDeviations) b);
- } else if (a instanceof SumOfCubedDeviations) {
- ((SumOfCubedDeviations) a).combine((SumOfCubedDeviations) b);
- } else if (a instanceof SumOfSquaredDeviations) {
- ((SumOfSquaredDeviations) a).combine((SumOfSquaredDeviations) b);
- } else if (a != null) {
- a.combine(b);
- }
- }
-
/**
* Sets the statistics configuration.
*
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntStatisticResult.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntStatisticResult.java
similarity index 100%
rename from commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntStatisticResult.java
rename to commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntStatisticResult.java
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntStatistics.java
similarity index 61%
copy from commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java
copy to commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntStatistics.java
index d77bc64..35b9248 100644
--- a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntStatistics.java
@@ -16,14 +16,15 @@
*/
package org.apache.commons.statistics.descriptive;
+import java.math.BigInteger;
import java.util.Objects;
import java.util.Set;
import java.util.function.DoubleConsumer;
-import java.util.function.DoubleSupplier;
import java.util.function.Function;
+import java.util.function.IntConsumer;
/**
- * Statistics for {@code double} values.
+ * Statistics for {@code int} values.
*
* <p>This class provides combinations of individual statistic implementations in the
* {@code org.apache.commons.statistics.descriptive} package.
@@ -33,69 +34,55 @@ import java.util.function.Function;
*
* @since 1.1
*/
-public final class DoubleStatistics implements DoubleConsumer {
- /** A no-operation double consumer. This is exposed for testing. */
- static final DoubleConsumer NOOP = new DoubleConsumer() {
- @Override
- public void accept(double value) {
- // Do nothing
- }
-
- @Override
- public DoubleConsumer andThen(DoubleConsumer after) {
- // Delegate to the after consumer
- return after;
- }
- };
+public final class IntStatistics implements IntConsumer {
/** Error message for non configured statistics. */
private static final String NO_CONFIGURED_STATISTICS = "No configured statistics";
/** Error message for an unsupported statistic. */
private static final String UNSUPPORTED_STATISTIC = "Unsupported statistic: ";
- /** Error message for an incompatible statistics. */
- private static final String INCOMPATIBLE_STATISTICS = "Incompatible statistics";
/** Count of values recorded. */
private long count;
/** The consumer of values. */
- private final DoubleConsumer consumer;
- /** The {@link Min} implementation. */
- private final Min min;
- /** The {@link Max} implementation. */
- private final Max max;
- /** The moment implementation. May be any instance of {@link FirstMoment}. */
+ private final IntConsumer consumer;
+ /** The {@link IntMin} implementation. */
+ private final IntMin min;
+ /** The {@link IntMax} implementation. */
+ private final IntMax max;
+ /** The moment implementation. May be any instance of {@link FirstMoment}.
+ * This implementation uses only the third and fourth moments. */
private final FirstMoment moment;
- /** The {@link Sum} implementation. */
- private final Sum sum;
+ /** The {@link IntSum} implementation. */
+ private final IntSum sum;
/** The {@link Product} implementation. */
private final Product product;
- /** The {@link SumOfSquares} implementation. */
- private final SumOfSquares sumOfSquares;
+ /** The {@link IntSumOfSquares} implementation. */
+ private final IntSumOfSquares sumOfSquares;
/** The {@link SumOfLogs} implementation. */
private final SumOfLogs sumOfLogs;
/** Configuration options for computation of statistics. */
private StatisticsConfiguration config;
/**
- * A builder for {@link DoubleStatistics}.
+ * A builder for {@link IntStatistics}.
*/
public static final class Builder {
/** An empty double array. */
- private static final double[] NO_VALUES = {};
+ private static final int[] NO_VALUES = {};
- /** The {@link Min} constructor. */
- private Function<double[], Min> min;
- /** The {@link Max} constructor. */
- private Function<double[], Max> max;
+ /** The {@link IntMin} constructor. */
+ private Function<int[], IntMin> min;
+ /** The {@link IntMax} constructor. */
+ private Function<int[], IntMax> max;
/** The moment constructor. May return any instance of {@link FirstMoment}. */
- private Function<double[], FirstMoment> moment;
- /** The {@link Sum} constructor. */
- private Function<double[], Sum> sum;
+ private Function<int[], FirstMoment> moment;
+ /** The {@link IntSum} constructor. */
+ private Function<int[], IntSum> sum;
/** The {@link Product} constructor. */
- private Function<double[], Product> product;
- /** The {@link SumOfSquares} constructor. */
- private Function<double[], SumOfSquares> sumOfSquares;
+ private Function<int[], Product> product;
+ /** The {@link IntSumOfSquares} constructor. */
+ private Function<int[], IntSumOfSquares> sumOfSquares;
/** The {@link SumOfLogs} constructor. */
- private Function<double[], SumOfLogs> sumOfLogs;
+ private Function<int[], SumOfLogs> sumOfLogs;
/** The order of the moment. It corresponds to the power computed by the {@link FirstMoment}
* instance constructed by {@link #moment}. This should only be increased from the default
* of zero (corresponding to no moment computation). */
@@ -126,13 +113,10 @@ public final class DoubleStatistics implements DoubleConsumer {
createMoment(4);
break;
case MAX:
- max = Max::of;
- break;
- case MEAN:
- createMoment(1);
+ max = IntMax::of;
break;
case MIN:
- min = Min::of;
+ min = IntMin::of;
break;
case PRODUCT:
product = Product::of;
@@ -142,13 +126,15 @@ public final class DoubleStatistics implements DoubleConsumer {
break;
case STANDARD_DEVIATION:
case VARIANCE:
- createMoment(2);
+ sum = IntSum::of;
+ sumOfSquares = IntSumOfSquares::of;
break;
+ case MEAN:
case SUM:
- sum = Sum::of;
+ sum = IntSum::of;
break;
case SUM_OF_SQUARES:
- sumOfSquares = SumOfSquares::of;
+ sumOfSquares = IntSumOfSquares::of;
break;
default:
throw new IllegalArgumentException(UNSUPPORTED_STATISTIC + statistic);
@@ -158,7 +144,7 @@ public final class DoubleStatistics implements DoubleConsumer {
/**
* Creates the moment constructor for the specified {@code order},
- * e.g. order=2 is sum of squared deviations.
+ * e.g. order=3 is sum of cubed deviations.
*
* @param order Order.
*/
@@ -167,13 +153,9 @@ public final class DoubleStatistics implements DoubleConsumer {
momentOrder = order;
if (order == 4) {
moment = SumOfFourthDeviations::of;
- } else if (order == 3) {
- moment = SumOfCubedDeviations::of;
- } else if (order == 2) {
- moment = SumOfSquaredDeviations::of;
} else {
- // Assume order == 1
- moment = FirstMoment::of;
+ // Assume order == 3
+ moment = SumOfCubedDeviations::of;
}
}
}
@@ -191,27 +173,27 @@ public final class DoubleStatistics implements DoubleConsumer {
}
/**
- * Builds a {@code DoubleStatistics} instance.
+ * Builds a {@code IntStatistics} instance.
*
- * @return {@code DoubleStatistics} instance.
+ * @return {@code IntStatistics} instance.
*/
- public DoubleStatistics build() {
+ public IntStatistics build() {
return build(NO_VALUES);
}
/**
- * Builds a {@code DoubleStatistics} instance using the input {@code values}.
+ * Builds a {@code IntStatistics} instance using the input {@code values}.
*
- * <p>Note: {@code DoubleStatistics} computed using
- * {@link DoubleStatistics#accept(double) accept} may be
+ * <p>Note: {@code IntStatistics} computed using
+ * {@link IntStatistics#accept(int) accept} may be
* different from this instance.
*
* @param values Values.
- * @return {@code DoubleStatistics} instance.
+ * @return {@code IntStatistics} instance.
*/
- public DoubleStatistics build(double... values) {
+ public IntStatistics build(int... values) {
Objects.requireNonNull(values, "values");
- return new DoubleStatistics(
+ return new IntStatistics(
values.length,
create(min, values),
create(max, values),
@@ -231,7 +213,7 @@ public final class DoubleStatistics implements DoubleConsumer {
* @param values Values
* @return the instance
*/
- private static <T> T create(Function<double[], T> constructor, double[] values) {
+ private static <T> T create(Function<int[], T> constructor, int[] values) {
if (constructor != null) {
return constructor.apply(values);
}
@@ -243,18 +225,18 @@ public final class DoubleStatistics implements DoubleConsumer {
* Create an instance.
*
* @param count Count of values.
- * @param min Min implementation.
- * @param max Max implementation.
+ * @param min IntMin implementation.
+ * @param max IntMax implementation.
* @param moment Moment implementation.
- * @param sum Sum implementation.
+ * @param sum IntSum implementation.
* @param product Product implementation.
* @param sumOfSquares Sum of squares implementation.
* @param sumOfLogs Sum of logs implementation.
* @param config Statistics configuration.
*/
- DoubleStatistics(long count, Min min, Max max, FirstMoment moment, Sum sum,
- Product product, SumOfSquares sumOfSquares, SumOfLogs sumOfLogs,
- StatisticsConfiguration config) {
+ IntStatistics(long count, IntMin min, IntMax max, FirstMoment moment, IntSum sum,
+ Product product, IntSumOfSquares sumOfSquares, SumOfLogs sumOfLogs,
+ StatisticsConfiguration config) {
this.count = count;
this.min = min;
this.max = max;
@@ -264,28 +246,25 @@ public final class DoubleStatistics implements DoubleConsumer {
this.sumOfSquares = sumOfSquares;
this.sumOfLogs = sumOfLogs;
this.config = config;
- consumer = compose(min, max, moment, sum, product, sumOfSquares, sumOfLogs);
+ // The final consumer should never be null as the builder is created
+ // with at least one statistic.
+ consumer = Statistics.compose(min, max, sum, sumOfSquares,
+ composeAsInt(moment, product, sumOfLogs));
}
/**
- * Chain the {@code consumers} into a single composite consumer. Ignore any {@code null}
- * consumer.
+ * Chain the {@code consumers} into a single composite {@code IntConsumer}.
+ * Ignore any {@code null} consumer.
*
* @param consumers Consumers.
- * @return a composed consumer
+ * @return a composed consumer (or null)
*/
- private static DoubleConsumer compose(DoubleConsumer... consumers) {
- DoubleConsumer action = NOOP;
- for (final DoubleConsumer consumer : consumers) {
- if (consumer != null) {
- action = action.andThen(consumer);
- }
+ private static IntConsumer composeAsInt(DoubleConsumer... consumers) {
+ final DoubleConsumer c = Statistics.compose(consumers);
+ if (c != null) {
+ return c::accept;
}
- if (action == NOOP) {
- // This should not be possible
- throw new IllegalStateException(NO_CONFIGURED_STATISTICS + ": Please file a bug report");
- }
- return action;
+ return null;
}
/**
@@ -298,7 +277,7 @@ public final class DoubleStatistics implements DoubleConsumer {
* @return the instance
* @throws IllegalArgumentException if there are no {@code statistics} to compute.
*/
- public static DoubleStatistics of(Statistic... statistics) {
+ public static IntStatistics of(Statistic... statistics) {
return builder(statistics).build();
}
@@ -307,10 +286,10 @@ public final class DoubleStatistics implements DoubleConsumer {
* populated using the input {@code values}.
*
* <p>Use this method to create an instance populated with a (variable) array of
- * {@code double[]} data:
+ * {@code int[]} data:
*
* <pre>
- * DoubleStatistics stats = DoubleStatistics.of(
+ * IntStatistics stats = IntStatistics.of(
* EnumSet.of(Statistic.MIN, Statistic.MAX),
* 1, 1, 2, 3, 5, 8, 13);
* </pre>
@@ -320,7 +299,7 @@ public final class DoubleStatistics implements DoubleConsumer {
* @return the instance
* @throws IllegalArgumentException if there are no {@code statistics} to compute.
*/
- public static DoubleStatistics of(Set<Statistic> statistics, double... values) {
+ public static IntStatistics of(Set<Statistic> statistics, int... values) {
if (statistics.isEmpty()) {
throw new IllegalArgumentException(NO_CONFIGURED_STATISTICS);
}
@@ -333,28 +312,28 @@ public final class DoubleStatistics implements DoubleConsumer {
* Returns a new builder configured to create instances to compute the specified
* {@code statistics}.
*
- * <p>Use this method to create an instance populated with an array of {@code double[]}
- * data using the {@link Builder#build(double...)} method:
+ * <p>Use this method to create an instance populated with an array of {@code int[]}
+ * data using the {@link Builder#build(int...)} method:
*
* <pre>
- * double[] data = ...
- * DoubleStatistics stats = DoubleStatistics.builder(
+ * int[] data = ...
+ * IntStatistics stats = IntStatistics.builder(
* Statistic.MIN, Statistic.MAX, Statistic.VARIANCE)
* .build(data);
* </pre>
*
- * <p>The builder can be used to create multiple instances of {@link DoubleStatistics}
- * to be used in parallel, or on separate arrays of {@code double[]} data. These may
- * be {@link #combine(DoubleStatistics) combined}. For example:
+ * <p>The builder can be used to create multiple instances of {@link IntStatistics}
+ * to be used in parallel, or on separate arrays of {@code int[]} data. These may
+ * be {@link #combine(IntStatistics) combined}. For example:
*
* <pre>
- * double[][] data = ...
- * DoubleStatistics.Builder builder = DoubleStatistics.builder(
+ * int[][] data = ...
+ * IntStatistics.Builder builder = IntStatistics.builder(
* Statistic.MIN, Statistic.MAX, Statistic.VARIANCE);
- * DoubleStatistics stats = Arrays.stream(data)
+ * IntStatistics stats = Arrays.stream(data)
* .parallel()
* .map(builder::build)
- * .reduce(DoubleStatistics::combine)
+ * .reduce(IntStatistics::combine)
* .get();
* </pre>
*
@@ -362,16 +341,16 @@ public final class DoubleStatistics implements DoubleConsumer {
* use on multiple data:
*
* <pre>{@code
- * DoubleStatistics.Builder builder = DoubleStatistics.builder(
+ * IntStatistics.Builder builder = IntStatistics.builder(
* Statistic.MIN, Statistic.MAX, Statistic.VARIANCE);
- * Collector<double[], DoubleStatistics, DoubleStatistics> collector =
+ * Collector<int[], IntStatistics, IntStatistics> collector =
* Collector.of(builder::build,
* (s, d) -> s.combine(builder.build(d)),
- * DoubleStatistics::combine);
+ * IntStatistics::combine);
*
* // Repeated
- * double[][] data = ...
- * DoubleStatistics stats = Arrays.stream(data).collect(collector);
+ * int[][] data = ...
+ * IntStatistics stats = Arrays.stream(data).collect(collector);
* }</pre>
*
* @param statistics Statistics to compute.
@@ -395,7 +374,7 @@ public final class DoubleStatistics implements DoubleConsumer {
* @param value Value.
*/
@Override
- public void accept(double value) {
+ public void accept(int value) {
count++;
consumer.accept(value);
}
@@ -417,7 +396,7 @@ public final class DoubleStatistics implements DoubleConsumer {
* @param statistic Statistic.
* @return {@code true} if supported
* @throws NullPointerException if the {@code statistic} is {@code null}
- * @see #get(Statistic)
+ * @see #getResult(Statistic)
*/
public boolean isSupported(Statistic statistic) {
// Check for the appropriate underlying implementation
@@ -429,8 +408,6 @@ public final class DoubleStatistics implements DoubleConsumer {
return moment instanceof SumOfFourthDeviations;
case MAX:
return max != null;
- case MEAN:
- return moment != null;
case MIN:
return min != null;
case PRODUCT:
@@ -439,7 +416,8 @@ public final class DoubleStatistics implements DoubleConsumer {
return moment instanceof SumOfCubedDeviations;
case STANDARD_DEVIATION:
case VARIANCE:
- return moment instanceof SumOfSquaredDeviations;
+ return sum != null && sumOfSquares != null;
+ case MEAN:
case SUM:
return sum != null;
case SUM_OF_SQUARES:
@@ -450,24 +428,87 @@ public final class DoubleStatistics implements DoubleConsumer {
}
/**
- * Gets the value of the specified {@code statistic}.
+ * Gets the value of the specified {@code statistic} as a {@code double}.
*
* @param statistic Statistic.
- * @return the double
+ * @return the value
* @throws IllegalArgumentException if the {@code statistic} is not supported
* @see #isSupported(Statistic)
- * @see #getSupplier(Statistic)
+ * @see #getResult(Statistic)
*/
- public double get(Statistic statistic) {
- return getSupplier(statistic).getAsDouble();
+ public double getAsDouble(Statistic statistic) {
+ return getResult(statistic).getAsDouble();
+ }
+
+ /**
+ * Gets the value of the specified {@code statistic} as an {@code int}.
+ *
+ * <p>Use this method to access the {@code int} result for exact integer statistics,
+ * for example {@link Statistic#MIN}.
+ *
+ * <p>Note: This method may throw an {@link ArithmeticException} if the result
+ * overflows an {@code int}.
+ *
+ * @param statistic Statistic.
+ * @return the value
+ * @throws IllegalArgumentException if the {@code statistic} is not supported
+ * @throws ArithmeticException if the {@code result} overflows an {@code int} or is not
+ * finite
+ * @see #isSupported(Statistic)
+ * @see #getResult(Statistic)
+ */
+ public int getAsInt(Statistic statistic) {
+ return getResult(statistic).getAsInt();
+ }
+
+ /**
+ * Gets the value of the specified {@code statistic} as a {@code long}.
+ *
+ * <p>Use this method to access the {@code long} result for exact integer statistics,
+ * for example {@link Statistic#SUM} for a {@link #getCount() count} less than or equal to
+ *2<sup>32</sup>.
+ *
+ * <p>Note: This method may throw an {@link ArithmeticException} if the result
+ * overflows an {@code long}.
+ *
+ * @param statistic Statistic.
+ * @return the value
+ * @throws IllegalArgumentException if the {@code statistic} is not supported
+ * @throws ArithmeticException if the {@code result} overflows an {@code long} or is not
+ * finite
+ * @see #isSupported(Statistic)
+ * @see #getResult(Statistic)
+ */
+ public long getAsLong(Statistic statistic) {
+ return getResult(statistic).getAsLong();
+ }
+
+ /**
+ * Gets the value of the specified {@code statistic} as a {@code BigInteger}.
+ *
+ * <p>Use this method to access the {@code BigInteger} result for exact integer statistics,
+ * for example {@link Statistic#SUM_OF_SQUARES}.
+ *
+ * <p>Note: This method may throw an {@link ArithmeticException} if the result
+ * is not finite.
+ *
+ * @param statistic Statistic.
+ * @return the value
+ * @throws IllegalArgumentException if the {@code statistic} is not supported
+ * @throws ArithmeticException if the {@code result} is not finite
+ * @see #isSupported(Statistic)
+ * @see #getResult(Statistic)
+ */
+ public BigInteger getAsBigInteger(Statistic statistic) {
+ return getResult(statistic).getAsBigInteger();
}
/**
* Gets a supplier for the value of the specified {@code statistic}.
*
* <p>The returned function will supply the correct result after
- * calls to {@link #accept(double) accept} or
- * {@link #combine(DoubleStatistics) combine} further values into
+ * calls to {@link #accept(int) accept} or
+ * {@link #combine(IntStatistics) combine} further values into
* {@code this} instance.
*
* <p>This method can be used to perform a one-time look-up of the statistic
@@ -477,15 +518,15 @@ public final class DoubleStatistics implements DoubleConsumer {
* @return the supplier
* @throws IllegalArgumentException if the {@code statistic} is not supported
* @see #isSupported(Statistic)
- * @see #get(Statistic)
+ * @see #getAsDouble(Statistic)
*/
- public DoubleSupplier getSupplier(Statistic statistic) {
+ public StatisticResult getResult(Statistic statistic) {
// Locate the implementation.
// Statistics that wrap an underlying implementation are created in methods.
- // The return argument should be a method reference and not an instance
- // of DoubleStatistic. This ensures the statistic implementation cannot
- // be updated with new values by casting the result and calling accept(double).
- DoubleSupplier stat = null;
+ // The return argument should be an interface reference and not an instance
+ // of IntStatistic. This ensures the statistic implementation cannot
+ // be updated with new values by casting the result and calling accept(int).
+ StatisticResult stat = null;
switch (statistic) {
case GEOMETRIC_MEAN:
stat = getGeometricMean();
@@ -494,16 +535,16 @@ public final class DoubleStatistics implements DoubleConsumer {
stat = getKurtosis();
break;
case MAX:
- stat = max;
+ stat = Statistics.getResultAsIntOrNull(max);
break;
case MEAN:
stat = getMean();
break;
case MIN:
- stat = min;
+ stat = Statistics.getResultAsIntOrNull(min);
break;
case PRODUCT:
- stat = product;
+ stat = Statistics.getResultAsDoubleOrNull(product);
break;
case SKEWNESS:
stat = getSkewness();
@@ -512,13 +553,13 @@ public final class DoubleStatistics implements DoubleConsumer {
stat = getStandardDeviation();
break;
case SUM:
- stat = sum;
+ stat = Statistics.getResultAsBigIntegerOrNull(sum);
break;
case SUM_OF_LOGS:
- stat = sumOfLogs;
+ stat = Statistics.getResultAsDoubleOrNull(sumOfLogs);
break;
case SUM_OF_SQUARES:
- stat = sumOfSquares;
+ stat = Statistics.getResultAsBigIntegerOrNull(sumOfSquares);
break;
case VARIANCE:
stat = getVariance();
@@ -527,9 +568,7 @@ public final class DoubleStatistics implements DoubleConsumer {
break;
}
if (stat != null) {
- return stat instanceof DoubleStatistic ?
- ((DoubleStatistic) stat)::getAsDouble :
- stat;
+ return stat;
}
throw new IllegalArgumentException(UNSUPPORTED_STATISTIC + statistic);
}
@@ -539,7 +578,7 @@ public final class DoubleStatistics implements DoubleConsumer {
*
* @return a geometric mean supplier (or null if unsupported)
*/
- private DoubleSupplier getGeometricMean() {
+ private StatisticResult getGeometricMean() {
if (sumOfLogs != null) {
// Return a function that has access to the count and sumOfLogs
return () -> GeometricMean.computeGeometricMean(count, sumOfLogs);
@@ -552,7 +591,7 @@ public final class DoubleStatistics implements DoubleConsumer {
*
* @return a kurtosis supplier (or null if unsupported)
*/
- private DoubleSupplier getKurtosis() {
+ private StatisticResult getKurtosis() {
if (moment instanceof SumOfFourthDeviations) {
return new Kurtosis((SumOfFourthDeviations) moment)
.setBiased(config.isBiased())::getAsDouble;
@@ -565,10 +604,11 @@ public final class DoubleStatistics implements DoubleConsumer {
*
* @return a mean supplier (or null if unsupported)
*/
- private DoubleSupplier getMean() {
- if (moment != null) {
- // Special case where wrapping with a Mean is not required
- return moment::getFirstMoment;
+ private StatisticResult getMean() {
+ if (sum != null) {
+ // Return a function that has access to the count and sum
+ final Int128 s = sum.getSum();
+ return () -> IntMean.computeMean(s, count);
}
return null;
}
@@ -578,7 +618,7 @@ public final class DoubleStatistics implements DoubleConsumer {
*
* @return a skewness supplier (or null if unsupported)
*/
- private DoubleSupplier getSkewness() {
+ private StatisticResult getSkewness() {
if (moment instanceof SumOfCubedDeviations) {
return new Skewness((SumOfCubedDeviations) moment)
.setBiased(config.isBiased())::getAsDouble;
@@ -591,12 +631,8 @@ public final class DoubleStatistics implements DoubleConsumer {
*
* @return a standard deviation supplier (or null if unsupported)
*/
- private DoubleSupplier getStandardDeviation() {
- if (moment instanceof SumOfSquaredDeviations) {
- return new StandardDeviation((SumOfSquaredDeviations) moment)
- .setBiased(config.isBiased())::getAsDouble;
- }
- return null;
+ private StatisticResult getStandardDeviation() {
+ return getVarianceOrStd(true);
}
/**
@@ -604,10 +640,23 @@ public final class DoubleStatistics implements DoubleConsumer {
*
* @return a variance supplier (or null if unsupported)
*/
- private DoubleSupplier getVariance() {
- if (moment instanceof SumOfSquaredDeviations) {
- return new Variance((SumOfSquaredDeviations) moment)
- .setBiased(config.isBiased())::getAsDouble;
+ private StatisticResult getVariance() {
+ return getVarianceOrStd(false);
+ }
+
+ /**
+ * Gets the variance or standard deviation.
+ *
+ * @param std Flag to control if the statistic is the standard deviation.
+ * @return a variance/standard deviation supplier (or null if unsupported)
+ */
+ private StatisticResult getVarianceOrStd(boolean std) {
+ if (sum != null && sumOfSquares != null) {
+ // Return a function that has access to the count, sum and sum of squares
+ final Int128 s = sum.getSum();
+ final UInt128 ss = sumOfSquares.getSumOfSquares();
+ final boolean biased = config.isBiased();
+ return () -> IntVariance.computeVarianceOrStd(ss, s, count, biased, std);
}
return null;
}
@@ -629,94 +678,33 @@ public final class DoubleStatistics implements DoubleConsumer {
* @return {@code this} instance after combining {@code other}.
* @throws IllegalArgumentException if the {@code other} is not compatible
*/
- public DoubleStatistics combine(DoubleStatistics other) {
+ public IntStatistics combine(IntStatistics other) {
// Check compatibility
- checkNullOrElseOtherNonNull(min, other.min);
- checkNullOrElseOtherNonNull(max, other.max);
- checkNullOrElseOtherNonNull(sum, other.sum);
- checkNullOrElseOtherNonNull(product, other.product);
- checkNullOrElseOtherNonNull(sumOfSquares, other.sumOfSquares);
- checkNullOrElseOtherNonNull(sumOfLogs, other.sumOfLogs);
- checkNullOrElseOtherIsAssignable(moment, other.moment);
+ Statistics.checkCombineCompatible(min, other.min);
+ Statistics.checkCombineCompatible(max, other.max);
+ Statistics.checkCombineCompatible(sum, other.sum);
+ Statistics.checkCombineCompatible(product, other.product);
+ Statistics.checkCombineCompatible(sumOfSquares, other.sumOfSquares);
+ Statistics.checkCombineCompatible(sumOfLogs, other.sumOfLogs);
+ Statistics.checkCombineAssignable(moment, other.moment);
// Combine
count += other.count;
- combine(min, other.min);
- combine(max, other.max);
- combine(sum, other.sum);
- combine(product, other.product);
- combine(sumOfSquares, other.sumOfSquares);
- combine(sumOfLogs, other.sumOfLogs);
- combineMoment(moment, other.moment);
+ Statistics.combine(min, other.min);
+ Statistics.combine(max, other.max);
+ Statistics.combine(sum, other.sum);
+ Statistics.combine(product, other.product);
+ Statistics.combine(sumOfSquares, other.sumOfSquares);
+ Statistics.combine(sumOfLogs, other.sumOfLogs);
+ Statistics.combineMoment(moment, other.moment);
return this;
}
- /**
- * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
- * argument {@code b} must also be non-{@code null}.
- *
- * @param a LHS.
- * @param b RHS.
- */
- private static void checkNullOrElseOtherNonNull(Object a, Object b) {
- if (a != null && b == null) {
- throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
- }
- }
-
- /**
- * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
- * argument {@code b} must be run-time assignable to the same class as {@code a}.
- *
- * @param a LHS.
- * @param b RHS.
- */
- private static void checkNullOrElseOtherIsAssignable(Object a, Object b) {
- if (a != null && (b == null || !a.getClass().isAssignableFrom(b.getClass()))) {
- throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
- }
- }
-
- /**
- * If the left-hand side argument {@code a} is non-{@code null}, combined it with the
- * right-hand side argument {@code b}.
- *
- * @param <T> {@link DoubleStatistic} being accumulated.
- * @param a LHS.
- * @param b RHS.
- */
- private static <T extends DoubleStatistic & StatisticAccumulator<T>> void combine(T a, T b) {
- if (a != null) {
- a.combine(b);
- }
- }
-
- /**
- * If the left-hand side argument {@code a} is non-{@code null}, combined it with the
- * right-hand side argument {@code b}. Assumes that the RHS is run-time assignable
- * to the same class as LHS.
- *
- * @param a LHS.
- * @param b RHS.
- */
- private static void combineMoment(FirstMoment a, FirstMoment b) {
- // Avoid reflection and use the simpler instanceof
- if (a instanceof SumOfFourthDeviations) {
- ((SumOfFourthDeviations) a).combine((SumOfFourthDeviations) b);
- } else if (a instanceof SumOfCubedDeviations) {
- ((SumOfCubedDeviations) a).combine((SumOfCubedDeviations) b);
- } else if (a instanceof SumOfSquaredDeviations) {
- ((SumOfSquaredDeviations) a).combine((SumOfSquaredDeviations) b);
- } else if (a != null) {
- a.combine(b);
- }
- }
-
/**
* Sets the statistics configuration.
*
* <p>These options only control the final computation of statistics. The configuration
* will not affect compatibility between instances during a
- * {@link #combine(DoubleStatistics) combine} operation.
+ * {@link #combine(IntStatistics) combine} operation.
*
* <p>Note: These options will affect any future computation of statistics. Supplier functions
* that have been previously created will not be updated with the new configuration.
@@ -724,9 +712,9 @@ public final class DoubleStatistics implements DoubleConsumer {
* @param v Value.
* @return {@code this} instance
* @throws NullPointerException if the value is null
- * @see #getSupplier(Statistic)
+ * @see #getResult(Statistic)
*/
- public DoubleStatistics setConfiguration(StatisticsConfiguration v) {
+ public IntStatistics setConfiguration(StatisticsConfiguration v) {
config = Objects.requireNonNull(v);
return this;
}
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntSum.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntSum.java
index 6b3b5d4..5857ce1 100644
--- a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntSum.java
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntSum.java
@@ -105,6 +105,17 @@ public final class IntSum implements IntStatistic, StatisticAccumulator<IntSum>
return new IntSum(Int128.of(s));
}
+ /**
+ * Gets the sum.
+ *
+ * <p>This is package private for use in {@link IntStatistics}.
+ *
+ * @return the sum
+ */
+ Int128 getSum() {
+ return sum;
+ }
+
/**
* Updates the state of the statistic to reflect the addition of {@code value}.
*
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntSumOfSquares.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntSumOfSquares.java
index 31dd481..9905f28 100644
--- a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntSumOfSquares.java
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntSumOfSquares.java
@@ -126,6 +126,17 @@ public final class IntSumOfSquares implements IntStatistic, StatisticAccumulator
return new IntSumOfSquares(UInt128.of(ss));
}
+ /**
+ * Gets the sum of squares.
+ *
+ * <p>This is package private for use in {@link IntStatistics}.
+ *
+ * @return the sum of squares
+ */
+ UInt128 getSumOfSquares() {
+ return sumSq;
+ }
+
/**
* Updates the state of the statistic to reflect the addition of {@code value}.
*
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongStatisticResult.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongStatisticResult.java
similarity index 100%
rename from commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongStatisticResult.java
rename to commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongStatisticResult.java
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongStatistics.java
similarity index 61%
copy from commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java
copy to commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongStatistics.java
index d77bc64..3255daf 100644
--- a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongStatistics.java
@@ -16,14 +16,15 @@
*/
package org.apache.commons.statistics.descriptive;
+import java.math.BigInteger;
import java.util.Objects;
import java.util.Set;
import java.util.function.DoubleConsumer;
-import java.util.function.DoubleSupplier;
import java.util.function.Function;
+import java.util.function.LongConsumer;
/**
- * Statistics for {@code double} values.
+ * Statistics for {@code long} values.
*
* <p>This class provides combinations of individual statistic implementations in the
* {@code org.apache.commons.statistics.descriptive} package.
@@ -33,69 +34,55 @@ import java.util.function.Function;
*
* @since 1.1
*/
-public final class DoubleStatistics implements DoubleConsumer {
- /** A no-operation double consumer. This is exposed for testing. */
- static final DoubleConsumer NOOP = new DoubleConsumer() {
- @Override
- public void accept(double value) {
- // Do nothing
- }
-
- @Override
- public DoubleConsumer andThen(DoubleConsumer after) {
- // Delegate to the after consumer
- return after;
- }
- };
+public final class LongStatistics implements LongConsumer {
/** Error message for non configured statistics. */
private static final String NO_CONFIGURED_STATISTICS = "No configured statistics";
/** Error message for an unsupported statistic. */
private static final String UNSUPPORTED_STATISTIC = "Unsupported statistic: ";
- /** Error message for an incompatible statistics. */
- private static final String INCOMPATIBLE_STATISTICS = "Incompatible statistics";
/** Count of values recorded. */
private long count;
/** The consumer of values. */
- private final DoubleConsumer consumer;
- /** The {@link Min} implementation. */
- private final Min min;
- /** The {@link Max} implementation. */
- private final Max max;
- /** The moment implementation. May be any instance of {@link FirstMoment}. */
+ private final LongConsumer consumer;
+ /** The {@link LongMin} implementation. */
+ private final LongMin min;
+ /** The {@link LongMax} implementation. */
+ private final LongMax max;
+ /** The moment implementation. May be any instance of {@link FirstMoment}.
+ * This implementation uses only the third and fourth moments. */
private final FirstMoment moment;
- /** The {@link Sum} implementation. */
- private final Sum sum;
+ /** The {@link LongSum} implementation. */
+ private final LongSum sum;
/** The {@link Product} implementation. */
private final Product product;
- /** The {@link SumOfSquares} implementation. */
- private final SumOfSquares sumOfSquares;
+ /** The {@link LongSumOfSquares} implementation. */
+ private final LongSumOfSquares sumOfSquares;
/** The {@link SumOfLogs} implementation. */
private final SumOfLogs sumOfLogs;
/** Configuration options for computation of statistics. */
private StatisticsConfiguration config;
/**
- * A builder for {@link DoubleStatistics}.
+ * A builder for {@link LongStatistics}.
*/
public static final class Builder {
/** An empty double array. */
- private static final double[] NO_VALUES = {};
+ private static final long[] NO_VALUES = {};
- /** The {@link Min} constructor. */
- private Function<double[], Min> min;
- /** The {@link Max} constructor. */
- private Function<double[], Max> max;
+ /** The {@link LongMin} constructor. */
+ private Function<long[], LongMin> min;
+ /** The {@link LongMax} constructor. */
+ private Function<long[], LongMax> max;
/** The moment constructor. May return any instance of {@link FirstMoment}. */
- private Function<double[], FirstMoment> moment;
- /** The {@link Sum} constructor. */
- private Function<double[], Sum> sum;
+ private Function<long[], FirstMoment> moment;
+ /** The {@link LongSum} constructor. */
+ private Function<long[], LongSum> sum;
/** The {@link Product} constructor. */
- private Function<double[], Product> product;
- /** The {@link SumOfSquares} constructor. */
- private Function<double[], SumOfSquares> sumOfSquares;
+ private Function<long[], Product> product;
+ /** The {@link LongSumOfSquares} constructor. */
+ private Function<long[], LongSumOfSquares> sumOfSquares;
/** The {@link SumOfLogs} constructor. */
- private Function<double[], SumOfLogs> sumOfLogs;
+ private Function<long[], SumOfLogs> sumOfLogs;
/** The order of the moment. It corresponds to the power computed by the {@link FirstMoment}
* instance constructed by {@link #moment}. This should only be increased from the default
* of zero (corresponding to no moment computation). */
@@ -126,13 +113,10 @@ public final class DoubleStatistics implements DoubleConsumer {
createMoment(4);
break;
case MAX:
- max = Max::of;
- break;
- case MEAN:
- createMoment(1);
+ max = LongMax::of;
break;
case MIN:
- min = Min::of;
+ min = LongMin::of;
break;
case PRODUCT:
product = Product::of;
@@ -142,13 +126,15 @@ public final class DoubleStatistics implements DoubleConsumer {
break;
case STANDARD_DEVIATION:
case VARIANCE:
- createMoment(2);
+ sum = LongSum::of;
+ sumOfSquares = LongSumOfSquares::of;
break;
+ case MEAN:
case SUM:
- sum = Sum::of;
+ sum = LongSum::of;
break;
case SUM_OF_SQUARES:
- sumOfSquares = SumOfSquares::of;
+ sumOfSquares = LongSumOfSquares::of;
break;
default:
throw new IllegalArgumentException(UNSUPPORTED_STATISTIC + statistic);
@@ -158,7 +144,7 @@ public final class DoubleStatistics implements DoubleConsumer {
/**
* Creates the moment constructor for the specified {@code order},
- * e.g. order=2 is sum of squared deviations.
+ * e.g. order=3 is sum of cubed deviations.
*
* @param order Order.
*/
@@ -167,13 +153,9 @@ public final class DoubleStatistics implements DoubleConsumer {
momentOrder = order;
if (order == 4) {
moment = SumOfFourthDeviations::of;
- } else if (order == 3) {
- moment = SumOfCubedDeviations::of;
- } else if (order == 2) {
- moment = SumOfSquaredDeviations::of;
} else {
- // Assume order == 1
- moment = FirstMoment::of;
+ // Assume order == 3
+ moment = SumOfCubedDeviations::of;
}
}
}
@@ -191,27 +173,27 @@ public final class DoubleStatistics implements DoubleConsumer {
}
/**
- * Builds a {@code DoubleStatistics} instance.
+ * Builds a {@code LongStatistics} instance.
*
- * @return {@code DoubleStatistics} instance.
+ * @return {@code LongStatistics} instance.
*/
- public DoubleStatistics build() {
+ public LongStatistics build() {
return build(NO_VALUES);
}
/**
- * Builds a {@code DoubleStatistics} instance using the input {@code values}.
+ * Builds a {@code LongStatistics} instance using the input {@code values}.
*
- * <p>Note: {@code DoubleStatistics} computed using
- * {@link DoubleStatistics#accept(double) accept} may be
+ * <p>Note: {@code LongStatistics} computed using
+ * {@link LongStatistics#accept(long) accept} may be
* different from this instance.
*
* @param values Values.
- * @return {@code DoubleStatistics} instance.
+ * @return {@code LongStatistics} instance.
*/
- public DoubleStatistics build(double... values) {
+ public LongStatistics build(long... values) {
Objects.requireNonNull(values, "values");
- return new DoubleStatistics(
+ return new LongStatistics(
values.length,
create(min, values),
create(max, values),
@@ -231,7 +213,7 @@ public final class DoubleStatistics implements DoubleConsumer {
* @param values Values
* @return the instance
*/
- private static <T> T create(Function<double[], T> constructor, double[] values) {
+ private static <T> T create(Function<long[], T> constructor, long[] values) {
if (constructor != null) {
return constructor.apply(values);
}
@@ -243,18 +225,18 @@ public final class DoubleStatistics implements DoubleConsumer {
* Create an instance.
*
* @param count Count of values.
- * @param min Min implementation.
- * @param max Max implementation.
+ * @param min LongMin implementation.
+ * @param max LongMax implementation.
* @param moment Moment implementation.
- * @param sum Sum implementation.
+ * @param sum LongSum implementation.
* @param product Product implementation.
* @param sumOfSquares Sum of squares implementation.
* @param sumOfLogs Sum of logs implementation.
* @param config Statistics configuration.
*/
- DoubleStatistics(long count, Min min, Max max, FirstMoment moment, Sum sum,
- Product product, SumOfSquares sumOfSquares, SumOfLogs sumOfLogs,
- StatisticsConfiguration config) {
+ LongStatistics(long count, LongMin min, LongMax max, FirstMoment moment, LongSum sum,
+ Product product, LongSumOfSquares sumOfSquares, SumOfLogs sumOfLogs,
+ StatisticsConfiguration config) {
this.count = count;
this.min = min;
this.max = max;
@@ -264,28 +246,25 @@ public final class DoubleStatistics implements DoubleConsumer {
this.sumOfSquares = sumOfSquares;
this.sumOfLogs = sumOfLogs;
this.config = config;
- consumer = compose(min, max, moment, sum, product, sumOfSquares, sumOfLogs);
+ // The final consumer should never be null as the builder is created
+ // with at least one statistic.
+ consumer = Statistics.compose(min, max, sum, sumOfSquares,
+ composeAsLong(moment, product, sumOfLogs));
}
/**
- * Chain the {@code consumers} into a single composite consumer. Ignore any {@code null}
- * consumer.
+ * Chain the {@code consumers} into a single composite {@code LongConsumer}.
+ * Ignore any {@code null} consumer.
*
* @param consumers Consumers.
- * @return a composed consumer
+ * @return a composed consumer (or null)
*/
- private static DoubleConsumer compose(DoubleConsumer... consumers) {
- DoubleConsumer action = NOOP;
- for (final DoubleConsumer consumer : consumers) {
- if (consumer != null) {
- action = action.andThen(consumer);
- }
- }
- if (action == NOOP) {
- // This should not be possible
- throw new IllegalStateException(NO_CONFIGURED_STATISTICS + ": Please file a bug report");
+ private static LongConsumer composeAsLong(DoubleConsumer... consumers) {
+ final DoubleConsumer c = Statistics.compose(consumers);
+ if (c != null) {
+ return c::accept;
}
- return action;
+ return null;
}
/**
@@ -298,7 +277,7 @@ public final class DoubleStatistics implements DoubleConsumer {
* @return the instance
* @throws IllegalArgumentException if there are no {@code statistics} to compute.
*/
- public static DoubleStatistics of(Statistic... statistics) {
+ public static LongStatistics of(Statistic... statistics) {
return builder(statistics).build();
}
@@ -307,10 +286,10 @@ public final class DoubleStatistics implements DoubleConsumer {
* populated using the input {@code values}.
*
* <p>Use this method to create an instance populated with a (variable) array of
- * {@code double[]} data:
+ * {@code long[]} data:
*
* <pre>
- * DoubleStatistics stats = DoubleStatistics.of(
+ * LongStatistics stats = LongStatistics.of(
* EnumSet.of(Statistic.MIN, Statistic.MAX),
* 1, 1, 2, 3, 5, 8, 13);
* </pre>
@@ -320,7 +299,7 @@ public final class DoubleStatistics implements DoubleConsumer {
* @return the instance
* @throws IllegalArgumentException if there are no {@code statistics} to compute.
*/
- public static DoubleStatistics of(Set<Statistic> statistics, double... values) {
+ public static LongStatistics of(Set<Statistic> statistics, long... values) {
if (statistics.isEmpty()) {
throw new IllegalArgumentException(NO_CONFIGURED_STATISTICS);
}
@@ -333,28 +312,28 @@ public final class DoubleStatistics implements DoubleConsumer {
* Returns a new builder configured to create instances to compute the specified
* {@code statistics}.
*
- * <p>Use this method to create an instance populated with an array of {@code double[]}
- * data using the {@link Builder#build(double...)} method:
+ * <p>Use this method to create an instance populated with an array of {@code long[]}
+ * data using the {@link Builder#build(long...)} method:
*
* <pre>
- * double[] data = ...
- * DoubleStatistics stats = DoubleStatistics.builder(
+ * long[] data = ...
+ * LongStatistics stats = LongStatistics.builder(
* Statistic.MIN, Statistic.MAX, Statistic.VARIANCE)
* .build(data);
* </pre>
*
- * <p>The builder can be used to create multiple instances of {@link DoubleStatistics}
- * to be used in parallel, or on separate arrays of {@code double[]} data. These may
- * be {@link #combine(DoubleStatistics) combined}. For example:
+ * <p>The builder can be used to create multiple instances of {@link LongStatistics}
+ * to be used in parallel, or on separate arrays of {@code long[]} data. These may
+ * be {@link #combine(LongStatistics) combined}. For example:
*
* <pre>
- * double[][] data = ...
- * DoubleStatistics.Builder builder = DoubleStatistics.builder(
+ * long[][] data = ...
+ * LongStatistics.Builder builder = LongStatistics.builder(
* Statistic.MIN, Statistic.MAX, Statistic.VARIANCE);
- * DoubleStatistics stats = Arrays.stream(data)
+ * LongStatistics stats = Arrays.stream(data)
* .parallel()
* .map(builder::build)
- * .reduce(DoubleStatistics::combine)
+ * .reduce(LongStatistics::combine)
* .get();
* </pre>
*
@@ -362,16 +341,16 @@ public final class DoubleStatistics implements DoubleConsumer {
* use on multiple data:
*
* <pre>{@code
- * DoubleStatistics.Builder builder = DoubleStatistics.builder(
+ * LongStatistics.Builder builder = LongStatistics.builder(
* Statistic.MIN, Statistic.MAX, Statistic.VARIANCE);
- * Collector<double[], DoubleStatistics, DoubleStatistics> collector =
+ * Collector<long[], LongStatistics, LongStatistics> collector =
* Collector.of(builder::build,
* (s, d) -> s.combine(builder.build(d)),
- * DoubleStatistics::combine);
+ * LongStatistics::combine);
*
* // Repeated
- * double[][] data = ...
- * DoubleStatistics stats = Arrays.stream(data).collect(collector);
+ * long[][] data = ...
+ * LongStatistics stats = Arrays.stream(data).collect(collector);
* }</pre>
*
* @param statistics Statistics to compute.
@@ -395,7 +374,7 @@ public final class DoubleStatistics implements DoubleConsumer {
* @param value Value.
*/
@Override
- public void accept(double value) {
+ public void accept(long value) {
count++;
consumer.accept(value);
}
@@ -417,7 +396,7 @@ public final class DoubleStatistics implements DoubleConsumer {
* @param statistic Statistic.
* @return {@code true} if supported
* @throws NullPointerException if the {@code statistic} is {@code null}
- * @see #get(Statistic)
+ * @see #getResult(Statistic)
*/
public boolean isSupported(Statistic statistic) {
// Check for the appropriate underlying implementation
@@ -429,8 +408,6 @@ public final class DoubleStatistics implements DoubleConsumer {
return moment instanceof SumOfFourthDeviations;
case MAX:
return max != null;
- case MEAN:
- return moment != null;
case MIN:
return min != null;
case PRODUCT:
@@ -439,7 +416,8 @@ public final class DoubleStatistics implements DoubleConsumer {
return moment instanceof SumOfCubedDeviations;
case STANDARD_DEVIATION:
case VARIANCE:
- return moment instanceof SumOfSquaredDeviations;
+ return sum != null && sumOfSquares != null;
+ case MEAN:
case SUM:
return sum != null;
case SUM_OF_SQUARES:
@@ -450,24 +428,65 @@ public final class DoubleStatistics implements DoubleConsumer {
}
/**
- * Gets the value of the specified {@code statistic}.
+ * Gets the value of the specified {@code statistic} as a {@code double}.
+ *
+ * @param statistic Statistic.
+ * @return the value
+ * @throws IllegalArgumentException if the {@code statistic} is not supported
+ * @see #isSupported(Statistic)
+ * @see #getResult(Statistic)
+ */
+ public double getAsDouble(Statistic statistic) {
+ return getResult(statistic).getAsDouble();
+ }
+
+ /**
+ * Gets the value of the specified {@code statistic} as a {@code long}.
+ *
+ * <p>Use this method to access the {@code long} result for exact integer statistics,
+ * for example {@link Statistic#MIN}.
+ *
+ * <p>Note: This method may throw an {@link ArithmeticException} if the result
+ * overflows an {@code long}.
+ *
+ * @param statistic Statistic.
+ * @return the value
+ * @throws IllegalArgumentException if the {@code statistic} is not supported
+ * @throws ArithmeticException if the {@code result} overflows an {@code long} or is not
+ * finite
+ * @see #isSupported(Statistic)
+ * @see #getResult(Statistic)
+ */
+ public long getAsLong(Statistic statistic) {
+ return getResult(statistic).getAsLong();
+ }
+
+ /**
+ * Gets the value of the specified {@code statistic} as a {@code BigInteger}.
+ *
+ * <p>Use this method to access the {@code BigInteger} result for exact integer statistics,
+ * for example {@link Statistic#SUM_OF_SQUARES}.
+ *
+ * <p>Note: This method may throw an {@link ArithmeticException} if the result
+ * is not finite.
*
* @param statistic Statistic.
- * @return the double
+ * @return the value
* @throws IllegalArgumentException if the {@code statistic} is not supported
+ * @throws ArithmeticException if the {@code result} is not finite
* @see #isSupported(Statistic)
- * @see #getSupplier(Statistic)
+ * @see #getResult(Statistic)
*/
- public double get(Statistic statistic) {
- return getSupplier(statistic).getAsDouble();
+ public BigInteger getAsBigInteger(Statistic statistic) {
+ return getResult(statistic).getAsBigInteger();
}
/**
* Gets a supplier for the value of the specified {@code statistic}.
*
* <p>The returned function will supply the correct result after
- * calls to {@link #accept(double) accept} or
- * {@link #combine(DoubleStatistics) combine} further values into
+ * calls to {@link #accept(long) accept} or
+ * {@link #combine(LongStatistics) combine} further values into
* {@code this} instance.
*
* <p>This method can be used to perform a one-time look-up of the statistic
@@ -477,15 +496,15 @@ public final class DoubleStatistics implements DoubleConsumer {
* @return the supplier
* @throws IllegalArgumentException if the {@code statistic} is not supported
* @see #isSupported(Statistic)
- * @see #get(Statistic)
+ * @see #getAsDouble(Statistic)
*/
- public DoubleSupplier getSupplier(Statistic statistic) {
+ public StatisticResult getResult(Statistic statistic) {
// Locate the implementation.
// Statistics that wrap an underlying implementation are created in methods.
- // The return argument should be a method reference and not an instance
- // of DoubleStatistic. This ensures the statistic implementation cannot
- // be updated with new values by casting the result and calling accept(double).
- DoubleSupplier stat = null;
+ // The return argument should be an interface reference and not an instance
+ // of LongStatistic. This ensures the statistic implementation cannot
+ // be updated with new values by casting the result and calling accept(long).
+ StatisticResult stat = null;
switch (statistic) {
case GEOMETRIC_MEAN:
stat = getGeometricMean();
@@ -494,16 +513,16 @@ public final class DoubleStatistics implements DoubleConsumer {
stat = getKurtosis();
break;
case MAX:
- stat = max;
+ stat = Statistics.getResultAsLongOrNull(max);
break;
case MEAN:
stat = getMean();
break;
case MIN:
- stat = min;
+ stat = Statistics.getResultAsLongOrNull(min);
break;
case PRODUCT:
- stat = product;
+ stat = Statistics.getResultAsDoubleOrNull(product);
break;
case SKEWNESS:
stat = getSkewness();
@@ -512,13 +531,13 @@ public final class DoubleStatistics implements DoubleConsumer {
stat = getStandardDeviation();
break;
case SUM:
- stat = sum;
+ stat = Statistics.getResultAsBigIntegerOrNull(sum);
break;
case SUM_OF_LOGS:
- stat = sumOfLogs;
+ stat = Statistics.getResultAsDoubleOrNull(sumOfLogs);
break;
case SUM_OF_SQUARES:
- stat = sumOfSquares;
+ stat = Statistics.getResultAsBigIntegerOrNull(sumOfSquares);
break;
case VARIANCE:
stat = getVariance();
@@ -527,9 +546,7 @@ public final class DoubleStatistics implements DoubleConsumer {
break;
}
if (stat != null) {
- return stat instanceof DoubleStatistic ?
- ((DoubleStatistic) stat)::getAsDouble :
- stat;
+ return stat;
}
throw new IllegalArgumentException(UNSUPPORTED_STATISTIC + statistic);
}
@@ -539,7 +556,7 @@ public final class DoubleStatistics implements DoubleConsumer {
*
* @return a geometric mean supplier (or null if unsupported)
*/
- private DoubleSupplier getGeometricMean() {
+ private StatisticResult getGeometricMean() {
if (sumOfLogs != null) {
// Return a function that has access to the count and sumOfLogs
return () -> GeometricMean.computeGeometricMean(count, sumOfLogs);
@@ -552,7 +569,7 @@ public final class DoubleStatistics implements DoubleConsumer {
*
* @return a kurtosis supplier (or null if unsupported)
*/
- private DoubleSupplier getKurtosis() {
+ private StatisticResult getKurtosis() {
if (moment instanceof SumOfFourthDeviations) {
return new Kurtosis((SumOfFourthDeviations) moment)
.setBiased(config.isBiased())::getAsDouble;
@@ -565,10 +582,11 @@ public final class DoubleStatistics implements DoubleConsumer {
*
* @return a mean supplier (or null if unsupported)
*/
- private DoubleSupplier getMean() {
- if (moment != null) {
- // Special case where wrapping with a Mean is not required
- return moment::getFirstMoment;
+ private StatisticResult getMean() {
+ if (sum != null) {
+ // Return a function that has access to the count and sum
+ final Int128 s = sum.getSum();
+ return () -> LongMean.computeMean(s, count);
}
return null;
}
@@ -578,7 +596,7 @@ public final class DoubleStatistics implements DoubleConsumer {
*
* @return a skewness supplier (or null if unsupported)
*/
- private DoubleSupplier getSkewness() {
+ private StatisticResult getSkewness() {
if (moment instanceof SumOfCubedDeviations) {
return new Skewness((SumOfCubedDeviations) moment)
.setBiased(config.isBiased())::getAsDouble;
@@ -591,12 +609,8 @@ public final class DoubleStatistics implements DoubleConsumer {
*
* @return a standard deviation supplier (or null if unsupported)
*/
- private DoubleSupplier getStandardDeviation() {
- if (moment instanceof SumOfSquaredDeviations) {
- return new StandardDeviation((SumOfSquaredDeviations) moment)
- .setBiased(config.isBiased())::getAsDouble;
- }
- return null;
+ private StatisticResult getStandardDeviation() {
+ return getVarianceOrStd(true);
}
/**
@@ -604,10 +618,23 @@ public final class DoubleStatistics implements DoubleConsumer {
*
* @return a variance supplier (or null if unsupported)
*/
- private DoubleSupplier getVariance() {
- if (moment instanceof SumOfSquaredDeviations) {
- return new Variance((SumOfSquaredDeviations) moment)
- .setBiased(config.isBiased())::getAsDouble;
+ private StatisticResult getVariance() {
+ return getVarianceOrStd(false);
+ }
+
+ /**
+ * Gets the variance or standard deviation.
+ *
+ * @param std Flag to control if the statistic is the standard deviation.
+ * @return a variance/standard deviation supplier (or null if unsupported)
+ */
+ private StatisticResult getVarianceOrStd(boolean std) {
+ if (sum != null && sumOfSquares != null) {
+ // Return a function that has access to the count, sum and sum of squares
+ final Int128 s = sum.getSum();
+ final UInt192 ss = sumOfSquares.getSumOfSquares();
+ final boolean biased = config.isBiased();
+ return () -> LongVariance.computeVarianceOrStd(ss, s, count, biased, std);
}
return null;
}
@@ -629,94 +656,33 @@ public final class DoubleStatistics implements DoubleConsumer {
* @return {@code this} instance after combining {@code other}.
* @throws IllegalArgumentException if the {@code other} is not compatible
*/
- public DoubleStatistics combine(DoubleStatistics other) {
+ public LongStatistics combine(LongStatistics other) {
// Check compatibility
- checkNullOrElseOtherNonNull(min, other.min);
- checkNullOrElseOtherNonNull(max, other.max);
- checkNullOrElseOtherNonNull(sum, other.sum);
- checkNullOrElseOtherNonNull(product, other.product);
- checkNullOrElseOtherNonNull(sumOfSquares, other.sumOfSquares);
- checkNullOrElseOtherNonNull(sumOfLogs, other.sumOfLogs);
- checkNullOrElseOtherIsAssignable(moment, other.moment);
+ Statistics.checkCombineCompatible(min, other.min);
+ Statistics.checkCombineCompatible(max, other.max);
+ Statistics.checkCombineCompatible(sum, other.sum);
+ Statistics.checkCombineCompatible(product, other.product);
+ Statistics.checkCombineCompatible(sumOfSquares, other.sumOfSquares);
+ Statistics.checkCombineCompatible(sumOfLogs, other.sumOfLogs);
+ Statistics.checkCombineAssignable(moment, other.moment);
// Combine
count += other.count;
- combine(min, other.min);
- combine(max, other.max);
- combine(sum, other.sum);
- combine(product, other.product);
- combine(sumOfSquares, other.sumOfSquares);
- combine(sumOfLogs, other.sumOfLogs);
- combineMoment(moment, other.moment);
+ Statistics.combine(min, other.min);
+ Statistics.combine(max, other.max);
+ Statistics.combine(sum, other.sum);
+ Statistics.combine(product, other.product);
+ Statistics.combine(sumOfSquares, other.sumOfSquares);
+ Statistics.combine(sumOfLogs, other.sumOfLogs);
+ Statistics.combineMoment(moment, other.moment);
return this;
}
- /**
- * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
- * argument {@code b} must also be non-{@code null}.
- *
- * @param a LHS.
- * @param b RHS.
- */
- private static void checkNullOrElseOtherNonNull(Object a, Object b) {
- if (a != null && b == null) {
- throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
- }
- }
-
- /**
- * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
- * argument {@code b} must be run-time assignable to the same class as {@code a}.
- *
- * @param a LHS.
- * @param b RHS.
- */
- private static void checkNullOrElseOtherIsAssignable(Object a, Object b) {
- if (a != null && (b == null || !a.getClass().isAssignableFrom(b.getClass()))) {
- throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
- }
- }
-
- /**
- * If the left-hand side argument {@code a} is non-{@code null}, combined it with the
- * right-hand side argument {@code b}.
- *
- * @param <T> {@link DoubleStatistic} being accumulated.
- * @param a LHS.
- * @param b RHS.
- */
- private static <T extends DoubleStatistic & StatisticAccumulator<T>> void combine(T a, T b) {
- if (a != null) {
- a.combine(b);
- }
- }
-
- /**
- * If the left-hand side argument {@code a} is non-{@code null}, combined it with the
- * right-hand side argument {@code b}. Assumes that the RHS is run-time assignable
- * to the same class as LHS.
- *
- * @param a LHS.
- * @param b RHS.
- */
- private static void combineMoment(FirstMoment a, FirstMoment b) {
- // Avoid reflection and use the simpler instanceof
- if (a instanceof SumOfFourthDeviations) {
- ((SumOfFourthDeviations) a).combine((SumOfFourthDeviations) b);
- } else if (a instanceof SumOfCubedDeviations) {
- ((SumOfCubedDeviations) a).combine((SumOfCubedDeviations) b);
- } else if (a instanceof SumOfSquaredDeviations) {
- ((SumOfSquaredDeviations) a).combine((SumOfSquaredDeviations) b);
- } else if (a != null) {
- a.combine(b);
- }
- }
-
/**
* Sets the statistics configuration.
*
* <p>These options only control the final computation of statistics. The configuration
* will not affect compatibility between instances during a
- * {@link #combine(DoubleStatistics) combine} operation.
+ * {@link #combine(LongStatistics) combine} operation.
*
* <p>Note: These options will affect any future computation of statistics. Supplier functions
* that have been previously created will not be updated with the new configuration.
@@ -724,9 +690,9 @@ public final class DoubleStatistics implements DoubleConsumer {
* @param v Value.
* @return {@code this} instance
* @throws NullPointerException if the value is null
- * @see #getSupplier(Statistic)
+ * @see #getResult(Statistic)
*/
- public DoubleStatistics setConfiguration(StatisticsConfiguration v) {
+ public LongStatistics setConfiguration(StatisticsConfiguration v) {
config = Objects.requireNonNull(v);
return this;
}
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongSum.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongSum.java
index 0c2779d..101e6ad 100644
--- a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongSum.java
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongSum.java
@@ -98,6 +98,17 @@ public final class LongSum implements LongStatistic, StatisticAccumulator<LongSu
return new LongSum(s);
}
+ /**
+ * Gets the sum.
+ *
+ * <p>This is package private for use in {@link LongStatistics}.
+ *
+ * @return the sum
+ */
+ Int128 getSum() {
+ return sum;
+ }
+
/**
* Updates the state of the statistic to reflect the addition of {@code value}.
*
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongSumOfSquares.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongSumOfSquares.java
index 74ea71b..fc0b7c0 100644
--- a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongSumOfSquares.java
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongSumOfSquares.java
@@ -101,6 +101,17 @@ public final class LongSumOfSquares implements LongStatistic, StatisticAccumulat
return new LongSumOfSquares(ss);
}
+ /**
+ * Gets the sum of squares.
+ *
+ * <p>This is package private for use in {@link IntStatistics}.
+ *
+ * @return the sum of squares
+ */
+ UInt192 getSumOfSquares() {
+ return sumSq;
+ }
+
/**
* Updates the state of the statistic to reflect the addition of {@code value}.
*
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Statistics.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Statistics.java
index 20a5846..34bda8f 100644
--- a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Statistics.java
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Statistics.java
@@ -24,6 +24,51 @@ import java.util.function.LongConsumer;
* Utility methods for statistics.
*/
final class Statistics {
+ /** A no-operation double consumer. This is exposed for testing. */
+ static final DoubleConsumer DOUBLE_NOOP = new DoubleConsumer() {
+ @Override
+ public void accept(double value) {
+ // Do nothing
+ }
+
+ @Override
+ public DoubleConsumer andThen(DoubleConsumer after) {
+ // Delegate to the after consumer
+ return after;
+ }
+ };
+
+ /** A no-operation int consumer. This is exposed for testing. */
+ static final IntConsumer INT_NOOP = new IntConsumer() {
+ @Override
+ public void accept(int value) {
+ // Do nothing
+ }
+
+ @Override
+ public IntConsumer andThen(IntConsumer after) {
+ // Delegate to the after consumer
+ return after;
+ }
+ };
+
+ /** A no-operation long consumer. This is exposed for testing. */
+ static final LongConsumer LONG_NOOP = new LongConsumer() {
+ @Override
+ public void accept(long value) {
+ // Do nothing
+ }
+
+ @Override
+ public LongConsumer andThen(LongConsumer after) {
+ // Delegate to the after consumer
+ return after;
+ }
+ };
+
+ /** Error message for an incompatible statistics. */
+ private static final String INCOMPATIBLE_STATISTICS = "Incompatible statistics";
+
/** No instances. */
private Statistics() {}
@@ -123,4 +168,177 @@ final class Statistics {
final double meanPrecision = 1e-15 * m1;
return m2 <= meanPrecision * meanPrecision;
}
+
+ /**
+ * Chain the {@code consumers} into a single composite consumer. Ignore any {@code null}
+ * consumer. Returns {@code null} if all arguments are {@code null}.
+ *
+ * @param consumers Consumers.
+ * @return a composed consumer (or null)
+ */
+ static DoubleConsumer compose(DoubleConsumer... consumers) {
+ DoubleConsumer action = DOUBLE_NOOP;
+ for (final DoubleConsumer consumer : consumers) {
+ if (consumer != null) {
+ action = action.andThen(consumer);
+ }
+ }
+ return action == DOUBLE_NOOP ? null : action;
+ }
+
+ /**
+ * Chain the {@code consumers} into a single composite consumer. Ignore any {@code null}
+ * consumer. Returns {@code null} if all arguments are {@code null}.
+ *
+ * @param consumers Consumers.
+ * @return a composed consumer (or null)
+ */
+ static IntConsumer compose(IntConsumer... consumers) {
+ IntConsumer action = INT_NOOP;
+ for (final IntConsumer consumer : consumers) {
+ if (consumer != null) {
+ action = action.andThen(consumer);
+ }
+ }
+ return action == INT_NOOP ? null : action;
+ }
+
+ /**
+ * Chain the {@code consumers} into a single composite consumer. Ignore any {@code null}
+ * consumer. Returns {@code null} if all arguments are {@code null}.
+ *
+ * @param consumers Consumers.
+ * @return a composed consumer (or null)
+ */
+ static LongConsumer compose(LongConsumer... consumers) {
+ LongConsumer action = LONG_NOOP;
+ for (final LongConsumer consumer : consumers) {
+ if (consumer != null) {
+ action = action.andThen(consumer);
+ }
+ }
+ return action == LONG_NOOP ? null : action;
+ }
+
+ /**
+ * Gets the statistic result using the {@code int} value.
+ * Return {@code null} is the statistic is {@code null}.
+ *
+ * @param s Statistic.
+ * @return the result or null
+ */
+ static StatisticResult getResultAsIntOrNull(StatisticResult s) {
+ if (s != null) {
+ return (IntStatisticResult) s::getAsInt;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the statistic result using the {@code long} value.
+ * Return {@code null} is the statistic is {@code null}.
+ *
+ * @param s Statistic.
+ * @return the result or null
+ */
+ static StatisticResult getResultAsLongOrNull(StatisticResult s) {
+ if (s != null) {
+ return (LongStatisticResult) s::getAsLong;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the statistic result using the {@code double} value.
+ * Return {@code null} is the statistic is {@code null}.
+ *
+ * @param s Statistic.
+ * @return the result or null
+ */
+ static StatisticResult getResultAsDoubleOrNull(StatisticResult s) {
+ if (s != null) {
+ return s::getAsDouble;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the statistic result using the {@code BigInteger} value.
+ * Return {@code null} is the statistic is {@code null}.
+ *
+ * @param s Statistic.
+ * @return the result or null
+ */
+ static StatisticResult getResultAsBigIntegerOrNull(StatisticResult s) {
+ if (s != null) {
+ return (BigIntegerStatisticResult) s::getAsBigInteger;
+ }
+ return null;
+ }
+
+ /**
+ * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
+ * argument {@code b} must also be non-{@code null} so the statistics can be combined.
+ *
+ * @param <T> {@link StatisticResult} being accumulated.
+ * @param a LHS.
+ * @param b RHS.
+ * @throws IllegalArgumentException if the objects cannot be combined
+ */
+ static <T extends StatisticResult & StatisticAccumulator<T>> void checkCombineCompatible(T a, T b) {
+ if (a != null && b == null) {
+ throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
+ }
+ }
+
+ /**
+ * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
+ * argument {@code b} must be run-time assignable to the same class as {@code a}
+ * so the statistics can be combined.
+ *
+ * @param a LHS.
+ * @param b RHS.
+ * @throws IllegalArgumentException if the objects cannot be combined
+ */
+ static void checkCombineAssignable(FirstMoment a, FirstMoment b) {
+ if (a != null && (b == null || !a.getClass().isAssignableFrom(b.getClass()))) {
+ throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
+ }
+ }
+
+ /**
+ * If the left-hand side argument {@code a} is non-{@code null}, combine it with the
+ * right-hand side argument {@code b}.
+ *
+ * @param <T> {@link StatisticResult} being accumulated.
+ * @param a LHS.
+ * @param b RHS.
+ */
+ static <T extends StatisticResult & StatisticAccumulator<T>> void combine(T a, T b) {
+ if (a != null) {
+ a.combine(b);
+ }
+ }
+
+ /**
+ * If the left-hand side argument {@code a} is non-{@code null}, combine it with the
+ * right-hand side argument {@code b}. Assumes that the RHS is run-time assignable
+ * to the same class as LHS.
+ *
+ * @param a LHS.
+ * @param b RHS.
+ * @see #checkCombineAssignable(FirstMoment, FirstMoment)
+ */
+ static void combineMoment(FirstMoment a, FirstMoment b) {
+ // Avoid reflection and use the simpler instanceof
+ if (a instanceof SumOfFourthDeviations) {
+ ((SumOfFourthDeviations) a).combine((SumOfFourthDeviations) b);
+ } else if (a instanceof SumOfCubedDeviations) {
+ ((SumOfCubedDeviations) a).combine((SumOfCubedDeviations) b);
+ } else if (a instanceof SumOfSquaredDeviations) {
+ ((SumOfSquaredDeviations) a).combine((SumOfSquaredDeviations) b);
+ } else if (a != null) {
+ a.combine(b);
+ }
+ }
}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java
index a73d855..096aa4e 100644
--- a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java
@@ -26,7 +26,6 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BiConsumer;
-import java.util.function.DoubleConsumer;
import java.util.function.DoubleSupplier;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -363,20 +362,6 @@ class DoubleStatisticsTest {
return s1;
}
- @Test
- void testNoOpConsumer() {
- final DoubleConsumer c = DoubleStatistics.NOOP;
- // Hit coverage
- c.accept(0);
- final double[] value = {0};
- final DoubleConsumer other = x -> value[0] = x;
- final DoubleConsumer combined = c.andThen(other);
- Assertions.assertSame(combined, other);
- final double y = 42;
- combined.accept(y);
- Assertions.assertEquals(y, value[0]);
- }
-
@Test
void testOfThrows() {
Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.of());
@@ -574,7 +559,7 @@ class DoubleStatisticsTest {
* @param setter Option setter.
* @return the number of unique results
*/
- private static <T extends DoubleStatistic & StatisticAccumulator<T>> int addBooleanOptionCase(
+ static <T extends DoubleStatistic & StatisticAccumulator<T>> int addBooleanOptionCase(
Stream.Builder<Arguments> builder, double[] values, Statistic statistic,
Function<double[], T> arrayConstructor, BiConsumer<T, Boolean> setter) {
final T stat = arrayConstructor.apply(values);
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntStatisticsTest.java
similarity index 54%
copy from commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java
copy to commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntStatisticsTest.java
index a73d855..fae8307 100644
--- a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntStatisticsTest.java
@@ -21,18 +21,19 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.EnumSet;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BiConsumer;
-import java.util.function.DoubleConsumer;
import java.util.function.DoubleSupplier;
import java.util.function.Function;
import java.util.function.Supplier;
-import java.util.function.ToDoubleFunction;
import java.util.stream.IntStream;
import java.util.stream.Stream;
+import org.apache.commons.statistics.distribution.DoubleTolerances;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
@@ -42,12 +43,22 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
/**
- * Test for {@link DoubleStatistics}.
+ * Test for {@link IntStatistics}.
*
* <p>This class verifies that the statistics computed using the summary
* class are an exact match to the statistics computed individually.
+ *
+ * <p>For simplicity some tests use only the {@code double} result of the statistic.
+ * The full {@link StatisticResult} interface is asserted in the test of the array or
+ * stream of input data.
+ *
+ * <p>Note: Some integer statistics are identical if computed using an array or a stream,
+ * e.g. sum, min, mean. However those that use a {@code double} implementation may vary
+ * based on input order, e.g. product, skewness. This test computes expected results
+ * for array and stream variations for all statistics and does not distinguish these two
+ * cases.
*/
-class DoubleStatisticsTest {
+class IntStatisticsTest {
/** Empty statistic array. */
private static final Statistic[] EMPTY_STATISTIC_ARRAY = {};
@@ -57,9 +68,12 @@ class DoubleStatisticsTest {
/** The expected result for each statistic on the test data. */
private static EnumMap<Statistic, List<ExpectedResult>> expectedResult;
- /** The statistics that are co-computed. */
+ /** The statistics that are co-computed by a single statistic. */
private static EnumMap<Statistic, EnumSet<Statistic>> coComputed;
+ /** The statistics that are co-computed by a combination of statistics. */
+ private static Map<EnumSet<Statistic>, EnumSet<Statistic>> coComputedAsCombination;
+
/**
* Container for test data.
*/
@@ -67,7 +81,7 @@ class DoubleStatisticsTest {
/** Identifier. */
private final int id;
/** The sample values. */
- private final double[][] values;
+ private final int[][] values;
/** The number of values. */
private final long size;
@@ -75,7 +89,7 @@ class DoubleStatisticsTest {
* @param id Identifier.
* @param values Sample values.
*/
- TestData(int id, double[]... values) {
+ TestData(int id, int[]... values) {
this.id = id;
this.values = values;
size = Arrays.stream(values).mapToLong(x -> x.length).sum();
@@ -91,7 +105,7 @@ class DoubleStatisticsTest {
/**
* @return the values as a stream
*/
- Stream<double[]> stream() {
+ Stream<int[]> stream() {
return Arrays.stream(values);
}
@@ -108,15 +122,15 @@ class DoubleStatisticsTest {
*/
private static class ExpectedResult {
/** The expected result for a stream of values. */
- private final double stream;
+ private final StatisticResult stream;
/** The expected result for an array of values. */
- private final double array;
+ private final StatisticResult array;
/**
* @param stream Stream result.
* @param array Array result.
*/
- ExpectedResult(double stream, double array) {
+ ExpectedResult(StatisticResult stream, StatisticResult array) {
this.stream = stream;
this.array = array;
}
@@ -124,27 +138,27 @@ class DoubleStatisticsTest {
/**
* @return the expected result for a stream of values.
*/
- double getStream() {
+ StatisticResult getStream() {
return stream;
}
/**
* @return the expected result for an array of values.
*/
- double getArray() {
+ StatisticResult getArray() {
return array;
}
}
@BeforeAll
static void setup() {
- // Random double[] of different lengths
- final double[][] arrays = IntStream.of(4, 5, 7)
- .mapToObj(i -> ThreadLocalRandom.current().doubles(i, 2.25, 3.75).toArray())
- .toArray(double[][]::new);
- // Create test data. IDs must be created in order of the data.
+ // Random int[] of different lengths, Strictly positive so sum-of-logs is finite.
+ final int[][] arrays = IntStream.of(4, 5, 7)
+ .mapToObj(i -> ThreadLocalRandom.current().ints(i, 1, Integer.MAX_VALUE).toArray())
+ .toArray(int[][]::new);
+ // Create test data. IDs must be created in order of the data so use the list size as ID.
testData = new ArrayList<>();
- testData.add(new TestData(testData.size(), new double[0]));
+ testData.add(new TestData(testData.size(), new int[0]));
testData.add(new TestData(testData.size(), arrays[0]));
testData.add(new TestData(testData.size(), arrays[1]));
testData.add(new TestData(testData.size(), arrays[2]));
@@ -152,61 +166,72 @@ class DoubleStatisticsTest {
testData.add(new TestData(testData.size(), arrays[1], arrays[2]));
// Create reference expected results
expectedResult = new EnumMap<>(Statistic.class);
- addExpected(Statistic.MIN, Min::create, Min::of);
- addExpected(Statistic.MAX, Max::create, Max::of);
- addExpected(Statistic.MEAN, Mean::create, Mean::of);
- addExpected(Statistic.STANDARD_DEVIATION, StandardDeviation::create, StandardDeviation::of);
- addExpected(Statistic.VARIANCE, Variance::create, Variance::of);
- addExpected(Statistic.SKEWNESS, Skewness::create, Skewness::of);
- addExpected(Statistic.KURTOSIS, Kurtosis::create, Kurtosis::of);
- addExpected(Statistic.PRODUCT, Product::create, Product::of);
- addExpected(Statistic.SUM, Sum::create, Sum::of);
- addExpected(Statistic.SUM_OF_LOGS, SumOfLogs::create, SumOfLogs::of);
- addExpected(Statistic.SUM_OF_SQUARES, SumOfSquares::create, SumOfSquares::of);
- addExpected(Statistic.GEOMETRIC_MEAN, GeometricMean::create, GeometricMean::of);
+ addExpected(Statistic.MIN, IntMin::create, IntMin::of);
+ addExpected(Statistic.MAX, IntMax::create, IntMax::of);
+ addExpected(Statistic.MEAN, IntMean::create, IntMean::of);
+ addExpected(Statistic.STANDARD_DEVIATION, IntStandardDeviation::create, IntStandardDeviation::of);
+ addExpected(Statistic.VARIANCE, IntVariance::create, IntVariance::of);
+ addExpected(Statistic.SKEWNESS,
+ () -> DoubleAsIntStatistic.from(Skewness.create()),
+ x -> DoubleAsIntStatistic.from(Skewness.of(x)));
+ addExpected(Statistic.KURTOSIS,
+ () -> DoubleAsIntStatistic.from(Kurtosis.create()),
+ x -> DoubleAsIntStatistic.from(Kurtosis.of(x)));
+ addExpected(Statistic.PRODUCT,
+ () -> DoubleAsIntStatistic.from(Product.create()),
+ x -> DoubleAsIntStatistic.from(Product.of(x)));
+ addExpected(Statistic.SUM, IntSum::create, IntSum::of);
+ addExpected(Statistic.SUM_OF_LOGS,
+ () -> DoubleAsIntStatistic.from(SumOfLogs.create()),
+ x -> DoubleAsIntStatistic.from(SumOfLogs.of(x)));
+ addExpected(Statistic.SUM_OF_SQUARES, IntSumOfSquares::create, IntSumOfSquares::of);
+ addExpected(Statistic.GEOMETRIC_MEAN,
+ () -> DoubleAsIntStatistic.from(GeometricMean.create()),
+ x -> DoubleAsIntStatistic.from(GeometricMean.of(x)));
// Create co-computed statistics
coComputed = new EnumMap<>(Statistic.class);
Arrays.stream(Statistic.values()).forEach(s -> coComputed.put(s, EnumSet.of(s)));
addCoComputed(Statistic.GEOMETRIC_MEAN, Statistic.SUM_OF_LOGS);
addCoComputed(Statistic.VARIANCE, Statistic.STANDARD_DEVIATION);
+ addCoComputed(Statistic.SUM, Statistic.MEAN);
+ // Create statistics computed as part of the main statistic
+ addComputedBy(Statistic.VARIANCE, Statistic.SUM, Statistic.MEAN, Statistic.SUM_OF_SQUARES);
+ addComputedBy(Statistic.STANDARD_DEVIATION, Statistic.SUM, Statistic.MEAN, Statistic.SUM_OF_SQUARES);
// Cascade moments up
- EnumSet<Statistic> m = coComputed.get(Statistic.MEAN);
- coComputed.get(Statistic.STANDARD_DEVIATION).addAll(m);
- coComputed.get(Statistic.VARIANCE).addAll(m);
- m = coComputed.get(Statistic.VARIANCE);
- coComputed.get(Statistic.SKEWNESS).addAll(m);
- m = coComputed.get(Statistic.SKEWNESS);
- coComputed.get(Statistic.KURTOSIS).addAll(m);
+ coComputed.get(Statistic.KURTOSIS).add(Statistic.SKEWNESS);
+ // Combinations
+ coComputedAsCombination = new HashMap<>();
+ final EnumSet<Statistic> s = EnumSet.of(Statistic.VARIANCE, Statistic.STANDARD_DEVIATION);
+ coComputedAsCombination.put(EnumSet.of(Statistic.MEAN, Statistic.SUM_OF_SQUARES), s);
+ coComputedAsCombination.put(EnumSet.of(Statistic.SUM, Statistic.SUM_OF_SQUARES), s);
}
/**
* Adds the expected result for the specified {@code statistic}.
*
- * @param <T> {@link DoubleStatistic} being computed.
+ * @param <T> {@link IntStatistic} being computed.
* @param statistic Statistic.
* @param constructor Constructor for an empty object.
* @param arrayConstructor Constructor using an array of values.
*/
- private static <T extends DoubleStatistic & StatisticAccumulator<T>> void addExpected(Statistic statistic,
- Supplier<T> constructor, Function<double[], T> arrayConstructor) {
+ private static <T extends IntStatistic & StatisticAccumulator<T>> void addExpected(Statistic statistic,
+ Supplier<T> constructor, Function<int[], T> arrayConstructor) {
final List<ExpectedResult> results = new ArrayList<>();
for (final TestData d : testData) {
// Stream values
- final double e1 = d.stream()
+ final StatisticResult e1 = d.stream()
.map(values -> Statistics.add(constructor.get(), values))
.reduce(StatisticAccumulator::combine)
- .orElseThrow(IllegalStateException::new)
- .getAsDouble();
+ .orElseThrow(IllegalStateException::new);
// Create from array
- final double e2 = d.stream()
+ final StatisticResult e2 = d.stream()
.map(arrayConstructor)
.reduce(StatisticAccumulator::combine)
- .orElseThrow(IllegalStateException::new)
- .getAsDouble();
+ .orElseThrow(IllegalStateException::new);
// Check that there is a finite value to compute during testing
if (d.size() != 0) {
- assertFinite(e1, statistic);
- assertFinite(e2, statistic);
+ assertFinite(e1.getAsDouble(), statistic);
+ assertFinite(e2.getAsDouble(), statistic);
}
results.add(new ExpectedResult(e1, e2));
}
@@ -226,6 +251,17 @@ class DoubleStatisticsTest {
coComputed.get(s2).add(s1);
}
+ /**
+ * Adds the child statistics that are computed by the parent statistic to the co-computed mapping.
+ * The statistics are a one-way relationship (a computes b but b does not compute a).
+ *
+ * @param parent First statistic.
+ * @param children Computed statistics.
+ */
+ private static void addComputedBy(Statistic parent, Statistic... children) {
+ coComputed.get(parent).addAll(EnumSet.of(parent, children));
+ }
+
@AfterAll
static void teardown() {
// Free memory
@@ -251,7 +287,7 @@ class DoubleStatisticsTest {
}
/**
- * Test the {@link DoubleStatistics} when all data is passed as a stream of single values.
+ * Test the {@link IntStatistics} when all data is passed as a stream of single values.
*/
@ParameterizedTest
@MethodSource(value = {"streamTestData"})
@@ -262,21 +298,29 @@ class DoubleStatisticsTest {
}
/**
- * Test the {@link DoubleStatistics} when data is passed as a {@code double[]} of values.
+ * Test the {@link IntStatistics} when data is passed as a {@code int[]} of values.
*/
@ParameterizedTest
@MethodSource(value = {"streamTestData"})
void testArray(EnumSet<Statistic> stats, TestData data) {
// Test creation from a builder
- final DoubleStatistics.Builder builder = DoubleStatistics.builder(stats.toArray(EMPTY_STATISTIC_ARRAY));
+ final IntStatistics.Builder builder = IntStatistics.builder(stats.toArray(EMPTY_STATISTIC_ARRAY));
assertStatistics(stats, data, builder::build, ExpectedResult::getArray);
}
+ /**
+ * Assert the computed statistics match the expected result.
+ *
+ * @param stats Statistics that are computed.
+ * @param data Test data.
+ * @param constructor Constructor to create the {@link IntStatistics}.
+ * @param expected Function to obtain the expected result.
+ */
private static void assertStatistics(EnumSet<Statistic> stats, TestData data,
- Function<double[], DoubleStatistics> constructor,
- ToDoubleFunction<ExpectedResult> expected) {
+ Function<int[], IntStatistics> constructor,
+ Function<ExpectedResult, StatisticResult> expected) {
final Statistic[] statsArray = stats.toArray(EMPTY_STATISTIC_ARRAY);
- final DoubleStatistics statistics = data.stream()
+ final IntStatistics statistics = data.stream()
.map(constructor)
.reduce((a, b) -> combine(statsArray, a, b))
.orElseThrow(IllegalStateException::new);
@@ -284,56 +328,88 @@ class DoubleStatisticsTest {
Assertions.assertEquals(data.size(), statistics.getCount(), "Count");
final EnumSet<Statistic> computed = EnumSet.copyOf(stats);
stats.forEach(s -> computed.addAll(coComputed.get(s)));
+ // Currently we only have to support pairs here as the test only uses paired stats.
+ // Add an assertion to ensure this is fixed in future if required (e.g. using an
+ // enumeration of all possible combinations of the input stats as keys into
+ // coComputedAsCombination).
+ Assertions.assertFalse(stats.size() > 2, "Combinations other than pairs are not supported");
+ computed.addAll(coComputedAsCombination.getOrDefault(stats, EnumSet.noneOf(Statistic.class)));
// Test if the statistics are correctly identified as supported
- EnumSet.allOf(Statistic.class).forEach(s ->
- Assertions.assertEquals(computed.contains(s), statistics.isSupported(s),
- () -> stats + " isSupported -> " + s.toString()));
-
- // Test the values
- computed.forEach(s ->
- Assertions.assertEquals(expected.applyAsDouble(expectedResult.get(s).get(id)), statistics.get(s),
- () -> stats + " value -> " + s.toString()));
+ EnumSet.allOf(Statistic.class).forEach(s -> {
+ final boolean isSupported = computed.contains(s);
+ Assertions.assertEquals(isSupported, statistics.isSupported(s),
+ () -> stats + " isSupported -> " + s.toString());
+ if (isSupported) {
+ final StatisticResult result = expected.apply(expectedResult.get(s).get(id));
+ // Test individual values
+ TestHelper.assertDoubleEquals(result, () -> statistics.getAsDouble(s),
+ DoubleTolerances.equals(),
+ () -> stats + " getAsDouble -> " + s.toString());
+ TestHelper.assertIntEquals(result, () -> statistics.getAsInt(s),
+ () -> stats + " getAsInt -> " + s.toString());
+ TestHelper.assertLongEquals(result, () -> statistics.getAsLong(s),
+ () -> stats + " getAsLong -> " + s.toString());
+ TestHelper.assertBigIntegerEquals(result, () -> statistics.getAsBigInteger(s),
+ DoubleTolerances.equals(),
+ () -> stats + " getAsBigInteger -> " + s.toString());
+ // Test the values from the result
+ TestHelper.assertEquals(result, statistics.getResult(s),
+ DoubleTolerances.equals(),
+ () -> stats + " getResult -> " + s.toString());
+ } else {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getAsDouble(s),
+ () -> stats + " getAsDouble -> " + s.toString());
+ Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getAsInt(s),
+ () -> stats + " getAsInt -> " + s.toString());
+ Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getAsLong(s),
+ () -> stats + " getAsLong -> " + s.toString());
+ Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getAsBigInteger(s),
+ () -> stats + " getAsBigInteger -> " + s.toString());
+ Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getResult(s),
+ () -> stats + " getResult -> " + s.toString());
+ }
+ });
}
/**
* Add all the {@code values} to an aggregator of the {@code statistics}.
*
- * <p>This method verifies that the {@link DoubleStatistics#get(Statistic)} and
- * {@link DoubleStatistics#getSupplier(Statistic)} methods return the same
+ * <p>This method verifies that the {@link IntStatistics#getAsDouble(Statistic)} and
+ * {@link IntStatistics#getResult(Statistic)} methods return the same
* result as values are added.
*
- * @param statistic Statistics.
+ * @param statistics Statistics.
* @param values Values.
* @return the statistics
*/
- private static DoubleStatistics acceptAll(Statistic[] statistics, double[] values) {
- final DoubleStatistics stats = DoubleStatistics.of(statistics);
- final DoubleSupplier[] f = getSuppliers(statistics, stats);
- for (final double x : values) {
+ private static IntStatistics acceptAll(Statistic[] statistics, int[] values) {
+ final IntStatistics stats = IntStatistics.of(statistics);
+ final StatisticResult[] f = getResults(statistics, stats);
+ for (final int x : values) {
stats.accept(x);
for (int i = 0; i < statistics.length; i++) {
final Statistic s = statistics[i];
- Assertions.assertEquals(stats.get(s), f[i].getAsDouble(),
- () -> "Supplier(" + s + ") after value " + x);
+ Assertions.assertEquals(stats.getAsDouble(s), f[i].getAsDouble(),
+ () -> "StatisticResult(" + s + ") after value " + x);
}
}
return stats;
}
/**
- * Gets the suppliers for the {@code statistics}.
+ * Gets the results for the {@code statistics}.
*
* @param statistics Statistics to compute.
* @param stats Statistic aggregator.
- * @return the suppliers
+ * @return the results
*/
- private static DoubleSupplier[] getSuppliers(Statistic[] statistics, final DoubleStatistics stats) {
- final DoubleSupplier[] f = new DoubleSupplier[statistics.length];
+ private static StatisticResult[] getResults(Statistic[] statistics, final IntStatistics stats) {
+ final StatisticResult[] f = new StatisticResult[statistics.length];
for (int i = 0; i < statistics.length; i++) {
- final DoubleSupplier supplier = stats.getSupplier(statistics[i]);
- Assertions.assertFalse(supplier instanceof DoubleStatistic,
- () -> "DoubleStatistic instance: " + supplier.getClass().getSimpleName());
+ final StatisticResult supplier = stats.getResult(statistics[i]);
+ Assertions.assertFalse(supplier instanceof IntStatistic,
+ () -> "IntStatistic instance: " + supplier.getClass().getSimpleName());
f[i] = supplier;
}
return f;
@@ -342,153 +418,115 @@ class DoubleStatisticsTest {
/**
* Combine the two statistic aggregators.
*
- * <p>This method verifies that the {@link DoubleStatistics#get(Statistic)} and
- * {@link DoubleStatistics#getSupplier(Statistic)} methods return the same
- * result after the {@link DoubleStatistics#combine(DoubleStatistics)}.
+ * <p>This method verifies that the {@link IntStatistics#getAsDouble(Statistic)} and
+ * {@link IntStatistics#getResult(Statistic)} methods return the same
+ * result after the {@link IntStatistics#combine(IntStatistics)}.
*
* @param statistics Statistics to compute.
* @param s1 Statistic aggregator.
* @param s2 Statistic aggregator.
- * @return the double statistics
+ * @return the combined IntStatistics
*/
- private static DoubleStatistics combine(Statistic[] statistics,
- DoubleStatistics s1, DoubleStatistics s2) {
- final DoubleSupplier[] f = getSuppliers(statistics, s1);
+ private static IntStatistics combine(Statistic[] statistics,
+ IntStatistics s1, IntStatistics s2) {
+ final StatisticResult[] f = getResults(statistics, s1);
s1.combine(s2);
for (int i = 0; i < statistics.length; i++) {
final Statistic s = statistics[i];
- Assertions.assertEquals(s1.get(s), f[i].getAsDouble(),
- () -> "Supplier(" + s + ") after combine");
+ Assertions.assertEquals(s1.getAsDouble(s), f[i].getAsDouble(),
+ () -> "StatisticResult(" + s + ") after combine");
}
return s1;
}
- @Test
- void testNoOpConsumer() {
- final DoubleConsumer c = DoubleStatistics.NOOP;
- // Hit coverage
- c.accept(0);
- final double[] value = {0};
- final DoubleConsumer other = x -> value[0] = x;
- final DoubleConsumer combined = c.andThen(other);
- Assertions.assertSame(combined, other);
- final double y = 42;
- combined.accept(y);
- Assertions.assertEquals(y, value[0]);
- }
-
@Test
void testOfThrows() {
- Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.of());
- Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.of(EMPTY_STATISTIC_ARRAY));
- Assertions.assertThrows(NullPointerException.class, () -> DoubleStatistics.of(new Statistic[1]));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> IntStatistics.of());
+ Assertions.assertThrows(IllegalArgumentException.class, () -> IntStatistics.of(EMPTY_STATISTIC_ARRAY));
+ Assertions.assertThrows(NullPointerException.class, () -> IntStatistics.of(new Statistic[1]));
}
@Test
void testOfSetThrows() {
final EnumSet<Statistic> s1 = EnumSet.noneOf(Statistic.class);
- Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.of(s1));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> IntStatistics.of(s1));
final EnumSet<Statistic> s2 = null;
- Assertions.assertThrows(NullPointerException.class, () -> DoubleStatistics.of(s2));
+ Assertions.assertThrows(NullPointerException.class, () -> IntStatistics.of(s2));
final EnumSet<Statistic> s3 = EnumSet.of(Statistic.MIN);
- Assertions.assertThrows(NullPointerException.class, () -> DoubleStatistics.of(s3, null));
+ final int[] nullValues = null;
+ Assertions.assertThrows(NullPointerException.class, () -> IntStatistics.of(s3, nullValues));
}
@Test
void testBuilderThrows() {
- Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.builder());
- Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.builder(EMPTY_STATISTIC_ARRAY));
- Assertions.assertThrows(NullPointerException.class, () -> DoubleStatistics.builder(new Statistic[1]));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> IntStatistics.builder());
+ Assertions.assertThrows(IllegalArgumentException.class, () -> IntStatistics.builder(EMPTY_STATISTIC_ARRAY));
+ Assertions.assertThrows(NullPointerException.class, () -> IntStatistics.builder(new Statistic[1]));
}
@Test
void testIsSupportedWithNull() {
- DoubleStatistics s = DoubleStatistics.of(Statistic.MIN);
+ IntStatistics s = IntStatistics.of(Statistic.MIN);
Assertions.assertThrows(NullPointerException.class, () -> s.isSupported(null));
}
- @ParameterizedTest
- @MethodSource
- void testNotSupported(Statistic stat) {
- DoubleStatistics statistics = DoubleStatistics.of(stat);
- for (final Statistic s : Statistic.values()) {
- Assertions.assertEquals(s == stat, statistics.isSupported(s),
- () -> stat + " isSupported -> " + s.toString());
- if (s == stat) {
- Assertions.assertDoesNotThrow(() -> statistics.get(s),
- () -> stat + " get -> " + s.toString());
- Assertions.assertNotNull(statistics.getSupplier(s),
- () -> stat + " getSupplier -> " + s.toString());
- } else {
- Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.get(s),
- () -> stat + " get -> " + s.toString());
- Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getSupplier(s),
- () -> stat + " getSupplier -> " + s.toString());
- }
- }
- }
-
- static Statistic[] testNotSupported() {
- return new Statistic[] {Statistic.MIN, Statistic.PRODUCT};
- }
-
@ParameterizedTest
@MethodSource
void testIncompatibleCombineThrows(EnumSet<Statistic> stat1, EnumSet<Statistic> stat2) {
- final double[] v1 = {1, 2, 3.5, 6};
- final double[] v2 = {3, 4, 5};
- DoubleStatistics statistics = DoubleStatistics.of(stat1, v1);
- DoubleStatistics other = DoubleStatistics.of(stat2, v2);
+ final int[] v1 = {1, 2, 4, 6};
+ final int[] v2 = {3, 4, 5};
+ IntStatistics statistics = IntStatistics.of(stat1, v1);
+ IntStatistics other = IntStatistics.of(stat2, v2);
// Store values
- final double[] values = stat1.stream().mapToDouble(statistics::get).toArray();
+ final double[] values = stat1.stream().mapToDouble(statistics::getAsDouble).toArray();
Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.combine(other),
() -> stat1 + " " + stat2);
// Values should be unchanged
final int[] i = {0};
stat1.stream().forEach(
- s -> Assertions.assertEquals(values[i[0]++], statistics.get(s), () -> s + " changed"));
+ s -> Assertions.assertEquals(values[i[0]++], statistics.getAsDouble(s), () -> s + " changed"));
}
static Stream<Arguments> testIncompatibleCombineThrows() {
return Stream.of(
Arguments.of(EnumSet.of(Statistic.MIN), EnumSet.of(Statistic.PRODUCT)),
Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.MIN)),
- // Note: MEAN is compatible with VARIANCE. The combine is not symmetric.
- Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.MEAN))
+ // Note: SKEWNESS is compatible with KURTOSIS. The combine is not symmetric.
+ Arguments.of(EnumSet.of(Statistic.KURTOSIS), EnumSet.of(Statistic.SKEWNESS))
);
}
@ParameterizedTest
@MethodSource
void testCompatibleCombine(EnumSet<Statistic> stat1, EnumSet<Statistic> stat2) {
- final double[] v1 = {1, 2, 3.5, 6};
- final double[] v2 = {3, 4, 5};
- final DoubleStatistics statistics1 = DoubleStatistics.of(stat1, v1);
- final DoubleStatistics statistics2 = DoubleStatistics.of(stat1, v1);
+ final int[] v1 = {1, 2, 4, 6};
+ final int[] v2 = {3, 4, 5};
+ final IntStatistics statistics1 = IntStatistics.of(stat1, v1);
+ final IntStatistics statistics2 = IntStatistics.of(stat1, v1);
// Note: other1 is intentionally using the same flags as statistics1
- final DoubleStatistics other1 = DoubleStatistics.of(stat1, v2);
- final DoubleStatistics other2 = DoubleStatistics.of(stat2, v2);
+ final IntStatistics other1 = IntStatistics.of(stat1, v2);
+ final IntStatistics other2 = IntStatistics.of(stat2, v2);
// This should work
statistics1.combine(other1);
// This should be compatible
statistics2.combine(other2);
// The stats should be the same
for (final Statistic s : stat1) {
- final double expected = statistics1.get(s);
+ final double expected = statistics1.getAsDouble(s);
assertFinite(expected, s);
- Assertions.assertEquals(expected, statistics2.get(s), () -> s.toString());
+ Assertions.assertEquals(expected, statistics2.getAsDouble(s), () -> s.toString());
}
}
static Stream<Arguments> testCompatibleCombine() {
return Stream.of(
Arguments.of(EnumSet.of(Statistic.MEAN), EnumSet.of(Statistic.VARIANCE)),
- Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.SKEWNESS)),
- Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.KURTOSIS)),
+ Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.SUM, Statistic.SUM_OF_SQUARES)),
+ Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.STANDARD_DEVIATION)),
Arguments.of(EnumSet.of(Statistic.GEOMETRIC_MEAN), EnumSet.of(Statistic.SUM_OF_LOGS)),
// Compatible combinations
Arguments.of(EnumSet.of(Statistic.VARIANCE, Statistic.MIN, Statistic.SKEWNESS),
- EnumSet.of(Statistic.KURTOSIS, Statistic.MEAN, Statistic.MIN))
+ EnumSet.of(Statistic.KURTOSIS, Statistic.SUM_OF_SQUARES, Statistic.MEAN, Statistic.MIN))
);
}
@@ -498,8 +536,8 @@ class DoubleStatisticsTest {
@ParameterizedTest
@MethodSource
- void testBiased(Statistic stat, double[] values, boolean[] options, double[] results) {
- final DoubleStatistics statistics1 = DoubleStatistics.builder(stat).build(values);
+ void testBiased(Statistic stat, int[] values, boolean[] options, double[] results) {
+ final IntStatistics statistics1 = IntStatistics.builder(stat).build(values);
StatisticsConfiguration c = StatisticsConfiguration.withDefaults();
DoubleSupplier s = null;
@@ -510,32 +548,32 @@ class DoubleStatisticsTest {
c = c.withBiased(value);
Assertions.assertSame(statistics1, statistics1.setConfiguration(c));
- Assertions.assertEquals(results[i], statistics1.get(stat),
- () -> options[i] + " get: " + BaseDoubleStatisticTest.format(values));
- final DoubleSupplier s1 = statistics1.getSupplier(stat);
+ Assertions.assertEquals(results[i], statistics1.getAsDouble(stat),
+ () -> options[i] + " get: " + BaseIntStatisticTest.format(values));
+ final DoubleSupplier s1 = statistics1.getResult(stat);
Assertions.assertEquals(results[i], s1.getAsDouble(),
- () -> options[i] + " supplier: " + BaseDoubleStatisticTest.format(values));
+ () -> options[i] + " supplier: " + BaseIntStatisticTest.format(values));
// Config change does not propagate to previous supplier
if (s != null) {
final int j = (i - 1 + options.length) % options.length;
Assertions.assertEquals(results[j], s.getAsDouble(),
- () -> options[j] + " previous supplier: " + BaseDoubleStatisticTest.format(values));
+ () -> options[j] + " previous supplier: " + BaseIntStatisticTest.format(values));
}
s = s1;
// Set through the builder
- final DoubleStatistics statistics2 = DoubleStatistics.builder(stat)
+ final IntStatistics statistics2 = IntStatistics.builder(stat)
.setConfiguration(c).build(values);
- Assertions.assertEquals(results[i], statistics2.get(stat),
- () -> options[i] + " get via builder: " + BaseDoubleStatisticTest.format(values));
+ Assertions.assertEquals(results[i], statistics2.getAsDouble(stat),
+ () -> options[i] + " get via builder: " + BaseIntStatisticTest.format(values));
}
}
static Stream<Arguments> testBiased() {
final Stream.Builder<Arguments> builder = Stream.builder();
// Data must generate different results for different options
- final double[][] data = {
+ final int[][] data = {
{1},
{1, 2},
{1, 2, 4},
@@ -545,9 +583,9 @@ class DoubleStatisticsTest {
final Statistic[] stats = {Statistic.STANDARD_DEVIATION, Statistic.VARIANCE,
Statistic.SKEWNESS, Statistic.KURTOSIS};
final int[] diff = new int[stats.length];
- for (final double[] d : data) {
- diff[0] += addBooleanOptionCase(builder, d, stats[0], StandardDeviation::of, StandardDeviation::setBiased);
- diff[1] += addBooleanOptionCase(builder, d, stats[1], Variance::of, Variance::setBiased);
+ for (final int[] d : data) {
+ diff[0] += addBooleanOptionCase(builder, d, stats[0], IntStandardDeviation::of, IntStandardDeviation::setBiased);
+ diff[1] += addBooleanOptionCase(builder, d, stats[1], IntVariance::of, IntVariance::setBiased);
diff[2] += addBooleanOptionCase(builder, d, stats[2], Skewness::of, Skewness::setBiased);
diff[3] += addBooleanOptionCase(builder, d, stats[3], Kurtosis::of, Kurtosis::setBiased);
}
@@ -566,7 +604,7 @@ class DoubleStatisticsTest {
* <p>This method returns the number of unique results. It does not enforce that the
* results are different.
*
- * @param <T> {@link DoubleStatistic} being computed.
+ * @param <T> {@link StatisticResult} being computed.
* @param builder Argument builder.
* @param values Values.
* @param statistic Statistic.
@@ -574,9 +612,9 @@ class DoubleStatisticsTest {
* @param setter Option setter.
* @return the number of unique results
*/
- private static <T extends DoubleStatistic & StatisticAccumulator<T>> int addBooleanOptionCase(
- Stream.Builder<Arguments> builder, double[] values, Statistic statistic,
- Function<double[], T> arrayConstructor, BiConsumer<T, Boolean> setter) {
+ private static <T extends StatisticResult & StatisticAccumulator<T>> int addBooleanOptionCase(
+ Stream.Builder<Arguments> builder, int[] values, Statistic statistic,
+ Function<int[], T> arrayConstructor, BiConsumer<T, Boolean> setter) {
final T stat = arrayConstructor.apply(values);
final boolean[] options = {true, false};
final double[] results = new double[2];
@@ -589,4 +627,12 @@ class DoubleStatisticsTest {
builder.accept(Arguments.of(statistic, values, options, results));
return all.size();
}
+
+ @Test
+ void testMoments() {
+ // Hits coverage within the builder for when kurtosis is ordered before skewness
+ final IntStatistics s = IntStatistics.of(Statistic.KURTOSIS, Statistic.SKEWNESS);
+ Assertions.assertTrue(s.isSupported(Statistic.KURTOSIS));
+ Assertions.assertTrue(s.isSupported(Statistic.SKEWNESS));
+ }
}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongStatisticsTest.java
similarity index 54%
copy from commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java
copy to commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongStatisticsTest.java
index a73d855..4ea1ba6 100644
--- a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongStatisticsTest.java
@@ -21,18 +21,19 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.EnumSet;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BiConsumer;
-import java.util.function.DoubleConsumer;
import java.util.function.DoubleSupplier;
import java.util.function.Function;
import java.util.function.Supplier;
-import java.util.function.ToDoubleFunction;
-import java.util.stream.IntStream;
+import java.util.stream.LongStream;
import java.util.stream.Stream;
+import org.apache.commons.statistics.distribution.DoubleTolerances;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
@@ -42,12 +43,22 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
/**
- * Test for {@link DoubleStatistics}.
+ * Test for {@link LongStatistics}.
*
* <p>This class verifies that the statistics computed using the summary
* class are an exact match to the statistics computed individually.
+ *
+ * <p>For simplicity some tests use only the {@code double} result of the statistic.
+ * The full {@link StatisticResult} interface is asserted in the test of the array or
+ * stream of input data.
+ *
+ * <p>Note: Some integer statistics are identical if computed using an array or a stream,
+ * e.g. sum, min, mean. However those that use a {@code double} implementation may vary
+ * based on input order, e.g. product, skewness. This test computes expected results
+ * for array and stream variations for all statistics and does not distinguish these two
+ * cases.
*/
-class DoubleStatisticsTest {
+class LongStatisticsTest {
/** Empty statistic array. */
private static final Statistic[] EMPTY_STATISTIC_ARRAY = {};
@@ -57,9 +68,12 @@ class DoubleStatisticsTest {
/** The expected result for each statistic on the test data. */
private static EnumMap<Statistic, List<ExpectedResult>> expectedResult;
- /** The statistics that are co-computed. */
+ /** The statistics that are co-computed by a single statistic. */
private static EnumMap<Statistic, EnumSet<Statistic>> coComputed;
+ /** The statistics that are co-computed by a combination of statistics. */
+ private static Map<EnumSet<Statistic>, EnumSet<Statistic>> coComputedAsCombination;
+
/**
* Container for test data.
*/
@@ -67,7 +81,7 @@ class DoubleStatisticsTest {
/** Identifier. */
private final int id;
/** The sample values. */
- private final double[][] values;
+ private final long[][] values;
/** The number of values. */
private final long size;
@@ -75,7 +89,7 @@ class DoubleStatisticsTest {
* @param id Identifier.
* @param values Sample values.
*/
- TestData(int id, double[]... values) {
+ TestData(int id, long[]... values) {
this.id = id;
this.values = values;
size = Arrays.stream(values).mapToLong(x -> x.length).sum();
@@ -91,7 +105,7 @@ class DoubleStatisticsTest {
/**
* @return the values as a stream
*/
- Stream<double[]> stream() {
+ Stream<long[]> stream() {
return Arrays.stream(values);
}
@@ -108,15 +122,15 @@ class DoubleStatisticsTest {
*/
private static class ExpectedResult {
/** The expected result for a stream of values. */
- private final double stream;
+ private final StatisticResult stream;
/** The expected result for an array of values. */
- private final double array;
+ private final StatisticResult array;
/**
* @param stream Stream result.
* @param array Array result.
*/
- ExpectedResult(double stream, double array) {
+ ExpectedResult(StatisticResult stream, StatisticResult array) {
this.stream = stream;
this.array = array;
}
@@ -124,27 +138,27 @@ class DoubleStatisticsTest {
/**
* @return the expected result for a stream of values.
*/
- double getStream() {
+ StatisticResult getStream() {
return stream;
}
/**
* @return the expected result for an array of values.
*/
- double getArray() {
+ StatisticResult getArray() {
return array;
}
}
@BeforeAll
static void setup() {
- // Random double[] of different lengths
- final double[][] arrays = IntStream.of(4, 5, 7)
- .mapToObj(i -> ThreadLocalRandom.current().doubles(i, 2.25, 3.75).toArray())
- .toArray(double[][]::new);
- // Create test data. IDs must be created in order of the data.
+ // Random long[] of different lengths. Strictly positive so sum-of-logs is finite.
+ final long[][] arrays = LongStream.of(4, 5, 7)
+ .mapToObj(i -> ThreadLocalRandom.current().longs(i, 1, Long.MAX_VALUE).toArray())
+ .toArray(long[][]::new);
+ // Create test data. IDs must be created in order of the data so use the list size as ID.
testData = new ArrayList<>();
- testData.add(new TestData(testData.size(), new double[0]));
+ testData.add(new TestData(testData.size(), new long[0]));
testData.add(new TestData(testData.size(), arrays[0]));
testData.add(new TestData(testData.size(), arrays[1]));
testData.add(new TestData(testData.size(), arrays[2]));
@@ -152,61 +166,72 @@ class DoubleStatisticsTest {
testData.add(new TestData(testData.size(), arrays[1], arrays[2]));
// Create reference expected results
expectedResult = new EnumMap<>(Statistic.class);
- addExpected(Statistic.MIN, Min::create, Min::of);
- addExpected(Statistic.MAX, Max::create, Max::of);
- addExpected(Statistic.MEAN, Mean::create, Mean::of);
- addExpected(Statistic.STANDARD_DEVIATION, StandardDeviation::create, StandardDeviation::of);
- addExpected(Statistic.VARIANCE, Variance::create, Variance::of);
- addExpected(Statistic.SKEWNESS, Skewness::create, Skewness::of);
- addExpected(Statistic.KURTOSIS, Kurtosis::create, Kurtosis::of);
- addExpected(Statistic.PRODUCT, Product::create, Product::of);
- addExpected(Statistic.SUM, Sum::create, Sum::of);
- addExpected(Statistic.SUM_OF_LOGS, SumOfLogs::create, SumOfLogs::of);
- addExpected(Statistic.SUM_OF_SQUARES, SumOfSquares::create, SumOfSquares::of);
- addExpected(Statistic.GEOMETRIC_MEAN, GeometricMean::create, GeometricMean::of);
+ addExpected(Statistic.MIN, LongMin::create, LongMin::of);
+ addExpected(Statistic.MAX, LongMax::create, LongMax::of);
+ addExpected(Statistic.MEAN, LongMean::create, LongMean::of);
+ addExpected(Statistic.STANDARD_DEVIATION, LongStandardDeviation::create, LongStandardDeviation::of);
+ addExpected(Statistic.VARIANCE, LongVariance::create, LongVariance::of);
+ addExpected(Statistic.SKEWNESS,
+ () -> DoubleAsLongStatistic.from(Skewness.create()),
+ x -> DoubleAsLongStatistic.from(Skewness.of(x)));
+ addExpected(Statistic.KURTOSIS,
+ () -> DoubleAsLongStatistic.from(Kurtosis.create()),
+ x -> DoubleAsLongStatistic.from(Kurtosis.of(x)));
+ addExpected(Statistic.PRODUCT,
+ () -> DoubleAsLongStatistic.from(Product.create()),
+ x -> DoubleAsLongStatistic.from(Product.of(x)));
+ addExpected(Statistic.SUM, LongSum::create, LongSum::of);
+ addExpected(Statistic.SUM_OF_LOGS,
+ () -> DoubleAsLongStatistic.from(SumOfLogs.create()),
+ x -> DoubleAsLongStatistic.from(SumOfLogs.of(x)));
+ addExpected(Statistic.SUM_OF_SQUARES, LongSumOfSquares::create, LongSumOfSquares::of);
+ addExpected(Statistic.GEOMETRIC_MEAN,
+ () -> DoubleAsLongStatistic.from(GeometricMean.create()),
+ x -> DoubleAsLongStatistic.from(GeometricMean.of(x)));
// Create co-computed statistics
coComputed = new EnumMap<>(Statistic.class);
Arrays.stream(Statistic.values()).forEach(s -> coComputed.put(s, EnumSet.of(s)));
addCoComputed(Statistic.GEOMETRIC_MEAN, Statistic.SUM_OF_LOGS);
addCoComputed(Statistic.VARIANCE, Statistic.STANDARD_DEVIATION);
+ addCoComputed(Statistic.SUM, Statistic.MEAN);
+ // Create statistics computed as part of the main statistic
+ addComputedBy(Statistic.VARIANCE, Statistic.SUM, Statistic.MEAN, Statistic.SUM_OF_SQUARES);
+ addComputedBy(Statistic.STANDARD_DEVIATION, Statistic.SUM, Statistic.MEAN, Statistic.SUM_OF_SQUARES);
// Cascade moments up
- EnumSet<Statistic> m = coComputed.get(Statistic.MEAN);
- coComputed.get(Statistic.STANDARD_DEVIATION).addAll(m);
- coComputed.get(Statistic.VARIANCE).addAll(m);
- m = coComputed.get(Statistic.VARIANCE);
- coComputed.get(Statistic.SKEWNESS).addAll(m);
- m = coComputed.get(Statistic.SKEWNESS);
- coComputed.get(Statistic.KURTOSIS).addAll(m);
+ coComputed.get(Statistic.KURTOSIS).add(Statistic.SKEWNESS);
+ // Combinations
+ coComputedAsCombination = new HashMap<>();
+ final EnumSet<Statistic> s = EnumSet.of(Statistic.VARIANCE, Statistic.STANDARD_DEVIATION);
+ coComputedAsCombination.put(EnumSet.of(Statistic.MEAN, Statistic.SUM_OF_SQUARES), s);
+ coComputedAsCombination.put(EnumSet.of(Statistic.SUM, Statistic.SUM_OF_SQUARES), s);
}
/**
* Adds the expected result for the specified {@code statistic}.
*
- * @param <T> {@link DoubleStatistic} being computed.
+ * @param <T> {@link LongStatistic} being computed.
* @param statistic Statistic.
* @param constructor Constructor for an empty object.
* @param arrayConstructor Constructor using an array of values.
*/
- private static <T extends DoubleStatistic & StatisticAccumulator<T>> void addExpected(Statistic statistic,
- Supplier<T> constructor, Function<double[], T> arrayConstructor) {
+ private static <T extends LongStatistic & StatisticAccumulator<T>> void addExpected(Statistic statistic,
+ Supplier<T> constructor, Function<long[], T> arrayConstructor) {
final List<ExpectedResult> results = new ArrayList<>();
for (final TestData d : testData) {
// Stream values
- final double e1 = d.stream()
+ final StatisticResult e1 = d.stream()
.map(values -> Statistics.add(constructor.get(), values))
.reduce(StatisticAccumulator::combine)
- .orElseThrow(IllegalStateException::new)
- .getAsDouble();
+ .orElseThrow(IllegalStateException::new);
// Create from array
- final double e2 = d.stream()
+ final StatisticResult e2 = d.stream()
.map(arrayConstructor)
.reduce(StatisticAccumulator::combine)
- .orElseThrow(IllegalStateException::new)
- .getAsDouble();
+ .orElseThrow(IllegalStateException::new);
// Check that there is a finite value to compute during testing
if (d.size() != 0) {
- assertFinite(e1, statistic);
- assertFinite(e2, statistic);
+ assertFinite(e1.getAsDouble(), statistic);
+ assertFinite(e2.getAsDouble(), statistic);
}
results.add(new ExpectedResult(e1, e2));
}
@@ -226,6 +251,17 @@ class DoubleStatisticsTest {
coComputed.get(s2).add(s1);
}
+ /**
+ * Adds the child statistics that are computed by the parent statistic to the co-computed mapping.
+ * The statistics are a one-way relationship (a computes b but b does not compute a).
+ *
+ * @param parent First statistic.
+ * @param children Computed statistics.
+ */
+ private static void addComputedBy(Statistic parent, Statistic... children) {
+ coComputed.get(parent).addAll(EnumSet.of(parent, children));
+ }
+
@AfterAll
static void teardown() {
// Free memory
@@ -251,7 +287,7 @@ class DoubleStatisticsTest {
}
/**
- * Test the {@link DoubleStatistics} when all data is passed as a stream of single values.
+ * Test the {@link LongStatistics} when all data is passed as a stream of single values.
*/
@ParameterizedTest
@MethodSource(value = {"streamTestData"})
@@ -262,21 +298,29 @@ class DoubleStatisticsTest {
}
/**
- * Test the {@link DoubleStatistics} when data is passed as a {@code double[]} of values.
+ * Test the {@link LongStatistics} when data is passed as a {@code long[]} of values.
*/
@ParameterizedTest
@MethodSource(value = {"streamTestData"})
void testArray(EnumSet<Statistic> stats, TestData data) {
// Test creation from a builder
- final DoubleStatistics.Builder builder = DoubleStatistics.builder(stats.toArray(EMPTY_STATISTIC_ARRAY));
+ final LongStatistics.Builder builder = LongStatistics.builder(stats.toArray(EMPTY_STATISTIC_ARRAY));
assertStatistics(stats, data, builder::build, ExpectedResult::getArray);
}
+ /**
+ * Assert the computed statistics match the expected result.
+ *
+ * @param stats Statistics that are computed.
+ * @param data Test data.
+ * @param constructor Constructor to create the {@link LongStatistics}.
+ * @param expected Function to obtain the expected result.
+ */
private static void assertStatistics(EnumSet<Statistic> stats, TestData data,
- Function<double[], DoubleStatistics> constructor,
- ToDoubleFunction<ExpectedResult> expected) {
+ Function<long[], LongStatistics> constructor,
+ Function<ExpectedResult, StatisticResult> expected) {
final Statistic[] statsArray = stats.toArray(EMPTY_STATISTIC_ARRAY);
- final DoubleStatistics statistics = data.stream()
+ final LongStatistics statistics = data.stream()
.map(constructor)
.reduce((a, b) -> combine(statsArray, a, b))
.orElseThrow(IllegalStateException::new);
@@ -284,56 +328,88 @@ class DoubleStatisticsTest {
Assertions.assertEquals(data.size(), statistics.getCount(), "Count");
final EnumSet<Statistic> computed = EnumSet.copyOf(stats);
stats.forEach(s -> computed.addAll(coComputed.get(s)));
+ // Currently we only have to support pairs here as the test only uses paired stats.
+ // Add an assertion to ensure this is fixed in future if required (e.g. using an
+ // enumeration of all possible combinations of the input stats as keys into
+ // coComputedAsCombination).
+ Assertions.assertFalse(stats.size() > 2, "Combinations other than pairs are not supported");
+ computed.addAll(coComputedAsCombination.getOrDefault(stats, EnumSet.noneOf(Statistic.class)));
// Test if the statistics are correctly identified as supported
- EnumSet.allOf(Statistic.class).forEach(s ->
- Assertions.assertEquals(computed.contains(s), statistics.isSupported(s),
- () -> stats + " isSupported -> " + s.toString()));
-
- // Test the values
- computed.forEach(s ->
- Assertions.assertEquals(expected.applyAsDouble(expectedResult.get(s).get(id)), statistics.get(s),
- () -> stats + " value -> " + s.toString()));
+ EnumSet.allOf(Statistic.class).forEach(s -> {
+ final boolean isSupported = computed.contains(s);
+ Assertions.assertEquals(isSupported, statistics.isSupported(s),
+ () -> stats + " isSupported -> " + s.toString());
+ if (isSupported) {
+ final StatisticResult result = expected.apply(expectedResult.get(s).get(id));
+ // Test individual values
+ TestHelper.assertDoubleEquals(result, () -> statistics.getAsDouble(s),
+ DoubleTolerances.equals(),
+ () -> stats + " getAsDouble -> " + s.toString());
+ TestHelper.assertLongEquals(result, () -> statistics.getAsLong(s),
+ () -> stats + " getAsLong -> " + s.toString());
+ TestHelper.assertLongEquals(result, () -> statistics.getAsLong(s),
+ () -> stats + " getAsLong -> " + s.toString());
+ TestHelper.assertBigIntegerEquals(result, () -> statistics.getAsBigInteger(s),
+ DoubleTolerances.equals(),
+ () -> stats + " getAsBigInteger -> " + s.toString());
+ // Test the values from the result
+ TestHelper.assertEquals(result, statistics.getResult(s),
+ DoubleTolerances.equals(),
+ () -> stats + " getResult -> " + s.toString());
+ } else {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getAsDouble(s),
+ () -> stats + " getAsDouble -> " + s.toString());
+ Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getAsLong(s),
+ () -> stats + " getAsLong -> " + s.toString());
+ Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getAsLong(s),
+ () -> stats + " getAsLong -> " + s.toString());
+ Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getAsBigInteger(s),
+ () -> stats + " getAsBigInteger -> " + s.toString());
+ Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getResult(s),
+ () -> stats + " getResult -> " + s.toString());
+ }
+ });
}
/**
* Add all the {@code values} to an aggregator of the {@code statistics}.
*
- * <p>This method verifies that the {@link DoubleStatistics#get(Statistic)} and
- * {@link DoubleStatistics#getSupplier(Statistic)} methods return the same
+ * <p>This method verifies that the {@link LongStatistics#getAsDouble(Statistic)} and
+ * {@link LongStatistics#getResult(Statistic)} methods return the same
* result as values are added.
*
- * @param statistic Statistics.
+ * @param statistics Statistics.
* @param values Values.
* @return the statistics
*/
- private static DoubleStatistics acceptAll(Statistic[] statistics, double[] values) {
- final DoubleStatistics stats = DoubleStatistics.of(statistics);
- final DoubleSupplier[] f = getSuppliers(statistics, stats);
- for (final double x : values) {
+ private static LongStatistics acceptAll(Statistic[] statistics, long[] values) {
+ final LongStatistics stats = LongStatistics.of(statistics);
+ final StatisticResult[] f = getResults(statistics, stats);
+ for (final long x : values) {
stats.accept(x);
for (int i = 0; i < statistics.length; i++) {
final Statistic s = statistics[i];
- Assertions.assertEquals(stats.get(s), f[i].getAsDouble(),
- () -> "Supplier(" + s + ") after value " + x);
+ Assertions.assertEquals(stats.getAsDouble(s), f[i].getAsDouble(),
+ () -> "StatisticResult(" + s + ") after value " + x);
}
}
return stats;
}
/**
- * Gets the suppliers for the {@code statistics}.
+ * Gets the results for the {@code statistics}.
*
* @param statistics Statistics to compute.
* @param stats Statistic aggregator.
- * @return the suppliers
+ * @return the results
*/
- private static DoubleSupplier[] getSuppliers(Statistic[] statistics, final DoubleStatistics stats) {
- final DoubleSupplier[] f = new DoubleSupplier[statistics.length];
+ private static StatisticResult[] getResults(Statistic[] statistics, final LongStatistics stats) {
+ final StatisticResult[] f = new StatisticResult[statistics.length];
for (int i = 0; i < statistics.length; i++) {
- final DoubleSupplier supplier = stats.getSupplier(statistics[i]);
- Assertions.assertFalse(supplier instanceof DoubleStatistic,
- () -> "DoubleStatistic instance: " + supplier.getClass().getSimpleName());
+ final StatisticResult supplier = stats.getResult(statistics[i]);
+ Assertions.assertFalse(supplier instanceof LongStatistic,
+ () -> "LongStatistic instance: " + supplier.getClass().getSimpleName());
f[i] = supplier;
}
return f;
@@ -342,153 +418,115 @@ class DoubleStatisticsTest {
/**
* Combine the two statistic aggregators.
*
- * <p>This method verifies that the {@link DoubleStatistics#get(Statistic)} and
- * {@link DoubleStatistics#getSupplier(Statistic)} methods return the same
- * result after the {@link DoubleStatistics#combine(DoubleStatistics)}.
+ * <p>This method verifies that the {@link LongStatistics#getAsDouble(Statistic)} and
+ * {@link LongStatistics#getResult(Statistic)} methods return the same
+ * result after the {@link LongStatistics#combine(LongStatistics)}.
*
* @param statistics Statistics to compute.
* @param s1 Statistic aggregator.
* @param s2 Statistic aggregator.
- * @return the double statistics
+ * @return the combined LongStatistics
*/
- private static DoubleStatistics combine(Statistic[] statistics,
- DoubleStatistics s1, DoubleStatistics s2) {
- final DoubleSupplier[] f = getSuppliers(statistics, s1);
+ private static LongStatistics combine(Statistic[] statistics,
+ LongStatistics s1, LongStatistics s2) {
+ final StatisticResult[] f = getResults(statistics, s1);
s1.combine(s2);
for (int i = 0; i < statistics.length; i++) {
final Statistic s = statistics[i];
- Assertions.assertEquals(s1.get(s), f[i].getAsDouble(),
- () -> "Supplier(" + s + ") after combine");
+ Assertions.assertEquals(s1.getAsDouble(s), f[i].getAsDouble(),
+ () -> "StatisticResult(" + s + ") after combine");
}
return s1;
}
- @Test
- void testNoOpConsumer() {
- final DoubleConsumer c = DoubleStatistics.NOOP;
- // Hit coverage
- c.accept(0);
- final double[] value = {0};
- final DoubleConsumer other = x -> value[0] = x;
- final DoubleConsumer combined = c.andThen(other);
- Assertions.assertSame(combined, other);
- final double y = 42;
- combined.accept(y);
- Assertions.assertEquals(y, value[0]);
- }
-
@Test
void testOfThrows() {
- Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.of());
- Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.of(EMPTY_STATISTIC_ARRAY));
- Assertions.assertThrows(NullPointerException.class, () -> DoubleStatistics.of(new Statistic[1]));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> LongStatistics.of());
+ Assertions.assertThrows(IllegalArgumentException.class, () -> LongStatistics.of(EMPTY_STATISTIC_ARRAY));
+ Assertions.assertThrows(NullPointerException.class, () -> LongStatistics.of(new Statistic[1]));
}
@Test
void testOfSetThrows() {
final EnumSet<Statistic> s1 = EnumSet.noneOf(Statistic.class);
- Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.of(s1));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> LongStatistics.of(s1));
final EnumSet<Statistic> s2 = null;
- Assertions.assertThrows(NullPointerException.class, () -> DoubleStatistics.of(s2));
+ Assertions.assertThrows(NullPointerException.class, () -> LongStatistics.of(s2));
final EnumSet<Statistic> s3 = EnumSet.of(Statistic.MIN);
- Assertions.assertThrows(NullPointerException.class, () -> DoubleStatistics.of(s3, null));
+ final long[] nullValues = null;
+ Assertions.assertThrows(NullPointerException.class, () -> LongStatistics.of(s3, nullValues));
}
@Test
void testBuilderThrows() {
- Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.builder());
- Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.builder(EMPTY_STATISTIC_ARRAY));
- Assertions.assertThrows(NullPointerException.class, () -> DoubleStatistics.builder(new Statistic[1]));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> LongStatistics.builder());
+ Assertions.assertThrows(IllegalArgumentException.class, () -> LongStatistics.builder(EMPTY_STATISTIC_ARRAY));
+ Assertions.assertThrows(NullPointerException.class, () -> LongStatistics.builder(new Statistic[1]));
}
@Test
void testIsSupportedWithNull() {
- DoubleStatistics s = DoubleStatistics.of(Statistic.MIN);
+ LongStatistics s = LongStatistics.of(Statistic.MIN);
Assertions.assertThrows(NullPointerException.class, () -> s.isSupported(null));
}
- @ParameterizedTest
- @MethodSource
- void testNotSupported(Statistic stat) {
- DoubleStatistics statistics = DoubleStatistics.of(stat);
- for (final Statistic s : Statistic.values()) {
- Assertions.assertEquals(s == stat, statistics.isSupported(s),
- () -> stat + " isSupported -> " + s.toString());
- if (s == stat) {
- Assertions.assertDoesNotThrow(() -> statistics.get(s),
- () -> stat + " get -> " + s.toString());
- Assertions.assertNotNull(statistics.getSupplier(s),
- () -> stat + " getSupplier -> " + s.toString());
- } else {
- Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.get(s),
- () -> stat + " get -> " + s.toString());
- Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getSupplier(s),
- () -> stat + " getSupplier -> " + s.toString());
- }
- }
- }
-
- static Statistic[] testNotSupported() {
- return new Statistic[] {Statistic.MIN, Statistic.PRODUCT};
- }
-
@ParameterizedTest
@MethodSource
void testIncompatibleCombineThrows(EnumSet<Statistic> stat1, EnumSet<Statistic> stat2) {
- final double[] v1 = {1, 2, 3.5, 6};
- final double[] v2 = {3, 4, 5};
- DoubleStatistics statistics = DoubleStatistics.of(stat1, v1);
- DoubleStatistics other = DoubleStatistics.of(stat2, v2);
+ final long[] v1 = {1, 2, 4, 6};
+ final long[] v2 = {3, 4, 5};
+ LongStatistics statistics = LongStatistics.of(stat1, v1);
+ LongStatistics other = LongStatistics.of(stat2, v2);
// Store values
- final double[] values = stat1.stream().mapToDouble(statistics::get).toArray();
+ final double[] values = stat1.stream().mapToDouble(statistics::getAsDouble).toArray();
Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.combine(other),
() -> stat1 + " " + stat2);
// Values should be unchanged
final int[] i = {0};
stat1.stream().forEach(
- s -> Assertions.assertEquals(values[i[0]++], statistics.get(s), () -> s + " changed"));
+ s -> Assertions.assertEquals(values[i[0]++], statistics.getAsDouble(s), () -> s + " changed"));
}
static Stream<Arguments> testIncompatibleCombineThrows() {
return Stream.of(
Arguments.of(EnumSet.of(Statistic.MIN), EnumSet.of(Statistic.PRODUCT)),
Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.MIN)),
- // Note: MEAN is compatible with VARIANCE. The combine is not symmetric.
- Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.MEAN))
+ // Note: SKEWNESS is compatible with KURTOSIS. The combine is not symmetric.
+ Arguments.of(EnumSet.of(Statistic.KURTOSIS), EnumSet.of(Statistic.SKEWNESS))
);
}
@ParameterizedTest
@MethodSource
void testCompatibleCombine(EnumSet<Statistic> stat1, EnumSet<Statistic> stat2) {
- final double[] v1 = {1, 2, 3.5, 6};
- final double[] v2 = {3, 4, 5};
- final DoubleStatistics statistics1 = DoubleStatistics.of(stat1, v1);
- final DoubleStatistics statistics2 = DoubleStatistics.of(stat1, v1);
+ final long[] v1 = {1, 2, 4, 6};
+ final long[] v2 = {3, 4, 5};
+ final LongStatistics statistics1 = LongStatistics.of(stat1, v1);
+ final LongStatistics statistics2 = LongStatistics.of(stat1, v1);
// Note: other1 is intentionally using the same flags as statistics1
- final DoubleStatistics other1 = DoubleStatistics.of(stat1, v2);
- final DoubleStatistics other2 = DoubleStatistics.of(stat2, v2);
+ final LongStatistics other1 = LongStatistics.of(stat1, v2);
+ final LongStatistics other2 = LongStatistics.of(stat2, v2);
// This should work
statistics1.combine(other1);
// This should be compatible
statistics2.combine(other2);
// The stats should be the same
for (final Statistic s : stat1) {
- final double expected = statistics1.get(s);
+ final double expected = statistics1.getAsDouble(s);
assertFinite(expected, s);
- Assertions.assertEquals(expected, statistics2.get(s), () -> s.toString());
+ Assertions.assertEquals(expected, statistics2.getAsDouble(s), () -> s.toString());
}
}
static Stream<Arguments> testCompatibleCombine() {
return Stream.of(
Arguments.of(EnumSet.of(Statistic.MEAN), EnumSet.of(Statistic.VARIANCE)),
- Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.SKEWNESS)),
- Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.KURTOSIS)),
+ Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.SUM, Statistic.SUM_OF_SQUARES)),
+ Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.STANDARD_DEVIATION)),
Arguments.of(EnumSet.of(Statistic.GEOMETRIC_MEAN), EnumSet.of(Statistic.SUM_OF_LOGS)),
// Compatible combinations
Arguments.of(EnumSet.of(Statistic.VARIANCE, Statistic.MIN, Statistic.SKEWNESS),
- EnumSet.of(Statistic.KURTOSIS, Statistic.MEAN, Statistic.MIN))
+ EnumSet.of(Statistic.KURTOSIS, Statistic.SUM_OF_SQUARES, Statistic.MEAN, Statistic.MIN))
);
}
@@ -498,8 +536,8 @@ class DoubleStatisticsTest {
@ParameterizedTest
@MethodSource
- void testBiased(Statistic stat, double[] values, boolean[] options, double[] results) {
- final DoubleStatistics statistics1 = DoubleStatistics.builder(stat).build(values);
+ void testBiased(Statistic stat, long[] values, boolean[] options, double[] results) {
+ final LongStatistics statistics1 = LongStatistics.builder(stat).build(values);
StatisticsConfiguration c = StatisticsConfiguration.withDefaults();
DoubleSupplier s = null;
@@ -510,32 +548,32 @@ class DoubleStatisticsTest {
c = c.withBiased(value);
Assertions.assertSame(statistics1, statistics1.setConfiguration(c));
- Assertions.assertEquals(results[i], statistics1.get(stat),
- () -> options[i] + " get: " + BaseDoubleStatisticTest.format(values));
- final DoubleSupplier s1 = statistics1.getSupplier(stat);
+ Assertions.assertEquals(results[i], statistics1.getAsDouble(stat),
+ () -> options[i] + " get: " + BaseLongStatisticTest.format(values));
+ final DoubleSupplier s1 = statistics1.getResult(stat);
Assertions.assertEquals(results[i], s1.getAsDouble(),
- () -> options[i] + " supplier: " + BaseDoubleStatisticTest.format(values));
+ () -> options[i] + " supplier: " + BaseLongStatisticTest.format(values));
// Config change does not propagate to previous supplier
if (s != null) {
final int j = (i - 1 + options.length) % options.length;
Assertions.assertEquals(results[j], s.getAsDouble(),
- () -> options[j] + " previous supplier: " + BaseDoubleStatisticTest.format(values));
+ () -> options[j] + " previous supplier: " + BaseLongStatisticTest.format(values));
}
s = s1;
// Set through the builder
- final DoubleStatistics statistics2 = DoubleStatistics.builder(stat)
+ final LongStatistics statistics2 = LongStatistics.builder(stat)
.setConfiguration(c).build(values);
- Assertions.assertEquals(results[i], statistics2.get(stat),
- () -> options[i] + " get via builder: " + BaseDoubleStatisticTest.format(values));
+ Assertions.assertEquals(results[i], statistics2.getAsDouble(stat),
+ () -> options[i] + " get via builder: " + BaseLongStatisticTest.format(values));
}
}
static Stream<Arguments> testBiased() {
final Stream.Builder<Arguments> builder = Stream.builder();
// Data must generate different results for different options
- final double[][] data = {
+ final long[][] data = {
{1},
{1, 2},
{1, 2, 4},
@@ -544,10 +582,10 @@ class DoubleStatisticsTest {
};
final Statistic[] stats = {Statistic.STANDARD_DEVIATION, Statistic.VARIANCE,
Statistic.SKEWNESS, Statistic.KURTOSIS};
- final int[] diff = new int[stats.length];
- for (final double[] d : data) {
- diff[0] += addBooleanOptionCase(builder, d, stats[0], StandardDeviation::of, StandardDeviation::setBiased);
- diff[1] += addBooleanOptionCase(builder, d, stats[1], Variance::of, Variance::setBiased);
+ final long[] diff = new long[stats.length];
+ for (final long[] d : data) {
+ diff[0] += addBooleanOptionCase(builder, d, stats[0], LongStandardDeviation::of, LongStandardDeviation::setBiased);
+ diff[1] += addBooleanOptionCase(builder, d, stats[1], LongVariance::of, LongVariance::setBiased);
diff[2] += addBooleanOptionCase(builder, d, stats[2], Skewness::of, Skewness::setBiased);
diff[3] += addBooleanOptionCase(builder, d, stats[3], Kurtosis::of, Kurtosis::setBiased);
}
@@ -566,7 +604,7 @@ class DoubleStatisticsTest {
* <p>This method returns the number of unique results. It does not enforce that the
* results are different.
*
- * @param <T> {@link DoubleStatistic} being computed.
+ * @param <T> {@link StatisticResult} being computed.
* @param builder Argument builder.
* @param values Values.
* @param statistic Statistic.
@@ -574,9 +612,9 @@ class DoubleStatisticsTest {
* @param setter Option setter.
* @return the number of unique results
*/
- private static <T extends DoubleStatistic & StatisticAccumulator<T>> int addBooleanOptionCase(
- Stream.Builder<Arguments> builder, double[] values, Statistic statistic,
- Function<double[], T> arrayConstructor, BiConsumer<T, Boolean> setter) {
+ private static <T extends StatisticResult & StatisticAccumulator<T>> int addBooleanOptionCase(
+ Stream.Builder<Arguments> builder, long[] values, Statistic statistic,
+ Function<long[], T> arrayConstructor, BiConsumer<T, Boolean> setter) {
final T stat = arrayConstructor.apply(values);
final boolean[] options = {true, false};
final double[] results = new double[2];
@@ -589,4 +627,12 @@ class DoubleStatisticsTest {
builder.accept(Arguments.of(statistic, values, options, results));
return all.size();
}
+
+ @Test
+ void testMoments() {
+ // Hits coverage within the builder for when kurtosis is ordered before skewness
+ final LongStatistics s = LongStatistics.of(Statistic.KURTOSIS, Statistic.SKEWNESS);
+ Assertions.assertTrue(s.isSupported(Statistic.KURTOSIS));
+ Assertions.assertTrue(s.isSupported(Statistic.SKEWNESS));
+ }
}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/StatisticsTest.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/StatisticsTest.java
new file mode 100644
index 0000000..d1dc7e1
--- /dev/null
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/StatisticsTest.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.statistics.descriptive;
+
+import java.util.function.DoubleConsumer;
+import java.util.function.IntConsumer;
+import java.util.function.LongConsumer;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test for {@link Statistics} utilities.
+ */
+class StatisticsTest {
+ @Test
+ void testNoOpDoubleConsumer() {
+ final DoubleConsumer c = Statistics.DOUBLE_NOOP;
+ // Hit coverage
+ c.accept(0);
+ final double[] value = {0};
+ final DoubleConsumer other = x -> value[0] = x;
+ final DoubleConsumer combined = c.andThen(other);
+ Assertions.assertSame(combined, other);
+ final double y = 42;
+ combined.accept(y);
+ Assertions.assertEquals(y, value[0]);
+ }
+
+ @Test
+ void testComposeDoubleConsumers() {
+ Assertions.assertNull(Statistics.compose((DoubleConsumer) null));
+ Assertions.assertNull(Statistics.compose((DoubleConsumer) null, (DoubleConsumer) null));
+ final double[] v1 = {0};
+ final double[] v2 = {0};
+ final DoubleConsumer c1 = x -> v1[0] = x;
+ final DoubleConsumer c2 = x -> v2[0] = x;
+ final DoubleConsumer combined = Statistics.compose(c1, c2);
+ final double y = 42;
+ combined.accept(y);
+ Assertions.assertEquals(y, v1[0]);
+ Assertions.assertEquals(y, v2[0]);
+ }
+
+ @Test
+ void testNoOpIntConsumer() {
+ final IntConsumer c = Statistics.INT_NOOP;
+ // Hit coverage
+ c.accept(0);
+ final int[] value = {0};
+ final IntConsumer other = x -> value[0] = x;
+ final IntConsumer combined = c.andThen(other);
+ Assertions.assertSame(combined, other);
+ final int y = 42;
+ combined.accept(y);
+ Assertions.assertEquals(y, value[0]);
+ }
+
+ @Test
+ void testComposeIntConsumers() {
+ Assertions.assertNull(Statistics.compose((IntConsumer) null));
+ Assertions.assertNull(Statistics.compose((IntConsumer) null, (IntConsumer) null));
+ final int[] v1 = {0};
+ final int[] v2 = {0};
+ final IntConsumer c1 = x -> v1[0] = x;
+ final IntConsumer c2 = x -> v2[0] = x;
+ final IntConsumer combined = Statistics.compose(c1, c2);
+ final int y = 42;
+ combined.accept(y);
+ Assertions.assertEquals(y, v1[0]);
+ Assertions.assertEquals(y, v2[0]);
+ }
+
+ @Test
+ void testNoOpLongConsumer() {
+ final LongConsumer c = Statistics.LONG_NOOP;
+ // Hit coverage
+ c.accept(0);
+ final long[] value = {0};
+ final LongConsumer other = x -> value[0] = x;
+ final LongConsumer combined = c.andThen(other);
+ Assertions.assertSame(combined, other);
+ final long y = 42;
+ combined.accept(y);
+ Assertions.assertEquals(y, value[0]);
+ }
+
+ @Test
+ void testComposeLongConsumers() {
+ Assertions.assertNull(Statistics.compose((LongConsumer) null));
+ Assertions.assertNull(Statistics.compose((LongConsumer) null, (LongConsumer) null));
+ final long[] v1 = {0};
+ final long[] v2 = {0};
+ final LongConsumer c1 = x -> v1[0] = x;
+ final LongConsumer c2 = x -> v2[0] = x;
+ final LongConsumer combined = Statistics.compose(c1, c2);
+ final long y = 42;
+ combined.accept(y);
+ Assertions.assertEquals(y, v1[0]);
+ Assertions.assertEquals(y, v2[0]);
+ }
+}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/TestHelper.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/TestHelper.java
index a1a57e5..11485c2 100644
--- a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/TestHelper.java
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/TestHelper.java
@@ -20,6 +20,9 @@ import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.Arrays;
+import java.util.function.DoubleSupplier;
+import java.util.function.IntSupplier;
+import java.util.function.LongSupplier;
import java.util.function.Supplier;
import org.apache.commons.numbers.core.DD;
import org.apache.commons.rng.UniformRandomProvider;
@@ -406,6 +409,24 @@ final class TestHelper {
*/
static void assertEquals(StatisticResult expected, StatisticResult actual, DoubleTolerance tol,
Supplier<String> msg) {
+ assertIntEquals(expected, actual::getAsInt, msg);
+ assertLongEquals(expected, actual::getAsLong, msg);
+ assertDoubleEquals(expected, actual::getAsDouble, tol, msg);
+ assertBigIntegerEquals(expected, actual::getAsBigInteger, tol, msg);
+ }
+
+ /**
+ * Assert the results are equal.
+ *
+ * <p>If the {@code expected} throws an exception, then the {@code actual} is asserted
+ * to throw an exception of the same class.
+ *
+ * @param expected Expected.
+ * @param actual Actual.
+ * @param msg Message used for failure.
+ */
+ static void assertIntEquals(StatisticResult expected, IntSupplier actual,
+ Supplier<String> msg) {
final Supplier<String> intMsg = () -> prefix(msg) + " int value";
Integer i = null;
try {
@@ -416,7 +437,20 @@ final class TestHelper {
if (i != null) {
Assertions.assertEquals(i.intValue(), actual.getAsInt(), intMsg);
}
+ }
+ /**
+ * Assert the results are equal.
+ *
+ * <p>If the {@code expected} throws an exception, then the {@code actual} is asserted
+ * to throw an exception of the same class.
+ *
+ * @param expected Expected.
+ * @param actual Actual.
+ * @param msg Message used for failure.
+ */
+ static void assertLongEquals(StatisticResult expected, LongSupplier actual,
+ Supplier<String> msg) {
final Supplier<String> longMsg = () -> prefix(msg) + " long value";
Long l = null;
try {
@@ -427,7 +461,26 @@ final class TestHelper {
if (l != null) {
Assertions.assertEquals(l.longValue(), actual.getAsLong(), longMsg);
}
+ }
+ /**
+ * Assert the results are equal.
+ *
+ * <p>If the tolerance is null then it is assumed the result is an exact integer type;
+ * the double value is compared using binary equality.
+ *
+ * <p>If the tolerance is not null then it is assumed the result is a {@code double} type.
+ *
+ * <p>If the {@code expected} throws an exception, then the {@code actual} is asserted
+ * to throw an exception of the same class.
+ *
+ * @param expected Expected.
+ * @param actual Actual.
+ * @param tol Tolerance for double equality.
+ * @param msg Message used for failure.
+ */
+ static void assertDoubleEquals(StatisticResult expected, DoubleSupplier actual, DoubleTolerance tol,
+ Supplier<String> msg) {
final Supplier<String> doubleMsg = () -> prefix(msg) + " double value";
Double d = null;
try {
@@ -442,21 +495,42 @@ final class TestHelper {
TestUtils.assertEquals(d, actual.getAsDouble(), tol, doubleMsg);
}
}
+ }
+ /**
+ * Assert the results are equal.
+ *
+ * <p>If the tolerance is null then it is assumed the result is an exact integer type;
+ * the double value is compared using binary equality.
+ *
+ * <p>If the tolerance is not null then it is assumed the result is a {@code double} type;
+ * the BigInteger value is compared using its {@code double} representation if the
+ * actual BigInteger result is not equal.
+ *
+ * <p>If the {@code expected} throws an exception, then the {@code actual} is asserted
+ * to throw an exception of the same class.
+ *
+ * @param expected Expected.
+ * @param actual Actual.
+ * @param tol Tolerance for double equality.
+ * @param msg Message used for failure.
+ */
+ static void assertBigIntegerEquals(StatisticResult expected, Supplier<BigInteger> actual, DoubleTolerance tol,
+ Supplier<String> msg) {
final Supplier<String> bigMsg = () -> prefix(msg) + " BigInteger value";
BigInteger b = null;
try {
b = expected.getAsBigInteger();
} catch (Throwable t) {
- Assertions.assertThrowsExactly(t.getClass(), () -> actual.getAsBigInteger(), bigMsg);
+ Assertions.assertThrowsExactly(t.getClass(), () -> actual.get(), bigMsg);
}
if (b != null) {
if (tol == null) {
// Assume exact
- Assertions.assertEquals(b, actual.getAsBigInteger(), bigMsg);
+ Assertions.assertEquals(b, actual.get(), bigMsg);
} else {
// Double computation may not be exact so check within tolerance
- final BigInteger bb = actual.getAsBigInteger();
+ final BigInteger bb = actual.get();
if (!b.equals(bb)) {
TestUtils.assertEquals(b.doubleValue(), bb.doubleValue(), tol, bigMsg);
}
diff --git a/src/conf/checkstyle/checkstyle-suppressions.xml b/src/conf/checkstyle/checkstyle-suppressions.xml
index dd425bc..e5d1b95 100644
--- a/src/conf/checkstyle/checkstyle-suppressions.xml
+++ b/src/conf/checkstyle/checkstyle-suppressions.xml
@@ -23,7 +23,7 @@
<suppress checks="LocalFinalVariableName" files=".*[/\\]ZipfDistribution.java" />
<suppress checks="LocalFinalVariableName" files=".*[/\\]HypergeometricDistribution.java" />
<suppress checks="ParameterNumber" files=".*[/\\]TTest.java" />
- <suppress checks="ParameterNumber" files=".*[/\\]DoubleStatistics.java" />
+ <suppress checks="ParameterNumber" files=".*[/\\](Double|Int|Long)Statistics.java" />
<!-- Be more lenient on tests. -->
<suppress checks="Javadoc" files=".*[/\\]test[/\\].*" />
<suppress checks="MultipleStringLiterals" files=".*[/\\]test[/\\].*" />
@@ -39,7 +39,7 @@
<suppress checks="ParameterNumber" files=".*[/\\]WilcoxonSignedRankTestTest.java" />
<suppress checks="ParameterNumber" files=".*[/\\]UnconditionedExactTestTest.java" />
<suppress checks="MethodLength" files=".*[/\\]WilcoxonSignedRankTestTest.java" />
- <suppress checks="IllegalCatch" files=".*[/\\]TestHelper.java" lines="390-450" />
+ <suppress checks="IllegalCatch" files=".*[/\\]TestHelper.java" lines="400-550" />
<suppress checks="IllegalCatch" files=".*[/\\]BaseStatisticTest.java" lines="280-400" />
<suppress checks="IllegalCatch" files=".*[/\\]IntMathTest.java" lines="170-185" />
</suppressions>
diff --git a/src/conf/pmd/pmd-ruleset.xml b/src/conf/pmd/pmd-ruleset.xml
index 72d7b54..87c02d0 100644
--- a/src/conf/pmd/pmd-ruleset.xml
+++ b/src/conf/pmd/pmd-ruleset.xml
@@ -167,7 +167,8 @@
or @SimpleName='KolmogorovSmirnovTest' or @SimpleName='DD' or @SimpleName='Arguments'
or @SimpleName='MannWhitneyUTest' or @SimpleName='WilcoxonSignedRankTest'
or @SimpleName='HypergeometricDistribution' or @SimpleName='UnconditionedExactTest'
- or @SimpleName='DoubleStatistics']"/>
+ or @SimpleName='DoubleStatistics' or @SimpleName='IntStatistics'
+ or @SimpleName='LongStatistics']"/>
</properties>
</rule>
<rule ref="category/java/design.xml/LogicInversion">