]> source.dussan.org Git - poi.git/commitdiff
[github-330] add averageif function support
authorPJ Fanning <fanningpj@apache.org>
Fri, 29 Apr 2022 19:55:12 +0000 (19:55 +0000)
committerPJ Fanning <fanningpj@apache.org>
Fri, 29 Apr 2022 19:55:12 +0000 (19:55 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1900394 13f79535-47bb-0310-9956-ffa450edef68

poi/src/main/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java
poi/src/main/java/org/apache/poi/ss/formula/functions/AverageIf.java [new file with mode: 0644]
poi/src/test/java/org/apache/poi/ss/formula/functions/TestAverageIf.java [new file with mode: 0644]

index eaaf1845769bea6bb0a084328d9e1277c2602a76..26ac1e2b3d757265dc4c040698c5713bbbddc022 100644 (file)
@@ -68,7 +68,7 @@ public final class AnalysisToolPak implements UDFFinder {
         r(m, "ACCRINTM", null);
         r(m, "AMORDEGRC", null);
         r(m, "AMORLINC", null);
-        r(m, "AVERAGEIF", null);
+        r(m, "AVERAGEIF", AverageIf.instance );
         r(m, "AVERAGEIFS", Averageifs.instance);
         r(m, "BAHTTEXT", null);
         r(m, "BESSELI", null);
diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/AverageIf.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/AverageIf.java
new file mode 100644 (file)
index 0000000..ae0f3ea
--- /dev/null
@@ -0,0 +1,118 @@
+/* ====================================================================
+   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.poi.ss.formula.functions;
+
+import org.apache.poi.ss.formula.OperationEvaluationContext;
+import org.apache.poi.ss.formula.eval.AreaEval;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.functions.CountUtils.I_MatchPredicate;
+
+/**
+ * Handler for singular AverageIf which has different operand handling than
+ * the generic AverageIfs version.
+ */
+public class AverageIf extends Baseifs {
+
+    public static final FreeRefFunction instance = new Averageifs();
+
+    @Override
+    public ValueEval evaluate(ValueEval[] _args, OperationEvaluationContext _ec) {
+        if (_args.length < 2) {
+            return ErrorEval.VALUE_INVALID;
+        }
+
+        try {
+            AreaEval sumRange = convertRangeArg(_args[0]);
+
+            if (_args.length == 3) {
+                sumRange = convertRangeArg(_args[2]);
+            }
+
+            // collect pairs of ranges and criteria
+            AreaEval ae = convertRangeArg(_args[0]);
+            I_MatchPredicate mp = Countif.createCriteriaPredicate(_args[1], _ec.getRowIndex(), _ec.getColumnIndex());
+
+            if (mp instanceof Countif.ErrorMatcher) {
+                throw new EvaluationException(ErrorEval.valueOf(((Countif.ErrorMatcher) mp).getValue()));
+            }
+
+            return aggregateMatchingCells(createAggregator(), sumRange, ae, mp);
+        } catch (EvaluationException e) {
+            return e.getErrorEval();
+        }
+    }
+
+    protected ValueEval aggregateMatchingCells(Aggregator aggregator, AreaEval sumRange, AreaEval testRange, I_MatchPredicate mp)
+            throws EvaluationException {
+
+        int height = testRange.getHeight();
+        int width = testRange.getWidth();
+
+        for (int r = 0; r < height; r++) {
+            for (int c = 0; c < width; c++) {
+
+                ValueEval _testValue = testRange.getRelativeValue(r, c);
+                ;
+
+                ValueEval _sumValue = sumRange.getRelativeValue(r, c);
+
+                if (mp != null && mp.matches(_testValue)) { // aggregate only if all of the corresponding criteria specified are true for that cell.
+                    if (_testValue instanceof ErrorEval) {
+                        throw new EvaluationException((ErrorEval) _testValue);
+                    }
+                    aggregator.addValue(_sumValue);
+                }
+            }
+        }
+        return aggregator.getResult();
+    }
+
+    @Override
+    protected boolean hasInitialRange() {
+        return false;
+    }
+
+
+    @Override
+    protected Aggregator createAggregator() {
+
+        return new Aggregator() {
+            Double sum = 0.0;
+            Integer count = 0;
+
+            @Override
+            public void addValue(ValueEval value) {
+
+                if (!(value instanceof NumberEval)) return;
+
+                double d = ((NumberEval) value).getNumberValue();
+                sum += d;
+                count++;
+
+            }
+
+            @Override
+            public ValueEval getResult() {
+                return count == 0 ? ErrorEval.DIV_ZERO : new NumberEval(sum / count);
+            }
+        };
+    }
+
+}
diff --git a/poi/src/test/java/org/apache/poi/ss/formula/functions/TestAverageIf.java b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestAverageIf.java
new file mode 100644 (file)
index 0000000..ed18f31
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ *  ====================================================================
+ *    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.poi.ss.formula.functions;
+
+import static org.apache.poi.ss.util.Utils.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.NumericValueEval;
+import org.apache.poi.ss.formula.eval.StringEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.usermodel.FormulaError;
+import org.junit.jupiter.api.Test;
+
+
+/**
+ * Test cases for AVERAGEIFS()
+ */
+final class TestAverageIf {
+
+    private static final OperationEvaluationContext EC = new OperationEvaluationContext(null, null, 0, 0, 0, null);
+
+    private static ValueEval invokeAverageif(ValueEval[] args) {
+        return new AverageIf().evaluate(args, EC);
+    }
+
+    private static void confirmDouble(double expected, ValueEval actualEval) {
+        assertTrue(actualEval instanceof NumericValueEval, "Expected numeric result");
+        NumericValueEval nve = (NumericValueEval) actualEval;
+        assertEquals(expected, nve.getNumberValue(), 0);
+    }
+
+    private static void confirm(double expectedResult, ValueEval[] args) {
+        confirmDouble(expectedResult, invokeAverageif(args));
+    }
+
+    private static void confirmError(ErrorEval errorEval, ValueEval[] args) {
+        ValueEval actualEval = invokeAverageif(args);
+        assertEquals(errorEval, actualEval);
+    }
+
+    /**
+     * Example 1 from
+     * https://support.microsoft.com/en-us/office/averageif-function-faec8e2e-0dec-4308-af69-f5576d8ac642
+     */
+    @Test
+    void testExample1() {
+        ValueEval[] b2b5 = new ValueEval[]{
+                new NumberEval(7000),
+                new NumberEval(14000),
+                new NumberEval(21000),
+                new NumberEval(28000)
+        };
+
+        ValueEval[] args;
+        // "=AVERAGEIF(B2:B5, "<23000")"
+        args = new ValueEval[]{
+                EvalFactory.createAreaEval("B2:B5", b2b5),
+                new StringEval("<23000")
+        };
+        confirm(14000, args);
+
+        ValueEval[] a2a5 = new ValueEval[]{
+                new NumberEval(100000),
+                new NumberEval(200000),
+                new NumberEval(300000),
+                new NumberEval(400000)
+        };
+        // "=AVERAGEIF(A2:A5, "<250000", A2:A5)"
+        args = new ValueEval[]{
+                EvalFactory.createAreaEval("A2:A5", a2a5),
+                new StringEval("<250000"),
+                EvalFactory.createAreaEval("A2:A5", a2a5)
+        };
+        confirm(150000, args);
+
+        // "=AVERAGEIF(A2:A5, "<95000")"
+        args = new ValueEval[]{
+                EvalFactory.createAreaEval("A2:A5", a2a5),
+                new StringEval("<95000"),
+                EvalFactory.createAreaEval("A2:A5", a2a5)
+        };
+
+        confirmError(ErrorEval.DIV_ZERO, args);
+
+        // "=AVERAGEIF(A2:A5, "<95000", B2:B5 )"
+        args = new ValueEval[]{
+                EvalFactory.createAreaEval("A2:A5", a2a5),
+                new StringEval(">250000"),
+                EvalFactory.createAreaEval("B2:B5", b2b5)
+        };
+        confirm(24500, args);
+    }
+
+}