301
社区成员
发帖
与我相关
我的任务
分享本人并未参加pre课程,因此代码架构比较混乱,水平有限,本篇博客仅作为交流学习,望与诸位共勉。
代码规模分析插件: static
复杂度分析插件: MetricsReloaded
架构分析
问题:对单变量且不涉及嵌套括号的表达式进行展开和简化。
通过深入分析表达式的层次结构,我们可以将其划分为更为细致的三个层级:表达式(Expr)、项(Term)和因子(Factor)。这种分层结构使得因子组成了项,而项则构成了整个表达式。尽管表达式和表达式因子在表面上看起来属于不同层级,但其内部结构实际上是完全相同的,所以可以借助一个类(Expr)来同时处理这两种层级的情况。
在完成对表达式的初步分析并创建类之后,我们迎来了本次任务中的的重点问题:如何解析输入的表达式?由于递归下降法不仅在于课程组提供了相应的代码模板,而且递归下降法在处理后续嵌套情况时表现出色,例如处理嵌套括号、嵌套自定义函数等。因此,我决定采用课程组推荐的解析方法——递归下降法。
对于输入的表达式,为了方便后续的解析工作,需要进行一些预处理工作,其中包括但不限于删除多余的空白字符、只保留连续加减符号中的一个等。此外,根据解析方法的要求,还可以进一步优化处理,例如对于第一项出现 -x 或 (-x) 的情况,可以通过添加前导的零来进行处理,使其变为 0-x 或 (0-x)。根据实际需求,还可以实施其他处理细节。总之,所有这些预处理工作都是为了方便后续的处理工作。对于词法分析器和语法分析器部分,也可以使用课程组提供的递归下降模板
大致流程:
读入
解析表达式
合并表达式
输出
代码规模分析

复杂度分析
均值偏高主要原因是if-else嵌套使用次数较多
绝大部分方法的复杂度均在可控范围之内

以第三次作业为例
由于递归下降的使用,代码支持自定义函数表达式定义中能有已经定义好的自定义函数因子
// 解析基本因子的方法
private Factor parseBasicFactor() {
int coefficient = 1; // 初始化系数为1
boolean hasCoefficient = false; // 标记是否已经解析系数
if (index < input.length() && (input.charAt(index) == '+' || input.charAt(index) == '-')) {
char sign = input.charAt(index);
index++;
coefficient = (sign == '-') ? -1 : 1;
hasCoefficient = true;
}
while (index < input.length() && Character.isDigit(input.charAt(index))) {
if (!hasCoefficient) { // 如果之前没有解析系数,则从输入中解析
coefficient = coefficient * 10 + (input.charAt(index) - '0');
}
index++;
hasCoefficient = true;
}
if (!hasCoefficient) { // 如果没有解析到系数,则默认系数为1
coefficient = 1;
}
String variable = "";
int exponent = 1; // 默认指数为1
if (index < input.length() && input.charAt(index) == 'x') {
variable = "x";
index++;
if (index < input.length() && input.charAt(index) == '^') {
index++; // 跳过'^'
int startIndex = index;
while (index < input.length() && Character.isDigit(input.charAt(index))) {
index++;
}
if (startIndex < index) { // 确保解析到指数
exponent = Integer.parseInt(input.substring(startIndex, index));
}
}
} else {
// 如果没有变量,意味着这是一个常数项,指数应为0
exponent = 0;
}
return new Factor(coefficient, variable, exponent);
}
}
// 递归展开括号的方法
private void expandBracketsHelper(int currentTermIndex, List<Factor> currentCombination, List<Term> expandedTerms) throws CloneNotSupportedException {
if (currentTermIndex == terms.size()) {
// 当前组合已经包含了所有的项,将其添加到结果中
expandedTerms.add(new Term(new ArrayList<>(currentCombination)));
} else {
// 获取当前项
Term currentTerm = terms.get(currentTermIndex);
boolean hasExponentZero = currentTerm.getFactors().stream().anyMatch(factor -> factor.getExponent() == 0);
if (hasExponentZero) {
currentCombination.add(new Factor(1, "", 0));
expandBracketsHelper(currentTermIndex + 1, currentCombination, expandedTerms);
currentCombination.remove(currentCombination.size() - 1);
} else {
for (Factor factor : currentTerm.getFactors()) {
if (factor instanceof ExpressionFactor) {
} else {
currentCombination.add(factor);
expandBracketsHelper(currentTermIndex + 1, currentCombination, expandedTerms);
currentCombination.remove(currentCombination.size() - 1);
}
}
}
}
}
// 解析表达式的方法
public Expression parseExpression() {
List<Term> terms = new ArrayList<>();
terms.add(parseTerm());
while (index < input.length() && (input.charAt(index) == '+' || input.charAt(index) == '-')) {
char operator = input.charAt(index);
index++;
Term term = parseTerm();
if (operator == '-') {
// 如果是减法,需要对所有系数取负
for (Factor factor : term.getFactors()) {
factor.setCoefficient(-factor.getCoefficient());
}
}
terms.add(term);
}
return new Expression(terms);
}
// 解析表达式因子的方法
private ExpressionFactor parseExpressionFactor() {
index++; // 跳过左括号
Expression expression = parseExpression();
index++; // 跳过右括号
return new ExpressionFactor(expression);
}
// 解析因子的方法
private Factor parseFactor() {
if (input.charAt(index) == '(') {
return parseExpressionFactor();
} else {
return parseBasicFactor();
}
}
// 解析项的方法
private Term parseTerm() {
List<Factor> factors = new ArrayList<>();
while (index < input.length() && (Character.isDigit(input.charAt(index)) || input.charAt(index) == 'x' || input.charAt(index) == '(')) {
factors.add(parseFactor());
}
return new Term(factors);
}
作业互测未出现bug。
在第二次强测中,出现多个嵌套的x^8数据点,导致程序无法得出结果。经过仔细检查后发现,问题出现在乘法方法中没有对零项进行处理,这导致了大量不必要的零项计算.在进行了修正后,即可成功通过测试。另外,用int存储指数会超过int范围,改为用BigInteger存储即可
在第三次作业中,新增求导函数 dx,同时允许在声明自定义函数时可以调用已经声明了的自定义函数。因此,在添加自定义函数时,我们会每次都调用替换的函数,实现自定义函数也被化简。
处理求导函数的过程是在每个因子下面添加了求导函数 der,然后在处理到 dx的时候,就像处理分析表达式一样进行操作即可。
hack策略:三次作业的代码测试都采用了大量随机数据点的生成,同时结合手动构造特殊或极端样例的方式。
第一单元的确学习到了非常多面向对象编程的思想。同时对层次化设计的工程实现有了更深层次的理解,继续努力。
相较于HW2,HW3的任务量似乎稍显不足,因此我们可以考虑进行适当调整,例如将自定义函数的处理放到HW3中。
在进行hack时,某些简单的样例也总是无法通过检测,而且并没有显示具体违反了哪条原则。希望可以优化这一点,在进行hack时能够更清晰地了解问题所在。
同学,最好不要在博客中直接放代码,如果不得不放,也要对代码做一些处理,尽量只提现必要的设计考虑。