判断一个数是否在指定的集合内是很简单的,只需要将集合存入java的List
或Set
(根据集合的特性Set
更适合)类型中,使用他的contains
方法即可判断,但是很多时候集合并不固定,我们需要把他写到配置文件中去,这个时候该怎么办呢?
比如:
- 有一个集合是
1
到100
的整数- 有一个集合是
1
到100
的偶数- 有一个集合是
1
到100
的偶数并且不要50
到60
之间的偶数- 有一个集合是
1
到100
的偶数并且里面的数也是3
的倍数
上面的两个问题我们应该怎么解决呢?怎么写到配置文件中呢?
最简单的方法当然是之间枚举所有的数出来(但是这样太不优雅了,数值过大就不太好配置了)
为了解决上面的问题,一个比较好的方式就是开发DSL了,使用antlr让开发更加简单
贴代码了:
# ^ 用来表示集合的交集运算
# + 用来表示集合的与运算(可以省略)
# - 用来表示集合的差运算
# {} 改变( '^' > '+' = '-' )之间的运算优先级
# [ 代表 包含start
# ( 代表 不包含start
# ] 代表 包含end
# ) 代表 不包含end
# speed 代表步长,如果前面加个%代表求余为零的数
grammar Test;
range : range op='^' range #Assoc
| range op=('+'|'-') range #AddSub
| range range #Add
| '{' range '}' #Simple
| ('['|'(') start ',' speed ',' end (')'|']') #SSE
| ('['|'(') start ',' end (')'|']') #SE
| ('['|'(') start (')'|']') #S
;
start : INT;
speed : '%'? INT;
end : INT;
SMALLBRACKETLEFT : '(';
SMALLBRACKETRIGHT : ')';
MEDIUMBRACKETLEFT : '[';
MEDIUMBRACKETRIGHT : ']';
ADDOPTION : '+';
SUBOPTION : '-';
INT : [0-9]+;
NL : '\r'?'\n';
WS : [ \t\r\n]+ -> skip;
编写visitor:
package cn.infomany.test0;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @description: 范围访问器
* @author: zhanjinbing
* @data: 2020-03-03 15:17
*/
public class RangesVisitor extends TestBaseVisitor<Object> {
@Override
public Object visitAdd(TestParser.AddContext ctx) {
Set<Integer> result = new HashSet<>();
Set<Integer> result0 = (Set<Integer>) visit(ctx.range(0));
Set<Integer> result1 = (Set<Integer>) visit(ctx.range(1));
result.addAll(result0);
result.addAll(result1);
return result;
}
@Override
public Object visitSE(TestParser.SEContext ctx) {
int speed = 1;
int start = (int) visit(ctx.start());
int end = (int) visit(ctx.end());
return visitSSE(start, speed, end,
ctx.start.getType() == TestLexer.MEDIUMBRACKETLEFT,
ctx.stop.getType() == TestLexer.MEDIUMBRACKETRIGHT);
}
private Set<Integer> visitSSE(int start, int speed, int end, boolean hasStart, boolean hasEnd) {
Set<Integer> result = new HashSet<>();
for (int i = start; i < end; i += speed) {
result.add(i);
}
if (!hasStart) {
result.remove(start);
}
if (hasEnd) {
result.add(end);
}
return result;
}
@Override
public Object visitSimple(TestParser.SimpleContext ctx) {
return visit(ctx.range());
}
@Override
public Object visitS(TestParser.SContext ctx) {
Set<Integer> results = new HashSet<>();
if (ctx.start.getType() == TestLexer.SMALLBRACKETLEFT
|| ctx.stop.getType() == TestLexer.SMALLBRACKETRIGHT) {
return results;
}
results.add((Integer) visit(ctx.start()));
return results;
}
@Override
public Object visitSSE(TestParser.SSEContext ctx) {
Object speedObj = visit(ctx.speed());
int start = (int) visit(ctx.start());
int end = (int) visit(ctx.end());
Set<Integer> results;
if (speedObj instanceof String) {
results = visitSSE(start, 1, end,
ctx.start.getType() == TestLexer.MEDIUMBRACKETLEFT,
ctx.stop.getType() == TestLexer.MEDIUMBRACKETRIGHT);
final Integer tmpInt = Integer.valueOf(((String) speedObj).substring(1));
results = results.stream().filter(it -> it % tmpInt == 0).collect(Collectors.toSet());
} else if (speedObj instanceof Integer) {
results = visitSSE(start, (Integer) speedObj, end,
ctx.start.getType() == TestLexer.MEDIUMBRACKETLEFT,
ctx.stop.getType() == TestLexer.MEDIUMBRACKETRIGHT);
} else {
throw new RuntimeException("[speed]格式有误");
}
return results;
}
@Override
public Object visitAddSub(TestParser.AddSubContext ctx) {
Set<Integer> result = new HashSet<>();
Set<Integer> result0 = (Set<Integer>) visit(ctx.range(0));
Set<Integer> result1 = (Set<Integer>) visit(ctx.range(1));
result.addAll(result0);
if (ctx.op.getType() == TestLexer.ADDOPTION) {
result.addAll(result1);
} else {
result.removeAll(result1);
}
return result;
}
@Override
public Object visitAssoc(TestParser.AssocContext ctx) {
Set<Integer> result = new HashSet<>();
Set<Integer> result0 = (Set<Integer>) visit(ctx.range(0));
Set<Integer> result1 = (Set<Integer>) visit(ctx.range(1));
result0.stream().forEach(it -> {
if (result1.contains(it)) {
result.add(it);
}
});
return result;
}
@Override
public Object visitStart(TestParser.StartContext ctx) {
return Integer.valueOf(ctx.INT().getText());
}
@Override
public Object visitSpeed(TestParser.SpeedContext ctx) {
// 检查是不是单纯的int
String reg = "\\d+";
if (ctx.getText().matches(reg)) {
return Integer.valueOf(ctx.INT().getText());
}
return ctx.getText();
}
@Override
public Object visitEnd(TestParser.EndContext ctx) {
return Integer.valueOf(ctx.INT().getText());
}
}
主类:
package cn.infomany.test0;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import java.util.ArrayList;
/**
* @description: 测试主程序
* @author: zhanjinbing
* @data: 2020-03-03 15:11
*/
public class TestMain {
public static void main(String[] args) throws Exception {
// 新建一个CharStreams,从标准输入读取数据
CharStream input = CharStreams.fromStream(System.in);
// 新建一个词法符号的缓冲区,用于存储词法分析器将生成的词法符号
TestLexer lexer = new TestLexer(input);
// 新建一个词法符号的缓冲区,用于存储词法分析器将生成的词法符号
CommonTokenStream tokens = new CommonTokenStream(lexer);
// 新建一个语法分析器,处理词法符号缓冲区中的内容
TestParser parser = new TestParser(tokens);
// 针对init规则,开始语法分析
ParseTree tree = parser.range();
RangesVisitor rangesVisitor = new RangesVisitor();
Object visit = rangesVisitor.visit(tree);
System.out.println("visit = " + visit);
}
}
问题一:
问题二:
问题三:
问题四:
数组是乱序的,因为使用的Set来保存的
引用文章请表明出处
使用javafx开发了一个集合编写的工具,下面是截图:
有高亮,动态生成,错误提示等功能。
源代码位置https://gitee.com/MIEAPP/listGenerate
网友评论