Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for Arithmetic Functions #35

Merged
merged 1 commit into from
May 22, 2024
Merged
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
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
Loading