面向对象程序设计作业集4~6的总结性Blog

写在前面

OOP作业集,以航空器配载与货运管理系统程序为中心,不断增加新的功能,我学到了很多:

更加熟悉了Java基本语法;
初步建立起了将继承,多态,接口等用于面向对象的设计思路;
提升阅读复杂项目需求的理解能力;
认识到了代码可拓展性的重要;
学会了使用Idea等IDE便利代码编辑。
学会了构建自动化测试工具。

对本人而言,三次的题目集的题量整体偏难,难度呈上升趋势,光是读懂题目就很需要时间。

Complexity Metrics(复杂度分析)

因为下面要用到复杂度分析,所以先在此给出一些相关概念。
我们需要使用的主要是方法和类的复杂度分析。
方法的复杂度分析主要基于循环复杂度的计算。循环复杂度是一种表示程序复杂度的软件度量,由程序流程图中的“基础路径”数量得来。

  • ev(G):即Essentail Complexity,用来表示一个方法的结构化程度,范围在[1,v(G)]​之间,值越大则程序的结构越“病态”,其计算过程和图的“缩点”有关。
  • iv(G):即Design Complexity,用来表示一个方法和他所调用的其他方法的紧密程度,范围也在[1,v(G)]​之间,值越大联系越紧密。
  • v(G):即循环复杂度,可以理解为穷尽程序流程每一条路径所需要的试验次数。
    对于类,有OCavg和WMC两个项目,分别代表类的方法的平均循环复杂度和总循环复杂度。
    下面我将从程序结构,公测、互测以及bug分析几个方面来总结我第一单元的三次作业。

【作业背景】

数字电路是一种处理离散信号的电子电路。与处理连续变化信号(如声音、温度)的模拟电路不同,数字电路只识别和运算两种基本状态:高电平(通常表示为“1”) 和 低电平(通常表示为“0”)。这正好与二进制数制系统相对应,使得数字电路成为所有计算机和数字系统的物理实现基础。
请编程实现数字电路模拟程序,
V2:在V1基础上新添了译码器,数据选择器,数据分配器三个元件,并对输入输出的格式做了调整。
V3:在V1(不是V2)基础上增加的子电路的设计,以及异常输入报错的机制。

OOP作业集04(V1)

【题目描述】

数字电路是一种处理离散信号的电子电路。与处理连续变化信号(如声音、温度)的模拟电路不同,数字电路只识别和运算两种基本状态:高电平(通常表示为“1”) 和 低电平(通常表示为“0”)。这正好与二进制数制系统相对应,使得数字电路成为所有计算机和数字系统的物理实现基础。
请编程实现数字电路模拟程序,
1、电路元件

电路中包含与门、或门、非门、异或门、同或门五种元件。元件特征如下:

与门:image

包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是高电平,输出引脚才是高电平,只要有一个输入引脚为低电平,输出引脚输出低电平。

或门:
image

包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是低电平,输出引脚才是低电平,只要有一个输入引脚为高电平,输出引脚输出高电平。

非门:
image

包含一个输入引脚和一个输出引脚。输出引脚的电平与输入引脚的电平相反,如输入为低电平,输出则为高电平。

异或门:
image

包含两个输入引脚和一个输出引脚。当两个输入引脚电平不一致时输出引脚输出高电平,否则输出低电平。

同或门:
image

包含两个输入引脚和一个输出引脚。当两个输入引脚电平一致时输出引脚输出高电平,否则输出低电平。

2、程序输入

1)元件信息:

用A、O、N、X、Y 分别用作与门、或门、非门、异或门、同或门五种元件的元件标识符。

电路中的每个与门、或门用“标识符(输入引脚数)+编号”作为其元件名。

例如:A(8)1表示一个8输入引脚的与门,O(4)2代表一个4输入引脚的或门。

电路中的每个非门、异或门、同或门用“标识符+编号”作为其元件名。

例如:X8表示一个异或门,Y4代表一个同或门,N1代表一个非门。

约束条件:

不同元件的编号可以相同,如X4、Y4。

同一电路中同种元件的编号不可重复,可以不连续

2)引脚信息:

引脚信息由“元件名-引脚号”构成,。

例如:A(8)1-2代表与门A(8)1的2号引脚。

3)电路的输入信息:

电路的输入格式:

INPUT:英文空格+输入1+”-”+输入信号1+英文空格+输入2+....+输入n+”-”+输入信号n

例如:

“INPUT: A-0 B-1 C-0”代表整个电路包括3个输入:A、B、C 分别输入0,1,0信号。

4)连接信息

引脚的连接信息格式:

[+输出引脚+英文空格+输入引脚1+。。。。+英文空格+输入引脚+]

例如:

[A A(8)1-1 A(8)1-3 X5-2]

代表信号从引脚A发送给与门A(8)1的1、3两个引脚,以及异或门X5的2号引脚。

[Y8-0 N1-1 O(4)2-3 Y2-1]

代表信号从引脚Y8-0发送给非门N1的1号引脚、或门O(4)2的3号引脚、同或门Y2的1号引脚。

约束条件:

一个输出引脚可以连接多个输入引脚,即将输出引脚的信号传给每一个输入引脚。但一个输入引脚不能连接多个输出引脚。

输出引脚不能短接在一起。

5)输入结束信息

所有输入以end为结束标志,end之后出现的内容忽略不计

3、程序输出

按照与门、或门、非门、异或门、同或门的顺序依次输出所有元件的输出引脚电平。同类元件按编号从小到大的顺序排序。

如果某个元件的引脚没有接有效输入,元件输出无法计算,程序输出结果忽略该元件

4、测试输入默认满足以下条件:

1)每个元件的输入引脚连续编号。假设元件有n个输入引脚,则其编号取值范围为[1,n],且引脚号不重复。

2)本题涉及的五种元件都只有一个输出引脚,输出引脚号默认为0。

5、后续迭代设计
数字电路模拟程序1(本次作业):

基础逻辑门元件构成电路

数字电路模拟程序2:

1、包含多输入输出的组合电路元件如数据选择器;

2、元件引脚类型除输入、输出之外,增加控制引脚,如三态门。

数字电路模拟程序3:

增加带反馈的电路、时序电路元件,如D触发器、JK触发器。

数字电路模拟程序4:

1、增加子电路;

2、增加程序异常输入的检测。

输入样例1:
在这里给出一组输入。例如:

INPUT: A-1 B-1
[A A(2)1-1]
[B A(2)1-2]
[A(2)1-0 OUT]
end
输出样例1:
在这里给出相应的输出。例如:

A(2)1-0:1

输入样例2:
在这里给出一组输入。例如:

INPUT: A-1 B-0
[A X1-1]
[B X1-2]
[X1-0 Y1-1]
[A Y1-2]
[Y1-0 OUT]
end
输出样例2:
在这里给出相应的输出。例如:

X1-0:1
Y1-0:1

输入样例3:
在这里给出一组输入。例如:

INPUT: A-1 B-1 C-1 D-0
[A A(4)1-1]
[B A(4)1-2]
[C A(4)1-3]
[D A(4)1-4]
[A(4)1-0 OUT]
end
输出样例3:
在这里给出相应的输出。例如:

A(4)1-0:0

输入样例4:
在这里给出一组输入。例如:

INPUT: A-1 B-0 C-1
[A A(2)1-1]
[B A(2)1-2]
[A(2)1-0 N1-1]
[N1-0 O(2)1-1]
[C O(2)1-2]
[O(2)1-0 OUT]
end
[O(2)1-0 N2-1]
输出样例4:
在这里给出相应的输出。例如:

A(2)1-0:0
O(2)1-0:1
N1-0:1
输入样例5:
在这里给出一组输入。例如:

INPUT: A-1 B-1 C-0 D-1
[A A(2)1-1 O(2)1-1 X1-1]
[B A(2)1-2]
[C O(2)1-2]
[D X1-2]
[O(2)1-0 A(2)1-0 X1-0 OUT]
end
输出样例5:
在这里给出相应的输出。例如:

A(2)1-0:1
O(2)1-0:1
X1-0:0
输入样例6:
X2输入不全,无法计算输出。例如:

INPUT: A-1 B-1 C-0 D-1
[A A(2)1-1 O(2)1-1 X1-1]
[B A(2)1-2 X2-1]
[C O(2)1-2]
[D X1-2]
[O(2)1-0 A(2)1-0 X1-0 OUT]
end
输出样例6:
输出结果忽略X2。例如:

A(2)1-0:1
O(2)1-0:1
X1-0:0

我提交的源码

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner cin = new Scanner(System.in);
        Map<String, Integer> initSignals = new HashMap<String, Integer>();
        List<String> wireLines = new ArrayList<String>();
        
        while (cin.hasNextLine()) {
            String line = cin.nextLine();
            if (line == null) {
                break;
            }
            line = line.trim();
            if (line.equals("")) {
                continue;
            }
            if (line.equals("end")) {
                break;
            }
            
            if (line.startsWith("INPUT:")) {
                try {
                    String content = line.substring(6).trim();
                    String[] parts = content.split("\\s+");
                    for (int i = 0; i < parts.length; i++) {
                        String item = parts[i].trim();
                        if (item.length() > 0) {
                            String[] pair = item.split("-");
                            if (pair.length >= 2) {
                                initSignals.put(pair[0].trim(), Integer.parseInt(pair[1].trim()));
                            }
                        }
                    }
                } catch (Exception e) {
                }
            } else if (line.startsWith("[")) {
                wireLines.add(line);
            }
        }
        
        Map<String, BaseGate> gMap = new HashMap<String, BaseGate>();
        Map<String, List<String>> linkGraph = new HashMap<String, List<String>>();
        
        for (int i = 0; i < wireLines.size(); i++) {
            String currentWire = wireLines.get(i);
            if (currentWire.length() < 3) {
                continue;
            }
            String core = currentWire.substring(1, currentWire.length() - 1).trim();
            String[] nodes = core.split("\\s+");
            if (nodes.length < 2) {
                continue;
            }
            
            String headNode = nodes[0].trim();
            for (int j = 1; j < nodes.length; j++) {
                String nextNode = nodes[j].trim();
                if (nextNode.equals("OUT") || nextNode.equals("")) {
                    continue;
                }
                
                if (!linkGraph.containsKey(headNode)) {
                    linkGraph.put(headNode, new ArrayList<String>());
                }
                linkGraph.get(headNode).add(nextNode);
                
                int pos = nextNode.lastIndexOf("-");
                if (pos != -1) {
                    String nameStr = nextNode.substring(0, pos);
                    if (!gMap.containsKey(nameStr)) {
                        BaseGate bg = GateHelper.create(nameStr);
                        if (bg != null) {
                            gMap.put(nameStr, bg);
                        }
                    }
                }
            }
            
            int srcPos = headNode.lastIndexOf("-");
            if (srcPos != -1) {
                String srcName = headNode.substring(0, srcPos);
                if (!gMap.containsKey(srcName)) {
                    BaseGate bg = GateHelper.create(srcName);
                    if (bg != null) {
                        gMap.put(srcName, bg);
                    }
                }
            }
        }
        
        Map<String, Integer> runValues = new HashMap<String, Integer>();
        Queue<String> runQueue = new LinkedList<String>();
        
        for (String key : initSignals.keySet()) {
            runValues.put(key, initSignals.get(key));
            runQueue.add(key);
        }
        
        while (runQueue.size() > 0) {
            String currentPin = runQueue.poll();
            Integer pinSignal = runValues.get(currentPin);
            if (pinSignal == null) {
                continue;
            }
            
            List<String> targets = linkGraph.get(currentPin);
            if (targets == null) {
                continue;
            }
            
            for (int i = 0; i < targets.size(); i++) {
                String targetStr = targets.get(i);
                int dash = targetStr.lastIndexOf("-");
                if (dash == -1) {
                    continue;
                }
                
                String gKey = targetStr.substring(0, dash);
                int slot = -1;
                try {
                    slot = Integer.parseInt(targetStr.substring(dash + 1)) - 1;
                } catch (Exception e) {
                    continue;
                }
                
                BaseGate currentGate = gMap.get(gKey);
                if (currentGate == null || slot < 0 || slot >= currentGate.inputs.length) {
                    continue;
                }
                
                currentGate.inputs[slot] = pinSignal;
                
                boolean readyFlag = true;
                for (int k = 0; k < currentGate.inputs.length; k++) {
                    if (currentGate.inputs[k] == -1) {
                        readyFlag = false;
                        break;
                    }
                }
                
                if (readyFlag) {
                    int outputVal = currentGate.compute();
                    String outPinName = gKey + "-0";
                    
                    Integer oldVal = runValues.get(outPinName);
                    if (oldVal == null || oldVal != outputVal) {
                        runValues.put(outPinName, outputVal);
                        runQueue.add(outPinName);
                    }
                }
            }
        }
        
        List<BaseGate> andList = new ArrayList<BaseGate>();
        List<BaseGate> orList = new ArrayList<BaseGate>();
        List<BaseGate> notList = new ArrayList<BaseGate>();
        List<BaseGate> xorList = new ArrayList<BaseGate>();
        List<BaseGate> xnorList = new ArrayList<BaseGate>();
        
        for (BaseGate bg : gMap.values()) {
            boolean valid = true;
            for (int k = 0; k < bg.inputs.length; k++) {
                if (bg.inputs[k] == -1) {
                    valid = false;
                    break;
                }
            }
            if (valid) {
                if (bg.kind.equals("A")) andList.add(bg);
                else if (bg.kind.equals("O")) orList.add(bg);
                else if (bg.kind.equals("N")) notList.add(bg);
                else if (bg.kind.equals("X")) xorList.add(bg);
                else if (bg.kind.equals("Y")) xnorList.add(bg);
            }
        }
        
        Comparator<BaseGate> idSorter = new Comparator<BaseGate>() {
            @Override
            public int compare(BaseGate b1, BaseGate b2) {
                if (b1.id < b2.id) return -1;
                if (b1.id > b2.id) return 1;
                return 0;
            }
        };
        
        andList.sort(idSorter);
        orList.sort(idSorter);
        notList.sort(idSorter);
        xorList.sort(idSorter);
        xnorList.sort(idSorter);
        
        for (int i = 0; i < andList.size(); i++) {
            System.out.println(andList.get(i).fullName + "-0:" + andList.get(i).getOutputValue());
        }
        for (int i = 0; i < orList.size(); i++) {
            System.out.println(orList.get(i).fullName + "-0:" + orList.get(i).getOutputValue());
        }
        for (int i = 0; i < notList.size(); i++) {
            System.out.println(notList.get(i).fullName + "-0:" + notList.get(i).getOutputValue());
        }
        for (int i = 0; i < xorList.size(); i++) {
            System.out.println(xorList.get(i).fullName + "-0:" + xorList.get(i).getOutputValue());
        }
        for (int i = 0; i < xnorList.size(); i++) {
            System.out.println(xnorList.get(i).fullName + "-0:" + xnorList.get(i).getOutputValue());
        }
    }

    interface CircuitElement {
        int getOutputValue();
    }

    abstract static class BaseGate implements CircuitElement {
        String fullName;
        String kind;
        int size;
        int id;
        int[] inputs;
        
        abstract int compute();
        
        @Override
        public int getOutputValue() {
            return this.compute();
        }
    }

    static class AndGate extends BaseGate {
        @Override
        int compute() {
            for (int i = 0; i < inputs.length; i++) {
                if (inputs[i] == 0) {
                    return 0;
                }
            }
            return 1;
        }
    }

    static class OrGate extends BaseGate {
        @Override
        int compute() {
            for (int i = 0; i < inputs.length; i++) {
                if (inputs[i] == 1) {
                    return 1;
                }
            }
            return 0;
        }
    }

    static class NotGate extends BaseGate {
        @Override
        int compute() {
            if (inputs[0] == 1) {
                return 0;
            } else {
                return 1;
            }
        }
    }

    static class XorGate extends BaseGate {
        @Override
        int compute() {
            if (inputs[0] != inputs[1]) {
                return 1;
            } else {
                return 0;
            }
        }
    }

    static class XnorGate extends BaseGate {
        @Override
        int compute() {
            if (inputs[0] == inputs[1]) {
                return 1;
            } else {
                return 0;
            }
        }
    }

    static class GateHelper {
        public static BaseGate create(String token) {
            try {
                if (token.startsWith("A(")) {
                    int idx1 = token.indexOf("(");
                    int idx2 = token.indexOf(")");
                    int pNum = Integer.parseInt(token.substring(idx1 + 1, idx2).trim());
                    int gId = Integer.parseInt(token.substring(idx2 + 1).trim());
                    AndGate g = new AndGate();
                    g.fullName = token;
                    g.kind = "A";
                    g.id = gId;
                    g.size = pNum;
                    g.inputs = new int[pNum];
                    Arrays.fill(g.inputs, -1);
                    return g;
                } else if (token.startsWith("O(")) {
                    int idx1 = token.indexOf("(");
                    int idx2 = token.indexOf(")");
                    int pNum = Integer.parseInt(token.substring(idx1 + 1, idx2).trim());
                    int gId = Integer.parseInt(token.substring(idx2 + 1).trim());
                    OrGate g = new OrGate();
                    g.fullName = token;
                    g.kind = "O";
                    g.id = gId;
                    g.size = pNum;
                    g.inputs = new int[pNum];
                    Arrays.fill(g.inputs, -1);
                    return g;
                } else if (token.startsWith("N")) {
                    int gId = Integer.parseInt(token.substring(1).trim());
                    NotGate g = new NotGate();
                    g.fullName = token;
                    g.kind = "N";
                    g.id = gId;
                    g.size = 1;
                    g.inputs = new int[1];
                    g.inputs[0] = -1;
                    return g;
                } else if (token.startsWith("X")) {
                    int gId = Integer.parseInt(token.substring(1).trim());
                    XorGate g = new XorGate();
                    g.fullName = token;
                    g.kind = "X";
                    g.id = gId;
                    g.size = 2;
                    g.inputs = new int[2];
                    g.inputs[0] = -1;
                    g.inputs[1] = -1;
                    return g;
                } else if (token.startsWith("Y")) {
                    int gId = Integer.parseInt(token.substring(1).trim());
                    XnorGate g = new XnorGate();
                    g.fullName = token;
                    g.kind = "Y";
                    g.id = gId;
                    g.size = 2;
                    g.inputs = new int[2];
                    g.inputs[0] = -1;
                    g.inputs[1] = -1;
                    return g;
                }
            } catch (Exception e) {
            }
            return null;
        }
    }
}

代码规模

V1的代码规模如下

可以看出本电路仿真项目共 9 个 Java 源码文件,项目总行数 544 行,有效业务代码 321 行,注释 142 行,注释覆盖率 26%,文档说明较为充足。代码拆分粒度清晰,各类逻辑职责划分明确,与门、或门等基础器件类仅十余行,体量轻巧。
项目代码体量集中在 CircuitSimulator 主仿真类,单文件有效代码 186 行,承担核心调度逻辑,是后续可进一步拆分优化的模块;整体空行占比 15%,代码排版整洁,属于标准小型课程项目规模。

类图

image

复杂度分析

image
整体项目平均圈复杂度、认知复杂度均为 4,整体代码逻辑负担偏高。逻辑门子类的计算方法复杂度很低,结构干净;复杂度压力全部集中在CircuitSimulator与GateHelper,其中构建电路、信号传播、解析输入、器件创建等方法分支判断多,圈复杂度最高达到 9,存在大量嵌套条件,可读性较差,后续可拆分多职责长方法来降低维护成本.

得分详情

image
V1比上次的迭代作业明显难了很多,但还能接受。

我的反思

本次作业的核心难点是信号传播。电路中的信号不是一次性计算完成的,而是从输入引脚开始,逐级传递到门电路的输入端口,门电路计算输出后再传递给下一级。

我采用了事件驱动的队列传播方式:

  1. 将所有初始输入信号放入队列

  2. 从队列中取出一个信号,找到它连接的所有目标引脚

  3. 将信号值写入目标门电路的对应输入端口

  4. 如果该门电路的所有输入都已就绪,计算其输出值

  5. 如果输出值发生了变化,将输出引脚放入队列,继续传播

这种方式的优点是不需要提前确定计算顺序,信号自然会按照连接关系逐级传播。

【具体问题】

1:INPUT: A-1 B-1 这一行,一开始我用 sc.next() 一个一个读,结果读到 [A 这种带方括号的就乱了。后来改成用 sc.nextLine() 整行读,再用 trim() 和 split() 处理,才稳定下来。
2:门的名字五花八门:A(2)1、O(4)2、N1、X5、Y4。每种名字的解析规则不一样。我一开始在 main 里面写了一大堆 if-else,后来专门弄了个 GateHelper 类来处理这件事,至少 main 干净了。

OOP作业集05(V2)

【题目描述】

与V1相比多了:
三态门:
image

三态门的作用类似于电路中的开关。包含一个输入引脚、一个输入控制引脚、一个输出引脚。当控制引脚为高电平时,三态门输入输出之间导通,输出电平等于输入电平;当控制引脚为低电平时,三态门输入输出之间呈现高阻态(类似开关断开),输出为无效状态。

译码器:
image

译码器的作用是讲输入的编码转换为一路有效信号。一个译码器包含两个或多个输入引脚(如图中的A2\A1\A0)、三个控制引脚(如图中的S3\S2\S1)、4个或多个输出引脚(如图中的Y7~Y0)。根据输入输出的数量有2-4线译码器、3-8线译码器等。

当控制引脚当S1 =1,S2 +S3 =0时,译码器正常工作,输出引脚只有一个输出信号0,其余输出为1;哪个引脚输出0由输入引脚的编码决定,例如:图中的3-8线译码器三个输入引脚信号的编码与输出引脚的编码对应,A2\A1\A0输入000时,Y0输出0,其余输出1;A2\A1\A0输入001时,Y1输出0,其余输出1;依次类推。

控制引脚不满足S1 =1,S2 +S3 =0时,译码器处于无效状态,所有输出为无效值。

数据选择器:

image

数据选择器的作用是从多路输入信号中选择一个,并将其信号直接送往唯一的输出端,选择哪一路输入信号由控制端决定。如图所示控制端有两个则输入端有4个,S1\S0是两个控制端,D3D0是输入端,S1\S0的4种信号组合00、01、10、11分别选择D3D0其中一路输入。如S1S0=00,则Y=D0;S1S0=01,则Y=D1;S1S0=10,则Y=D2;S1S0=11,则Y=D3

根据输入引脚数量的不同有二选一数据选择器(1个控制端)、四选一数据选择器(2个控制端)、八选一数据选择器(3个控制端)等

数据分配器:

image

数据分配器的作用与数据选择器正好相反,是将唯一的一路输入信号输出到多路输出引脚的其中之一,选择哪一路输出引脚输出由控制端决定。如图所示控制端有两个AB,输出端有4个W0\W1\W2\W3,D是输入端,AB的4种信号组合00、01、10、11分别选择W3~W0其中一路输出,其他三路输出为无效状态。如AB=00,则W0=D;AB=01,则W1=D;AB=10,则W2=D;AB=11,则W3=D。

根据输出引脚数量的不同有二路数据分配器(1个控制端)、四路数据分配器(2个控制端)、八路数据分配器(3个控制端)等。
用A、O、N、X、Y、S 、M、Z、F分别用作
与门、或门、非门、异或门、同或门、
三态门、译码器、数据选择器、数据分配器九种元件的元件标识符。
“电路中的每个与门、或门用“标识符(输入引脚数)+编号”作为其元件名。

例如:A(8)1表示一个8输入引脚的与门,O(4)2代表一个4输入引脚的或门。

电路中的每个非门、异或门、同或门用“标识符+编号”作为其元件名。

例如:X8表示一个异或门,Y4代表一个同或门,N1代表一个非门。

电路中的数据选择器、数据分配器用“标识符(控制引脚数)+编号”作为其元件名。

例如:Z(2)2代表一个四选一数据选择器,F(3)2代表一个8路数据分配器。

译码器用“标识符(输入引脚数)+编号”作为其元件名。

例如:M(3)1表示一个3-8线译码器。”

我提交的源码

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        Map<String, Integer> init = new HashMap<>();
        List<String> wires = new ArrayList<>();
        while (sc.hasNextLine()) {
            String line = sc.nextLine().trim();
            if (line.equals("end")) break;
            if (line.startsWith("INPUT:")) {
                String[] parts = line.substring(6).trim().split("\\s+");
                for (String p : parts) {
                    String[] kv = p.split("-");
                    init.put(kv[0], Integer.parseInt(kv[1]));
                }
            } else if (line.startsWith("[")) {
                wires.add(line);
            }
        }
        Map<String, Gate> gates = new HashMap<>();
        Map<String, List<String>> graph = new HashMap<>();
        for (String w : wires) {
            String inner = w.substring(1, w.length() - 1).trim();
            String[] nodes = inner.split("\\s+");
            String src = nodes[0];
            for (int i = 1; i < nodes.length; i++) {
                String dst = nodes[i];
                if (dst.equals("OUT")) continue;
                graph.computeIfAbsent(src, k -> new ArrayList<>()).add(dst);
                String gname = getGateName(dst);
                if (gname != null && !gates.containsKey(gname))
                    gates.put(gname, GateFactory.create(gname));
            }
            String gname = getGateName(src);
            if (gname != null && !gates.containsKey(gname))
                gates.put(gname, GateFactory.create(gname));
        }
        Map<String, Integer> values = new HashMap<>(init);
        Queue<String> q = new LinkedList<>(init.keySet());
        while (!q.isEmpty()) {
            String cur = q.poll();
            if (!values.containsKey(cur)) continue;
            int val = values.get(cur);
            List<String> targets = graph.get(cur);
            if (targets == null) continue;
            for (String t : targets) {
                int dash = t.lastIndexOf("-");
                if (dash == -1) continue;
                String gname = t.substring(0, dash);
                int pin = Integer.parseInt(t.substring(dash + 1));
                Gate g = gates.get(gname);
                if (g == null) continue;
                g.set(pin, val);
                if (g.ready()) {
                    g.compute();
                    for (Map.Entry<String, Integer> e : g.outputs().entrySet()) {
                        String outPin = e.getKey();
                        int outVal = e.getValue();
                        if (outVal != -1 && (!values.containsKey(outPin) || values.get(outPin) != outVal)) {
                            values.put(outPin, outVal);
                            q.add(outPin);
                        }
                    }
                }
            }
        }
        List<Gate> and = new ArrayList<>(), or = new ArrayList<>(), not = new ArrayList<>();
        List<Gate> xor = new ArrayList<>(), xnor = new ArrayList<>(), tri = new ArrayList<>();
        List<Gate> dec = new ArrayList<>(), mux = new ArrayList<>(), demux = new ArrayList<>();
        for (Gate g : gates.values()) {
            if (!g.valid()) continue;
            switch (g.type) {
                case "A": and.add(g); break;
                case "O": or.add(g); break;
                case "N": not.add(g); break;
                case "X": xor.add(g); break;
                case "Y": xnor.add(g); break;
                case "S": tri.add(g); break;
                case "M": dec.add(g); break;
                case "Z": mux.add(g); break;
                case "F": demux.add(g); break;
            }
        }
        Comparator<Gate> c = Comparator.comparingInt(g -> g.id);
        and.sort(c); or.sort(c); not.sort(c); xor.sort(c); xnor.sort(c);
        tri.sort(c); dec.sort(c); mux.sort(c); demux.sort(c);
        for (Gate g : and) System.out.println(g.name + "-0:" + g.getOutput());
        for (Gate g : or) System.out.println(g.name + "-0:" + g.getOutput());
        for (Gate g : not) System.out.println(g.name + "-0:" + g.getOutput());
        for (Gate g : xor) System.out.println(g.name + "-0:" + g.getOutput());
        for (Gate g : xnor) System.out.println(g.name + "-0:" + g.getOutput());
        for (Gate g : tri) System.out.println(g.name + "-2:" + g.getOutput());
        for (Gate g : dec) {
            String s = g.getDecoder();
            if (s != null) System.out.println(s);
        }
        for (Gate g : mux) {
            if (g.valid()) System.out.println(g.name + "-" + g.getOutPin() + ":" + g.getOutput());
        }
        for (Gate g : demux) {
            String s = g.getDemux();
            if (s != null) System.out.println(s);
        }
    }

    static String getGateName(String s) {
        int d = s.lastIndexOf("-");
        return d == -1 ? null : s.substring(0, d);
    }
}

abstract class Gate {
    String name; String type; int id;
    Map<Integer, Integer> in = new HashMap<>();
    Map<String, Integer> out = new HashMap<>();
    abstract void compute();
    abstract boolean ready();
    abstract boolean valid();
    void set(int pin, int v) { in.put(pin, v); }
    Map<String, Integer> outputs() { return out; }
    int getOutput() { return out.isEmpty() ? -1 : out.values().iterator().next(); }
    int getOutPin() { return Integer.parseInt(out.keySet().iterator().next().split("-")[1]); }
    String getDecoder() { return null; }
    String getDemux() { return null; }
}

class AndGate extends Gate {
    int n;
    AndGate(String s) {
        name = s; type = "A";
        int l = s.indexOf('('), r = s.indexOf(')');
        n = Integer.parseInt(s.substring(l+1, r));
        id = Integer.parseInt(s.substring(r+1));
    }
    void compute() {
        int r = 1;
        for (int i=1; i<=n; i++) if (in.getOrDefault(i, -1)==0) { r=0; break; }
        out.clear(); out.put(name+"-0", r);
    }
    boolean ready() { for (int i=1; i<=n; i++) if (!in.containsKey(i)) return false; return true; }
    boolean valid() { return ready(); }
}

class OrGate extends Gate {
    int n;
    OrGate(String s) {
        name = s; type = "O";
        int l = s.indexOf('('), r = s.indexOf(')');
        n = Integer.parseInt(s.substring(l+1, r));
        id = Integer.parseInt(s.substring(r+1));
    }
    void compute() {
        int r = 0;
        for (int i=1; i<=n; i++) if (in.getOrDefault(i, -1)==1) { r=1; break; }
        out.clear(); out.put(name+"-0", r);
    }
    boolean ready() { for (int i=1; i<=n; i++) if (!in.containsKey(i)) return false; return true; }
    boolean valid() { return ready(); }
}

class NotGate extends Gate {
    NotGate(String s) { name = s; type = "N"; id = Integer.parseInt(s.substring(1)); }
    void compute() { out.clear(); out.put(name+"-0", in.get(1)==1?0:1); }
    boolean ready() { return in.containsKey(1); }
    boolean valid() { return ready(); }
}

class XorGate extends Gate {
    XorGate(String s) { name = s; type = "X"; id = Integer.parseInt(s.substring(1)); }
    void compute() { out.clear(); out.put(name+"-0", in.get(1)!=in.get(2)?1:0); }
    boolean ready() { return in.containsKey(1) && in.containsKey(2); }
    boolean valid() { return ready(); }
}

class XnorGate extends Gate {
    XnorGate(String s) { name = s; type = "Y"; id = Integer.parseInt(s.substring(1)); }
    void compute() { out.clear(); out.put(name+"-0", in.get(1)==in.get(2)?1:0); }
    boolean ready() { return in.containsKey(1) && in.containsKey(2); }
    boolean valid() { return ready(); }
}

class TriGate extends Gate {
    TriGate(String s) { name = s; type = "S"; id = Integer.parseInt(s.substring(1)); }
    void compute() {
        out.clear();
        if (in.getOrDefault(0,0)==1) out.put(name+"-2", in.get(1));
    }
    boolean ready() { return in.containsKey(0) && in.containsKey(1); }
    boolean valid() { return ready() && in.getOrDefault(0,0)==1 && out.containsKey(name+"-2"); }
}

class DecoderGate extends Gate {
    int bits;
    DecoderGate(String s) {
        name = s; type = "M";
        int l = s.indexOf('('), r = s.indexOf(')');
        bits = Integer.parseInt(s.substring(l+1, r));
        id = Integer.parseInt(s.substring(r+1));
    }
    void compute() {
        out.clear();
        if (!valid()) return;
        int code = 0;
        for (int i=0; i<bits; i++) if (in.getOrDefault(3+i,0)==1) code |= (1<<i);
        int base = 3+bits;
        for (int i=0; i<(1<<bits); i++) out.put(name+"-"+(base+i), i==code?0:1);
    }
    boolean ready() { for (int i=0; i<3+bits; i++) if (!in.containsKey(i)) return false; return true; }
    boolean valid() { return ready() && in.getOrDefault(0,0)==1 && in.getOrDefault(1,0)==0 && in.getOrDefault(2,0)==0; }
    String getDecoder() {
        if (!valid()) return null;
        int code = 0;
        for (int i=0; i<bits; i++) if (in.getOrDefault(3+i,0)==1) code |= (1<<i);
        return name+":"+code;
    }
}

class MuxGate extends Gate {
    int selBits, dataPins;
    MuxGate(String s) {
        name = s; type = "Z";
        int l = s.indexOf('('), r = s.indexOf(')');
        selBits = Integer.parseInt(s.substring(l+1, r));
        dataPins = 1 << selBits;
        id = Integer.parseInt(s.substring(r+1));
    }
    void compute() {
        out.clear();
        int sel = 0;
        for (int i = 0; i < selBits; i++) if (in.getOrDefault(i,0)==1) sel |= (1 << i);
        int outVal = in.getOrDefault(selBits + sel, -1);
        if (outVal != -1) {
            out.put(name + "-" + (selBits + dataPins), outVal);
        }
    }
    boolean ready() { for (int i=0; i<selBits+dataPins; i++) if (!in.containsKey(i)) return false; return true; }
    boolean valid() { return ready() && out.containsKey(name + "-" + (selBits + dataPins)); }
    int getOutPin() { return selBits + dataPins; }
}

class DemuxGate extends Gate {
    int selBits, outPins;
    DemuxGate(String s) {
        name = s; type = "F";
        int l = s.indexOf('('), r = s.indexOf(')');
        selBits = Integer.parseInt(s.substring(l+1, r));
        outPins = 1 << selBits;
        id = Integer.parseInt(s.substring(r+1));
    }
    void compute() {
        out.clear();
        int sel = 0;
        for (int i = 0; i < selBits; i++) if (in.getOrDefault(i,0)==1) sel |= (1 << i);
        int data = in.getOrDefault(selBits, -1);
        int base = selBits + 1;
        for (int i = 0; i < outPins; i++) {
            out.put(name + "-" + (base + i), i == sel ? data : -1);
        }
    }
    boolean ready() { for (int i=0; i<=selBits; i++) if (!in.containsKey(i)) return false; return true; }
    boolean valid() { return ready(); }
    String getDemux() {
        if (!ready()) return null;
        StringBuilder sb = new StringBuilder(name + ":");
        int base = selBits + 1;
        for (int i = 0; i < outPins; i++) {
            Integer v = out.get(name + "-" + (base + i));
            sb.append(v == null || v == -1 ? "-" : v);
        }
        return sb.toString();
    }
}

class GateFactory {
    static Gate create(String s) {
        if (s.startsWith("A(")) return new AndGate(s);
        if (s.startsWith("O(")) return new OrGate(s);
        if (s.startsWith("N")) return new NotGate(s);
        if (s.startsWith("X")) return new XorGate(s);
        if (s.startsWith("Y")) return new XnorGate(s);
        if (s.startsWith("S")) return new TriGate(s);
        if (s.startsWith("M(")) return new DecoderGate(s);
        if (s.startsWith("Z(")) return new MuxGate(s);
        if (s.startsWith("F(")) return new DemuxGate(s);
        return null;
    }
}

代码规模

image
本版本共 12 个 Java 源文件,总代码 798 行,有效业务代码 530 行,注释 148 行,注释覆盖率 19%,代码留白占比 15%,排版规整。各类门器件子类体量小巧,逻辑集中在 CircuitSimulator 主仿真类(有效代码 175 行);GateFactory 注释占比过半,文档完善,整体属于小型数字电路仿真项目。

类图

image

复杂度分析

image
image
整体水平:项目平均圈复杂度仅 2.55、平均认知复杂度 1.94,整体代码分支嵌套少,基础门器件类的方法复杂度普遍低于 4,结构清晰易维护。
问题集中点:两处高复杂度方法存在明显优化空间,CircuitSimulator.printSimResult圈复杂度 12、GateFactory.create圈复杂度 12,二者存在大量 if 分支判断,阅读与单元测试成本高;CircuitSimulator.propagateSignal认知复杂度 14,嵌套逻辑深,理解难度偏大。
分层表现:基础逻辑门(与 / 或 / 异或 / 多路选择器等)的计算、状态校验方法复杂度极低,职责单一;全部复杂度压力集中在电路仿真主类与器件工厂方法,后续可通过拆分分支逻辑、抽取独立子方法完成优化。

得分详情

image
失分点:
image
(我说出题老师真得公开测试点,就算因为资源匮乏公开不了也不要拿“以后客户会给你测试点吗”来压人啊QAQ)

我的反思

V2 版本我新增了四种元件,其中译码器是最复杂的。我在实现时犯了一个低级错误:把译码器的输出电平写反了。题目要求选中通道输出 0,其余输出 1,我却写成了选中输出 1,其余输出 0。这个错误导致所有用到译码器的测试点全部失败,包括基础元件测试和复杂电路测试。

这个教训告诉我,对于复杂元件的实现,不能只凭记忆写代码,应该在写完后对照题目要求逐条验证。如果我当时画一张表格,把每个引脚的功能和对应的输出值列出来,就不容易写反。

另外,数据选择器和数据分配器的引脚编号计算也需要格外小心,这类数字逻辑题的得分往往取决于这些细节是否准确。

OOP作业集06(V3)

【题目描述】

在V1基础上新增:
子电路信息(本题在数字电路模拟程序-1基础上新增的内容)

可以将一部分电路设定为一个子电路,子电路可以在主电路中被引用。子电路的输入按顺序包括以下几部分:

子电路起始信息:C+子电路编号+:
子电路输入信息:

INPUT:输入1+英文空格+输入2+英文空格+....+英文空格+输入n,
子电路输出信息:

OUT:输出1+英文空格+输出2+英文空格+....+英文空格+输出n,

子电路连接信息:(格式与主电路输入信息相同),子电路的元件编号与主电路中的编号可能相同。

子电路结束标志:endc

异常输入信息(本题在数字电路模拟程序-1基础上新增的内容)

当出现各类异常输入情况时,电路应输出相应提示,异常情况包括:

1.一个连接信息中包含两个或多个输入,输出:ERROR:“连接信息”+“英文空格”+include more than one input

例如:

INPUT: A-1 B-1

[A B A2-1]

应输出:

ERROR: [A B A2-1] include more than one input

2.一个连接信息中没有输入信息,输出:ERROR:“连接信息”+“英文空格”+include none input

例如:

INPUT: A-1 B-1

[C1-1 C2-1]

应输出:

ERROR: [C1-1 C2-1] include none input
注意:C1-1 C2-1引脚是元件的输入引脚,但作为连接信息,元件的输入引脚属于连接系统的输出,所以是none input

3.一个连接信息中没有输出,输出:ERROR:“连接信息”+“英文空格”+include none output

例如:

INPUT: A-1 B-1

[A]

应输出:ERROR: [A] include none output

4.一个连接信息中输入输出写反,输出:ERROR:“连接信息”+“英文空格”+input and output sequence error

例如:

INPUT: A-1 B-1

[A2-1 A]

应输出:ERROR: [A2-1 A] input and output sequence error

5.一个输入引脚接受中来自多个不同输出的信号,输出:ERROR:“输入引脚”+“英文空格”+input signal conflict

例如:

INPUT: A-1 B-1

[A A2-1]

[B A2-1]

应输出:ERROR: A2-1 input signal conflict

注:如果一条输入出现了多种异常,按以上异常先后顺序为优先级,顺序靠前者优先级越高,最后输出优先级最高的异常输出。

当电路输入信息中多条输入都包含异常,只处理排在最前面的异常信息。

我提交的源码

import java.util.*;

abstract class Gate {
    String name;
    String type;
    int id;
    Map<Integer, Integer> inputPins = new HashMap<>();
    abstract int getInputCount();
    abstract boolean ready();
    abstract int compute();
    void setInput(int pin, int value) {
        inputPins.put(pin, value);
    }
}

class AndGate extends Gate {
    int inCount;
    AndGate(String s) {
        name = s; type = "A";
        int l = s.indexOf('('), r = s.indexOf(')');
        inCount = Integer.parseInt(s.substring(l + 1, r));
        id = Integer.parseInt(s.substring(r + 1));
    }
    int getInputCount() { return inCount; }
    boolean ready() {
        for (int i = 1; i <= inCount; i++)
            if (!inputPins.containsKey(i)) return false;
        return true;
    }
    int compute() {
        for (int i = 1; i <= inCount; i++)
            if (inputPins.get(i) == 0) return 0;
        return 1;
    }
}

class OrGate extends Gate {
    int inCount;
    OrGate(String s) {
        name = s; type = "O";
        int l = s.indexOf('('), r = s.indexOf(')');
        inCount = Integer.parseInt(s.substring(l + 1, r));
        id = Integer.parseInt(s.substring(r + 1));
    }
    int getInputCount() { return inCount; }
    boolean ready() {
        for (int i = 1; i <= inCount; i++)
            if (!inputPins.containsKey(i)) return false;
        return true;
    }
    int compute() {
        for (int i = 1; i <= inCount; i++)
            if (inputPins.get(i) == 1) return 1;
        return 0;
    }
}

class NotGate extends Gate {
    NotGate(String s) { name = s; type = "N"; id = Integer.parseInt(s.substring(1)); }
    int getInputCount() { return 1; }
    boolean ready() { return inputPins.containsKey(1); }
    int compute() { return inputPins.get(1) == 0 ? 1 : 0; }
}

class XorGate extends Gate {
    XorGate(String s) { name = s; type = "X"; id = Integer.parseInt(s.substring(1)); }
    int getInputCount() { return 2; }
    boolean ready() { return inputPins.containsKey(1) && inputPins.containsKey(2); }
    int compute() { return inputPins.get(1) != inputPins.get(2) ? 1 : 0; }
}

class XnorGate extends Gate {
    XnorGate(String s) { name = s; type = "Y"; id = Integer.parseInt(s.substring(1)); }
    int getInputCount() { return 2; }
    boolean ready() { return inputPins.containsKey(1) && inputPins.containsKey(2); }
    int compute() { return inputPins.get(1).equals(inputPins.get(2)) ? 1 : 0; }
}

class SubCircuitDef {
    int id;
    List<String> inputs = new ArrayList<>();
    List<String> outputs = new ArrayList<>();
    List<String[]> connections = new ArrayList<>();
}

public class Main {
    static Map<Integer, SubCircuitDef> subDefs = new LinkedHashMap<>();
    static Map<String, Integer> externalInputs = new LinkedHashMap<>();
    static Map<String, List<String>> allConns = new LinkedHashMap<>();
    static Map<String, Gate> allGates = new LinkedHashMap<>();
    static String errorMsg = null;
    static Set<String> assignedInputPins = new HashSet<>();

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        List<String> lines = new ArrayList<>();
        while (sc.hasNextLine()) {
            String line = sc.nextLine().stripTrailing();
            if (line.trim().isEmpty()) continue;
            lines.add(line.trim());
        }
        sc.close();

        parseAll(lines);
        if (errorMsg != null) {
            System.out.print(errorMsg);
            return;
        }

        expandAllSubCircuits();

        simulate();

        List<Gate> sortedGates = new ArrayList<>(allGates.values());
        // FIX: sort by (typeOrder, id, then numeric subcircuit-prefix id, then full name)
        // instead of plain string comparison, which breaks for 2+ digit subcircuit ids (e.g. C10 vs C2)
        sortedGates.sort((a, b) -> {
            int ta = typeOrder(a.type), tb = typeOrder(b.type);
            if (ta != tb) return ta - tb;
            if (a.id != b.id) return Integer.compare(a.id, b.id);
            int pa = subPrefixId(a.name), pb = subPrefixId(b.name);
            if (pa != pb) return Integer.compare(pa, pb);
            return a.name.compareTo(b.name);
        });

        StringBuilder sb = new StringBuilder();
        for (Gate g : sortedGates) {
            if (!g.ready()) continue;
            int val = g.compute();
            sb.append(g.name).append("-0:").append(val).append("\n");
        }
        System.out.print(sb.toString());
    }

    // Returns the numeric subcircuit id if name starts with "C<digits>-", else -1 (main circuit gates sort first)
    static int subPrefixId(String name) {
        if (name.matches("C\\d+-.*")) {
            String numPart = name.substring(1, name.indexOf('-'));
            return Integer.parseInt(numPart);
        }
        return -1;
    }

    static int typeOrder(String t) {
        switch (t) {
            case "A": return 0;
            case "O": return 1;
            case "N": return 2;
            case "X": return 3;
            case "Y": return 4;
            default: return 5;
        }
    }

    static void parseAll(List<String> lines) {
        int idx = 0;
        while (idx < lines.size()) {
            String line = lines.get(idx);
            if (line.matches("C\\d+:")) {
                int subId = Integer.parseInt(line.substring(1, line.length() - 1));
                SubCircuitDef def = new SubCircuitDef();
                def.id = subId;
                idx++;
                while (idx < lines.size()) {
                    String l = lines.get(idx++);
                    if (l.equals("endc")) break;
                    // FIX: also accept fullwidth colon ":" since it appears in the official problem text's own example
                    if (l.startsWith("INPUT:") || l.startsWith("INPUT:")) {
                        String rest = l.substring(l.indexOf(':') >= 0 ? l.indexOf(':') + 1
                                : l.indexOf(':') + 1).trim();
                        for (String p : rest.split("\\s+")) if (!p.isEmpty()) def.inputs.add(p);
                    } else if (l.startsWith("OUT:") || l.startsWith("OUT:")) {
                        String rest = l.substring(l.indexOf(':') >= 0 ? l.indexOf(':') + 1
                                : l.indexOf(':') + 1).trim();
                        for (String p : rest.split("\\s+")) if (!p.isEmpty()) def.outputs.add(p);
                    } else if (l.startsWith("[") && l.endsWith("]")) {
                        String inner = l.substring(1, l.length() - 1).trim();
                        String[] toks = inner.split("\\s+");
                        // FIX: also validate connections INSIDE subcircuit definitions.
                        // Previously only main-circuit connection lines were checked for errors,
                        // so malformed connections inside a subcircuit body were silently ignored.
                        if (errorMsg == null) {
                            String err = checkErrorInSub(l, toks, def);
                            if (err != null) { errorMsg = err; return; }
                        }
                        def.connections.add(toks);
                    }
                }
                subDefs.put(subId, def);
            } else {
                break;
            }
        }

        while (idx < lines.size()) {
            String line = lines.get(idx++);
            if (line.equals("end")) break;
            if (line.startsWith("INPUT:")) {
                String rest = line.substring(6).trim();
                for (String p : rest.split("\\s+")) {
                    int dash = p.lastIndexOf('-');
                    if (dash > 0) {
                        String key = p.substring(0, dash);
                        int val = Integer.parseInt(p.substring(dash + 1));
                        externalInputs.put(key, val);
                    }
                }
            } else if (line.startsWith("[") && line.endsWith("]")) {
                String inner = line.substring(1, line.length() - 1).trim();
                String[] toks = inner.split("\\s+");
                if (errorMsg == null) {
                    String err = checkError(line, toks);
                    if (err != null) {
                        errorMsg = err;
                        return;
                    }
                }
                String src = toks[0];
                List<String> dests = new ArrayList<>();
                for (int i = 1; i < toks.length; i++) dests.add(toks[i]);
                for (String d : dests) {
                    if (assignedInputPins.contains(d)) {
                        errorMsg = "ERROR: " + d + " input signal conflict";
                        return;
                    }
                    assignedInputPins.add(d);
                }
                processMainConnection(src, dests);
            }
        }
    }

    static String checkError(String fullLine, String[] toks) {
        int outputSideCount = 0;
        boolean firstIsOutputSide = false;
        for (int i = 0; i < toks.length; i++) {
            boolean isOut = isOutputSide(toks[i]);
            if (isOut) outputSideCount++;
            if (i == 0) firstIsOutputSide = isOut;
        }
        if (outputSideCount > 1) return "ERROR: " + fullLine + " include more than one input";
        if (outputSideCount == 0) return "ERROR: " + fullLine + " include none input";
        if (!firstIsOutputSide) return "ERROR: " + fullLine + " input and output sequence error";
        if (toks.length == 1) return "ERROR: " + fullLine + " include none output";
        return null;
    }

    static boolean isOutputSide(String pin) {
        if (externalInputs.containsKey(pin)) return true;
        if (pin.matches(".+-0$")) return true;
        if (pin.matches("C\\d+-.+")) {
            String[] parts = pin.split("-", 2);
            int subId = Integer.parseInt(parts[0].substring(1));
            SubCircuitDef def = subDefs.get(subId);
            if (def != null && def.outputs.contains(parts[1])) return true;
        }
        return false;
    }

    // FIX (new): error checking for connection lines INSIDE a subcircuit definition.
    // "Output side" here means: the subcircuit's own INPUT ports (they source the signal
    // from inside the subcircuit's perspective) or a gate output pin ("-0").
    static String checkErrorInSub(String fullLine, String[] toks, SubCircuitDef def) {
        int outputSideCount = 0;
        boolean firstIsOutputSide = false;
        for (int i = 0; i < toks.length; i++) {
            boolean isOut = isOutputSideInSub(toks[i], def);
            if (isOut) outputSideCount++;
            if (i == 0) firstIsOutputSide = isOut;
        }
        if (outputSideCount > 1) return "ERROR: " + fullLine + " include more than one input";
        if (outputSideCount == 0) return "ERROR: " + fullLine + " include none input";
        if (!firstIsOutputSide) return "ERROR: " + fullLine + " input and output sequence error";
        if (toks.length == 1) return "ERROR: " + fullLine + " include none output";
        return null;
    }

    static boolean isOutputSideInSub(String pin, SubCircuitDef def) {
        if (def.inputs.contains(pin)) return true;
        if (pin.matches(".+-0$")) return true;
        return false;
    }

    static void processMainConnection(String src, List<String> dests) {
        List<String> expandedDests = new ArrayList<>();
        for (String d : dests) {
            if (isSubCircuitInputPort(d)) {
                expandSubCircuitInput(d, expandedDests);
            } else {
                expandedDests.add(d);
                registerGate(d);
            }
        }
        if (isSubCircuitOutputPort(src)) {
            String resolved = resolveSubCircuitOutput(src);
            if (resolved != null) addConn(resolved, expandedDests);
        } else {
            registerGate(src);
            addConn(src, expandedDests);
        }
    }

    static boolean isSubCircuitInputPort(String pin) {
        if (!pin.matches("C\\d+-.+")) return false;
        String[] parts = pin.split("-", 2);
        int subId = Integer.parseInt(parts[0].substring(1));
        SubCircuitDef def = subDefs.get(subId);
        return def != null && def.inputs.contains(parts[1]);
    }

    static boolean isSubCircuitOutputPort(String pin) {
        if (!pin.matches("C\\d+-.+")) return false;
        String[] parts = pin.split("-", 2);
        int subId = Integer.parseInt(parts[0].substring(1));
        SubCircuitDef def = subDefs.get(subId);
        return def != null && def.outputs.contains(parts[1]);
    }

    static void expandSubCircuitInput(String port, List<String> result) {
        String[] parts = port.split("-", 2);
        String prefix = parts[0];
        String portName = parts[1];
        int subId = Integer.parseInt(prefix.substring(1));
        SubCircuitDef def = subDefs.get(subId);
        if (def == null) return;
        for (String[] conn : def.connections) {
            if (conn.length > 0 && conn[0].equals(portName)) {
                for (int i = 1; i < conn.length; i++) {
                    String d = conn[i];
                    String flat = prefix + "-" + d;
                    result.add(flat);
                    registerGate(flat);
                }
            }
        }
    }

    static String resolveSubCircuitOutput(String port) {
        String[] parts = port.split("-", 2);
        String prefix = parts[0];
        String portName = parts[1];
        int subId = Integer.parseInt(prefix.substring(1));
        SubCircuitDef def = subDefs.get(subId);
        if (def == null) return null;
        for (String[] conn : def.connections) {
            for (int i = 1; i < conn.length; i++) {
                if (conn[i].equals(portName)) {
                    String flatSrc = prefix + "-" + conn[0];
                    registerGate(flatSrc);
                    return flatSrc;
                }
            }
        }
        return null;
    }

    static void registerGate(String pin) {
        String gateName = extractGateName(pin);
        if (gateName == null || externalInputs.containsKey(gateName) || allGates.containsKey(gateName)) return;
        Gate g = createGate(gateName);
        if (g != null) allGates.put(gateName, g);
    }

    static String extractGateName(String pin) {
        int dash = pin.lastIndexOf('-');
        if (dash < 0) return null;
        String suffix = pin.substring(dash + 1);
        if (!suffix.matches("\\d+")) return null;
        String gname = pin.substring(0, dash);
        if (externalInputs.containsKey(gname)) return null;
        return gname;
    }

    static Gate createGate(String name) {
        String local = name;
        if (name.matches("C\\d+-.+")) {
            local = name.replaceFirst("C\\d+-", "");
        }
        Gate g = null;
        if (local.startsWith("A(")) g = new AndGate(local);
        else if (local.startsWith("O(")) g = new OrGate(local);
        else if (local.startsWith("N")) g = new NotGate(local);
        else if (local.startsWith("X")) g = new XorGate(local);
        else if (local.startsWith("Y")) g = new XnorGate(local);
        if (g != null) g.name = name;
        return g;
    }

    static void addConn(String src, List<String> dests) {
        List<String> list = allConns.computeIfAbsent(src, k -> new ArrayList<>());
        for (String d : dests) {
            if (!list.contains(d)) list.add(d);
        }
    }

    static void expandAllSubCircuits() {
        Set<String> prefixes = new HashSet<>();
        for (String k : allConns.keySet()) if (k.matches("C\\d+-.+")) prefixes.add(k.split("-")[0]);
        for (List<String> vs : allConns.values()) for (String v : vs) if (v.matches("C\\d+-.+")) prefixes.add(v.split("-")[0]);

        for (String prefix : prefixes) {
            int subId = Integer.parseInt(prefix.substring(1));
            SubCircuitDef def = subDefs.get(subId);
            if (def == null) continue;
            for (String[] conn : def.connections) {
                String src = conn[0];
                if (def.inputs.contains(src)) continue;
                String flatSrc = prefix + "-" + src;
                registerGate(flatSrc);
                List<String> dests = new ArrayList<>();
                for (int i = 1; i < conn.length; i++) {
                    String d = conn[i];
                    if (def.outputs.contains(d)) continue;
                    String flatD = prefix + "-" + d;
                    registerGate(flatD);
                    dests.add(flatD);
                }
                if (!dests.isEmpty()) addConn(flatSrc, dests);
            }
        }
    }

    static void simulate() {
        Map<String, Integer> values = new LinkedHashMap<>();
        for (Map.Entry<String, Integer> e : externalInputs.entrySet()) {
            values.put(e.getKey(), e.getValue());
        }

        Queue<String> queue = new LinkedList<>(values.keySet());
        Set<String> inQueue = new HashSet<>(values.keySet());

        while (!queue.isEmpty()) {
            String src = queue.poll();
            inQueue.remove(src);
            Integer val = values.get(src);
            if (val == null) continue;
            List<String> dests = allConns.get(src);
            if (dests == null) continue;
            for (String dest : dests) {
                Integer old = values.put(dest, val);
                if (old != null && old.equals(val)) continue;

                String gname = extractGateName(dest);
                if (gname != null) {
                    Gate g = allGates.get(gname);
                    if (g != null) {
                        try {
                            int pin = Integer.parseInt(dest.substring(dest.lastIndexOf('-') + 1));
                            g.setInput(pin, val);
                            if (g.ready()) {
                                int out = g.compute();
                                String outKey = g.name + "-0";
                                Integer oldOut = values.put(outKey, out);
                                if (oldOut == null || oldOut != out) {
                                    if (!inQueue.contains(outKey)) {
                                        queue.add(outKey);
                                        inQueue.add(outKey);
                                    }
                                }
                            }
                        } catch (Exception ignored) {}
                    }
                } else {
                    if (!inQueue.contains(dest)) {
                        queue.add(dest);
                        inQueue.add(dest);
                    }
                }
            }
        }
    }
}

代码规模

image
共 13 个 Java 源文件,总行数 988 行,有效业务代码 618 行,注释 222 行,整体注释覆盖率 22%,代码空行占比 15%,排版规范整洁。项目体量集中在电路解析模块CircuitParser.java(有效代码 191 行),承担核心文本解析逻辑;基础逻辑门类行数普遍 30 行左右,拆分粒度小、职责单一;校验、信号传播、子电路拓展等辅助类体量适中,仅数据定义类SubCircuitDef.java注释占比超五成,文档说明充足,整体属于中等规模课程仿真项目。

类图

image

复杂度分析

image
项目平均圈复杂度 3.39、平均认知复杂度 4.05,整体逻辑负担偏高,基础逻辑门类方法复杂度普遍偏低,问题高度集中在核心业务方法:
最严重痛点:CircuitParser.parse认知复杂度 67、圈复杂度 24,SignalPropagator.propagate认知复杂度 23、圈复杂度 10,嵌套与分支极多,可读性、单元测试难度极大;
次要高复杂度方法:GateFactory 创建器件、错误校验、子电路展开等方法圈复杂度达到 8~10,存在大量条件判断;
分层表现:基础门(与 / 或 / 非 / 异或)的计算、状态查询方法复杂度大多在 1~3,职责单一干净;全部复杂逻辑集中在解析、信号传播、子电路扩展三大核心模块,后续可抽取独立子方法拆分长流程、简化分支判断完成优化。

得分详情

image
失分点:
image
image

我的反思

V3 最大的教训是:子电路不是一个简单的“把内部连线复制过来”就能解决的问题。它本质上是一个新的抽象层次,需要把子电路当作一个独立的计算单元来处理,而不是把它拆散后融入主电路。

我选择“展开”的方式,表面上简化了问题,但实际上引入了更多复杂的问题:信号源的映射、端口名的解析、输出格式的生成等。如果采用组合模式,把子电路封装成一个独立的 Gate 类型,每个子电路有自己的 compute() 和 ready() 方法,可能反而更清晰。

这也提醒我:在遇到复杂问题时,不要急于找一个“看起来简单”的解决方案,而应该先理解问题的本质,再选择合适的设计模式。V4 还要增加反馈电路和时序元件,如果继续用这种“展开”的方式,代码会越来越难以维护。

总结

三轮迭代的核心教训是:功能正确只是及格线,选择正确的设计策略才是决定代码能否持续扩展的关键。V1我靠堆代码勉强跑通,但缺乏分层设计;V2新增组合逻辑元件时,译码器输出电平取反这一处细节错误导致基础测试挂掉;到了V3,子电路的引入让我的“展开式”设计策略彻底暴露问题——我试图把子电路内部连线复制到全局连接图来简化逻辑,但这种做法反而把信号映射、端口解析、输出格式生成全部搅在一起,最终子电路相关测试点全军覆没。回头来看,如果V3采用组合模式,把子电路封装成一个特殊的Gate类型,让它有自己的compute()ready()方法,而不是强行展开,代码会更清晰。接下来的V4要处理反馈电路和时序元件,我需要在动手之前先画清楚数据流图,并且针对每个新功能写独立的本地测试用例,而不是依赖PTA的测试点告诉我哪里错了。

posted @ 2026-06-22 17:03  负重静好  阅读(6)  评论(0)    收藏  举报