我有一个传入记录过滤器,其中存储了如下给出的逻辑子句。
Acct1 = 'Y' AND Acct2 = 'N' AND Acct3 = 'N' AND Acct4 = 'N' AND Acct5 = 'N' AND ((Acct6 = 'N' OR Acct7 = 'N' AND Acct1 = 'Y') AND Formatted= 'N' AND Acct9 = 'N' AND (Acct10 = 'N' AND Acct11 = 'N') AND EditableField= 'N' )
我输入到此条款的数据将来自 Csv 文件,如下所示。
Country,Type,Usage,Acct1,Acct2,Acct3,Acct4,Acct5,Acct6,Acct7,Formatted,Acct9,Acct10,Acct11,EditableField
USA,Premium,Corporate,Y,N,Y,N,N,N,Y,N,Y,N,Y,N,
Mexico,Premium,Corporate,Y,N,Y,N,Y,N,Y,N,Y,N,Y,N,
USA,Premium,Corporate,Y,N,Y,N,N,N,N,Y,Y,N,Y,N,
USA,Premium,Corporate,Y,N,Y,N,Y,N,Y,Y,Y,N,Y,N,
我将不得不根据子句中定义的条件过滤掉文件中的记录。这是一个简单子(monad)句的示例,但会有比这更多的内部条件,用户可以随时更改子句,记录必须依次通过 10 个这样的子句。
所以我正在寻找一种动态解释子句并将其应用于传入记录的方法。请向我提供有关如何设计的建议/任何示例(如果有)。
最佳答案
这是完整的解决方案,不包括第三方库,如 ANTLR 或 JavaCC。请注意,虽然它是可扩展的,但它的功能仍然有限。如果您想创建更复杂的表达式,最好使用语法生成器。
首先,让我们编写一个分词器,将输入字符串拆分为分词。以下是 token 类型:
private static enum TokenType {
WHITESPACE, AND, OR, EQUALS, LEFT_PAREN, RIGHT_PAREN, IDENTIFIER, LITERAL, EOF
}
token 类本身:
private static class Token {
final TokenType type;
final int start; // start position in input (for error reporting)
final String data; // payload
public Token(TokenType type, int start, String data) {
this.type = type;
this.start = start;
this.data = data;
}
@Override
public String toString() {
return type + "[" + data + "]";
}
}
为了简化标记化,让我们创建一个正则表达式,它从输入字符串中读取下一个标记:
private static final Pattern TOKENS =
Pattern.compile("(\\s+)|(AND)|(OR)|(=)|(\\()|(\\))|(\\w+)|\'([^\']+)\'");
请注意,它有很多组,每个 TokenType
一组。以相同的顺序(首先是 WHITESPACE
,然后是 AND
等等)。最后是分词器方法:
private static TokenStream tokenize(String input) throws ParseException {
Matcher matcher = TOKENS.matcher(input);
List<Token> tokens = new ArrayList<>();
int offset = 0;
TokenType[] types = TokenType.values();
while (offset != input.length()) {
if (!matcher.find() || matcher.start() != offset) {
throw new ParseException("Unexpected token at " + offset, offset);
}
for (int i = 0; i < types.length; i++) {
if (matcher.group(i + 1) != null) {
if (types[i] != TokenType.WHITESPACE)
tokens.add(new Token(types[i], offset, matcher.group(i + 1)));
break;
}
}
offset = matcher.end();
}
tokens.add(new Token(TokenType.EOF, input.length(), ""));
return new TokenStream(tokens);
}
我正在使用 java.text.ParseException
.这里我们应用正则表达式 Matcher
直到输入结束。如果它在当前位置不匹配,我们将抛出异常。否则,我们寻找找到的匹配组并从中创建一个标记,忽略 WHITESPACE
token 。最后我们添加一个EOF
表示输入结束的标记。结果返回为特殊 TokenStream
目的。这是 TokenStream
将帮助我们进行解析的类:
private static class TokenStream {
final List<Token> tokens;
int offset = 0;
public TokenStream(List<Token> tokens) {
this.tokens = tokens;
}
// consume next token of given type (throw exception if type differs)
public Token consume(TokenType type) throws ParseException {
Token token = tokens.get(offset++);
if (token.type != type) {
throw new ParseException("Unexpected token at " + token.start
+ ": " + token + " (was looking for " + type + ")",
token.start);
}
return token;
}
// consume token of given type (return null and don't advance if type differs)
public Token consumeIf(TokenType type) {
Token token = tokens.get(offset);
if (token.type == type) {
offset++;
return token;
}
return null;
}
@Override
public String toString() {
return tokens.toString();
}
}
所以我们有了分词器,哇哦。您现在可以使用 System.out.println(tokenize("Acct1 = 'Y' AND (Acct2 = 'N' OR Acct3 = 'N')"));
对其进行测试
现在让我们编写解析器,它将创建表达式的树状表示。首先是界面Expr
对于所有树节点:
public interface Expr {
public boolean evaluate(Map<String, String> data);
}
它是唯一用于评估给定数据集的表达式并在数据集匹配时返回 true 的方法。
最基本的表达式是 EqualsExpr
这就像 Acct1 = 'Y'
或 'Y' = Acct1
:
private static class EqualsExpr implements Expr {
private final String identifier, literal;
public EqualsExpr(TokenStream stream) throws ParseException {
Token token = stream.consumeIf(TokenType.IDENTIFIER);
if(token != null) {
this.identifier = token.data;
stream.consume(TokenType.EQUALS);
this.literal = stream.consume(TokenType.LITERAL).data;
} else {
this.literal = stream.consume(TokenType.LITERAL).data;
stream.consume(TokenType.EQUALS);
this.identifier = stream.consume(TokenType.IDENTIFIER).data;
}
}
@Override
public String toString() {
return identifier+"='"+literal+"'";
}
@Override
public boolean evaluate(Map<String, String> data) {
return literal.equals(data.get(identifier));
}
}
toString()
方法仅供引用,您可以将其删除。
接下来我们将定义 SubExpr
类是 EqualsExpr
或括号中更复杂的内容(如果我们看到括号):
private static class SubExpr implements Expr {
private final Expr child;
public SubExpr(TokenStream stream) throws ParseException {
if(stream.consumeIf(TokenType.LEFT_PAREN) != null) {
child = new OrExpr(stream);
stream.consume(TokenType.RIGHT_PAREN);
} else {
child = new EqualsExpr(stream);
}
}
@Override
public String toString() {
return "("+child+")";
}
@Override
public boolean evaluate(Map<String, String> data) {
return child.evaluate(data);
}
}
接下来是AndExpr
这是一组 SubExpr
由 AND
连接的表达式运算符(operator):
private static class AndExpr implements Expr {
private final List<Expr> children = new ArrayList<>();
public AndExpr(TokenStream stream) throws ParseException {
do {
children.add(new SubExpr(stream));
} while(stream.consumeIf(TokenType.AND) != null);
}
@Override
public String toString() {
return children.stream().map(Object::toString).collect(Collectors.joining(" AND "));
}
@Override
public boolean evaluate(Map<String, String> data) {
for(Expr child : children) {
if(!child.evaluate(data))
return false;
}
return true;
}
}
我在 toString
中使用 Java-8 Stream API为简洁起见。如果你不能使用 Java-8,你可以用 for 循环重写它或者删除 toString
完全。
最后我们定义OrExpr
这是一组 AndExpr
由 OR
加入(通常 OR
的优先级低于 AND
)。它与 AndExpr
非常相似:
private static class OrExpr implements Expr {
private final List<Expr> children = new ArrayList<>();
public OrExpr(TokenStream stream) throws ParseException {
do {
children.add(new AndExpr(stream));
} while(stream.consumeIf(TokenType.OR) != null);
}
@Override
public String toString() {
return children.stream().map(Object::toString).collect(Collectors.joining(" OR "));
}
@Override
public boolean evaluate(Map<String, String> data) {
for(Expr child : children) {
if(child.evaluate(data))
return true;
}
return false;
}
}
最后的parse
方法:
public static Expr parse(TokenStream stream) throws ParseException {
OrExpr expr = new OrExpr(stream);
stream.consume(TokenType.EOF); // ensure that we parsed the whole input
return expr;
}
所以你可以解析你的表达式来得到 Expr
对象,然后根据 CSV 文件的行评估它们。我假设您能够将 CSV 行解析为 Map<String, String>
.这是用法示例:
Map<String, String> data = new HashMap<>();
data.put("Acct1", "Y");
data.put("Acct2", "N");
data.put("Acct3", "Y");
data.put("Acct4", "N");
Expr expr = parse(tokenize("Acct1 = 'Y' AND (Acct2 = 'Y' OR Acct3 = 'Y')"));
System.out.println(expr.evaluate(data)); // true
expr = parse(tokenize("Acct1 = 'N' OR 'Y' = Acct2 AND Acct3 = 'Y'"));
System.out.println(expr.evaluate(data)); // false
关于java - 解析逻辑运算 - AND、OR、动态循环条件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32193036/