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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ private Map<String, FreeRefFunction> createFunctionsMap() {
r(m, "AMORDEGRC", null);
r(m, "AMORLINC", null);
r(m, "AVERAGEIF", null);
r(m, "AVERAGEIFS", null);
r(m, "AVERAGEIFS", Averageifs.instance);
r(m, "BAHTTEXT", null);
r(m, "BESSELI", null);
r(m, "BESSELJ", null);
Expand Down Expand Up @@ -142,7 +142,9 @@ private Map<String, FreeRefFunction> createFunctionsMap() {
r(m, "ISODD", ParityFunction.IS_ODD);
r(m, "JIS", null);
r(m, "LCM", null);
r(m, "MAXIFS", Maxifs.instance);
r(m, "MDURATION", null);
r(m, "MINIFS", Minifs.instance);
r(m, "MROUND", MRound.instance);
r(m, "MULTINOMIAL", null);
r(m, "NETWORKDAYS", NetworkdaysFunction.instance);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* ====================================================================
* 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.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.NumberEval;
import org.apache.poi.ss.formula.eval.ValueEval;

/**
* Implementation for the Excel function AVERAGEIFS<p>
*
* Syntax : <p>
* AVERAGEIFS ( <b>average_range</b>, <b>criteria_range1</b>, <b>criteria1</b>,
* [<b>criteria_range2</b>, <b>criteria2</b>], ...)
* <ul>
* <li><b>min_range</b> Required. One or more cells to average, including numbers or names, ranges,
* or cell references that contain numbers. Blank and text values are ignored.</li>
* <li><b>criteria1_range</b> Required. The first range in which
* to evaluate the associated criteria.</li>
* <li><b>criteria1</b> Required. The criteria in the form of a number, expression,
* cell reference, or text that define which cells in the criteria_range1
* argument will be added</li>
* <li><b> criteria_range2, criteria2, ...</b> Optional. Additional ranges and their associated criteria.
* Up to 127 range/criteria pairs are allowed.
* </ul>
*/
public final class Averageifs extends Baseifs {
/**
* Singleton
*/
public static final FreeRefFunction instance = new Averageifs();

/**
* https://support.microsoft.com/en-us/office/averageifs-function-48910c45-1fc0-4389-a028-f7c5c3001690
* AVERAGEIFS(average_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...)
* need at least 3 arguments and need to have an odd number of arguments (average_range plus x*(criteria_range, criteria))
*/
@Override
protected boolean hasInitialRange() {
return true;
}

@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);
}
};
}
}
56 changes: 24 additions & 32 deletions poi/src/main/java/org/apache/poi/ss/formula/functions/Baseifs.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,17 @@
* @return true if there should be a range argument before the criteria pairs
*/
protected abstract boolean hasInitialRange();


/**
* Implements the details of a specific aggregation function
*/
protected static interface Aggregator {
void addValue(ValueEval d);
ValueEval getResult();
}

protected abstract Aggregator createAggregator();

public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
final boolean hasInitialRange = hasInitialRange();
final int firstCriteria = hasInitialRange ? 1 : 0;
Expand All @@ -67,8 +77,7 @@ public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
validateCriteriaRanges(sumRange, ae);
validateCriteria(mp);

double result = aggregateMatchingCells(sumRange, ae, mp);
return new NumberEval(result);
return aggregateMatchingCells(createAggregator(), sumRange, ae, mp);
} catch (EvaluationException e) {
return e.getErrorEval();
}
Expand Down Expand Up @@ -123,12 +132,11 @@ private static void validateCriteria(I_MatchPredicate[] criteria) throws Evaluat
* @return the computed value
* @throws EvaluationException if there is an issue with eval
*/
private static double aggregateMatchingCells(AreaEval sumRange, AreaEval[] ranges, I_MatchPredicate[] predicates)
private static ValueEval aggregateMatchingCells(Aggregator aggregator, AreaEval sumRange, AreaEval[] ranges, I_MatchPredicate[] predicates)
throws EvaluationException {
int height = ranges[0].getHeight();
int width = ranges[0].getWidth();

double result = 0.0;
for (int r = 0; r < height; r++) {
for (int c = 0; c < width; c++) {

Expand All @@ -142,38 +150,22 @@ private static double aggregateMatchingCells(AreaEval sumRange, AreaEval[] range
matches = false;
break;
}

}

if(matches) { // sum only if all of the corresponding criteria specified are true for that cell.
result += accumulate(sumRange, r, c);
if(matches) { // aggregate only if all of the corresponding criteria specified are true for that cell.
if(sumRange != null) {
ValueEval value = sumRange.getRelativeValue(r, c);
if (value instanceof ErrorEval) {
throw new EvaluationException((ErrorEval)value);
}
aggregator.addValue(value);
} else {
aggregator.addValue(null);
}
}
}
}
return result;
}

/**
* For counts, this would return 1, for sums it returns a cell value or zero.
* This is only called after all the criteria are confirmed true for the coordinates.
* @param sumRange if used
* @param relRowIndex
* @param relColIndex
* @return the aggregate input value corresponding to the given range coordinates
* @throws EvaluationException if there is an issue with eval
*/
private static double accumulate(AreaEval sumRange, int relRowIndex, int relColIndex) throws EvaluationException {
if (sumRange == null) return 1.0; // count

ValueEval addend = sumRange.getRelativeValue(relRowIndex, relColIndex);
if (addend instanceof NumberEval) {
return ((NumberEval) addend).getNumberValue();
} else if (addend instanceof ErrorEval) {
throw new EvaluationException((ErrorEval)addend);
} else {
// everything else (including string and boolean values) counts as zero
return 0.0;
}
return aggregator.getResult();
}

protected static AreaEval convertRangeArg(ValueEval eval) throws EvaluationException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ Licensed to the Apache Software Foundation (ASF) under one or more

package org.apache.poi.ss.formula.functions;

import org.apache.poi.ss.formula.eval.NumberEval;
import org.apache.poi.ss.formula.eval.ValueEval;

/**
* Implementation for the function COUNTIFS
* <p>
Expand All @@ -41,5 +44,22 @@ public class Countifs extends Baseifs {
protected boolean hasInitialRange() {
return false;
}

@Override
protected Aggregator createAggregator() {
return new Aggregator() {
double accumulator = 0.0;

@Override
public void addValue(ValueEval value) {
accumulator += 1.0;
}

@Override
public ValueEval getResult() {
return new NumberEval(accumulator);
}
};
}
}

78 changes: 78 additions & 0 deletions poi/src/main/java/org/apache/poi/ss/formula/functions/Maxifs.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* ====================================================================
* 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.eval.NumberEval;
import org.apache.poi.ss.formula.eval.ValueEval;

/**
* Implementation for the Excel function MAXIFS<p>
*
* Syntax : <p>
* MAXIFS ( <b>max_range</b>, <b>criteria_range1</b>, <b>criteria1</b>,
* [<b>criteria_range2</b>, <b>criteria2</b>], ...)
* <ul>
* <li><b>min_range</b> Required. One or more cells to determine the maximum value of, including numbers or names, ranges,
* or cell references that contain numbers. Blank and text values are ignored.</li>
* <li><b>criteria1_range</b> Required. The first range in which
* to evaluate the associated criteria.</li>
* <li><b>criteria1</b> Required. The criteria in the form of a number, expression,
* cell reference, or text that define which cells in the criteria_range1
* argument will be added</li>
* <li><b> criteria_range2, criteria2, ...</b> Optional. Additional ranges and their associated criteria.
* Up to 127 range/criteria pairs are allowed.
* </ul>
*/
public final class Maxifs extends Baseifs {
/**
* Singleton
*/
public static final FreeRefFunction instance = new Maxifs();

/**
* https://support.microsoft.com/en-us/office/maxifs-function-dfd611e6-da2c-488a-919b-9b6376b28883
* MAXIFS(max_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...)
* need at least 3 arguments and need to have an odd number of arguments (max-range plus x*(criteria_range, criteria))
*/
@Override
protected boolean hasInitialRange() {
return true;
}

@Override
protected Aggregator createAggregator() {
return new Aggregator() {
Double accumulator = null;

@Override
public void addValue(ValueEval value) {
double d = (value instanceof NumberEval) ? ((NumberEval) value).getNumberValue() : 0.0;
if(accumulator == null || accumulator < d) {
accumulator = d;
}
}

@Override
public ValueEval getResult() {
return new NumberEval(accumulator == null ? 0.0 : accumulator);
}
};
}
}
78 changes: 78 additions & 0 deletions poi/src/main/java/org/apache/poi/ss/formula/functions/Minifs.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* ====================================================================
* 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.eval.NumberEval;
import org.apache.poi.ss.formula.eval.ValueEval;

/**
* Implementation for the Excel function MINIFS<p>
*
* Syntax : <p>
* MINIFS ( <b>min_range</b>, <b>criteria_range1</b>, <b>criteria1</b>,
* [<b>criteria_range2</b>, <b>criteria2</b>], ...)
* <ul>
* <li><b>min_range</b> Required. One or more cells to determine the minimum value of, including numbers or names, ranges,
* or cell references that contain numbers. Blank and text values are ignored.</li>
* <li><b>criteria1_range</b> Required. The first range in which
* to evaluate the associated criteria.</li>
* <li><b>criteria1</b> Required. The criteria in the form of a number, expression,
* cell reference, or text that define which cells in the criteria_range1
* argument will be added</li>
* <li><b> criteria_range2, criteria2, ...</b> Optional. Additional ranges and their associated criteria.
* Up to 127 range/criteria pairs are allowed.
* </ul>
*/
public final class Minifs extends Baseifs {
/**
* Singleton
*/
public static final FreeRefFunction instance = new Minifs();

/**
* https://support.microsoft.com/en-us/office/minifs-function-6ca1ddaa-079b-4e74-80cc-72eef32e6599
* MINIFS(min_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...
* need at least 3 arguments and need to have an odd number of arguments (min-range plus x*(criteria_range, criteria))
*/
@Override
protected boolean hasInitialRange() {
return true;
}

@Override
protected Aggregator createAggregator() {
return new Aggregator() {
Double accumulator = null;

@Override
public void addValue(ValueEval value) {
double d = (value instanceof NumberEval) ? ((NumberEval) value).getNumberValue() : 0.0;
if(accumulator == null || accumulator > d) {
accumulator = d;
}
}

@Override
public ValueEval getResult() {
return new NumberEval(accumulator == null ? 0.0 : accumulator);
}
};
}
}
Loading