Skip to content

Commit d5bddfd

Browse files
Supports arrays inside function AVERAGE (#504)
1 parent 35c576d commit d5bddfd

File tree

3 files changed

+113
-9
lines changed

3 files changed

+113
-9
lines changed

docs/references/functions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Available through the _ExpressionConfiguration.StandardFunctionsDictionary_ cons
1414
| Name | Description |
1515
|---------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
1616
| ABS(value) | Absolute (non-negative) value |
17-
| AVERAGE(value, ...) | Returns the average (arithmetic mean) of all parameters. |
17+
| AVERAGE(value, ...) | Returns the average (arithmetic mean) of all parameters. If a parameter is of type _ARRAY_, the average of all elements is calculated. |
1818
| CEILING(value) | Rounds the given value an integer using the rounding mode CEILING |
1919
| COALESCE(value, ...) | Returns the first non-null parameter, or NULL if all parameters are null |
2020
| FACT(base) | Calculates the factorial of a base value |

src/main/java/com/ezylang/evalex/functions/basic/AverageFunction.java

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
import com.ezylang.evalex.parser.Token;
2222
import java.math.BigDecimal;
2323
import java.math.MathContext;
24-
import java.util.Arrays;
2524

2625
/**
27-
* Returns the average (arithmetic mean) of the numeric arguments.
26+
* Returns the average (arithmetic mean) of the numeric arguments, with recursive support for
27+
* arrays.
2828
*
2929
* @author oswaldo.bapvic.jr
3030
*/
@@ -35,12 +35,45 @@ public class AverageFunction extends AbstractMinMaxFunction {
3535
public EvaluationValue evaluate(
3636
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
3737
MathContext mathContext = expression.getConfiguration().getMathContext();
38-
BigDecimal sum =
39-
Arrays.stream(parameterValues)
40-
.map(EvaluationValue::getNumberValue)
41-
.reduce(BigDecimal.ZERO, BigDecimal::add);
42-
BigDecimal count = BigDecimal.valueOf(parameterValues.length);
43-
BigDecimal average = sum.divide(count, mathContext);
38+
BigDecimal average = average(mathContext, parameterValues);
4439
return expression.convertValue(average);
4540
}
41+
42+
private BigDecimal average(MathContext mathContext, EvaluationValue... parameterValues) {
43+
SumAndCount aux = new SumAndCount();
44+
for (EvaluationValue parameter : parameterValues) {
45+
aux = aux.plus(recursiveSumAndCount(parameter));
46+
}
47+
48+
return aux.sum.divide(aux.count, mathContext);
49+
}
50+
51+
private SumAndCount recursiveSumAndCount(EvaluationValue parameter) {
52+
SumAndCount aux = new SumAndCount();
53+
if (parameter.isArrayValue()) {
54+
for (EvaluationValue element : parameter.getArrayValue()) {
55+
aux = aux.plus(recursiveSumAndCount(element));
56+
}
57+
return aux;
58+
}
59+
return new SumAndCount(parameter.getNumberValue(), BigDecimal.ONE);
60+
}
61+
62+
private final class SumAndCount {
63+
private final BigDecimal sum;
64+
private final BigDecimal count;
65+
66+
private SumAndCount() {
67+
this(BigDecimal.ZERO, BigDecimal.ZERO);
68+
}
69+
70+
private SumAndCount(BigDecimal sum, BigDecimal count) {
71+
this.sum = sum;
72+
this.count = count;
73+
}
74+
75+
private SumAndCount plus(SumAndCount other) {
76+
return new SumAndCount(sum.add(other.sum), count.add(other.count));
77+
}
78+
}
4679
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
Copyright 2012-2024 Udo Klimaschewski
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package com.ezylang.evalex.functions.basic;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import com.ezylang.evalex.EvaluationException;
21+
import com.ezylang.evalex.Expression;
22+
import com.ezylang.evalex.parser.ParseException;
23+
import java.util.List;
24+
import org.junit.jupiter.api.Test;
25+
26+
/**
27+
* Unit tests for the AVERAGE function with arrays.
28+
*
29+
* @author oswaldo.bapvic.jr
30+
*/
31+
class AverageArrayTest {
32+
33+
@Test
34+
void testAverageSingleArray() throws EvaluationException, ParseException {
35+
Integer[] numbers = {1, 2, 3};
36+
37+
Expression expression = new Expression("AVERAGE(numbers)").with("numbers", numbers);
38+
39+
assertThat(expression.evaluate().getNumberValue().doubleValue()).isEqualTo(2);
40+
}
41+
42+
@Test
43+
void testAverageMultipleArray() throws EvaluationException, ParseException {
44+
List<Number> numbers1 = List.of(1, 2, 3);
45+
List<Number> numbers2 = List.of(4, 5, 6);
46+
47+
Expression expression =
48+
new Expression("AVERAGE(numbers1, numbers2)")
49+
.with("numbers1", numbers1)
50+
.with("numbers2", numbers2);
51+
52+
assertThat(expression.evaluate().getNumberValue().doubleValue()).isEqualTo(3.5);
53+
}
54+
55+
@Test
56+
void testAverageMixedArrayNumber() throws EvaluationException, ParseException {
57+
Double[] numbers = {1.0, 2.0, 3.0};
58+
59+
Expression expression = new Expression("AVERAGE(numbers, 4)").with("numbers", numbers);
60+
61+
assertThat(expression.evaluate().getNumberValue().doubleValue()).isEqualTo(2.5);
62+
}
63+
64+
@Test
65+
void testAverageNestedArray() throws EvaluationException, ParseException {
66+
Integer[][] numbers = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
67+
68+
Expression expression = new Expression("AVERAGE(numbers)").with("numbers", numbers);
69+
assertThat(expression.evaluate().getNumberValue().doubleValue()).isEqualTo(5);
70+
}
71+
}

0 commit comments

Comments
 (0)