diff options
author | Lester Solbakken <lesters@yahoo-inc.com> | 2017-11-15 14:39:56 +0100 |
---|---|---|
committer | Lester Solbakken <lesters@yahoo-inc.com> | 2017-11-15 14:39:56 +0100 |
commit | 9b913de9cd46de589d8f29436bfc46c1159a53de (patch) | |
tree | 4db87949d8b854e88c572b04900cea4b10756fa5 /searchlib | |
parent | 797270c0a6d6d59e55eb59f6d1a43ec323645026 (diff) |
Add boolean operators to Java ranking evaluation
Diffstat (limited to 'searchlib')
10 files changed, 187 insertions, 19 deletions
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleCompatibleValue.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleCompatibleValue.java index 0ed2bdd6331..0868af9bc72 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleCompatibleValue.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleCompatibleValue.java @@ -44,6 +44,21 @@ public abstract class DoubleCompatibleValue extends Value { } @Override + public Value and(Value value) { + return new BooleanValue(asBoolean() && value.asBoolean()); + } + + @Override + public Value or(Value value) { + return new BooleanValue(asBoolean() || value.asBoolean()); + } + + @Override + public Value not() { + return new BooleanValue(!asBoolean()); + } + + @Override public Value compare(TruthOperator operator, Value value) { return new BooleanValue(operator.evaluate(asDouble(), value.asDouble())); } diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/StringValue.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/StringValue.java index 5374a9d3ce6..b62081f2c6a 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/StringValue.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/StringValue.java @@ -54,22 +54,37 @@ public class StringValue extends Value { @Override public Value subtract(Value value) { - throw new UnsupportedOperationException("String values ('" + value + "') does not support subtraction"); + throw new UnsupportedOperationException("String values ('" + value + "') do not support subtraction"); } @Override public Value multiply(Value value) { - throw new UnsupportedOperationException("String values ('" + value + "') does not support multiplication"); + throw new UnsupportedOperationException("String values ('" + value + "') do not support multiplication"); } @Override public Value divide(Value value) { - throw new UnsupportedOperationException("String values ('" + value + "') does not support division"); + throw new UnsupportedOperationException("String values ('" + value + "') do not support division"); } @Override public Value modulo(Value value) { - throw new UnsupportedOperationException("String values ('" + value + "') does not support modulo"); + throw new UnsupportedOperationException("String values ('" + value + "') do not support modulo"); + } + + @Override + public Value and(Value value) { + throw new UnsupportedOperationException("String values ('" + value + "') do not support and"); + } + + @Override + public Value or(Value value) { + throw new UnsupportedOperationException("String values ('" + value + "') do not support or"); + } + + @Override + public Value not() { + throw new UnsupportedOperationException("String values ('" + value + "') do not support not"); } @Override diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java index b283603e713..919a23eeaf5 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java @@ -89,6 +89,26 @@ public class TensorValue extends Value { return new TensorValue(value.map((value) -> value % argument.asDouble())); } + @Override + public Value and(Value argument) { + if (argument instanceof TensorValue) + return new TensorValue(value.join(((TensorValue)argument).value, (a, b) -> ((a!=0.0) && (b!=0.0)) ? 1.0 : 0.0 )); + else + return new TensorValue(value.map((value) -> ((value!=0.0) && argument.asBoolean()) ? 1 : 0)); + } + + @Override + public Value or(Value argument) { + if (argument instanceof TensorValue) + return new TensorValue(value.join(((TensorValue)argument).value, (a, b) -> ((a!=0.0) || (b!=0.0)) ? 1.0 : 0.0 )); + else + return new TensorValue(value.map((value) -> ((value!=0.0) || argument.asBoolean()) ? 1 : 0)); + } + + @Override + public Value not() { + return new TensorValue(value.map((value) -> (value==0.0) ? 1.0 : 0.0)); + } private Tensor asTensor(Value value, String operationName) { if ( ! (value instanceof TensorValue)) diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Value.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Value.java index f42082321b3..bcbce6e646f 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Value.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Value.java @@ -43,6 +43,12 @@ public abstract class Value { public abstract Value modulo(Value value); + public abstract Value and(Value value); + + public abstract Value or(Value value); + + public abstract Value not(); + /** Perform the comparison specified by the operator between this value and the given value */ public abstract Value compare(TruthOperator operator, Value value); diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ArithmeticNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ArithmeticNode.java index 91d8abec1be..518a15bcc87 100755 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ArithmeticNode.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ArithmeticNode.java @@ -77,7 +77,7 @@ public final class ArithmeticNode extends CompositeNode { Iterator<ExpressionNode> child = children.iterator(); Deque<ValueItem> stack = new ArrayDeque<>(); - stack.push(new ValueItem(ArithmeticOperator.PLUS, child.next().evaluate(context))); + stack.push(new ValueItem(ArithmeticOperator.OR, child.next().evaluate(context))); for (Iterator<ArithmeticOperator> it = operators.iterator(); it.hasNext() && child.hasNext();) { ArithmeticOperator op = it.next(); if (!stack.isEmpty()) { diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ArithmeticOperator.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ArithmeticOperator.java index 2187a96ba4d..aae59fe2af8 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ArithmeticOperator.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ArithmeticOperator.java @@ -14,22 +14,28 @@ import java.util.List; */ public enum ArithmeticOperator { - PLUS(0, "+") { public Value evaluate(Value x, Value y) { + OR(0, "||") { public Value evaluate(Value x, Value y) { + return x.or(y); + }}, + AND(1, "&&") { public Value evaluate(Value x, Value y) { + return x.and(y); + }}, + PLUS(2, "+") { public Value evaluate(Value x, Value y) { return x.add(y); }}, - MINUS(1, "-") { public Value evaluate(Value x, Value y) { + MINUS(3, "-") { public Value evaluate(Value x, Value y) { return x.subtract(y); }}, - MULTIPLY(2, "*") { public Value evaluate(Value x, Value y) { + MULTIPLY(4, "*") { public Value evaluate(Value x, Value y) { return x.multiply(y); }}, - DIVIDE(3, "/") { public Value evaluate(Value x, Value y) { + DIVIDE(5, "/") { public Value evaluate(Value x, Value y) { return x.divide(y); }}, - MODULO(4, "%") { public Value evaluate(Value x, Value y) { + MODULO(6, "%") { public Value evaluate(Value x, Value y) { return x.modulo(y); }}; - + /** A list of all the operators in this in order of decreasing precedence */ public static final List<ArithmeticOperator> operatorsByPrecedence = operatorsByPrecedence(); @@ -60,6 +66,8 @@ public enum ArithmeticOperator { operators.add(MULTIPLY); operators.add(MINUS); operators.add(PLUS); + operators.add(AND); + operators.add(OR); return Collections.unmodifiableList(operators); } diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/NotNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/NotNode.java new file mode 100644 index 00000000000..8c459a032bd --- /dev/null +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/NotNode.java @@ -0,0 +1,50 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.searchlib.rankingexpression.rule; + +import com.yahoo.searchlib.rankingexpression.evaluation.Context; +import com.yahoo.searchlib.rankingexpression.evaluation.Value; + +import java.util.Collections; +import java.util.Deque; +import java.util.List; + +/** + * A node which flips the logical value produced from the nested expression. + * + * @author lesters + */ +public class NotNode extends BooleanNode { + + private final ExpressionNode value; + + public NotNode(ExpressionNode value) { + this.value = value; + } + + public ExpressionNode getValue() { + return value; + } + + @Override + public List<ExpressionNode> children() { + return Collections.singletonList(value); + } + + @Override + public String toString(SerializationContext context, Deque<String> path, CompositeNode parent) { + return "!" + value.toString(context, path, parent); + } + + @Override + public Value evaluate(Context context) { + return value.evaluate(context).not(); + } + + @Override + public NotNode setChildren(List<ExpressionNode> children) { + if (children.size() != 1) throw new IllegalArgumentException("Expected 1 children but got " + children.size()); + return new NotNode(children.get(0)); + } + +} + diff --git a/searchlib/src/main/javacc/RankingExpressionParser.jj b/searchlib/src/main/javacc/RankingExpressionParser.jj index 01fed00202c..035a92b0365 100755 --- a/searchlib/src/main/javacc/RankingExpressionParser.jj +++ b/searchlib/src/main/javacc/RankingExpressionParser.jj @@ -86,6 +86,10 @@ TOKEN : <IN: "in"> | <F: "f"> | + <NOT: "!"> | + <AND: "&&"> | + <OR: "||"> | + <ABS: "abs"> | <ACOS: "acos"> | <ASIN: "asin"> | @@ -204,7 +208,9 @@ ArithmeticOperator arithmetic() : { } <SUB> { return ArithmeticOperator.MINUS; } | <DIV> { return ArithmeticOperator.DIVIDE; } | <MUL> { return ArithmeticOperator.MULTIPLY; } | - <MOD> { return ArithmeticOperator.MODULO; } ) + <MOD> { return ArithmeticOperator.MODULO; } | + <AND> { return ArithmeticOperator.AND; } | + <OR> { return ArithmeticOperator.OR; } ) { return null; } } @@ -224,16 +230,23 @@ ExpressionNode value() : { ExpressionNode ret; boolean neg = false; + boolean not = false; } { - ( [ LOOKAHEAD(2) <SUB> { neg = true; } ] - ( ret = constantPrimitive() | - LOOKAHEAD(2) ret = ifExpression() | - LOOKAHEAD(4) ret = function() | - ret = feature() | - ret = queryFeature() | + ( + [ <NOT> { not = true; } ] + [ LOOKAHEAD(2) <SUB> { neg = true; } ] + ( ret = constantPrimitive() | + LOOKAHEAD(2) ret = ifExpression() | + LOOKAHEAD(4) ret = function() | + ret = feature() | + ret = queryFeature() | ( <LBRACE> ret = expression() <RBRACE> { ret = new EmbracedNode(ret); } ) ) ) - { return neg ? new NegativeNode(ret) : ret; } + { + ret = not ? new NotNode(ret) : ret; + ret = neg ? new NegativeNode(ret) : ret; + return ret; + } } IfNode ifExpression() : diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java index 5d357777657..26d3695dd07 100644 --- a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java +++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java @@ -37,6 +37,7 @@ public class EvaluationTestCase { tester.assertEvaluates(26, "2*3+4*5"); tester.assertEvaluates(1, "2/6+4/6"); tester.assertEvaluates(2 * 3 * 4 + 3 * 4 * 5 - 4 * 200 / 10, "2*3*4+3*4*5-4*200/10"); + tester.assertEvaluates(3, "1 + 10 % 6 / 2"); // Conditionals tester.assertEvaluates(2 * (3 * 4 + 3) * (4 * 5 - 4 * 200) / 10, "2*(3*4+3)*(4*5-4*200)/10"); @@ -89,6 +90,38 @@ public class EvaluationTestCase { } @Test + public void testBooleanEvaluation() { + EvaluationTester tester = new EvaluationTester(); + + // and + tester.assertEvaluates(false, "0 && 0"); + tester.assertEvaluates(false, "0 && 1"); + tester.assertEvaluates(false, "1 && 0"); + tester.assertEvaluates(true, "1 && 1"); + tester.assertEvaluates(true, "1 && 2"); + tester.assertEvaluates(true, "1 && 0.1"); + + // or + tester.assertEvaluates(false, "0 || 0"); + tester.assertEvaluates(true, "0 || 0.1"); + tester.assertEvaluates(true, "0 || 1"); + tester.assertEvaluates(true, "1 || 0"); + tester.assertEvaluates(true, "1 || 1"); + + // not + tester.assertEvaluates(true, "!0"); + tester.assertEvaluates(false, "!1"); + tester.assertEvaluates(false, "!2"); + tester.assertEvaluates(true, "!0 && 1"); + + // precedence + tester.assertEvaluates(0, "2 * (0 && 1)"); + tester.assertEvaluates(2, "2 * (1 && 1)"); + tester.assertEvaluates(true, "2 + 0 && 1"); + tester.assertEvaluates(true, "1 && 0 + 2"); + } + + @Test public void testTensorEvaluation() { EvaluationTester tester = new EvaluationTester(); tester.assertEvaluates("{}", "tensor0", "{}"); diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTester.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTester.java index d67c9dfd9dc..ee2b1c147e3 100644 --- a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTester.java +++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTester.java @@ -58,10 +58,18 @@ public class EvaluationTester { return assertEvaluates(value, expressionString, defaultContext); } + public RankingExpression assertEvaluates(boolean value, String expressionString) { + return assertEvaluates(value, expressionString, defaultContext); + } + public RankingExpression assertEvaluates(double value, String expressionString, Context context) { return assertEvaluates(new DoubleValue(value), expressionString, context, ""); } + public RankingExpression assertEvaluates(boolean value, String expressionString, Context context) { + return assertEvaluates(new BooleanValue(value), expressionString, context, ""); + } + public RankingExpression assertEvaluates(Value value, String expressionString, Context context, String explanation) { try { RankingExpression expression = new RankingExpression(expressionString); |