BUAAOO-Third-Summary
目录
- 从DBC到JML
- SMT solver 使用
- JML toolchain的可视化输出 和我的测试结果
- 规格的完善策略
- 架构设计
- debug情况
- 心得体会
一、从DBC到JML
契约式设计(Design by Contract)是一种开发软件的新思路。不妨通过商业活动的中真实的Contract(契约)来理解这个例子:
- 供应商必须提供某种产品(这是供应商的义务),并且有权期望客户付费(这是供应商的权利)。
- 客户必须支付费用(这是客户的义务),并且有权得到产品(这是客户的权利)。
- 双方必须满足应用于合约的某些义务,如法律和规定。
那我们可以从程序设计的角度看需要哪些契约呢:
- 可接受和不可接受的输入的值或类型,以及它们的含义
- 返回的值或类型,以及它们的含义
- 错误和异常的值或类型,以及它们的含义
- 副作用
- 先验条件
- 后验条件
- 不变性
- (不太常见)性能保证,例如所需的时间和空间。我在后文中建立了性能相关的规格约定模式。
可以说JML是为DBC而生的(或者在学术上称之为DBC语言)。
我们从JML的原生语法来看,就非常符合契约设计的理念:
- requires 描述先验条件
- ensures 描述后验条件
- old 描述和消除副作用
- exceptional_behavior 描述错误和异常
所以说JML是教学和学术研究中对于DBC理论探索的一个利器。
形式化<->可以消除歧义。
形式化<->可以被程序读取!
形式化<->可以自动分析和推导!
这样,不仅写代码的人可以准确读懂需求,测试人员可以从测试上透视功能!
二、SMT solver 使用
笔者的java SMT代码一览,采用Microsoft Z3 SMT solver
void prove(Context ctx, BoolExpr f, boolean useMBQI) throws TestFailedException
{
BoolExpr[] assumptions = new BoolExpr[0];
prove(ctx, f, useMBQI, assumptions);
}
void prove(Context ctx, BoolExpr f, boolean useMBQI,
BoolExpr... assumptions) throws TestFailedException
{
System.out.println("Proving: " + f);
Solver s = ctx.mkSolver();
Params p = ctx.mkParams();
p.add("mbqi", useMBQI);
s.setParameters(p);
for (BoolExpr a : assumptions)
s.add(a);
s.add(ctx.mkNot(f));
Status q = s.check();
switch (q)
{
case UNKNOWN:
System.out.println("Unknown because: " + s.getReasonUnknown());
break;
case SATISFIABLE:
throw new TestFailedException();
case UNSATISFIABLE:
System.out.println("OK, proof: " + s.getProof());
break;
}
}
void disprove(Context ctx, BoolExpr f, boolean useMBQI)
throws TestFailedException
{
BoolExpr[] a = {};
disprove(ctx, f, useMBQI, a);
}
void disprove(Context ctx, BoolExpr f, boolean useMBQI,
BoolExpr... assumptions) throws TestFailedException
{
System.out.println("Disproving: " + f);
Solver s = ctx.mkSolver();
Params p = ctx.mkParams();
p.add("mbqi", useMBQI);
s.setParameters(p);
for (BoolExpr a : assumptions)
s.add(a);
s.add(ctx.mkNot(f));
Status q = s.check();
switch (q)
{
case UNKNOWN:
System.out.println("Unknown because: " + s.getReasonUnknown());
break;
case SATISFIABLE:
System.out.println("OK, model: " + s.getModel());
break;
case UNSATISFIABLE:
throw new TestFailedException();
}
}
跑一个简单的数独验证先测试一下SMT 在本地有效(代码来自官方template,有很多改动)
void sudokuExample(Context ctx) throws TestFailedException
{
System.out.println("SudokuExample");
Log.append("SudokuExample");
// 9x9 matrix of integer variables
IntExpr[][] X = new IntExpr[9][];
for (int i = 0; i < 9; i++)
{
X[i] = new IntExpr[9];
for (int j = 0; j < 9; j++)
X[i][j] = (IntExpr) ctx.mkConst(
ctx.mkSymbol("x_" + (i + 1) + "_" + (j + 1)),
ctx.getIntSort());
}
// each cell contains a value in {1, ..., 9}
BoolExpr[][] cells_c = new BoolExpr[9][];
for (int i = 0; i < 9; i++)
{
cells_c[i] = new BoolExpr[9];
for (int j = 0; j < 9; j++)
cells_c[i][j] = ctx.mkAnd(ctx.mkLe(ctx.mkInt(1), X[i][j]),
ctx.mkLe(X[i][j], ctx.mkInt(9)));
}
// each row contains a digit at most once
BoolExpr[] rows_c = new BoolExpr[9];
for (int i = 0; i < 9; i++)
rows_c[i] = ctx.mkDistinct(X[i]);
// each column contains a digit at most once
BoolExpr[] cols_c = new BoolExpr[9];
for (int j = 0; j < 9; j++)
cols_c[j] = ctx.mkDistinct(X[j]);
// each 3x3 square contains a digit at most once
BoolExpr[][] sq_c = new BoolExpr[3][];
for (int i0 = 0; i0 < 3; i0++)
{
sq_c[i0] = new BoolExpr[3];
for (int j0 = 0; j0 < 3; j0++)
{
IntExpr[] square = new IntExpr[9];
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
square[3 * i + j] = X[3 * i0 + i][3 * j0 + j];
sq_c[i0][j0] = ctx.mkDistinct(square);
}
}
BoolExpr sudoku_c = ctx.mkTrue();
for (BoolExpr[] t : cells_c)
sudoku_c = ctx.mkAnd(ctx.mkAnd(t), sudoku_c);
sudoku_c = ctx.mkAnd(ctx.mkAnd(rows_c), sudoku_c);
sudoku_c = ctx.mkAnd(ctx.mkAnd(cols_c), sudoku_c);
for (BoolExpr[] t : sq_c)
sudoku_c = ctx.mkAnd(ctx.mkAnd(t), sudoku_c);
// sudoku instance, we use '0' for empty cells
int[][] instance = { { 0, 0, 0, 0, 9, 4, 0, 3, 0 },
{ 0, 0, 0, 5, 1, 0, 0, 0, 7 }, { 0, 8, 9, 0, 0, 0, 0, 4, 0 },
{ 0, 0, 0, 0, 0, 0, 2, 0, 8 }, { 0, 6, 0, 2, 0, 1, 0, 5, 0 },
{ 1, 0, 2, 0, 0, 0, 0, 0, 0 }, { 0, 7, 0, 0, 0, 0, 5, 2, 0 },
{ 9, 0, 0, 0, 6, 5, 0, 0, 0 }, { 0, 4, 0, 9, 7, 0, 0, 0, 0 } };
BoolExpr instance_c = ctx.mkTrue();
for (int i = 0; i < 9; i++)
for (int j = 0; j < 9; j++)
instance_c = ctx.mkAnd(
instance_c,
(BoolExpr) ctx.mkITE(
ctx.mkEq(ctx.mkInt(instance[i][j]),
ctx.mkInt(0)), ctx.mkTrue(),
ctx.mkEq(X[i][j], ctx.mkInt(instance[i][j]))));
Solver s = ctx.mkSolver();
s.add(sudoku_c);
s.add(instance_c);
if (s.check() == Status.SATISFIABLE)
{
Model m = s.getModel();
Expr[][] R = new Expr[9][9];
for (int i = 0; i < 9; i++)
for (int j = 0; j < 9; j++)
R[i][j] = m.evaluate(X[i][j], false);
System.out.println("Sudoku solution:");
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 9; j++)
System.out.print(" " + R[i][j]);
System.out.println();
}
} else
{
System.out.println("Failed to solve sudoku");
throw new TestFailedException();
}
}
用SMT solver 很难测试像图论这样的问题,所以我的策略是先测试一个经典的数独问题,如上。
此外我做了两个测试:
- 验证连通块个数实验
这种验证确实非常高级非常有效,但是写代码还是相当长的,(包括生成代码和模板代码)。
测试用代码如下
private BoolExpr generate(BinopExpr expr) {
BoolExpr ret = null;
if(expr instanceof ConditionExpr){
//get two sides and the operator
ConditionExpr condExpr = (ConditionExpr)expr;
//lhs can either be constant or a local
//12-29-14, not right now when we
//have more general formula
Value lhs = condExpr.getOp1();
IntExpr lhsExpr = evaluateExpr(lhs);
//rhs can also be an arithmetic expression
//from converting assignments to equality
Value rhs = condExpr.getOp2();
ArithExpr rhsExpr = null;
//add conditionals here first to check
if(rhs instanceof BinopExpr){
BinopExpr rhsBinop = (BinopExpr) rhs;
IntExpr lhsArith = evaluateExpr(rhsBinop.getOp1());
IntExpr rhsArith = evaluateExpr(rhsBinop.getOp2());
//now determine the operator add, sub, mult
try {
if(rhsBinop instanceof AddExpr){
ArithExpr[] operands = new ArithExpr[]{lhsArith, rhsArith};
rhsExpr = ctx.MkAdd(operands);
} else if (rhsBinop instanceof SubExpr){
ArithExpr[] operands = new ArithExpr[]{lhsArith, rhsArith};
rhsExpr = ctx.MkSub(operands);
} else if (rhsBinop instanceof MulExpr){
ArithExpr[] operands = new ArithExpr[]{lhsArith, rhsArith};
rhsExpr = ctx.MkMul(operands);
} else if (rhsBinop instanceof DivExpr){
rhsExpr = ctx.MkDiv(lhsArith, rhsArith);
} else if(rhsBinop instanceof RemExpr){
rhsExpr = ctx.MkMod(lhsArith, rhsArith);
} else if (rhsBinop instanceof ShrExpr){
//can only handle when rhs,i.e., y is not a variable
// x >> y = x / (2^y)
if(rhsArith.IsArithmeticNumeral()){
IntNum number = (IntNum)rhsArith;
rhsArith = ctx.MkInt(1<<number.Int()); // this is 2^y
rhsExpr = ctx.MkDiv(lhsArith, rhsArith);
} else {
System.out.println("Rhs in ShrExpr is not a number " + rhsArith.getClass());
System.exit(2);
}
} else if(rhsBinop instanceof ShlExpr){
//can only handle when rhs, i.e., u is not a variable
// x << y = x * (2^y)
if(rhsArith.IsArithmeticNumeral()){
IntNum number = (IntNum)rhsArith;
rhsArith = ctx.MkInt(1<<number.Int()); // this is 2^y
ArithExpr[] operands = new ArithExpr[]{lhsArith, rhsArith};
rhsExpr = ctx.MkMul(operands);
} else {
System.out.println("Rhs in ShlExpr is not a number " + rhsArith.getClass());
System.exit(2);
}
} else {
System.out.println("Cannot process rhsBinop " + rhsBinop.getClass());
System.exit(2);
}
} catch (Z3Exception e) {
e.printStackTrace();
}
} else if (rhs instanceof NegExpr){
try {
ArithExpr[] operands;
operands = new ArithExpr[]{ctx.MkInt(0), evaluateExpr(((NegExpr)rhs).getOp())};
rhsExpr = ctx.MkSub(operands);
} catch (Z3Exception e) {
e.printStackTrace();
}
} else {
rhsExpr = evaluateExpr(rhs);
}
//now generate the condition
try {
if(expr instanceof EqExpr){
ret = ctx.MkEq(lhsExpr, rhsExpr);
} else if (expr instanceof GeExpr){
ret = ctx.MkGe(lhsExpr, rhsExpr);
} else if (expr instanceof GtExpr){
ret = ctx.MkGt(lhsExpr, rhsExpr);
} else if (expr instanceof LeExpr){
ret = ctx.MkLe(lhsExpr, rhsExpr);
} else if (expr instanceof LtExpr){
ret = ctx.MkLt(lhsExpr, rhsExpr);
} else if (expr instanceof NeExpr){
ret = ctx.MkNot(ctx.MkEq(lhsExpr, rhsExpr));
}
} catch (Z3Exception e) {
e.printStackTrace();
}
} else if (expr instanceof OrExpr){
BoolExpr lhs = generate((BinopExpr)expr.getOp1());
BoolExpr rhs = generate((BinopExpr)expr.getOp2());
try {
ret = ctx.MkOr(new BoolExpr[]{lhs, rhs});
} catch (Z3Exception e) {
e.printStackTrace();
}
} else if (expr instanceof AndExpr){
BoolExpr lhs = generate((BinopExpr)expr.getOp1());
BoolExpr rhs = generate((BinopExpr)expr.getOp2());
try {
ret = ctx.MkAnd(new BoolExpr[]{lhs, rhs});
} catch (Z3Exception e) {
e.printStackTrace();
}
} else {
//something else that we don't handle yet :(
System.out.println("Cannot process " + expr);
System.exit(2);
}
return ret;
}
public boolean disjoint_solve(BoolExpr z3Formula){
boolean ret = true;
try {
Solver solver = ctx.MkSolver();
Params p = ctx.MkParams();
p.Add("soft_timeout", timeout);
solver.setParameters(p);
solver.Assert(z3Formula);
Status result = solver.Check();
if(result.equals(Status.SATISFIABLE)){
ret = true;
} else if (result.equals(Status.UNSATISFIABLE)){
ret = false;
} else {
//unknown
System.out.println("Warning: " + result + " for " + z3Formula);
}
} catch (Z3Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return ret;
}
- 验证多个最短路算法的等价性
此外,部分代码在python下(验证)_
三、JML toolchain 用于测试
目前JML的工具有这些:
- ESC/Java2 1, an extended static checker which uses JML annotations to perform more rigorous static checking than is otherwise possible.
- OpenJML declares itself the successor of ESC/Java2.
- Daikon, a dynamic invariant generator.
- KeY, which provides an open source theorem prover with a JML front-end and an Eclipse plug-in (JML Editing) with support for syntax highlighting of JML.
- Krakatoa, a static verification tool based on the Why verification platform and using the Coq proof assistant.
- JMLEclipse, a plugin for the Eclipse integrated development environment with support for JML syntax and interfaces to various tools that make use of JML annotations.
- Sireum/Kiasan, a symbolic execution based static analyzer which supports JML as a contract language.
- JMLUnit, a tool to generate files for running JUnit tests on JML annotated Java files.
- TACO, an open source program analysis tool that statically checks the compliance of a Java program against its Java Modeling Language specification.
- VerCors verifier
但是这些工具并不能满足我的开发和证明要求,由于我后续加入,我自行开发了一个命令行工具 Advanced JML check with TesTNG ( AJCT )。(功能很不完善)。
其原理其实是OpenJML
+ JMLUnitNG
+ jprofiler
+ Python
+ Bash
的一个组合脚本。
根据我自己的类生成的测试代码截图如下:
生成结果如下
.
├── AJCT
├── Main.java
├── Mydisgraph.java
├── Mydisgraph_sssp_int_euler_path_gen.class
├── Mydisgraph_sssp_int_euler_path_gen.java
├── Mydisgraph_sssp_int_general_gen.class
├── Mydisgraph_sssp_int_general_gen.java
├── Mydisgraph_sssp_int_rebuild_gen.class
├── Mydisgraph_sssp_int_rebuild_gen.java
├── Mydisgraph_sssp_int_shortest_path_tree_gen.class
├── Mydisgraph_sssp_int_shortest_path_tree_gen.java
├── MyGraph_containsEdge_int_int.class
├── MyGraph_containsEdge_int_int.java
├── MyGraph_containsNode_int.class
├── MyGraph_containsNode_int.java
├── MyGraph_getnodelabel_int.class
├── MyGraph_getnodelabel_int.java
├── MyGraph_getShortestPathLength_int_int.class
├── MyGraph_getShortestPathLength_int_int.java
├── MyGraph_isConnected_int_int.class
├── MyGraph_isConnected_int_int.java
├── MyGraph.java
├── MyNode.java
├── MyPathContainer.java
├── MyPath.java
├── MyRailwaySystem_getFa_int.class
├── MyRailwaySystem_getFa_int.java
├── MyRailwaySystem_getLeastTicketPrice_int_int.class
├── MyRailwaySystem_getLeastTicketPrice_int_int.java
├── MyRailwaySystem_getLeastTransferCount_int_int.class
├── MyRailwaySystem_getLeastTransferCount_int_int.java
├── MyRailwaySystem_getLeastUnpleasantValue_int_int.class
├── MyRailwaySystem_getLeastUnpleasantValue_int_int.java
├── MyRailwaySystem.java
├── MyRailwaySystem_merge_int_int.class
└── MyRailwaySystem_merge_int_int.java
测试结果截图如下:
性能测试结果如下:
四、规格完善策略
我发现了现有的规格体系的一个缺点:即无法保证描述方法的时空间复杂度,因此,我在此基础上,加入了关于复杂度的描述规格,用于测试我自己的代码。
(目前,我的规格只能测试图的最短路算法和图论其他基本算法。)
我的复杂度规格描述策略如下:
- pre-condition: 表示算法中某一些集合,和他们的大小范围。
- parameter: 表示参数属于算法中的哪一个集合,和他们的大小范围。
- time complexity: 用时间复杂度的标准形式,要求有 online, offline, worst-case几个关键描述。(省略大O记号)
- space complexity: 用空间复杂度的标准形式。
这部分的开发过程有很多困难,由于还没有彻底完善,不方便开源(还在做进一步测试)。
引入这个规格的考虑有如下考量:
后续重构要考量该接口是否会丧失原有性能、导致各种问题(进程不同步、需求无法满足)。
对调用者友好,能不用透视代码,而从性能和功能两个层次考量是否调用该方法、调用的条件是什么。
对验证有效,工程问题的正确性和效率(开发效率、测试效率)都和基本的性能要求分不开,能提前做好性能的规格设计,在没有遇到性能问题之前大概率无需顾虑。
性能规格可以做理论推导、可以通过调用方法和后续方法的性能规格推导该规格的bound,做到防御性设计。
关于我的性能测试报告可以看我的优化博客,在此只做简要的叙述。
在性能测试阶段,利用多种输入情况对程序的运行时间情况做拟合,得到的表达式和规格表达式对比,从而得到验证复杂度的效果。
我的验证结果如下
利用
\(f(n) - g(n)\) 这样的表达式表达:修改复杂度为\(f(n)\),询问复杂度为\(g(n)\)
algo \ case | general gen | rebuild gen | Euler path gen | shortest path tree gen |
---|---|---|---|---|
A-star-algorithm | \(O(n^2)-O(n+m)\) | \(O(n^2)-O(n+m)\) | \(O(n^2)-O(n+m)\) | \(O(n+m)-O(n+m)\) |
\(O(n^2)\) transfer line algorithm | \(O(n^2)-O(m)\) | \(O(n^2)-O(m)\) | \(O(n^2)-O(m)\) | \(O(n^2)-O(m)\) |
my algorithm | $O(n)-O(n + m \log n) $ | $O(n)-O(n \log n + m) $ | $O(n)-O(n \log n + m) $ | $O(n)-O(n + m) $ |
floyd algorithm | $O(n^3)-O(1) $ | $O(n^3)-O(1) $ | $O(n^3)-O(1) $ | $O(n^3)-O(1) $ |
五、架构设计
这次的架构设计有助教和老师的经验在里面,我们自己设计的部分不多!老师和助教的前瞻性充分在这个单元体现出来。
第一次的架构,非常基本
第二次的架构,非常基本
第三次,抽象出了Mydisgraph
封装出了最短路算法
用于:
- 随时调试性能
- 随时替换其他算法
- 封装共性代码
三次的架构一脉相成在架构上我改进了以前的开发策略:
- 学习相关的模式、接口设计、画流程图 (确定如何对象的属性)
- 模块绑定、避免重构
- 做头脑风暴,考察多种测试数据和思路,并完成代码
- 测试数据构造和覆盖性测试
- 代码回顾和思考
由此我实现了代码的复用和解耦。
六、debug情况
我发现了一个核心bug,滥用hashcode
有同学的第一次代码在equals方法内直接用hashcode判断,我使用了中间相遇的思路攻击了他的hash算法:
示例如下:
PATH_ADD 1 1 1
PATH_ADD 1 29792
这两条路径具有相同的hashcode。
这里其实反映了要如何使用hashcode的问题,hashcode的冲突要用equals去避免,值得同学记忆。
七、心得体会
经过这一段时间对JML规格的阅读以及上次上机时自己真正尝试写JML规格,我深深感受到了JML语言的重要性。只有使用JML语言,在进行程序编写,特别是不同人组队完成一个大型程序的编写时,才可以在最大程度上保证不同人完成在代码可以融合在一起,而不会产生各种各样奇妙的bug。
在这一单元的学习后,对前置条件,后置条件,副作用,有了比较好的理解,这是一个方法行为的核心,有了这样的规范,程序员间可以在架构层面上进行交流,也就可以在编码前期,架构设计时期进行交流,减少实现时的错误。这是我在这一单元最为印象深刻的,之后使用JML,我会在代码的注释中写明前置条件,后置条件,以及副作用,保持一个良好的习惯。
感谢OO感谢OO课程教会了我从规格层次审视代码,从更高的角度去设计去思考,做代码的主人。
BUAAOO-Third-Summary的更多相关文章
- BUAAOO——UNIT2 SUMMARY
本单元的题目为设计电梯,通过这单元的学习,我初步了解了关于java多线程编程及线程之间并发安全性设计等方面的内容.以下为对这三次作业的分析与总结. 作业分析 序号 楼层 电梯数量 可停靠楼层 调度策略 ...
- Summary of Critical and Exploitable iOS Vulnerabilities in 2016
Summary of Critical and Exploitable iOS Vulnerabilities in 2016 Author:Min (Spark) Zheng, Cererdlong ...
- 三个不常用的HTML元素:<details>、<summary>、<dialog>
前面的话 HTML5不仅新增了语义型区块级元素及表单类元素,也新增了一些其他的功能性元素,这些元素由于浏览器支持等各种原因,并没有被广泛使用 文档描述 <details>主要用于描述文档或 ...
- [LeetCode] Summary Ranges 总结区间
Given a sorted integer array without duplicates, return the summary of its ranges. For example, give ...
- Network Basic Commands Summary
Network Basic Commands Summary set or modify hostname a) temporary ways hostname NEW_HOSTNAME, b ...
- Summary - SNMP Tutorial
30.13 Summary Network management protocols allow a manager to monitor and control routers and hosts. ...
- Mac Brew Install Nginx Summary
==> Downloading https://homebrew.bintray.com/bottles/nginx-1.10.1.el_capitan.bot################# ...
- Leetcode: LFU Cache && Summary of various Sets: HashSet, TreeSet, LinkedHashSet
Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the f ...
- How to add taxonomy element to a summary view?
[re: Orchard CMS] This caused me scratching my head for days and now I can even feel it's bleeding. ...
- (转) Summary of NIPS 2016
转自:http://blog.evjang.com/2017/01/nips2016.html Eric Jang Technology, A.I., Careers ...
随机推荐
- 【2019.11.20】SDN上机第4次作业
安装OpenDayLight控制器 配置JAVA环境 https://www.opendaylight.org/ 在官网进行下载OpenDayLight控制器 启动OpenDayLight控制器和安装 ...
- Vue 自定义编译打包路径
在 vue.config.js 文件下添加 outputDir 配置项: module.exports = { outputDir:"my_target_direct", // o ...
- Service Function Chaining Resource Allocation: A Survey
摘要: 服务功能链(SFC)是未来Internet的一项关键技术. 它旨在克服当前部署模型的僵化和静态限制. 该技术的应用依赖于可以将SFC最佳映射到衬底网络的算法. 这类算法称为"服务功能 ...
- [转]OpenGL图形渲染管线、VBO、VAO、EBO概念及用例
直接给出原文链接吧 1.OpenGL图形渲染管线.VBO.VAO.EBO概念及用例 2.OpenGL中glVertex.显示列表(glCallList).顶点数组(Vertex array).VBO及 ...
- Python3基础 tuple 使用格式化字符串进行输出
Python : 3.7.3 OS : Ubuntu 18.04.2 LTS IDE : pycharm-community-2019.1.3 ...
- Java算法 -- 桶排序
桶排序(Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶里.每个桶再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序).桶排序是鸽巢排序 ...
- dockerfile运行mysql并初始化数据
本文目的不仅仅是创建一个MySQL的镜像,而是在其基础上再实现启动过程中自动导入数据及数据库用户的权限设置,并且在新创建出来的容器里自动启动MySQL服务接受外部连接,主要是通过Dockerfile和 ...
- Shell中的通配符
shell常见的通配符,注意与正则稍有不同: 字符 含义 实例 * 匹配0个或多个任意字符 a*b,a与b之间可以有任意长度的字符,也可以没有. 例如:aabcb,ab,azxcb... ? 匹配一个 ...
- 钉钉服务端api接口使用
原文链接:http://www.cnblogs.com/xiaosongJiang/p/9991573.html 第一步:注册钉钉企业账号 打开链接:https://oa.dingtalk.com/# ...
- mysql查询之上升的温度,有趣的电影,超过5名学生的课,大国,反转性别, 换座位
最近发现一个网站 力扣 查看 上面有很多算法和数据库的题目,做了一下,发现自己平时都疏忽了,因此边做边记录下来 1.上升的温度 给定一个 Weather 表,编写一个 SQL 查询,来查找与之前(昨天 ...