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
The following commit(s) were added to refs/heads/master by this push:
new 4633d9b STATISTICS-76: Max Implementation
4633d9b is described below
commit 4633d9bb7989dc7b168ab04456b1a085d59d53b5
Author: AJoshi <[email protected]>
AuthorDate: Sun Jul 16 23:34:56 2023 +0530
STATISTICS-76: Max Implementation
---
.../apache/commons/statistics/descriptive/Max.java | 134 +++++++++++++++
.../commons/statistics/descriptive/MaxTest.java | 188 +++++++++++++++++++++
src/conf/pmd/pmd-ruleset.xml | 2 +-
3 files changed, 323 insertions(+), 1 deletion(-)
diff --git
a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Max.java
b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Max.java
new file mode 100644
index 0000000..82d625a
--- /dev/null
+++
b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Max.java
@@ -0,0 +1,134 @@
+/*
+ * 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.Arrays;
+
+/**
+ * Returns the maximum of the available values.
+ *
+ * <p>The result is <code>NaN</code> if any of the values is <code>NaN</code>.
+ *
+ * <p>The result is <code>NEGATIVE_INFINITY</code> if no values are added.
+ *
+ * <p>This class is designed to work with (though does not require)
+ * {@linkplain java.util.stream streams}.
+ *
+ * <p><strong>This implementation is not thread safe.</strong>
+ * If multiple threads access an instance of this class concurrently,
+ * and at least one of the threads invokes the <code>accept()</code> or
+ * <code>combine()</code> method, it must be synchronized externally.
+ *
+ * <p>However, it is safe to use <code>accept()</code> and
<code>combine()</code>
+ * as <code>accumulator</code> and <code>combiner</code> functions of
+ * {@link java.util.stream.Collector Collector} on a parallel stream,
+ * because the parallel implementation of {@link
java.util.stream.Stream#collect Stream.collect()}
+ * provides the necessary partitioning, isolation, and merging of results for
+ * safe and efficient parallel execution.
+ *
+ * @since 1.1
+ */
+public abstract class Max implements DoubleStatistic,
DoubleStatisticAccumulator<Max> {
+
+ /**
+ * Create a Max instance.
+ */
+ Max() {
+ //No-op
+ }
+
+ /**
+ * Creates a {@code Max} implementation which does not store the input
value(s) it consumes.
+ *
+ * <p>The result is <code>NaN</code> if any of the values is
<code>NaN</code>.
+ *
+ * <p>The result is {@link Double#NEGATIVE_INFINITY NEGATIVE_INFINITY}
+ * if no values have been added.
+ *
+ * @return {@code Max} implementation.
+ */
+ public static Max create() {
+ return new StorelessMax();
+ }
+
+ /**
+ * Returns a {@code Max} instance that has the maximum of all input
value(s).
+ *
+ * <p>The result is <code>NaN</code> if any of the values is
<code>NaN</code>.
+ *
+ * <p>When the input is an empty array, the result is
+ * {@link Double#NEGATIVE_INFINITY NEGATIVE_INFINITY}.
+ *
+ * @param values Values.
+ * @return {@code Max} instance.
+ */
+ public static Max of(double... values) {
+ final StorelessMax max = new StorelessMax();
+ Arrays.stream(values).forEach(max);
+ return max;
+ }
+
+ /**
+ * Updates the state of the statistic to reflect the addition of {@code
value}.
+ * @param value Value.
+ */
+ @Override
+ public abstract void accept(double value);
+
+ /**
+ * Gets the maximum of all input values.
+ *
+ * <p>When no values have been added, the result is
+ * {@link Double#NEGATIVE_INFINITY NEGATIVE_INFINITY}.
+ *
+ * @return {@code Maximum} of all values seen so far.
+ */
+ @Override
+ public abstract double getAsDouble();
+
+ /** {@inheritDoc} */
+ @Override
+ public abstract Max combine(Max other);
+
+ /**
+ * {@code Max} implementation that does not store the input value(s)
processed so far.
+ *
+ * <p>Uses JDK's {@link Math#max Math.max} as an underlying function
+ * to compute the {@code maximum}.
+ */
+ private static class StorelessMax extends Max {
+
+ /** Current max. */
+ private double max = Double.NEGATIVE_INFINITY;
+
+ @Override
+ public void accept(double value) {
+ max = Double.max(max, value);
+ }
+
+ @Override
+ public double getAsDouble() {
+ return max;
+ }
+
+ @Override
+ public Max combine(Max other) {
+ accept(other.getAsDouble());
+ return this;
+ }
+ }
+}
diff --git
a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MaxTest.java
b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MaxTest.java
new file mode 100644
index 0000000..0051d1d
--- /dev/null
+++
b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MaxTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.Arrays;
+import java.util.function.DoubleSupplier;
+import java.util.stream.Stream;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Test for {@link Max}.
+ */
+final class MaxTest {
+
+ @Test
+ void testEmpty() {
+ Max max = Max.create();
+ Assertions.assertEquals(Double.NEGATIVE_INFINITY, max.getAsDouble());
+ }
+
+ @Test
+ void testIncrement() {
+ // Test the max after each incremental update
+ // First parameter of testArray is the value that would be added
+ // Second parameter of testArray is the max we expect after adding the
value
+ double[][] testArray = {
+ {1729.22, 1729.22},
+ {2520.35, 2520.35},
+ {2010.87, 2520.35},
+ {100000000.1, 100000000.1},
+ {+0.0, 100000000.1},
+ {-0.0, 100000000.1},
+ {Double.MAX_VALUE, Double.MAX_VALUE},
+ {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY},
+ {Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY}
+ };
+
+ Max stat = Max.create();
+ for (final double[] valueAndExpected: testArray) {
+ final double value = valueAndExpected[0];
+ final double expected = valueAndExpected[1];
+ stat.accept(value);
+ Assertions.assertEquals(expected, stat.getAsDouble());
+ }
+ }
+
+ @Test
+ void testNaN() {
+ // Test non-nan values cannot revert a NaN
+ double[] testArray = {Double.NaN, +0.0d, -0.0d,
Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY};
+ Max stat = Max.create();
+ for (final double x : testArray) {
+ stat.accept(x);
+ Assertions.assertEquals(Double.NaN, stat.getAsDouble());
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testMax(double[] values, double expected) {
+ Max stat = Max.create();
+ Arrays.stream(values).forEach(stat);
+ double actual = stat.getAsDouble();
+ Assertions.assertEquals(expected, actual, "max");
+ Assertions.assertEquals(expected, Max.of(values).getAsDouble(), "max");
+ }
+
+ static Stream<Arguments> testMax() {
+ return Stream.of(
+ Arguments.of(new double[] {}, Double.NEGATIVE_INFINITY),
+ Arguments.of(new double[] {3.14}, 3.14),
+ Arguments.of(new double[] {12.34, 56.78, -2.0}, 56.78),
+ Arguments.of(new double[] {Double.NaN, 3.14, Double.NaN,
Double.NaN}, Double.NaN),
+ Arguments.of(new double[] {-1d, 1d, Double.NaN}, Double.NaN),
+ Arguments.of(new double[] {Double.NaN, Double.NaN, Double.NaN},
Double.NaN),
+ Arguments.of(new double[] {0.0d, Double.NaN, +0.0d, -0.0d,
Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY}, Double.NaN),
+ Arguments.of(new double[] {+0.0d, -0.0d, 1.0, 3.14}, 3.14),
+ Arguments.of(new double[] {-0.0, +0.0}, 0.0),
+ Arguments.of(new double[] {0.0, -0.0}, 0.0),
+ Arguments.of(new double[] {0.0, +0.0}, 0.0),
+ Arguments.of(new double[] {1.2, -34.56, 456.789, -5678.9012},
456.789),
+ Arguments.of(new double[] {-23467824, 23648, 2368, 23749, -23424,
-23492, -92397747}, 23749),
+ Arguments.of(new double[] {1.7976931348623157e+307,
1.7976931348623157e+306, 0.0, Double.MAX_VALUE}, Double.MAX_VALUE),
+ Arguments.of(new double[] {1.7976931348623157e+307,
1.7976931348623157e+306, 0.0, -Double.MAX_VALUE}, 1.7976931348623157e+307),
+ Arguments.of(new double[] {Double.NEGATIVE_INFINITY,
-Double.MAX_VALUE, Double.MAX_VALUE, Double.POSITIVE_INFINITY},
Double.POSITIVE_INFINITY),
+ Arguments.of(new double[] {Double.NEGATIVE_INFINITY,
-Double.MAX_VALUE, -Double.MIN_VALUE}, -Double.MIN_VALUE)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = "testMax")
+ void testParallelStream(double[] values, double expected) {
+ double actual = Arrays.stream(values).parallel().collect(Max::create,
Max::accept, Max::combine).getAsDouble();
+ Assertions.assertEquals(expected, actual);
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = "testMax")
+ void testMaxRandomOrder(double[] values, double expected) {
+ UniformRandomProvider rng = TestHelper.createRNG();
+ for (int i = 0; i < 10; i++) {
+ testMax(TestHelper.shuffle(rng, values), expected);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testCombine(double[] first, double[] second, double expected) {
+ Max firstMax = Max.create();
+ Max secondMax = Max.create();
+
+ Arrays.stream(first).forEach(firstMax);
+ Arrays.stream(second).forEach(secondMax);
+
+ double secondMaxBeforeCombine = secondMax.getAsDouble();
+ firstMax.combine(secondMax);
+ Assertions.assertEquals(expected, firstMax.getAsDouble());
+ Assertions.assertEquals(secondMaxBeforeCombine,
secondMax.getAsDouble());
+ }
+
+ static Stream<Arguments> testCombine() {
+ return Stream.of(
+ Arguments.of(new double[] {}, new double[] {},
Double.NEGATIVE_INFINITY),
+ Arguments.of(new double[] {3.14}, new double[] {}, 3.14),
+ Arguments.of(new double[] {}, new double[] {2.718}, 2.718),
+ Arguments.of(new double[] {}, new double[] {Double.NaN},
Double.NaN),
+ Arguments.of(new double[] {Double.NaN, Double.NaN}, new double[]
{}, Double.NaN),
+ Arguments.of(new double[] {3.14}, new double[] {2.718}, 3.14),
+ Arguments.of(new double[] {-1, 0, 1}, new double[] {1.1, 2.2,
3.3}, 3.3),
+ Arguments.of(new double[] {3.14, 1.1, 22.22}, new double[] {2.718,
1.1, 333.333}, 333.333),
+ Arguments.of(new double[] {12.34, 56.78, -2.0}, new double[] {0.0,
23.45}, 56.78),
+ Arguments.of(new double[] {-2023.79, 11.11, 333.333}, new double[]
{1.1}, 333.333),
+ Arguments.of(new double[] {1.1, +0.0, 3.14}, new double[] {22.22,
2.718, -0.0}, 22.22),
+ Arguments.of(new double[] {0.0, -Double.MAX_VALUE,
Double.POSITIVE_INFINITY},
+ new double[] {Double.NEGATIVE_INFINITY, -0.0,
Double.NEGATIVE_INFINITY, Double.MAX_VALUE},
+ Double.POSITIVE_INFINITY),
+ Arguments.of(new double[] {0.0, Double.NaN, -Double.MAX_VALUE,
Double.POSITIVE_INFINITY},
+ new double[] {Double.NaN, -0.0, Double.NaN,
Double.NEGATIVE_INFINITY, Double.MAX_VALUE},
+ Double.NaN)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testArrayOfArrays(double[][] input, double expected) {
+ double actual = Arrays.stream(input)
+ .map(Max::of)
+ .reduce(Max::combine)
+ .map(DoubleSupplier::getAsDouble)
+ .orElseThrow(RuntimeException::new);
+
+ Assertions.assertEquals(expected, actual);
+ }
+
+ static Stream<Arguments> testArrayOfArrays() {
+ return Stream.of(
+ Arguments.of(new double[][] {{}, {}, {}},
Double.NEGATIVE_INFINITY),
+ Arguments.of(new double[][] {{}, {Double.NaN}, {-1.7}},
Double.NaN),
+ Arguments.of(new double[][] {{}, {Double.NaN}, {}},
Double.NaN),
+ Arguments.of(new double[][] {{}, {1.1, 2}, {-1.7}}, 2),
+ Arguments.of(new double[][] {{1, 2}, {3, 4}}, 4),
+ Arguments.of(new double[][] {{+0.0, 2.0}, {1.0, -0.0, 3.14}},
3.14),
+ Arguments.of(new double[][] {{+0.0, Double.NEGATIVE_INFINITY},
{-0.0, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY}},
Double.POSITIVE_INFINITY),
+ Arguments.of(new double[][] {{1.1, 22.22}, {34.56, -5678.9,
2.718}, {Double.NaN, 0}},
+ Double.NaN),
+ Arguments.of(new double[][] {{Double.NaN, Double.NaN},
{Double.NaN}, {Double.NaN, Double.NaN, Double.NaN}}, Double.NaN)
+ );
+ }
+}
diff --git a/src/conf/pmd/pmd-ruleset.xml b/src/conf/pmd/pmd-ruleset.xml
index 8bfff93..03bb30b 100644
--- a/src/conf/pmd/pmd-ruleset.xml
+++ b/src/conf/pmd/pmd-ruleset.xml
@@ -90,7 +90,7 @@
<properties>
<property name="violationSuppressXPath"
value="./ancestor-or-self::ClassOrInterfaceDeclaration[@SimpleName='DD'
- or @SimpleName='Two' or @SimpleName='One' or @SimpleName='Min']"/>
+ or @SimpleName='Two' or @SimpleName='One' or @SimpleName='Min' or
@SimpleName='Max']"/>
</properties>
</rule>
<rule ref="category/java/codestyle.xml/PrematureDeclaration">