Skip to content

Commit

Permalink
Merge pull request #35 from sidhant92/array_math_functions
Browse files Browse the repository at this point in the history
Add Support for Arithmetic Functions
  • Loading branch information
sidhant92 authored May 22, 2024
2 parents 8ebbe99 + 16829d2 commit 6008d7c
Show file tree
Hide file tree
Showing 28 changed files with 1,487 additions and 398 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ out/
`

gradle.properties

*.DS_Store
**/.DS_Store
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ dependencies {
implementation 'io.vavr:vavr:0.10.4'
implementation 'com.github.ben-manes.caffeine:caffeine:2.9.3'
implementation 'org.projectlombok:lombok:1.18.26'
implementation 'org.apache.commons:commons-math3:3.6.1'

annotationProcessor 'org.projectlombok:lombok:1.18.26'
testAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.36'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.github.sidhant92.boolparser.application;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
import com.github.sidhant92.boolparser.constant.ContainerDataType;
import com.github.sidhant92.boolparser.constant.DataType;
Expand All @@ -11,7 +15,9 @@
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticNode;
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticUnaryNode;
import com.github.sidhant92.boolparser.domain.Node;
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticFunctionNode;
import com.github.sidhant92.boolparser.exception.UnsupportedToken;
import com.github.sidhant92.boolparser.function.FunctionEvaluatorService;
import com.github.sidhant92.boolparser.operator.OperatorService;
import com.github.sidhant92.boolparser.parser.BoolExpressionParser;
import com.github.sidhant92.boolparser.util.ValueUtils;
Expand All @@ -28,9 +34,12 @@ public class ArithmeticExpressionEvaluator {

private final OperatorService operatorService;

private final FunctionEvaluatorService functionEvaluatorService;

public ArithmeticExpressionEvaluator(final BoolExpressionParser boolExpressionParser) {
this.boolExpressionParser = boolExpressionParser;
operatorService = new OperatorService();
functionEvaluatorService = new FunctionEvaluatorService();
}

public Try<Object> evaluate(final String expression, final Map<String, Object> data) {
Expand All @@ -50,6 +59,8 @@ private Object evaluateToken(final Node node, final Map<String, Object> data) {
return evaluateArithmeticLeafToken((ArithmeticLeafNode) node, data);
case ARITHMETIC_UNARY:
return evaluateUnaryArithmeticToken((ArithmeticUnaryNode) node, data);
case ARITHMETIC_FUNCTION:
return evaluateArithmeticFunctionToken((ArithmeticFunctionNode) node, data);
case STRING:
return evaluateStringToken((StringNode) node, data);
default:
Expand All @@ -63,7 +74,8 @@ private Object evaluateStringToken(final StringNode stringNode, final Map<String
}

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

private Object evaluateArithmeticFunctionToken(final ArithmeticFunctionNode arithmeticFunctionNode, final Map<String, Object> data) {
final List<Pair<Object, DataType>> resolvedValues = arithmeticFunctionNode.getItems()
.stream()
.map(item -> evaluateArithmeticLeafToken(item, data))
.collect(Collectors.toList());
final List<Pair<Object, DataType>> flattenedValues = new ArrayList<>();
resolvedValues.forEach(value -> {
if (value.getKey() instanceof Collection) {
((Collection<?>) value.getKey()).forEach(v -> flattenedValues.add(Pair.of(v, ValueUtils.getDataType(v))));
} else {
flattenedValues.add(value);
}
});
return functionEvaluatorService.evaluateArithmeticFunction(arithmeticFunctionNode.getFunctionType(), flattenedValues);
}

private Object evaluateArithmeticToken(final ArithmeticNode arithmeticNode, final Map<String, Object> data) {
final Object leftValue = evaluateToken(arithmeticNode.getLeft(), data);
final Object rightValue = evaluateToken(arithmeticNode.getRight(), data);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.github.sidhant92.boolparser.constant;

import java.util.Arrays;
import java.util.Optional;

/**
* @author sidhant.aggarwal
* @since 21/05/2024
*/
public enum FunctionType {
MIN,
MAX,
AVG,
SUM,
MEAN,
MODE,
MEDIAN,
INT,
LEN;

public static Optional<FunctionType> getArrayFunctionFromSymbol(final String symbol) {
final String symbolUpperCase = symbol.toUpperCase();
return Arrays
.stream(FunctionType.values())
.filter(function -> function.name().equals(symbolUpperCase))
.findFirst();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ public enum NodeType {
ARITHMETIC,
ARITHMETIC_LEAF,
ARITHMETIC_UNARY,
ARITHMETIC_FUNCTION,
STRING
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.github.sidhant92.boolparser.domain.arithmetic;

import java.util.List;
import com.github.sidhant92.boolparser.constant.FunctionType;
import com.github.sidhant92.boolparser.constant.NodeType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

/**
* @author sidhant.aggarwal
* @since 21/05/2024
*/
@AllArgsConstructor
@Getter
@Setter
@Builder
public class ArithmeticFunctionNode extends ArithmeticBaseNode {
private FunctionType functionType;


private final List<ArithmeticLeafNode> items;


@Override
public NodeType getTokenType() {
return NodeType.ARITHMETIC_FUNCTION;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.github.sidhant92.boolparser.function;

import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
import com.github.sidhant92.boolparser.constant.ContainerDataType;
import com.github.sidhant92.boolparser.constant.DataType;
import com.github.sidhant92.boolparser.constant.FunctionType;
import com.github.sidhant92.boolparser.exception.InvalidContainerTypeException;
import com.github.sidhant92.boolparser.exception.InvalidDataType;
import com.github.sidhant92.boolparser.exception.InvalidExpressionException;
import com.github.sidhant92.boolparser.function.arithmetic.AbstractFunction;
import lombok.extern.slf4j.Slf4j;

/**
* @author sidhant.aggarwal
* @since 21/05/2024
*/
@Slf4j
public class FunctionEvaluatorService {
public FunctionEvaluatorService() {
FunctionFactory.initialize();
}

public Object evaluateArithmeticFunction(final FunctionType functionType, final List<Pair<Object, DataType>> items) {
final AbstractFunction abstractFunction = FunctionFactory.getArithmeticFunction(functionType);
if (items.isEmpty()) {
log.error("Empty items not allowed");
throw new InvalidExpressionException();
}
final ContainerDataType containerDataType = items.size() > 1 ? ContainerDataType.LIST : ContainerDataType.PRIMITIVE;
if (!abstractFunction.getAllowedContainerTypes().contains(containerDataType)) {
log.error("Invalid container type {} for function {}", containerDataType, functionType.name());
throw new InvalidContainerTypeException();
}
final boolean validDataType = items
.stream().allMatch(item -> abstractFunction.getAllowedDataTypes().contains(item.getValue()));
if (!validDataType) {
log.error("Invalid data type {} for function {}", items, functionType.name());
throw new InvalidDataType();
}

items.forEach(item -> {
if (!ContainerDataType.PRIMITIVE.isValid(item.getValue(), item.getKey())) {
log.error("Validation failed for the function {} for the operand {}", functionType.name(), item.getKey());
throw new InvalidDataType();
}
});
return abstractFunction.evaluate(items);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.github.sidhant92.boolparser.function;

import java.util.EnumMap;
import java.util.Map;
import com.github.sidhant92.boolparser.constant.FunctionType;
import com.github.sidhant92.boolparser.function.arithmetic.AbstractFunction;
import com.github.sidhant92.boolparser.function.arithmetic.AvgFunction;
import com.github.sidhant92.boolparser.function.arithmetic.IntFunction;
import com.github.sidhant92.boolparser.function.arithmetic.LenFunction;
import com.github.sidhant92.boolparser.function.arithmetic.MaxFunction;
import com.github.sidhant92.boolparser.function.arithmetic.MeanFunction;
import com.github.sidhant92.boolparser.function.arithmetic.MedianFunction;
import com.github.sidhant92.boolparser.function.arithmetic.MinFunction;
import com.github.sidhant92.boolparser.function.arithmetic.ModeFunction;
import com.github.sidhant92.boolparser.function.arithmetic.SumFunction;

/**
* @author sidhant.aggarwal
* @since 21/05/2024
*/
public class FunctionFactory {
private static final Map<FunctionType, AbstractFunction> arithmeticFunctionrMap = new EnumMap<>(FunctionType.class);

private FunctionFactory() {
super();
}

public static void initialize() {
arithmeticFunctionrMap.put(FunctionType.MIN, new MinFunction());
arithmeticFunctionrMap.put(FunctionType.MAX, new MaxFunction());
arithmeticFunctionrMap.put(FunctionType.AVG, new AvgFunction());
arithmeticFunctionrMap.put(FunctionType.SUM, new SumFunction());
arithmeticFunctionrMap.put(FunctionType.MEAN, new MeanFunction());
arithmeticFunctionrMap.put(FunctionType.MEDIAN, new MedianFunction());
arithmeticFunctionrMap.put(FunctionType.MODE, new ModeFunction());
arithmeticFunctionrMap.put(FunctionType.INT, new IntFunction());
arithmeticFunctionrMap.put(FunctionType.LEN, new LenFunction());
}

public static AbstractFunction getArithmeticFunction(final FunctionType functionType) {
return arithmeticFunctionrMap.get(functionType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.github.sidhant92.boolparser.function.arithmetic;

import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
import com.github.sidhant92.boolparser.constant.ContainerDataType;
import com.github.sidhant92.boolparser.constant.DataType;
import com.github.sidhant92.boolparser.constant.FunctionType;
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticFunctionNode;

/**
* @author sidhant.aggarwal
* @since 21/05/2024
*/
public abstract class AbstractFunction {
public abstract Object evaluate(final List<Pair<Object, DataType>> items);

public abstract FunctionType getFunctionType();

public abstract List<ContainerDataType> getAllowedContainerTypes();

public abstract List<DataType> getAllowedDataTypes();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.github.sidhant92.boolparser.function.arithmetic;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
import com.github.sidhant92.boolparser.constant.ContainerDataType;
import com.github.sidhant92.boolparser.constant.DataType;
import com.github.sidhant92.boolparser.constant.FunctionType;
import com.github.sidhant92.boolparser.util.ValueUtils;

/**
* @author sidhant.aggarwal
* @since 21/05/2024
*/
public class AvgFunction extends AbstractFunction {
@Override
public Object evaluate(final List<Pair<Object, DataType>> items) {
if (items
.stream().anyMatch(a -> a.getValue().equals(DataType.DECIMAL))) {
return ValueUtils.caseDouble(items
.stream().mapToDouble(a -> Double.parseDouble(a.getKey().toString())).average().getAsDouble());
}
if (items
.stream().anyMatch(a -> a.getValue().equals(DataType.LONG))) {
return ValueUtils.caseDouble(items
.stream().mapToLong(a -> Long.parseLong(a.getKey().toString())).average().getAsDouble());
}
return ValueUtils.caseDouble(items
.stream().mapToInt(a -> Integer.parseInt(a.getKey().toString())).average().getAsDouble());
}

@Override
public FunctionType getFunctionType() {
return FunctionType.AVG;
}

@Override
public List<ContainerDataType> getAllowedContainerTypes() {
return Collections.singletonList(ContainerDataType.LIST);
}

@Override
public List<DataType> getAllowedDataTypes() {
return Arrays.asList(DataType.INTEGER, DataType.LONG, DataType.DECIMAL);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.github.sidhant92.boolparser.function.arithmetic;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
import com.github.sidhant92.boolparser.constant.ContainerDataType;
import com.github.sidhant92.boolparser.constant.DataType;
import com.github.sidhant92.boolparser.constant.FunctionType;

/**
* @author sidhant.aggarwal
* @since 21/05/2024
*/
public class IntFunction extends AbstractFunction {
@Override
public Object evaluate(final List<Pair<Object, DataType>> items) {
final Pair<Object, DataType> item = items.get(0);
if (item.getValue() == DataType.DECIMAL) {
return ((Double) item.getKey()).intValue();
}
if (item.getValue() == DataType.LONG) {
return ((Long) item.getKey()).intValue();
}
return item.getKey();
}

@Override
public FunctionType getFunctionType() {
return FunctionType.INT;
}

@Override
public List<ContainerDataType> getAllowedContainerTypes() {
return Collections.singletonList(ContainerDataType.PRIMITIVE);
}

@Override
public List<DataType> getAllowedDataTypes() {
return Arrays.asList(DataType.INTEGER, DataType.LONG, DataType.DECIMAL);
}
}
Loading

0 comments on commit 6008d7c

Please sign in to comment.