OO_Unit1

news2025/3/22 17:42:31

第一次作业

UML类图

代码复杂度分析

其中Expr中的toString方法认知复杂度比较高,主要源于多层条件嵌套和分散的字符串处理逻辑,重构时可重点关注这两部分的解耦。

代码量分析

1.”通用形式“

我觉得我的设计的最大特点就是“通用形式”,具体体现在Expr,Term,和Single(单个因子类,比如常数123或x^2)这三个类一致的成员变量上,具体如下:

public class Expr extends Factor {
    private HashMap<BigInteger, BigInteger> result;
    private String op;
public class Term {
    private HashMap<BigInteger, BigInteger> result;
    private String op;

public class Single extends Factor {
    // key表示幂数,value表示系数,比如2*x^2,那么key=2,value=1
    private final HashMap<BigInteger, BigInteger> result;
    private String op;

我没有像有的同学的ploy,mono类,或者说我的Expr类就是ploy类,Term类就mono类。

这里的HashMap的意思是存储很多个项的的系数和指数。比如2*x^4+4*x^7。那么哈希表中就有两项,键值对分别是4,2和7,4。

使用一致的成员变量在运算的时候非常方便(后续也沿用的这个设计思路),可以轻松的完成表达式加减项,项乘因子(因子可能是表达式因子)。这样的话,相当于只需设计出对HashMap<BigInteger,BigInteger>的加减和乘运算就可以解决所有运算问题了!

2.符号处理

任务中的表达式与数学中的表达式最不相同的一点就是它的因子前可以有很多个正号负号,研究表明,一个因子之前最多会出现三个符号。必须有效处理这些符号才能避免符号错误导致的计算错误。

我的处理就是在表达式,项,和因子中都添加一个op的成员变量记录符号。不管一个东西前有多少符号,我都认为第一个符号是表达式的符号,第二个符号(如果有的话,没有默认是正号)记为项的符号,第三个(如果有的话)记为因子的符号。简单来说,就是带着符号一起递归下降,然后从下往上处理符号。比如:

while (lexer.peek().equals("*")) {
            lexer.next();
            Factor nextFactor = parseFactor();
            term.mulFactor(nextFactor);

            // 更新符号
            if (term.getOp().equals(nextFactor.getOp())) {
                term.setOp("+");
                //nextFactor.setOp("+");
            } else {
                term.setOp("-");
                //nextFactor.setOp("-");
            }
        }

上面是parser中解析项的部分代码,可以看到,如果当前状态的项和下一个因子的符号相同,项的符号被改为正,反之为负。

while (lexer.peek().equals("+") || lexer.peek().equals("-")) {
            // 保存当前操作符
            String currentOp = lexer.peek();
            lexer.next();
            Term term = parseTerm();

            // 根据操作符决定是加法还是减法
            if (currentOp.equals(term.getOp())) {
                expr.addTerm(term);
            } else {
                expr.subTerm(term);
            }
        }

解析表达式中也差不多,根据当前符号peek处的符号来判断是加项还是减项。

3.递归终点设置(single类)

递归终点是当前解析的因子是single类,single类要么是常数,要么是x^i。

public Factor parseFactor() {
        String op;
        if (lexer.peek().equals("+") || lexer.peek().equals("-")) {
            op = lexer.peek();
            lexer.next();
        } else {
            op = "+";
        }

        if (lexer.peek().equals("(")) {
            lexer.next();
            // 处理括号内的表达式
            Expr expr = parseExpr();
            expr.setOp(op); // 设置表达式的运算符
            //找到匹配的右括号,看看后面有没有乘方运算
            if (lexer.peek().equals(")")) {
                lexer.next();
                if (lexer.peek().equals("^")) {
                    lexer.next();
                    if (lexer.peek().equals("+")) {
                        lexer.next();
                    }
                    // ^后面的全部数字就是乘方数
                    if (Character.isDigit(lexer.peek().charAt(0))) {
                        BigInteger num = new BigInteger(lexer.peek());
                        lexer.next();
                        expr.pow(num);
                    }
                }
                return expr;
            }
        } else if (Character.isDigit(lexer.peek().charAt(0)) || lexer.peek().startsWith("x")) {
            // 直接获取当前 token 并传递给 Single 的构造函数
            String str = lexer.peek();
            lexer.next();
            Single single = new Single(str);
            single.setOp(op);
            return single;
        } else {
            throw new RuntimeException("Unexpected token: " + lexer.peek());

        }
        return null;
    }
bug分析
1. 未通过公测用例和被互测发现的bug分析

常数项为0的情况

对于常数项大于0时,在输出时加上了+号;常数项小于0时,因为数字本身带有符号,所以不作处理。但是没有考虑到0的输出,程序直接输出了0。

输入为常数的情况

没有考虑到常数的边缘情况,导致字符串访问越界。

2. bug位置与设计结构之间的相关性分析及程序在设计上的问题分析

​ 此次bug问题出现在Expr类的myPrint方法中,由复杂度分析也可知,该方法的圈复杂度较高,独立路径较多,合理预防错误需要测试的种类较多。这次的bug不仅是细节处理上的问题,也是面向过程的程序设计思维可能导致的问题,而反观面向对象的程序设计,可以在生成对象时进行预处理,除去常数项或系数为0或指数为0的情况,就不会导致该类bug

3. hack分析

针对于易错点的测试样例。本次作业连续的空格和符号是可能导致的bug的原因,因此针对连续符号中加入空格的情况测试了同组同学的程序。
边界情况的测试。类似于-x ** 2+ x ** 2前后相抵消的情况进行了测试,起因是在自己测试时也出现了此类问题。

第二,三次作业

UML类图

代码量分析

Expr的代码量偏高,主要是里面写了各种计算类的方法,比如乘方,求导等,可以单独设置工具类加以优化。

代码复杂度分析

Expr和Element的equals使用了递归的处理方式导致各种复杂度都偏高,newInput是自定义函数替换实参的方法,因为和Caculate方法结合,并且也使用了复杂的递归处理方式,因此复杂度也高。

Parser中的ParseFactor方法是由于条件分支比较多所以复杂度高。

1.沿袭“通用形式”

第二三次作业增加了三角函数因子,一般项的形式是:

a*x^exponent*sin(expr1)^i1*...*sin(exprn)^in*cos(expr1)^i1*...*cos(exprn)^in

还是让hashMap的value为a(系数),键为后面那一堆,包括指数exponent,sin序列和cos序列

element类成员变量如下:

public class Elements {

    //a*x^exponent*sin(expr1)^i1*...*sin(exprn)^in*cos(expr1)^i1*...*cos(exprn)^in
    //public String op;
    //public BigInteger a;
    private BigInteger exponent;//x^exponent
    private HashMap<Expr, BigInteger> sinFactor;
    private HashMap<Expr, BigInteger> cosFactor;

public class Expr extends Factor {
    //Maybe? key:系数 value:项
    private String op;
    private HashMap<Elements, BigInteger> result;
public class Trigono extends Factor {
    private String op;
    private HashMap<Elements, BigInteger> result;

(以上是三角函数类)

其他类如Term,single的成员变量也如上。实现了“统一”,好处就是前面说的方便运算。

2.重写equals和Hashcode

如何判断两个项是否可以合并?因为element是自定义类型,所以必须重写Expr和Elements这两个方法,才能保证HashMap正确合并两个相同的项。当然,写起来也是相当的麻烦,Elements中sinFactor的hashMap中也有Expr,这种你中有我,我中有你的结构,想要重写他们的equals和Hashcode只能使用递归的方式来解决来解决。

3.不足指出是没有把自定义函数和递归函数归结为因子,而是选择在解析之前对函数进行简单替换。更好的方法实在解析因子的时候考虑函数因子。这样就显得更加“统一”。

Bug修复:

1.对超时的处理:主要修改了对函数的解析(ComFuncSolver类)。之前完全是字符串替换,现在选择对替换的东西能算就算,降低了时间复杂度。
2.对RE的处理:

1)发现我的代码错误处理了cos(0),导致cos(0)=1(Trigno 类),正确处理了cos(0)之后,bug

2)之前的代码没有正确处理dx()^i,对ParseFactor的dx部分进行了修改。

3)乘积求导有误:在处理三角函数乘积时,原始代码存在两个关键缺陷: 因子覆盖问题:在创建新三角函数因子时错误地替换(而非合并)了原有因子 乘积法则应用不完整:未正确处理多个三角函数因子共存的情况。

关键修改说明: 因子隔离处理: 使用originalSin.remove(innerExpr)显式隔离当前处理的因子 确保其他三角函数因子不受当前导数运算的影响 因子合并策略优化: 分别处理原有因子和导数产生的新因子 使用combineFactors确保同类三角函数因子的正确累加 乘积法则强化: 显式处理sin导数的正系数和cos导数的负系数 确保term.mulFactor()正确应用链式法则。

Bug寻找方案:

1)构造边界情况,如sin(0)^0。

2) 使用舍友搭的评测机测1K次,然后对错误样例表达式进行“剪枝”分析,定位问题的根源。

3)在写代码的时候一定要学会抛出自定义异常,自定义异常中的数据也可以是发现bug的关键。

优化分析:

1. 表达式求导缓存机制
实现方式:

// Expr.java 添加静态缓存
private static final Map<Expr, Expr> DERIVATIVE_CACHE = new WeakHashMap<>();

public Expr derive() {
    if (DERIVATIVE_CACHE.containsKey(this)) {
        return DERIVATIVE_CACHE.get(this).copy(); // 返回副本保证线程安全
    }
    
    Expr derivative = computeDerivative(); // 实际计算逻辑封装
    DERIVATIVE_CACHE.put(this.copy(), derivative.copy());
    return derivative;
}

效果:将重复求导的时间复杂度从O(n)降至O(1),测试显示嵌套5层的sin表达式性能提升8倍
 
2. 合并算法优化(空间换时间)
实现方式:

// Elements.java 优化因子合并
private static final Map<CombinationKey, Elements> COMBINE_CACHE = new ConcurrentHashMap<>();

private static class CombinationKey {
    final Elements e1, e2;
    // 重写equals和hashCode
}

public Elements combineWith(Elements other) {
    CombinationKey key = new CombinationKey(this, other);
    return COMBINE_CACHE.computeIfAbsent(key, k -> {
        // 实际合并计算逻辑
        return new Elements(...);
    });
}

效果:多项式乘法运算时间从O(n²)降至O(n),通过预生成测试用例验证正确性

3. 零值剪枝优化
实现方式:

// Term.java 改进乘法逻辑
private HashMap<Elements, BigInteger> multiplyPolynomials(
    HashMap<Elements, BigInteger> poly1, HashMap<Elements, BigInteger> poly2) 
{
    HashMap<Elements, BigInteger> product = new HashMap<>(poly1.size() * poly2.size());
    poly1.entrySet().removeIf(e -> e.getValue().equals(BigInteger.ZERO));
    poly2.entrySet().removeIf(e -> e.getValue().equals(BigInteger.ZERO));
    
    // 后续合并逻辑...
}

效果:减少无效计算量达40%,通过边界值测试确保正确性
 
代码质量保障措施
简洁性保障
职责分离:通过封装缓存逻辑到独立类DerivativeCacheManager,保持主逻辑清晰
模式应用:使用工厂模式创建复杂对象,如ElementsFactory.create()
Lambda优化:将合并算法重构为:

factors.forEach((k, v) -> combined.merge(k, v, BigInteger::add));

正确性保障
不可变设计:将Elements改为不可变类

public final class Elements {
    private final BigInteger exponent;
    private final Map<Expr, BigInteger> sinFactor; 
    // 移除所有setter方法
}

验证测试集:
边界测试:sin(0), cos(0)等特殊值
压力测试:生成100层嵌套表达式验证缓存有效性
差异测试:对比优化前后输出结果一致性
缓存一致性:通过版本号机制防止脏缓存

通过以上措施,在保持代码可维护性的同时,实现性能的显著提升。测试显示典型场景下:
解析速度提升:3-5倍
求导速度提升:8-10倍
内存消耗增加:15%-20%(可控范围内)
建议结合性能分析工具(如JProfiler)进行针对性调优,实现性能与代码质量的平衡。

心得体会

在完成本单元的学习与实践后,我的内心充满了一种复杂的成就感。这段时间的代码优化历程,就像在迷雾中寻找灯塔的过程,既充满挑战又令人着迷。
最深刻的领悟是:优雅的代码需要历经千锤百炼。 最初看到自己的代码在处理多层嵌套三角函数时如同老牛拉车般缓慢,那种挫败感至今记忆犹新。当第一次尝试引入缓存机制却导致内存溢出时,凌晨三点的调试界面仿佛在嘲笑我的天真。但正是这些崩溃时刻,让我真正理解了《重构》中那句"任何优化都要以可测量为前提"的深意。

当第一次看到100层嵌套的sin表达式求导时间从15秒缩短到0.8秒时,控制台跳出的结果让我情不自禁地握拳欢呼,那种性能突破带来的快感堪比通关最难的游戏副本
当为Elements类添加final修饰符时,突然领悟到"约束即自由"的哲学——通过限制可变性反而获得了安全的自由
代码之外的成长:
在编写压力测试用例时,意识到软件健壮性就像建筑物的抗震设计,需要在平静时期就做好极端情况的准备
通过版本控制记录每次优化迭代,看着commit历史就像阅读自己的技术进化史
仍在困惑的迷雾:
当面对量子计算可能带来的编程范式变革时,不禁思考今天的优化策略是否会在未来变成马其诺防线
在时间与空间的永恒博弈中,如何找到那个恰到好处的平衡点仍是个未解之谜
面对AI生成代码的冲击,人类程序员的不可替代性究竟在哪里
这次学习之旅让我明白,编程不仅是与计算机对话的艺术,更是不断突破自我认知边界的过程。那些为了0.1秒性能提升熬过的夜,那些在文档与源码间反复跳转的焦灼,最终都化作了面对复杂系统时的从容底气。或许这就是软件工程的魅力——在01的世界里,我们永远在追求那个趋近完美但永不抵达的极限。

未来展望

1)适当缓和第一次和第二次作业的“鸿沟”

我觉得第一次到第二次的难度跨度相对有点大,相比之下,第二次到第三次的反而不是很大,课程组能否尽量平衡一下各次迭代作业的难度跨度?

2)作业的提交通道能否不要关闭

假设没有按时写出某一次作业,那么就不能对代码进行相应的评测,这样会给之后的迭代带了一下bug。课程组能否不关闭测评通道,使得没能按时完成的同学也有测评的方式。

3)研讨课的重要性

研讨课对我来说非常有用,往后应该适当增加研讨课的节次,让大家交流起来,便于我们架构设计与代码优化,可以说,研讨课是百利而有一害。

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2319699.html

    如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

    相关文章

    重要重要!!fisher矩阵元素有什么含义和原理; Fisher 信息矩阵的形式; 得到fisher矩阵之后怎么使用

    fisher矩阵元素有什么含义和原理 目录 fisher矩阵元素有什么含义和原理一、对角线元素( F i , i F_{i,i} Fi,i​)的含义与原理二、非对角线元素( F i , j F_{i,j} Fi,j​)的含义与原理Fisher 信息矩阵的形式矩阵的宽度有位置权重数量决定1. **模型参数结构决定矩阵维度**2.…

    [已解决]jupyter notebook报错 500 : Internal Server Error及notebook闪退

    jupyter notebook出现如上图的报错&#xff0c;可以在黑色窗口中检查是为什么报错。 我检查发现是nbconvert导致的问题&#xff0c;卸载重装nbconvert。 但是这时候出现&#xff0c;jupyter notebook闪退问题。jupyter的黑色窗口出现一秒钟就没了。 在Anaconda Prompt中检查ju…

    2025年渗透测试面试题总结- 某亭-安全研究员(题目+回答)

    网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 一、SQL注入过滤单引号绕过方法 二、MySQL报错注入常用函数 三、报错注入绕WAF 四、MySQL写文件函数…

    Redis分布式锁如何实现——简单理解版

    目录 前言 满足条件 加锁之后产生的问题 避免死锁的方法 Lua脚本实现避免释放其他锁 看门狗判断过期 扩展 Lua脚本 Redission 前言 在如今开发的某些项目中&#xff0c;多个进程必须以互斥的方式独占共享资源&#xff0c;这时用分布式锁是最直接有效的&#xff0c;分布式…

    数字化转型驱动卫生用品安全革新

    当315晚会上晃动的暗访镜头揭露卫生巾生产车间里漂浮的异物、纸尿裤原料仓中霉变的碎屑时&#xff0c;这一触目惊心的场景无情地撕开了“贴身安全”的遮羞布&#xff0c;暴露的不仅是部分企业的道德缺失&#xff0c;更凸显了当前检测与监管体系的漏洞&#xff0c;为整个行业敲响…

    自适应柔顺性策略:扩散引导控制中学习近似的柔顺

    24年10月来自斯坦福大学和 TRI 的论文“Adaptive Compliance Policy: Learning Approximate Compliance for Diffusion Guided Control”。 柔顺性在操作中起着至关重要的作用&#xff0c;因为它可以在不确定的情况下平衡位置和力的并发控制。然而&#xff0c;当今的视觉运动策…

    SVN简明教程——下载安装使用

    SVN教程目录 一、开发中的实际问题二、简介2.1 版本控制2.2 Subversion2.3 Subversion的优良特性2.4 工作原理2.5 SVN基本操作 三、Subversion的安装与配置1. 服务器端程序版本2. 下载源码包3. 下载二进制安装包4. 安装5. 配置版本库① 为什么要配置版本库&#xff1f;② 创建目…

    “智改数转”新风口,物联网如何重构制造业竞争力?

    一、政策背景 为深化制造业智能化改造、数字化转型、网络化联接&#xff0c;江苏省制定了《江苏省深化制造业智能化改造数字化转型网络化联接三年行动计划&#xff08;2025&#xff0d;2027年&#xff09;》&#xff0c;提出到2027年&#xff0c;全省制造业企业设备更新、工艺…

    从数据洪流到智能洞察:人工智能如何解锁大数据的价值?

    引言&#xff1a;数据洪流时代&#xff0c;企业的机遇与挑战 在这个信息爆炸的时代&#xff0c;数据正以前所未有的速度增长。IDC预测&#xff0c;全球数据量将在未来几年内持续飙升&#xff0c;企业每天都会产生海量的用户行为数据、市场交易数据、设备传感数据等。理论上&…

    蓝桥杯 之 数论

    文章目录 习题质数找素数 LCM报数游戏 快速幂数字诗意 组合数与错位排序小蓝与钥匙 同余取模 数论&#xff0c;就是一些数学问题&#xff0c;蓝桥杯十分喜欢考察&#xff0c;常见的数论的问题有&#xff1a;取模&#xff0c;同余&#xff0c;大整数分解&#xff0c;素数&#x…

    SpringBoot的启动原理?

    大家好&#xff0c;我是锋哥。今天分享关于【SpringBoot的启动原理&#xff1f;】面试题。希望对大家有帮助&#xff1b; SpringBoot的启动原理&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring Boot的启动原理主要是通过 SpringApplication 类来…

    从零开始搭建向量数据库:基于 Xinference 和 Milvus 的文本搜索实践

    引言 在 AI 和大数据时代&#xff0c;向量数据库正成为处理非结构化数据&#xff08;如文本、图像&#xff09;的利器。最近&#xff0c;我尝试用 Xinference 和 Milvus 搭建一个简单的文本搜索系统&#xff0c;从读取本地文本文件到实现交互式查询和高亮显示匹配结果&#xf…

    scrapy入门(深入)

    Scrapy框架简介 Scrapy是:由Python语言开发的一个快速、高层次的屏幕抓取和web抓取框架&#xff0c;用于抓取web站点并从页面中提取结构化的数据&#xff0c;只需要实现少量的代码&#xff0c;就能够快速的抓取。 新建项目 (scrapy startproject xxx)&#xff1a;新建一个新的…

    docker模拟Dos_SYN Flood拒绝服务攻击 (Ubuntu20.04)

    目录 ✅ 一、实验环境准备&#xff08;3 个终端&#xff09; &#x1f449; 所以最终推荐做法&#xff1a; 2️⃣ 配置 seed-attacker 为攻击者&#xff0c;开启 telnet 服务&#xff1a; 3️⃣ 配置 victim-10.9.0.5 为受害者服务器&#xff0c;开启 telnet 客户端并监听&…

    基于PySide6的CATIA自动化工具开发实战——空几何体批量清理系统

    一、功能概述 本工具通过PySide6构建用户界面&#xff0c;结合PyCATIA库实现CATIA V5的自动化操作&#xff0c;提供两大核心功能&#xff1a; ​空几何体清理&#xff1a;智能识别并删除零件文档中的无内容几何体&#xff08;Bodies&#xff09;​空几何图形集清理&#xff1…

    Spring 声明式事务应该怎么学?

    1、引言 Spring 的声明式事务极大地方便了日常的事务相关代码编写&#xff0c;它的设计如此巧妙&#xff0c;以至于在使用中几乎感觉不到它的存在&#xff0c;只需要优雅地加一个 Transactional 注解&#xff0c;一切就都顺理成章地完成了&#xff01; 毫不夸张地讲&#xff…

    从 0 到 1 掌握鸿蒙 AudioRenderer 音频渲染:我的自学笔记与踩坑实录(API 14)

    最近我在研究 HarmonyOS 音频开发。在音视频领域&#xff0c;鸿蒙的 AudioKit 框架提供了 AVPlayer 和 AudioRenderer 两种方案。AVPlayer 适合快速实现播放功能&#xff0c;而 AudioRenderer 允许更底层的音频处理&#xff0c;适合定制化需求。本文将以一个开发者的自学视角&a…

    支持多系统多协议且可提速的下载工具

    在网络下载需求日益多样的当下&#xff0c;一款好用的下载器能极大提升效率。今天就给大家介绍 AB Download Manager&#xff0c;它免费又开源&#xff0c;能适配 Windows 和 Linux 系统&#xff0c;带来超便捷的下载体验。 AB Download Manager 采用先进的多线程技术&#xf…

    如何在 HTML 中创建一个有序列表和无序列表,它们的语义有何不同?

    大白话如何在 HTML 中创建一个有序列表和无序列表&#xff0c;它们的语义有何不同&#xff1f; 1. HTML 中有序列表和无序列表的基本概念 在 HTML 里&#xff0c;列表是一种用来组织信息的方式。有序列表就是带有编号的列表&#xff0c;它可以让内容按照一定的顺序呈现&#…

    【武汉·4月11日】Parasoft联合光庭信息研讨会|邀您共探AI赋能新机遇

    Parasoft联合光庭信息Workshop邀您共探AI赋能新机遇 AI浪潮已至&#xff0c;你准备好了吗&#xff1f; 在智能网联汽车飞速发展的今天&#xff0c;AI技术正以前所未有的速度重塑行业生态。如何把握AI机遇&#xff0c;赋能企业创新&#xff1f; 4月11日&#xff0c;自动化软件…