301
社区成员
发帖
与我相关
我的任务
分享分成表达式解析和多项式计算两个部分
这里觉得将解析与计算尽量解耦有利于后续迭代,而不是实验课中构建表达式树,找到每个符号节点然后算上去。或者嫌麻烦可以直接将factor中的tostring改成topoly,直接进行计算,但我觉得写代码跨度有点大就没有选择。
其实思路差不多,但感觉分开会清晰点。

uml图
处理各种指导书中出现的情况,如
将中缀表达式转换为后缀表达式。
参考oolens公众号中的思路,运用递归下降,我这里是用lexer扫一遍句子,生成对应的tokens,之后再用parser去递归解析,最后在每个factor中的tostring具体实现后缀表达式的生成(就是先递归再加符号)。这一步完成后可以先测试一下。
lexer初始化时候直接用for循环来遍历字符串一遍就好。
expr,term,pow,sum or var,见uml图。+-同一优先级,因此parseExpr()需要额外传一个符号(lexer.peek())进去。+/-,因此直接解析parseTerm()就好parseFactor麻烦一点,需要遇到括号调用parseExpr,然后将每个变量和数字存起来。toString中将符号再加上,+/-需要传额外的识别信息ops,这其实就是大一学到的用两个栈将前缀表达式转化为中缀。parser解析完会有一个expr,之后对其递归进行tostring就可以得到一个后缀字符串了。
sb.append(" ").append(iter.next().toString());
sb.append(" ").append(ops.get(pos++).content());
这里手动加了空格是为了之后变成多项式的时候好解析。
我这一步完全没考虑BigInteger,在多项式计算中才将系数设为BigInteger,这一点也简化了思考。
用正则表达式来匹配上一步生成的后缀表达式的每一项。
建立了单项式类mono和多项式类poly,在poly中实现了加减乘幂(可以写个快速幂)。然后用个栈顺着计算就好。
poly为了之后的拓展,建立了单项式类mono和多项式类poly,poly中没有采用容易化简的hashmap,而是arraylist,因为感觉添加了三更多函数后可能还是arraylist更好想一点,用底数或系数当key感觉怪怪的,有点割裂单项式了。
以三角函数为例,后面可以在mono中加个
private Poly trigArgument; // 三角函数的参数,即内部的多项式
然后在poly中更新对应的操作方法,比如加个canMerge
private boolean canMerge(Mono m1, Mono m2) {
// 检查指数、三角函数类型和内部多项式是否相同
if (m1.getExp() != m2.getExp()) return false;
if (m1.getTrigFunctionType() != m2.getTrigFunctionType()) return false;
if (m1.getTrigFunctionType() != TrigFunctionType.NONE) {
// 对于三角函数,还需要比较内部多项式
return m1.getTrigArgument().equals(m2.getTrigArgument());
}
return true;
}
0,则最终结果为0ToString计算完的多项式还需要去tostring,就是考虑每一个mono间需不需要添加+,以及底数和指数间的*,这里可以写个测评机来看是否少考虑了情况。
如果单项式系数为1,则可以省略系数,简化为x^n
如果单项式系数为-1,则可以省略系数,简化为x^-n
如果单项式x的指数为0,则最终结果只输出系数
如果单项式x的指数为1,则指数部分可以省略,简化为ax
if (sb.length() > 0 && !coeStr.startsWith("-")) {
sb.append("+");
}
if (!Objects.equals(coeStr, "")
&& !Objects.equals(expStr, "")
&& !Objects.equals(coeStr, "-")) {
coeStr = coeStr + '*';
}
这里采用将x带为131计算,中测
import random
def generate_whitespace():
# 控制空白字符的随机生成
return random.choice([' ', '\t', '']) * random.randint(0, 2)
def generate_big_integer(max_digits=10):
# 生成一个大整数,长度可达max_digits位
return str(random.randint(1, 9)) + ''.join(random.choice('0123456789') for _ in range(random.randint(0, max_digits - 1)))
def generate_small_integer(max_value=8):
# 生成一个小整数,用于指数,最大值为8
return str(random.randint(1, max_value))
def generate_exponent():
# 生成指数部分,最大值为8
return '^' + generate_whitespace() + generate_small_integer()
def generate_variable_factor():
# 生成变量因子,可能带有指数
return 'x' + (generate_exponent() if random.choice([True, False]) else '')
def generate_constant_factor():
# 生成常数因子,可以是大整数
return generate_big_integer()
def generate_factor(depth=0):
choices = [generate_variable_factor, generate_constant_factor]
if depth < 1: # 限制括号层数至多1层
choices.append(lambda: generate_expression_factor(depth + 1))
return random.choice(choices)()
def generate_item(depth=0):
item = generate_factor(depth)
while random.choice([True, False]):
item += generate_whitespace() + '*' + generate_whitespace() + generate_factor(depth)
if len(item) > 25: break
return item
def generate_expression(depth=0):
expr = generate_item(depth)
while random.choice([True, False]):
expr += generate_whitespace() + random.choice(['+', '-']) + generate_whitespace() + generate_item(depth)
if len(expr.replace(' ', '').replace('\t', '')) > 45: break
return expr
def generate_expression_factor(depth=0):
expr = generate_expression(depth + 1)
exp_part = generate_exponent() if random.choice([True, False]) else ''
return '(' + expr + ')' + (generate_whitespace() + exp_part if exp_part else '')
def clean_expression(expr):
# 移除表达式中的所有空白字符以满足有效长度要求
return expr.replace(' ', '').replace('\t', '')
def generate_valid_expression():
while True:
expr = generate_expression()
cleaned_expr = clean_expression(expr)
if 1 <= len(cleaned_expr) <= 50:
return cleaned_expr
# 生成并打印优化后的表达式
print(generate_valid_expression())
z strong
import random
maxLen = 200
def generate_whitespace():
return random.choice([' ', '\t', '']) * random.randint(1, 2)
def generate_big_integer(max_digits=10): # 调整大整数的最大位数
num = str(random.randint(1, 9)) + ''.join(random.choice('0123456789') for _ in range(random.randint(0, max_digits - 1)))
sign = random.choice(['', '+', '-']) # 添加对带符号整数的支持
return sign + num
def generate_small_integer(max_value=8):
return str(random.randint(0, max_value)) # 包括0,以处理特殊情况0^0
def generate_exponent():
return '^' + generate_small_integer()
# return '^' + generate_whitespace() + generate_small_integer()
def generate_variable_factor():
return 'x' + (generate_exponent() if random.choice([True, False]) else '')
def generate_constant_factor():
return generate_big_integer()
def generate_factor(depth=0):
choices = [generate_variable_factor, generate_constant_factor]
if depth < 4:
choices.append(lambda: generate_expression_factor(depth + 1))
return random.choice(choices)()
def generate_item(depth=0):
item = generate_factor(depth)
while len(item) < maxLen and random.choice([True, False]):
item += '*' + generate_factor(depth)
# item += '*' + generate_whitespace() + generate_factor(depth)
return item
def generate_expression(depth=0):
expr = generate_item(depth)
while len(expr) < maxLen and random.choice([True, False]):
expr += random.choice(['+', '-']) + generate_item(depth)
# expr += generate_whitespace() + random.choice(['+', '-']) + generate_whitespace() + generate_item(depth)
return expr
def generate_expression_factor(depth=0):
expr = generate_expression(depth + 1)
exp_part = generate_exponent() if random.choice([True, False]) else ''
return '(' + expr + ')' + exp_part
# return '(' + expr + ')' + (generate_whitespace() + exp_part)
def clean_expression(expr):
return expr.replace(' ', '').replace('\t', '')
def generate_valid_expression():
while True:
expr = generate_expression()
cleaned_expr = clean_expression(expr)
return cleaned_expr
# 生成并打印优化后的表达式
print(generate_valid_expression())
main.py
import os
import subprocess
from sympy import symbols, sympify
n = 5000 # 定义测试的次数
def calculate_expression(expression: str):
x = symbols('x') # 定义符号x
expression_with_value = expression.replace('^', '**') # 替换^为**以适应Python的乘方运算
try:
expr = sympify(expression_with_value) # 使用sympify解析表达式
result = expr.subs(x, 131) # 代入x=131
result = result.evalf() # 计算表达式的数值结果
except Exception as e:
print(f"Error evaluating expression: {expression}. Error: {e}")
result = None
return result
def isEqual(expression: str, java_expression: str) -> bool:
python_result = calculate_expression(expression)
java_result = calculate_expression(java_expression)
if python_result is None or java_result is None:
return False, python_result, java_result
return abs(python_result - java_result) < 1e-9, python_result, java_result
# 初始化计数器
correct_count = 0
error_count = 0
# 打开一个文件以记录错误信息
with open('wrong.txt', 'w') as wrong_file, open('expression.txt', 'w') as expr_file:
for i in range(1, n + 1): # 从1开始计数,以符合通常的编号方式
# 运行Python脚本生成表达式并写入in.txt
subprocess.run(['python', 'data_generate.py'], stdout=open('in.txt', 'w'))
# 读取Python生成的表达式并写入expression.txt
with open('in.txt', 'r') as f:
expression = f.readline().strip()
expr_file.write(f"{i}. Python Expression: {expression}\n")
# 运行Java程序处理Python生成的表达式
os.system('java -jar 1.jar < in.txt > out.txt')
# 读取Java程序的输出并追加到expression.txt
with open('out.txt', 'r') as f:
java_expression = f.read().strip()
expr_file.write(f" Java Expression: {java_expression}\n")
# 比较结果
equal, python_result, java_result = isEqual(expression, java_expression)
if not equal:
error_count += 1
wrong_file.write(f"Mismatch found. Original Expression: {expression}, Python result: {python_result}, Java expression result: {java_result}, Java expression: {java_expression}\n")
else:
correct_count += 1
# 实时输出正确和错误的条数
print(f"Correct: {correct_count}, Errors: {error_count}")
# 最后,输出总结信息
print(f"Final tally: Correct: {correct_count}, Errors: {error_count}")
下个sympy库就可以运行啦~
终版
import random
maxLen = 200
def generate_whitespace():
return random.choice([' ', '\t', '']) * random.randint(1, 2)
def generate_big_integer(max_digits=10): # 调整大整数的最大位数
num = str(random.randint(1, 9)) + ''.join(random.choice('0123456789') for _ in range(random.randint(0, max_digits - 1)))
sign = random.choice(['', '+', '-']) # 添加对带符号整数的支持
return sign + num
def generate_small_integer(max_value=8):
return str(random.randint(0, max_value)) # 包括0,以处理特殊情况0^0
def generate_exponent():
return '^' + generate_small_integer()
# return '^' + generate_whitespace() + generate_small_integer()
def generate_variable_factor():
return 'x' + (generate_exponent() if random.choice([True, False]) else '')
def generate_constant_factor():
return generate_big_integer()
def generate_factor(depth=0):
choices = [generate_variable_factor, generate_constant_factor]
if depth < 4:
choices.append(lambda: generate_expression_factor(depth + 1))
return random.choice(choices)()
def generate_item(depth=0):
item = generate_factor(depth)
while len(item) < maxLen and random.choice([True, False]):
item += '*' + generate_factor(depth)
# item += '*' + generate_whitespace() + generate_factor(depth)
return item
def generate_expression(depth=0):
expr = generate_item(depth)
while len(expr) < maxLen and random.choice([True, False]):
expr += random.choice(['+', '-']) + generate_item(depth)
# expr += generate_whitespace() + random.choice(['+', '-']) + generate_whitespace() + generate_item(depth)
return expr
def generate_expression_factor(depth=0):
expr = generate_expression(depth + 1)
exp_part = generate_exponent() if random.choice([True, False]) else ''
return '(' + expr + ')' + exp_part
# return '(' + expr + ')' + (generate_whitespace() + exp_part)
def clean_expression(expr):
return expr.replace(' ', '').replace('\t', '')
def generate_valid_expression():
while True:
expr = generate_expression()
cleaned_expr = clean_expression(expr)
return cleaned_expr
# 生成并打印优化后的表达式
print(generate_valid_expression())
n = 500 # 定义测试的次数,这里用一个较小的数字以便快速测试
def calculate_expression(expression: str):
x = symbols('x')
expression_with_value = expression.replace('^', '**')
try:
expr = sympify(expression_with_value)
result = expr.subs(x, 131)
result = result.evalf()
except Exception as e:
result = None
return result
def isEqual(expression: str, java_expression: str) -> bool:
python_result = calculate_expression(expression)
java_result = calculate_expression(java_expression)
if python_result is None or java_result is None:
return False
return abs(python_result - java_result) < 1e-9
def simplify_expression(expression: str) -> str:
x = symbols('x')
try:
expr = sympify(expression.replace('^', '**'))
simplified_expr = simplify(expr)
expanded_expr = expand(simplified_expr)
final_expr_str = str(expanded_expr).replace('**', '^').replace(' ', '')
return final_expr_str
except Exception as e:
return expression
# 循环测试六个Java文件
import os
import subprocess
from sympy import symbols, sympify, simplify, expand
n = 500 # 定义测试的次数,这里用一个较小的数字以便快速测试
# 准备测试数据
test_data = []
for _ in range(n):
# 假设data_generate.py输出的数据直接是我们需要的表达式
proc = subprocess.run(['python', 'data_generate.py'], capture_output=True, text=True)
expression = proc.stdout.strip()
simplified_expression = simplify_expression(expression)
test_data.append((expression, simplified_expression))
def print_progress_bar(iteration, total, prefix='', suffix='', decimals=1, length=50, fill='█'):
"""
Call in a loop to create terminal progress bar
@params:
iteration - Required : current iteration (Int)
total - Required : total iterations (Int)
prefix - Optional : prefix string (Str)
suffix - Optional : suffix string (Str)
decimals - Optional : positive number of decimals in percent complete (Int)
length - Optional : character length of bar (Int)
fill - Optional : bar fill character (Str)
"""
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
filled_length = int(length * iteration // total)
bar = fill * filled_length + '-' * (length - filled_length)
print(f'\r{prefix} |{bar}| {percent}% {suffix}', end='\r')
if iteration == total:
print()
# 程序状态记录
program_status = {}
# 循环测试六个Java文件
for i in range(1, 7):
jar_file = f"{i}.jar"
wrong_file_name = f"wrong_{i}.txt"
error_found = False
print(f"Testing {jar_file}...")
print_progress_bar(0, n, prefix='Progress:', suffix='Complete', length=50)
with open(wrong_file_name, 'w') as wrong_file:
for j, (expression, simplified_expression) in enumerate(test_data, start=1):
with open('in.txt', 'w') as f:
f.write(expression + '\n')
subprocess.run(f"java -jar {jar_file} < in.txt > out.txt", shell=True, check=True)
with open('out.txt', 'r') as f:
java_expression = f.read().strip()
if not isEqual(simplified_expression, java_expression):
error_found = True
wrong_file.write(
f"Mismatch at line {j}. Python Expression: {expression}, Simplified Python Expression: {simplified_expression}, Java Expression: {java_expression}\n")
print_progress_bar(j, n, prefix='Progress:', suffix='Complete', length=50)
# 记录程序是否有错
program_status[jar_file] = 'No errors' if not error_found else 'Errors found'
print("测试完毕。")
# 输出每个程序的状态
for jar_file, status in program_status.items():
print(f"{jar_file}: {status}")
import os
import subprocess
from sympy import symbols, sympify, simplify, expand
n = 5000 # 定义测试的次数
def calculate_expression(expression: str):
x = symbols('x') # 定义符号x
expression_with_value = expression.replace('^', '**') # 替换^为**以适应Python的乘方运算
try:
expr = sympify(expression_with_value) # 使用sympify解析表达式
result = expr.subs(x, 131) # 代入x=131
result = result.evalf() # 计算表达式的数值结果
except Exception as e:
print(f"Error evaluating expression: {expression}. Error: {e}")
result = None
return result
def isEqual(expression: str, java_expression: str) -> bool:
python_result = calculate_expression(expression)
java_result = calculate_expression(java_expression)
if python_result is None or java_result is None:
return False
return abs(python_result - java_result) < 1e-9
def simplify_expression(expression: str) -> str:
x = symbols('x')
try:
# 使用sympify解析并化简表达式
expr = sympify(expression.replace('^', '**'))
simplified_expr = simplify(expr)
# 使用expand尝试进一步展开表达式,减少括号
expanded_expr = expand(simplified_expr)
# 将**替换为^并移除空格,以及尽可能移除括号
expanded_expr_str = str(expanded_expr).replace('**', '^').replace(' ', '')
# 移除表达式中不必要的括号(简单示例,可能不适用于所有情况)
expanded_expr_str_no_parentheses = expanded_expr_str.replace('(', '').replace(')', '')
return expanded_expr_str_no_parentheses
except Exception as e:
print(f"Error simplifying expression: {expression}. Error: {e}")
return expression # 如果无法化简,返回原始表达式
# 初始化计数器和列表
correct_count = 0
error_count = 0
different_lines = []
# 打开一个文件以记录错误信息和所有生成的表达式
with open('wrong.txt', 'w') as wrong_file, open('expression.txt', 'w') as expr_file:
for i in range(1, n + 1):
subprocess.run(['python', 'data_generate.py'], stdout=open('in.txt', 'w'))
with open('in.txt', 'r') as f:
expression = f.readline().strip()
simplified_expression = simplify_expression(expression)
expr_file.write(f"{i}. Python Expression: {expression}\n")
expr_file.write(f" Simplified Expression: {simplified_expression}\n")
os.system('java -jar 1.jar < in.txt > out.txt')
with open('out.txt', 'r') as f:
java_expression = f.read().strip()
expr_file.write(f" Java Expression: {java_expression}\n")
# 检查表达式结果是否相等
equal = isEqual(simplified_expression, java_expression)
# 检查化简后的Python表达式与Java表达式的长度
len_diff = abs(len(simplified_expression) - len(java_expression))
if len_diff > 1 or (len_diff == 1 and len(java_expression) > len(simplified_expression)):
different_lines.append(i)
elif not equal:
error_count += 1
wrong_file.write(f"Result mismatch at line {i}. Python Expression: {expression}, Simplified Python Expression: {simplified_expression}, Java Expression: {java_expression}\n")
else:
correct_count += 1
# 实时输出正确和错误的条数以及长度不一致的行号
if different_lines:
different_str = ", ".join(map(str, different_lines))
print(f"Correct: {correct_count}, Errors: {error_count}, Different: {different_str}")
else:
print(f"Correct: {correct_count}, Errors: {error_count}")
# 最后,输出总结信息
print(f"Final tally: Correct: {correct_count}, Errors: {error_count}, Different: {', '.join(map(str, different_lines))}")
ast树和访问者模式因为第一次完成代码后剩余时间较多,受到这篇博客的启发,打算对代码进行优化。原来的思路虽然好想好写,但是太过于臃肿也并不美观,迭代的时候会需要分开考虑,因此还是选择了构建ast树,再对树进行运算,写完发现代码很美观整洁,就分享一下思路。

ast树AST能够精确表示表达式中各元素之间的关系,包括操作符的优先级和操作数的分组。这种结构化表示使得后续对表达式的处理(比如求值或转换)基于一个明确和准确的基础。
通过AST,可以直接按照树的结构递归地求值,从叶节点开始,逐步向上直到根节点。这种方式自然地遵循了数学表达式的求值规则,包括操作符的优先级和左右结合性。
之后的计算MonoNode都会变成PolyMode,这个强制类型转换有点烦,但是在引入了下文提到的访问者模式后代码变得十分清晰。
在AST上进行表达式的修改和优化变得更加直接和方便。
大大地简化了所谓"基本项",只剩了两个类
BinaryOperationNode和MonoNode两种,这里MonoNode我觉得有很大的可拓展性,完全可以去存cos,sin更多的信息。
public class MonoNode extends AstNode {
private final BigInteger coefficient;
private final String variable;
private final BigInteger exponent;
public class BinaryOperationNode extends AstNode {
private final AstNode left;
private final AstNode right;
private final String operator;
构建AST之后,实现诸如符号解析等编译器高级功能变得更加直接。AST为这些分析提供了必要的信息和结构基础。
将复杂的表达式处理流程分解为构建AST和在AST上进行操作两个阶段,可以使整个处理过程更加模块化,代码的可读性和可维护性得到提升。
首先还是lexer用来解析,不再赘述。
在parser的写作上,我们不用再去纠结每个节点到底是expr,term,pow,还是num,var,统统当作AstNode就好。只需要梳理清楚运算符的优先级就好,就像下面这这样,递归去构建树就好,调试也很清晰。
public AstNode parseExpr() {
AstNode node = parseTerm();
while (true) {
.......
AstNode right = parseTerm();
String operator = op.getType() == Token.Type.PLUS ? "+" : "-";
node = new BinaryOperationNode(node, right, operator);
} else {
break;
}
}
return node;
}
在构建完整个ast树后,最后会返回根节点,从这里开始递归计算就好。
访问者模式在思考后,我使用了访问者模式来处理ast树,这里仅仅是我个人的思考,想法有误或者代码有误还请大家指正。
accept和visit方法,利用方法重载机制自动选择正确的操作,从而避免了显式的类型判断。上面这几个优点在代码中能很好地体现。
``main.java
PolyNode result = tree.accept(evaluator);
String resultString = PolyUtils.toString(result)
从main中可以看到,只调用类tree的accept方法,就计算出来了,下面来看看访问者模式是怎么样将不同的Node进行统一。
下面是实现的visitor接口
package ast;
import ast.poly.PolyNode;
public interface AstVisitor {
PolyNode visit(MonoNode node);
PolyNode visit(BinaryOperationNode node);
PolyNode visit(PolyNode node);
}
可以发现,我们去访问的时候是各种Node,但是返回的总是我们需要的PolyNode类型。
public class PolyNode extends AstNode {
private final HashSet<MonoNode> monos;
tips:我这里PolyNode中只有加减乘乘方,其他有关于化简的操作被我放到PolyUtils这个类中了。
回到visitor中,只需要在BinaryOperationNode和MonoNode加个accept方法就好
public PolyNode accept(AstVisitor visitor) {
return visitor.visit(this);
}
}
最后我们在EvaluatorVisitor中实现这三个具体的visit方法:
例如:
public PolyNode visit(MonoNode node) {
PolyNode result = new PolyNode();
result.addMono(node);
return result;
}
而对于符号节点来说,我们只需要拿到左边和右边算出的polyNode,再调用polyNode中的就算方法就可以算出来了。
如
case "+":
return leftResult.add(rightResult);
通过这个模式,我们就有效的实现了所有节点的统一,在外面来看,他们只做了相同的一件事,就是在收到Vistor的访问申请后,进行了允许访问的动作,而访问本身——就是计算。
在我们的例子中,一个访问者evaluator从树的根节点进入,得到accept后进入两个子节点
PolyNode leftResult = node.getLeft().accept(this);
PolyNode rightResult = node.getRight().accept(this);
上面的this就是我们创建的evaluator,当他走到最底层,就会返回MonoNode,这样从下往上一走,我们的计算过程就完成了。当然在他走的路途中,他的每个分身都PolyNode,最后会合到一起就得到了一个结果多项式。
ps:polyNode中我用的是HashSet去存每一项,这样更符合每一项的的平等性
private final HashSet<MonoNode> monos;
以上就是我重构的思路。至于如何化简什么的其他同学说的都很好了。

n,然后读取n行的自定义函数定义,最后读取待展开的表达式。这些步骤将通过InputProcessor类实现。Definer类来存储自定义函数的定义和参数。当读取到自定义函数定义时,解析函数名、参数列表和函数体,存储到Definer中。Pre类处理待展开的表达式,去除多余的空格,简化表达式等。Parser类解析预处理后的表达式,将其转换为AST。Definer查找函数定义,将实参替换形参,然后将替换后的表达式解析为AST的一部分。我在ast树中又加入unaryOperationNode类来解析exp和负号——负号的解析出现在parserfactor中
public class UnaryOperationNode extends AstNode {
private final AstNode operand;
private final String operator; //exp,-,
case MINUS:
AstNode pow = parsePow();
return new UnaryOperationNode(pow, "negate");
exp节点的添加
private AstNode parseExpFunction() {
expect(Token.Type.LPAREN);
AstNode operand = parseExpr();
expect(Token.Type.RPAREN);
return new UnaryOperationNode(operand, "exp");
}
注意这里的节点类型需要是^级别的
经过思考,选择了如下的结构
public class MonoNode extends AstNode {
private BigInteger coe;
private BigInteger varExp;
private HashMap<PolyNode, Integer> eMap;
这里用hashmap不用arraylist的主要原因就是在多项式计算的时候,需要一个能比较单项式x指数和e指数的函数equals,可以通过hashcode的重写轻松实现,避免了繁琐的递归与遍历。
hashcode与equals需要写polynode和mononode的equals和hashcode方法。这里使用其本身的自带的函数就可以很容易地实现了。
PolyNode
public boolean equals(Object o) {
....... this.monos.containsAll(polyNode.monos) && polyNode.monos.containsAll(this.monos);
}
@Override
public int hashCode() {
return Objects.hash(monos);
}
MonoNode
public boolean equals(Object o) {
.......
return varExp == monoNode.varExp && Objects.equals(eMap, monoNode.eMap);
}
public int hashCode() {
return Objects.hash(varExp, eMap);
}
嗯这需要这几行,多项式时候合并同类项最大的问题就解决啦。
在 MonoNode 的 equals 方法中,如果你使用 Objects.equals 来比较两个 eMap,Java 会自动调用 HashMap 的 equals 方法来检查两个映射是否等价,即它们是否包含完全相同的键值对。因此,即使是不同的 PolyNode 对象,只要它们内部的 eMap 包含相同的键值对,这些 PolyNode 就会被判断为相等。
这个机制确保了即使 eMap 不是同一个对象实例,只要它们的内容相同(即包含相同的 PolyNode 和对应的指数),MonoNode 的相等性判断逻辑就会认为它们相等。这对于在多项式中正确合并同类项至关重要。
进行多项式计算时候出现了为0的项要记得删除。
这里合并的时候无疑是可以优化的,关键就在于要不要把指数后面的次方乘进去,这个的话哪种更短可能也需要比较,但总体能提出来的公因式指数越大越好。
这里选择了对字符串进行替换,而不是直接将字符串替换为节点,这样的话可以将definer与ast树解耦,definer中仅仅进行了对字符串的处理与更改,返回替换后的字符串。而单纯对字符串的处理也更容易一些。
替换通常涉及以下步骤:
()要记录括号层数避免传参错误;对x的替换问题(解决方法很多......)
与预处理时候就进行字符串替换相比,这个可以与parser公用一个lexer,只需要替换后更新其内容与pos即可。替换后要调用的应该是parseFactor()
parser中
// 调用Definer.callFunc替换函数体
String replacedExpression = Definer.callFunc(functionName, args);
......
lexer.updateInput(
lexer.getInput().substring(0, startReplacePos - 2) + replacedExpression +
lexer.getInput().substring(endReplacePos - 1), startReplacePos - 2);
// 重新解析更新后的表达式,用于调试
System.out.println(lexer.getInput()+lexer.getCurrentPos());
return parseFactor();
ps:我觉得叶佩霖那个使用exec进行比较的思路很好,sympy对本次作业内的都可以计算
换进来的函数可能有前面的符号!!!!
因此对函数表达式也要预处理,还要加一个加号解析
加个解析加号就可以避免预处理0了
poly.getMonoNodes().removeIf(mono -> mono.getCoefficient().equals(BigInteger.ZERO));
很关键!!!
private HashMap<PolyNode, BigInteger> mergeEMaps(HashMap<PolyNode, BigInteger> mapE1,
HashMap<PolyNode, BigInteger> mapE2) {
HashMap<PolyNode, BigInteger> result = new HashMap<>();
mapE1.forEach((key1, value1) -> mapE2.forEach((key2, value2) -> {
PolyNode newKey = key1.add(key2);
result.put(newKey, BigInteger.ONE);
}));
return result;
}
if (gcd != null && !gcd.equals(BigInteger.ONE) && !gcd.equals(BigInteger.ZERO)) {
gcdPolyNode = new PolyNode();
for (MonoNode monoNode : polyNode.getMonoNodes()) {
BigInteger newCoe = monoNode.getCoefficient().divide(gcd);
MonoNode gcdMono = new MonoNode(newCoe,monoNode.getVarExp(),monoNode.getEMap());
gcdPolyNode.addMono(gcdMono);
}
eMap.entrySet().removeIf(en -> BigInteger.ONE.equals(en.getValue()));
eMap.put(gcdPolyNode, gcd);
}
深浅克隆背大锅
maxFuncLen = 15
maxLen = 20 # 20 已经足够长了
maxDepth = 3
pairs = []
import random
def generate_special_character():
# 在这里可以扩展更多特殊字符
special_characters = ['(0)', '(-0)', '(1)', '(-1)', 'exp(0)', 'exp(-0)', '(0-0)','(1+exp((1)^2))',
'(exp(0)^0)']
#special_characters = ['(0)', '(-0)', '(1)', '(-1)', 'exp(-0)', , '((0)^0)','exp(0)', '((-0)^0)', '((0)^0)', '(exp(0)^0)'
#'(exp(-0)^0)'] # 例子中只包括0,可以根据需要添加更多
return random.choice(special_characters)
# 修改generate_big_integer以包含特殊字符的生成
def generate_big_integer(max_digits=1, include_special=False): # 添加include_special参数
if include_special and random.choice([True, False]):
# 一半机会返回一个特殊字符
return generate_special_character()
else:
num = str(random.randint(1, 9)) + ''.join(
random.choice('0123456789') for _ in range(random.randint(0, max_digits - 1)))
sign = random.choice(['', '+', '-']) # 添加对带符号整数的支持
return sign + num
def generate_small_integer(max_value=2):
return str(random.randint(0, max_value)) # 包括0,以处理特殊情况0^0
def generate_exponent():
return '^' + generate_small_integer()
def generate_power_function(selected_symbol='x'):
return random.choice(selected_symbol) + (generate_exponent() if random.choice([True, False]) else '')
def generate_trigonometric_function(Len=maxLen, mode='CLOSE', selected_symbol='x', depth=0):
return random.choice(['sin', 'cos']) + "(" + generate_factor(Len - 3, mode, selected_symbol, depth + 1) + ")" + (
generate_exponent() if random.choice([True, False]) else '')
def generate_exponential_function(Len=maxLen, mode='CLOSE', selected_symbol='x', depth=0):
return "exp(" + generate_factor(Len - 3, mode, selected_symbol, depth + 1) + ")" + (
generate_exponent() if random.choice([True, False]) else '')
def generate_variable_factor(selected_symbol='x'):
return random.choice(selected_symbol) + (generate_exponent() if random.choice([True, False]) else '')
# 修改generate_constant_factor以随机选择生成特殊字符或常数
def generate_constant_factor():
if random.choice([True, False]): # 随机选择生成特殊字符或正常的大整数
return generate_special_character()
else:
return generate_big_integer(max_digits=2, include_special=True) # 确保特殊字符也可能在这里生成
def generate_custom_function(Len=maxLen, depth=0):
if len(pairs) == 0:
return ""
pair = random.choice(pairs) # 随机选择一个 pair
function_name, cnt = pair
if cnt == 1:
return function_name + "(" + generate_expression(Len - 3, 'OPEN', 'x', depth + 1) + ")"
else:
return function_name + "(" + ','.join(
[generate_expression(Len - 3, 'OPEN', 'x', depth + 1) for _ in range(cnt)]) + ")"
def generate_factor(Len=maxLen, mode='CLOSE', selected_symbol='x', depth=0):
# 当达到最大深度时,只从不需要进一步递归的选项中选择
if depth >= maxDepth:
choices = [lambda: generate_variable_factor(selected_symbol), generate_constant_factor]
else:
choices = [lambda: generate_variable_factor(selected_symbol), generate_constant_factor,
lambda: generate_expression_factor(Len - 2, mode, selected_symbol, depth + 1),
lambda: generate_exponential_function(Len, mode, selected_symbol, depth + 1)]
if mode == 'OPEN' and len(pairs) > 0:
choices.extend([lambda: generate_custom_function(Len, depth + 1)])
return random.choice(choices)()
def generate_item(Len=maxLen, mode='CLOSE', selected_symbol='x', depth=0):
item = generate_factor(Len, mode, selected_symbol, depth)
flag = 0
while len(item) < Len and random.choice([True, False]) and depth < maxDepth:
flag = 1
item += '*' + generate_factor(Len - len(item), mode, selected_symbol, depth)
if flag == 1:
return "(" + item + ")"
return item
def generate_expression(Len=maxLen, mode='CLOSE', selected_symbol='x', depth=0):
expr = generate_item(Len, mode, selected_symbol, depth)
flag = 0
while len(expr) < Len and random.choice([True, False]) and depth < maxDepth:
expr += random.choice(['+', '-']) + generate_item(Len - len(expr), mode, selected_symbol, depth)
flag = 1
if flag == 1:
return "(" + expr + ")"
else:
return expr
def generate_expression_factor(Len=maxLen, mode='CLOSE', selected_symbol='x', depth=0):
# 确保深度参数在递归调用时正确递增
if depth < maxDepth:
expr = generate_expression(Len, mode, selected_symbol, depth + 1)
exp_part = generate_exponent() if random.choice([True, False]) else ''
return '(' + expr + ')' + exp_part
else:
# 达到最大深度时的处理逻辑
return generate_variable_factor(selected_symbol)
def clean_expression(expr):
return expr.replace(' ', '').replace('\t', '')
def generate_valid_expression(len=maxLen, mode='CLOSE', selected_symbol='x'):
expr = generate_expression(len, mode, selected_symbol)
cleaned_expr = clean_expression(expr)
return cleaned_expr
def is_contain_all_symbols(string, selected_symbol):
for symbol in selected_symbol:
if symbol not in string:
return False
return True
def generate_data():
origin_function = ['f', 'g', 'h']
used_function = []
origin_symbols = ['x', 'y', 'z']
n = random.randint(0, 3)
print(n)
for i in range(n):
function_name = random.choice(origin_function)
used_function.append(function_name)
origin_function.remove(function_name)
cnt = random.randint(1, 3)
selected_symbol = random.sample(origin_symbols, cnt)
random.shuffle(selected_symbol)
pairs.append((function_name, cnt))
tmp = generate_valid_expression(maxFuncLen, 'CLOSE', selected_symbol).replace('exp', 'e')
while is_contain_all_symbols(tmp, selected_symbol) == False:
tmp = generate_valid_expression(maxFuncLen, 'CLOSE', selected_symbol).replace('exp', 'e')
print(function_name + "(" + ",".join(selected_symbol) + ")=" + tmp.replace("e", "exp"))
s = generate_valid_expression(maxLen, 'OPEN')
# print(used_function)
while len(pairs) > 0 and is_contain_all_symbols(s, used_function) == False:
s = generate_valid_expression(maxLen, 'OPEN')
print(s) # 生成含有自定义函数的表达式
# 生成并打印优化后的表达式
generate_data()
多人成绩
import subprocess
from tqdm import tqdm
from sympy import symbols, sympify, simplify, expand, exp, parse_expr, sin, cos
import concurrent.futures
n = 10 # 定义测试的次数
def equals_timeout(expression1, java_expression):
python_result = parse_expr(expression1)
java_result = parse_expr(java_expression.replace('^', '**'))
return python_result.equals(java_result)
def isEqual(expression2: str, java_expression: str):
if len(java_expression) > 1000: # 跳过比较
return 2
try:
return equals_timeout(expression2, java_expression)
except TimeoutError: # 如果equals判断运行超时,则返回True
return 2
def perform_task(i2, jar_index2, expression_str2: str):
"""执行单个任务,比如运行Java程序并进行表达式比较"""
try:
# 调用Java程序处理同一输入
try:
subprocess.run(['java', '-jar', f'{jar_index2}.jar'], stdin=open('in.txt', 'r'),
stdout=open('out.txt', 'w'), timeout=2)
except subprocess.TimeoutExpired:
print("Java execution timed out")
return 2
with open('out.txt', 'r') as f:
java_expression = f.read().strip()
equal = isEqual(expression_str2, java_expression)
return equal
except Exception as e:
print(f"Error during task {i2}: {e}")
return 2 # 发生错误时返回None
for jar_index in range(1, 7):
correct_count = 0
error_count = 0
timeout_count = 0
with open(f'{jar_index}_wrong'
f'.txt', 'w') as wrong_file:
for i in tqdm(range(1, 1888), desc=f'Evaluating jar {jar_index}'):
# 以下是对每个表达式的处理逻辑
# 请在这里按照原逻辑准备expression_str等变量
subprocess.run(['python', 'data_generate.py'], stdout=open('in.txt', 'w'))
with open('in.txt', 'r') as file1:
in_contents = file1.read()
# with open('expression.txt', 'a') as file2:
# file2.write(str(i) + ".\n")
# file2.write(in_contents + '\n')
with open('in.txt', 'r') as file:
lines = file.readlines()
# 读取函数定义数量
n = int(lines[0].strip())
# 定义可能用到的符号
x, y, z = symbols('x y z')
# 动态执行函数定义,并更新局部字典
for line in lines[1:n + 1]:
function_name, expression = line.split('=')
# 去除空格
function_name = function_name.strip()
expression = expression.strip()
# 替换符号和幂运算
expression = expression.replace('^', '**')
# 拼接输出字符串
output_str = f"def {function_name}:return {expression}"
exec(output_str)
# print(f"定义了函数: {function_name} = {expression}")
# 读取要化简的表达式并替换 ^ 为 **
expression_str = lines[n + 1].strip()
if len(expression_str) > 48:
# print("too long")
continue
else:
pass
# print(f"处理的表达式: {expression_str}")
# exec("expression_str = " + expression_str.replace('^', '**'))
# 现在,确保表达式是正确的格式
try:
exec("expression_str = " + expression_str.replace('^', '**')) # 这里尝试执行可能导致问题的代码
expression_str = str(expression_str)
except (ValueError, OverflowError, TypeError) as e:
# 如果是已知可能的错误类型,打印错误并跳过当前迭代
print(f"Skipping due to error: {e}")
continue
# print(f"Processing expression: {expression}")
# 使用ThreadPoolExecutor管理并行任务
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
# 提交任务
future = executor.submit(perform_task, i, jar_index, expression_str)
try:
# 等待任务完成,超时设置为4秒
result = future.result(timeout=4)
if result == 1:
correct_count += 1
continue
elif result == 0:
error_count += 1
wrong_file.write(
f"{in_contents}{expression_str}\n")
continue
else: # None表示超时或错误
timeout_count += 1
wrong_file.write("time\n" +
f"{in_contents}\n")
continue
except concurrent.futures.TimeoutError:
print(f"Task {i} timed out.")
wrong_file.write(
f"{in_contents}\n")
timeout_count += 1
continue
print(
f"Final Results for jar {jar_index}: Correct: {correct_count}, Errors: {error_count}, "
f"Timeouts: {timeout_count}. Check '{jar_index}_wrong.txt' for mismatches.")
上次作业用递归解决的话自然而然就能实现嵌套了
对于我的架构来说,求导就相当于一套新的运算规则,而原本的所有运算规则都在EvaluatorVistor中实现,想要在解析到dx的时候改变规则,就用一个新的访问者去挨个解析不就好了吗。因此启发我来创建一个的访问者:DerivativeVistor中来实现所有的求导规则,只需要重写所有节点的visit函数即可。
调用的话就将dx当成一个一元运算节点就好,创建一个新的访问者,去用求导的运算规则一一访问节点。
如下EvaluatorVistor
case "dx":
DerivativeVisitor derivativeVisitor = new DerivativeVisitor();
return node.getOperand().accept(derivativeVisitor);
如下给出示例:DerivativeVistor
public PolyNode visit(BinaryOperationNode node) {
switch (node.getOperator()) {
case "+":
case "-":
......
case "*":
PolyNode left = node.getLeft().accept(new EvaluatorVisitor());
PolyNode right = node.getRight().accept(new EvaluatorVisitor());
PolyNode leftDerive = left.accept(this);
PolyNode rightDerive = right.accept(this);
return leftDerive.mul(right).add(left.mul(rightDerive));
这个是对乘法这个二元运算符的法则重写,也是求导运算中的乘法法则。
@Override
public PolyNode visit(MonoNode node)
@Override
public PolyNode visit(PolyNode node)
@Override
public PolyNode visit(BinaryOperationNode node)
@Override
public PolyNode visit(UnaryOperationNode node)
可以发现,用这种方法使得结构十分清晰,将所有的运算操作集成到了一个visit的类中,也就是将运算与节点因子进行了解耦,节点本身只提供了一个visit和accept的接口。这一点就成功的避免了节点中写过多的方法让人眼花缭乱,也将所有的求导操作都集成到了一个类中,便于阅读及debug。
maxFuncLen = 10
maxLen = 50 # 20 已经足够长了
maxDepth = 3
maxDerivCnt = 10
DerivCnt = 0
import random
def generate_special_character():
# 在这里可以扩展更多特殊字符
special_characters = ['1', '-1', 'exp(-0)'] # 例子中只包括0,可以根据需要添加更多
return random.choice(special_characters)
# 修改generate_big_integer以包含特殊字符的生成
def generate_big_integer(max_digits=1, include_special=False): # 添加include_special参数
if include_special and random.choice([True, False, False]):
# 一半机会返回一个特殊字符
return generate_special_character()
else:
num = str(random.randint(1, 9)) + ''.join(random.choice('0123456789') for _ in range(random.randint(0, max_digits - 1)))
sign = random.choice(['', '+', '-']) # 添加对带符号整数的支持
return sign + num
def generate_small_integer(max_value=3):
return str(random.randint(0, max_value)) # 包括0,以处理特殊情况0^0
def generate_exponent():
return '^' + generate_small_integer()
def generate_power_function(selected_symbol='x'):
return random.choice(selected_symbol) + (generate_exponent() if random.choice([True, False]) else '')
def generate_trigonometric_function(used_function, Len=maxLen, mode='CLOSE', selected_symbol='x', depth=0):
return random.choice(['sin', 'cos']) + "(" + generate_factor(used_function, Len - 3, mode, selected_symbol, depth+1) + ")" + (generate_exponent() if random.choice([True, False]) else '')
def generate_exponential_function(used_function, Len=maxLen, mode='CLOSE', selected_symbol='x', depth=0):
return "exp(" + generate_factor(used_function, Len-3, mode, selected_symbol, depth+1) + ")" + (generate_exponent() if random.choice([True, False]) else '')
def generate_variable_factor(selected_symbol='x'):
return random.choice(selected_symbol) + (generate_exponent() if random.choice([True, False]) else '')
# 修改generate_constant_factor以随机选择生成特殊字符或常数
def generate_constant_factor():
if random.choice([True, False]): # 随机选择生成特殊字符或正常的大整数
return generate_special_character()
else:
return generate_big_integer(max_digits=1, include_special=True) # 确保特殊字符也可能在这里生成
def generate_custom_function(used_function, Len=maxLen, mode='CLOSE', depth=0):
if len(used_function) == 0:
return ""
pair = random.choice(used_function) # 随机选择一个 pair
function_name, cnt = pair
if cnt == 1:
return function_name + "(" + generate_expression(used_function, Len-3, mode, 'x', depth+1) + ")"
return function_name + "(" + ','.join([generate_expression(used_function, Len-3, mode, 'x', depth+1) for _ in range(cnt)]) + ")"
def generate_derivative_factor(used_function, Len=maxLen, mode='ClOSE', selected_symbol='x', depth=0):
global DerivCnt
DerivCnt += 1
return 'dx(' + generate_expression(used_function, Len-2, mode, selected_symbol, depth) + ')'
def generate_factor(used_function, Len=maxLen, mode='CLOSE', selected_symbol='x', depth=0):
# 当达到最大深度时,只从不需要进一步递归的选项中选择
if depth >= maxDepth:
choices = [lambda: generate_variable_factor(selected_symbol), generate_constant_factor]
else:
choices = [lambda: generate_variable_factor(selected_symbol), generate_constant_factor,
lambda: generate_expression_factor(used_function, Len-2, mode, selected_symbol, depth+1),
lambda: generate_exponential_function(used_function, Len, mode, selected_symbol, depth + 1)]
if len(used_function) > 0:
choices.extend([lambda: generate_custom_function(used_function, Len-2, mode, depth+1)])
if DerivCnt < maxDerivCnt and mode == 'OPEN':
choices.extend([lambda: generate_derivative_factor(used_function, Len-2, mode, selected_symbol, depth + 1)])
choices.extend(
[lambda: generate_derivative_factor(used_function, Len - 2, mode, selected_symbol, depth + 1)])
choices.extend(
[lambda: generate_derivative_factor(used_function, Len - 2, mode, selected_symbol, depth + 1)])
return random.choice(choices)()
def generate_item(used_function, Len=maxLen, mode='CLOSE', selected_symbol='x', depth=0):
item = generate_factor(used_function, Len, mode, selected_symbol, depth)
flag = 0
while len(item) < Len and depth < maxDepth and random.choice([True, False]):
item += '*' + generate_factor(used_function, Len - len(item), mode, selected_symbol, depth)
flag = 1
if flag :return '(' + item + ')'
return item
def generate_expression(used_function, Len=maxLen, mode='CLOSE',selected_symbol='x', depth=0):
expr = generate_item(used_function, Len, mode, selected_symbol, depth)
flag = 0
while len(expr) < Len and depth < maxDepth and random.choice([True, False]):
flag = 1
expr += random.choice(['+', '-']) + generate_item(used_function, Len - len(expr), mode, selected_symbol, depth)
if flag == 0 : return expr
else : return '(' + expr + ')'
def generate_expression_factor(used_function, Len=maxLen, mode='CLOSE', selected_symbol='x', depth=0):
# 确保深度参数在递归调用时正确递增
if depth < maxDepth:
expr = generate_expression(used_function, Len, mode, selected_symbol, depth + 1)
exp_part = generate_exponent() if random.choice([True, False]) else ''
return '(' + expr + ')' + exp_part
else:
# 达到最大深度时的处理逻辑
return '(' + generate_variable_factor(selected_symbol) + ')'
def clean_expression(expr):
return expr.replace(' ', '').replace('\t', '')
def generate_valid_expression(used_function, len = maxLen, mode='CLOSE',selected_symbol='x'):
expr = generate_expression(used_function, len, mode, selected_symbol)
cleaned_expr = clean_expression(expr)
return cleaned_expr
def is_contain_all_symbols(string, selected_symbol):
if len(selected_symbol) == 0:
return True
for symbol in selected_symbol:
if symbol not in string:
return False
return True
def generate_data():
origin_function = ['f', 'g', 'h']
used_function = []
used_function_name = []
origin_symbols = ['x', 'y', 'z']
n = random.randint(0, 3)
print(n)
for i in range(n):
function_name = random.choice(origin_function)
origin_function.remove(function_name)
cnt = random.randint(1, 3)
selected_symbol = random.sample(origin_symbols, cnt)
random.shuffle(selected_symbol)
tmp = generate_valid_expression(used_function, maxFuncLen, 'OPEN', selected_symbol).replace('exp', 'e')
while is_contain_all_symbols(tmp, selected_symbol) == False or is_contain_all_symbols(tmp, used_function_name) == False:
tmp = generate_valid_expression(used_function, maxFuncLen, 'OPEN', selected_symbol).replace('exp', 'e')
used_function_name.append(function_name)
used_function.append((function_name, cnt))
print(function_name + "(" + ",".join(selected_symbol) + ")=" + tmp.replace("e", "exp"))
s = generate_valid_expression(used_function, maxLen, 'OPEN')
# print(used_function)
while len(used_function) > 0 and is_contain_all_symbols(s, used_function_name) == False:
s = generate_valid_expression(used_function, maxLen, 'OPEN')
print('dx(' + s + ')') # 生成含有自定义函数的表达式
# 生成并打印优化后的表达式
generate_data()
uml图
<>中为软件包
InputProcessor, Pre): 输入预处理部分处理原始输入,包括函数定义和表达式。它负责去除空格、添加首个零、简化符号等,以标准化输入字符串。Lexer): 将预处理后的字符串输入转换为一系列令牌(Tokens),为语法解析阶段准备数据。语法分析 (Parser): 使用词法分析器的输出构建一个抽象语法树(AST)。这个树结构表示了输入表达式的数学逻辑结构。AstNode 及其子类): 定义了表达式的基本构件,如单项式(MonoNode)、二元操作节点(BinaryOperationNode)、一元操作节点(UnaryOperationNode)等。AstVisitor 接口及其实现): 定义了操作AST的一系列方法,使得在不修改节点类的情况下,可以添加新的操作,如求值或求导。Definer): 管理用户定义的函数,允许在表达式中使用这些函数,po。Main): 整合上述组件,实现从输入到输出的完整流程。访问者模式在这个项目中扮演了至关重要的角色,它通过定义一个外部访问者接口来操作组成表达式的各种元素,而不需要修改这些元素本身的代码。
AstVisitor)visit方法。每种节点类型都有其对应的visit方法。EvaluatorVisitor): 这个访问者通过递归遍历AST,计算并返回表达式的值。对于二元操作节点,它会先计算左右子节点的值,然后根据操作符(加、减、乘、除)计算结果。对于一元操作节点,它会先计算子节点的值,然后应用一元操作(如取负、求指数,dx)。DerivativeVisitor): 这个访问者遍历下面的astNode,实现了对表达式的符号微分。对于基本的数学操作(加、减、乘、除、指数),它应用相应的微分规则。对于更复杂的函数调用或表达式,需要递归地应用链式法则或乘积法则等。在三次作业中出现了一个bug,就是在parse中解析负号的逻辑不对,应该是到幂次级别,而不是因子级别。

可以发现average都在合理的范围内,模块复杂度小于圈复杂度。
下面是按复杂度从高到低进行排序:

PolyUtils.formatExp 和 PolyUtils.toString 函数:这两个函数具有较高的认知复杂度和圈复杂度,表明它们在理解和测试上可能需要较大的努力。尤其是toString方法,具有高基本复杂度和模块设计复杂度,指出可能存在多个非结构化的控制流和复杂的模块调用关系。Pre.simplifySigns 和 Pre.deleteMulAdd**:这些函数有较低的认知复杂度和较低的模块设计复杂度,但其圈复杂度相对较高,表明尽管它们比较易于理解,但需要更多的测试用例来覆盖所有执行路径。Parser.parseFunctionCall 和 PolyNode.addMono**:这些函数的认知复杂度和圈复杂度表明它们在逻辑上相对复杂且测试覆盖困难。特别是parseFunctionCall,其模块设计复杂度较高,指示在模块间调用关系上可能较为复杂。可以发现集中在了一些工具函数上,主要是对数据的预处理用了很多判断以及对最后结果的合并与exp的化简(我把排序,提公因式,一些小特判放在了tostring中)
Parser.parseFunctionCall主要是对自定义函数的替换逻辑,为了保证参数识别正确,用了一个判断括号的大循环和一些变量,虽然可以将这个再分出去一个函数,但觉得没有必要。
以上。
lexer和parser: 采用lexer扫描句子和parser进行解析的做法,展现了通过递归下降的方式精准地将中缀表达式转换为后缀表达式的高效性。这种方法的逻辑清晰,易于实现,且为后续的多项式计算打下了坚实基础。HashSet)管理多项式中的单项式,使得相同底数的单项式合并、系数为0的项移除等化简操作变得简洁高效。课程改进建议:
lexer)和语法分析(parser)等编译原理基础知识的讲解,以帮助学生更好地理解表达式解析的背后逻辑。