Skip to content

Commit 6008d7c

Browse files
authored
Merge pull request #35 from sidhant92/array_math_functions
Add Support for Arithmetic Functions
2 parents 8ebbe99 + 16829d2 commit 6008d7c

28 files changed

+1487
-398
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,6 @@ out/
3030
`
3131

3232
gradle.properties
33+
34+
*.DS_Store
35+
**/.DS_Store

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ dependencies {
3737
implementation 'io.vavr:vavr:0.10.4'
3838
implementation 'com.github.ben-manes.caffeine:caffeine:2.9.3'
3939
implementation 'org.projectlombok:lombok:1.18.26'
40+
implementation 'org.apache.commons:commons-math3:3.6.1'
4041

4142
annotationProcessor 'org.projectlombok:lombok:1.18.26'
4243
testAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.36'

src/main/java/com/github/sidhant92/boolparser/application/ArithmeticExpressionEvaluator.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package com.github.sidhant92.boolparser.application;
22

3+
import java.util.ArrayList;
4+
import java.util.Collection;
5+
import java.util.List;
36
import java.util.Map;
47
import java.util.Optional;
8+
import java.util.stream.Collectors;
59
import org.apache.commons.lang3.tuple.Pair;
610
import com.github.sidhant92.boolparser.constant.ContainerDataType;
711
import com.github.sidhant92.boolparser.constant.DataType;
@@ -11,7 +15,9 @@
1115
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticNode;
1216
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticUnaryNode;
1317
import com.github.sidhant92.boolparser.domain.Node;
18+
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticFunctionNode;
1419
import com.github.sidhant92.boolparser.exception.UnsupportedToken;
20+
import com.github.sidhant92.boolparser.function.FunctionEvaluatorService;
1521
import com.github.sidhant92.boolparser.operator.OperatorService;
1622
import com.github.sidhant92.boolparser.parser.BoolExpressionParser;
1723
import com.github.sidhant92.boolparser.util.ValueUtils;
@@ -28,9 +34,12 @@ public class ArithmeticExpressionEvaluator {
2834

2935
private final OperatorService operatorService;
3036

37+
private final FunctionEvaluatorService functionEvaluatorService;
38+
3139
public ArithmeticExpressionEvaluator(final BoolExpressionParser boolExpressionParser) {
3240
this.boolExpressionParser = boolExpressionParser;
3341
operatorService = new OperatorService();
42+
functionEvaluatorService = new FunctionEvaluatorService();
3443
}
3544

3645
public Try<Object> evaluate(final String expression, final Map<String, Object> data) {
@@ -50,6 +59,8 @@ private Object evaluateToken(final Node node, final Map<String, Object> data) {
5059
return evaluateArithmeticLeafToken((ArithmeticLeafNode) node, data);
5160
case ARITHMETIC_UNARY:
5261
return evaluateUnaryArithmeticToken((ArithmeticUnaryNode) node, data);
62+
case ARITHMETIC_FUNCTION:
63+
return evaluateArithmeticFunctionToken((ArithmeticFunctionNode) node, data);
5364
case STRING:
5465
return evaluateStringToken((StringNode) node, data);
5566
default:
@@ -63,7 +74,8 @@ private Object evaluateStringToken(final StringNode stringNode, final Map<String
6374
}
6475

6576
private Pair<Object, DataType> evaluateArithmeticLeafToken(final ArithmeticLeafNode arithmeticLeafNode, final Map<String, Object> data) {
66-
final Optional<Object> fetchedValue = ValueUtils.getValueFromMap(arithmeticLeafNode.getOperand().toString(), data);
77+
final Optional<Object> fetchedValue = arithmeticLeafNode.getDataType() != DataType.STRING ? Optional.of(
78+
arithmeticLeafNode.getOperand()) : ValueUtils.getValueFromMap(arithmeticLeafNode.getOperand().toString(), data);
6779
return fetchedValue
6880
.map(o -> Pair.of(o, ValueUtils.getDataType(o)))
6981
.orElseGet(() -> Pair.of(arithmeticLeafNode.getOperand(), arithmeticLeafNode.getDataType()));
@@ -80,6 +92,22 @@ private Object evaluateUnaryArithmeticToken(final ArithmeticUnaryNode arithmetic
8092
return operatorService.evaluateArithmeticOperator(resolvedValue, dataType, null, null, Operator.UNARY, ContainerDataType.PRIMITIVE);
8193
}
8294

95+
private Object evaluateArithmeticFunctionToken(final ArithmeticFunctionNode arithmeticFunctionNode, final Map<String, Object> data) {
96+
final List<Pair<Object, DataType>> resolvedValues = arithmeticFunctionNode.getItems()
97+
.stream()
98+
.map(item -> evaluateArithmeticLeafToken(item, data))
99+
.collect(Collectors.toList());
100+
final List<Pair<Object, DataType>> flattenedValues = new ArrayList<>();
101+
resolvedValues.forEach(value -> {
102+
if (value.getKey() instanceof Collection) {
103+
((Collection<?>) value.getKey()).forEach(v -> flattenedValues.add(Pair.of(v, ValueUtils.getDataType(v))));
104+
} else {
105+
flattenedValues.add(value);
106+
}
107+
});
108+
return functionEvaluatorService.evaluateArithmeticFunction(arithmeticFunctionNode.getFunctionType(), flattenedValues);
109+
}
110+
83111
private Object evaluateArithmeticToken(final ArithmeticNode arithmeticNode, final Map<String, Object> data) {
84112
final Object leftValue = evaluateToken(arithmeticNode.getLeft(), data);
85113
final Object rightValue = evaluateToken(arithmeticNode.getRight(), data);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.github.sidhant92.boolparser.constant;
2+
3+
import java.util.Arrays;
4+
import java.util.Optional;
5+
6+
/**
7+
* @author sidhant.aggarwal
8+
* @since 21/05/2024
9+
*/
10+
public enum FunctionType {
11+
MIN,
12+
MAX,
13+
AVG,
14+
SUM,
15+
MEAN,
16+
MODE,
17+
MEDIAN,
18+
INT,
19+
LEN;
20+
21+
public static Optional<FunctionType> getArrayFunctionFromSymbol(final String symbol) {
22+
final String symbolUpperCase = symbol.toUpperCase();
23+
return Arrays
24+
.stream(FunctionType.values())
25+
.filter(function -> function.name().equals(symbolUpperCase))
26+
.findFirst();
27+
}
28+
}

src/main/java/com/github/sidhant92/boolparser/constant/NodeType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ public enum NodeType {
1414
ARITHMETIC,
1515
ARITHMETIC_LEAF,
1616
ARITHMETIC_UNARY,
17+
ARITHMETIC_FUNCTION,
1718
STRING
1819
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.github.sidhant92.boolparser.domain.arithmetic;
2+
3+
import java.util.List;
4+
import com.github.sidhant92.boolparser.constant.FunctionType;
5+
import com.github.sidhant92.boolparser.constant.NodeType;
6+
import lombok.AllArgsConstructor;
7+
import lombok.Builder;
8+
import lombok.Getter;
9+
import lombok.Setter;
10+
11+
/**
12+
* @author sidhant.aggarwal
13+
* @since 21/05/2024
14+
*/
15+
@AllArgsConstructor
16+
@Getter
17+
@Setter
18+
@Builder
19+
public class ArithmeticFunctionNode extends ArithmeticBaseNode {
20+
private FunctionType functionType;
21+
22+
23+
private final List<ArithmeticLeafNode> items;
24+
25+
26+
@Override
27+
public NodeType getTokenType() {
28+
return NodeType.ARITHMETIC_FUNCTION;
29+
}
30+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.github.sidhant92.boolparser.function;
2+
3+
import java.util.List;
4+
import org.apache.commons.lang3.tuple.Pair;
5+
import com.github.sidhant92.boolparser.constant.ContainerDataType;
6+
import com.github.sidhant92.boolparser.constant.DataType;
7+
import com.github.sidhant92.boolparser.constant.FunctionType;
8+
import com.github.sidhant92.boolparser.exception.InvalidContainerTypeException;
9+
import com.github.sidhant92.boolparser.exception.InvalidDataType;
10+
import com.github.sidhant92.boolparser.exception.InvalidExpressionException;
11+
import com.github.sidhant92.boolparser.function.arithmetic.AbstractFunction;
12+
import lombok.extern.slf4j.Slf4j;
13+
14+
/**
15+
* @author sidhant.aggarwal
16+
* @since 21/05/2024
17+
*/
18+
@Slf4j
19+
public class FunctionEvaluatorService {
20+
public FunctionEvaluatorService() {
21+
FunctionFactory.initialize();
22+
}
23+
24+
public Object evaluateArithmeticFunction(final FunctionType functionType, final List<Pair<Object, DataType>> items) {
25+
final AbstractFunction abstractFunction = FunctionFactory.getArithmeticFunction(functionType);
26+
if (items.isEmpty()) {
27+
log.error("Empty items not allowed");
28+
throw new InvalidExpressionException();
29+
}
30+
final ContainerDataType containerDataType = items.size() > 1 ? ContainerDataType.LIST : ContainerDataType.PRIMITIVE;
31+
if (!abstractFunction.getAllowedContainerTypes().contains(containerDataType)) {
32+
log.error("Invalid container type {} for function {}", containerDataType, functionType.name());
33+
throw new InvalidContainerTypeException();
34+
}
35+
final boolean validDataType = items
36+
.stream().allMatch(item -> abstractFunction.getAllowedDataTypes().contains(item.getValue()));
37+
if (!validDataType) {
38+
log.error("Invalid data type {} for function {}", items, functionType.name());
39+
throw new InvalidDataType();
40+
}
41+
42+
items.forEach(item -> {
43+
if (!ContainerDataType.PRIMITIVE.isValid(item.getValue(), item.getKey())) {
44+
log.error("Validation failed for the function {} for the operand {}", functionType.name(), item.getKey());
45+
throw new InvalidDataType();
46+
}
47+
});
48+
return abstractFunction.evaluate(items);
49+
}
50+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.github.sidhant92.boolparser.function;
2+
3+
import java.util.EnumMap;
4+
import java.util.Map;
5+
import com.github.sidhant92.boolparser.constant.FunctionType;
6+
import com.github.sidhant92.boolparser.function.arithmetic.AbstractFunction;
7+
import com.github.sidhant92.boolparser.function.arithmetic.AvgFunction;
8+
import com.github.sidhant92.boolparser.function.arithmetic.IntFunction;
9+
import com.github.sidhant92.boolparser.function.arithmetic.LenFunction;
10+
import com.github.sidhant92.boolparser.function.arithmetic.MaxFunction;
11+
import com.github.sidhant92.boolparser.function.arithmetic.MeanFunction;
12+
import com.github.sidhant92.boolparser.function.arithmetic.MedianFunction;
13+
import com.github.sidhant92.boolparser.function.arithmetic.MinFunction;
14+
import com.github.sidhant92.boolparser.function.arithmetic.ModeFunction;
15+
import com.github.sidhant92.boolparser.function.arithmetic.SumFunction;
16+
17+
/**
18+
* @author sidhant.aggarwal
19+
* @since 21/05/2024
20+
*/
21+
public class FunctionFactory {
22+
private static final Map<FunctionType, AbstractFunction> arithmeticFunctionrMap = new EnumMap<>(FunctionType.class);
23+
24+
private FunctionFactory() {
25+
super();
26+
}
27+
28+
public static void initialize() {
29+
arithmeticFunctionrMap.put(FunctionType.MIN, new MinFunction());
30+
arithmeticFunctionrMap.put(FunctionType.MAX, new MaxFunction());
31+
arithmeticFunctionrMap.put(FunctionType.AVG, new AvgFunction());
32+
arithmeticFunctionrMap.put(FunctionType.SUM, new SumFunction());
33+
arithmeticFunctionrMap.put(FunctionType.MEAN, new MeanFunction());
34+
arithmeticFunctionrMap.put(FunctionType.MEDIAN, new MedianFunction());
35+
arithmeticFunctionrMap.put(FunctionType.MODE, new ModeFunction());
36+
arithmeticFunctionrMap.put(FunctionType.INT, new IntFunction());
37+
arithmeticFunctionrMap.put(FunctionType.LEN, new LenFunction());
38+
}
39+
40+
public static AbstractFunction getArithmeticFunction(final FunctionType functionType) {
41+
return arithmeticFunctionrMap.get(functionType);
42+
}
43+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.github.sidhant92.boolparser.function.arithmetic;
2+
3+
import java.util.List;
4+
import org.apache.commons.lang3.tuple.Pair;
5+
import com.github.sidhant92.boolparser.constant.ContainerDataType;
6+
import com.github.sidhant92.boolparser.constant.DataType;
7+
import com.github.sidhant92.boolparser.constant.FunctionType;
8+
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticFunctionNode;
9+
10+
/**
11+
* @author sidhant.aggarwal
12+
* @since 21/05/2024
13+
*/
14+
public abstract class AbstractFunction {
15+
public abstract Object evaluate(final List<Pair<Object, DataType>> items);
16+
17+
public abstract FunctionType getFunctionType();
18+
19+
public abstract List<ContainerDataType> getAllowedContainerTypes();
20+
21+
public abstract List<DataType> getAllowedDataTypes();
22+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.github.sidhant92.boolparser.function.arithmetic;
2+
3+
import java.util.Arrays;
4+
import java.util.Collections;
5+
import java.util.List;
6+
import org.apache.commons.lang3.tuple.Pair;
7+
import com.github.sidhant92.boolparser.constant.ContainerDataType;
8+
import com.github.sidhant92.boolparser.constant.DataType;
9+
import com.github.sidhant92.boolparser.constant.FunctionType;
10+
import com.github.sidhant92.boolparser.util.ValueUtils;
11+
12+
/**
13+
* @author sidhant.aggarwal
14+
* @since 21/05/2024
15+
*/
16+
public class AvgFunction extends AbstractFunction {
17+
@Override
18+
public Object evaluate(final List<Pair<Object, DataType>> items) {
19+
if (items
20+
.stream().anyMatch(a -> a.getValue().equals(DataType.DECIMAL))) {
21+
return ValueUtils.caseDouble(items
22+
.stream().mapToDouble(a -> Double.parseDouble(a.getKey().toString())).average().getAsDouble());
23+
}
24+
if (items
25+
.stream().anyMatch(a -> a.getValue().equals(DataType.LONG))) {
26+
return ValueUtils.caseDouble(items
27+
.stream().mapToLong(a -> Long.parseLong(a.getKey().toString())).average().getAsDouble());
28+
}
29+
return ValueUtils.caseDouble(items
30+
.stream().mapToInt(a -> Integer.parseInt(a.getKey().toString())).average().getAsDouble());
31+
}
32+
33+
@Override
34+
public FunctionType getFunctionType() {
35+
return FunctionType.AVG;
36+
}
37+
38+
@Override
39+
public List<ContainerDataType> getAllowedContainerTypes() {
40+
return Collections.singletonList(ContainerDataType.LIST);
41+
}
42+
43+
@Override
44+
public List<DataType> getAllowedDataTypes() {
45+
return Arrays.asList(DataType.INTEGER, DataType.LONG, DataType.DECIMAL);
46+
}
47+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.github.sidhant92.boolparser.function.arithmetic;
2+
3+
import java.util.Arrays;
4+
import java.util.Collections;
5+
import java.util.List;
6+
import org.apache.commons.lang3.tuple.Pair;
7+
import com.github.sidhant92.boolparser.constant.ContainerDataType;
8+
import com.github.sidhant92.boolparser.constant.DataType;
9+
import com.github.sidhant92.boolparser.constant.FunctionType;
10+
11+
/**
12+
* @author sidhant.aggarwal
13+
* @since 21/05/2024
14+
*/
15+
public class IntFunction extends AbstractFunction {
16+
@Override
17+
public Object evaluate(final List<Pair<Object, DataType>> items) {
18+
final Pair<Object, DataType> item = items.get(0);
19+
if (item.getValue() == DataType.DECIMAL) {
20+
return ((Double) item.getKey()).intValue();
21+
}
22+
if (item.getValue() == DataType.LONG) {
23+
return ((Long) item.getKey()).intValue();
24+
}
25+
return item.getKey();
26+
}
27+
28+
@Override
29+
public FunctionType getFunctionType() {
30+
return FunctionType.INT;
31+
}
32+
33+
@Override
34+
public List<ContainerDataType> getAllowedContainerTypes() {
35+
return Collections.singletonList(ContainerDataType.PRIMITIVE);
36+
}
37+
38+
@Override
39+
public List<DataType> getAllowedDataTypes() {
40+
return Arrays.asList(DataType.INTEGER, DataType.LONG, DataType.DECIMAL);
41+
}
42+
}

0 commit comments

Comments
 (0)