java – 动态解析逻辑运算 – AND,OR,循环条件

前端之家收集整理的这篇文章主要介绍了java – 动态解析逻辑运算 – AND,OR,循环条件前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我有一个传入的记录过滤器与逻辑子句一起存储,如下所示.
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,Mexico,USA,

我将不得不根据条款中定义的条件过滤掉文件中的记录.这是一个简单子句的示例,但是会有比这更多的内部条件,并且只要用户需要就可以更改子句,并且记录必须按顺序传递10个这样的子句.

所以我正在寻找一种动态解释该子句并将其应用于传入记录的方法.请提供您有关如何设计/任何示例(如果有)的建议.

解决方法

这是完整的解决方案,不包括第三方库,如ANTLR或JavaCC.请注意,虽然它是可扩展的,但其功能仍然有限.如果要创建更复杂的表达式,最好使用语法生成器.

首先,让我们编写一个tokenizer,将输入字符串拆分为标记.这是令牌类型:

private static enum TokenType {
    WHITESPACE,AND,OR,EQUALS,LEFT_PAREN,RIGHT_PAREN,IDENTIFIER,LITERAL,EOF
}

令牌类本身:

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 + "]";
    }
}

为了简化标记化,我们创建一个regexp,它从输入字符串中读取下一个标记

private static final Pattern TOKENS = 
        Pattern.compile("(\\s+)|(AND)|(OR)|(=)|(\\()|(\\))|(\\w+)|\'([^\']+)\'");

请注意,它有许多组,每个TokenType有一个组,顺序相同(首先是WHITESPACE,然后是AND,依此类推).最后是tokenizer方法

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.这里我们应用正则表达式匹配器直到输入结束.如果它在当前位置不匹配,我们抛出异常.否则,我们会查找找到的匹配组并从中创建一个令牌,忽略WHITESPACE令牌.最后,我们添加一个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();
    }
}

所以我们有一个标记器,hoorah.您现在可以使用System.out.println进行测试(tokenize(“Acct1 =’Y’AND(Acct2 =’N’或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,它是由AND运算符连接的一组SubExpr表达式:

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,它是由OR连接的一组AndExpr(通常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;
    }
}

最后的解析方法

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
原文链接:https://www.f2er.com/java/121455.html

猜你在找的Java相关文章