BUAA OO 2026 第一单元博客作业

张振羽-24373282 2026-04-01 22:54:23

第一单元博客

基于度量分析程序结构

ev(G)是基本复杂度,是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。因此,基本复杂度高意味着非结构化程度高,难以模块化和维护。实际上,消除了一个错误有时会引起其他的错误。

Iv(G)是模块设计复杂度,是用来衡量模块判定结构,即模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。模块设计复杂度是从模块流程图中移去那些不包含调用子模块的判定和循环结构后得出的圈复杂度,因此模块设计复杂度不能大于圈复杂度,通常是远小于圈复杂度。

v(G)是圈复杂度,用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,经验表明,程序的可能错误和高的圈复杂度有着很大关系。

MethodCogCev(G)iv(G)v(G)
BinOpNode.BinOpNode(Node, String, Node)0111
BinOpNode.evaluate()1$\color{red}{\text{5}}$55
BinOpNode.getLeft()0111
BinOpNode.getOp()0111
BinOpNode.getRight()0111
FuncNode.FuncNode(Node, Poly)0111
FuncNode.evaluate()0111
FunctionManager.containsFunction(String)0111
FunctionManager.containsRecursiveFunction(String)0111
FunctionManager.getFunction(String)0111
FunctionManager.getRecursiveFunction(String, int)0111
FunctionManager.putFunction(String, Poly)0111
FunctionManager.putRecursiveFunction(String, RecursiveFunc)0111
FunctionParser.FunctionDefinition.FunctionDefinition(String, Poly)0111
FunctionParser.FunctionDefinition.getName()0111
FunctionParser.FunctionDefinition.getValue()0111
FunctionParser.FunctionParser(FunctionManager)0111
FunctionParser.ParsedHead.ParsedHead(String, String)0111
FunctionParser.RecursiveFunctionDefinition.RecursiveFunctionDefinition(String, RecursiveFunc)0111
FunctionParser.RecursiveFunctionDefinition.getName()0111
FunctionParser.RecursiveFunctionDefinition.getValue()0111
FunctionParser.materializeRecursiveIndices(String, int)3133
FunctionParser.parseFunction(String)0111
FunctionParser.parseHead(String)0111
FunctionParser.parseRecursiveFunction(String[])12$\color{red}{\text{4}}$37
Lexer.Lexer(String)0111
Lexer.getNumber()2133
Lexer.getString()2133
Lexer.next()7347
Lexer.peek()0111
Lexer.peekNext()0111
Lexer.readTokenAt(int)11$\color{red}{\text{4}}$610
Main.main(String[])2133
NumberNode.NumberNode(Poly)0111
NumberNode.evaluate()0111
NumberNode.getValue()0111
Parser.Parser(Lexer, FunctionManager)0111
Parser.expect(String)1222
Parser.parseAtom()3133
Parser.parseExpression()2133
Parser.parseFactor()8$\color{red}{\text{8}}$$\color{red}{\text{9}}$9
Parser.parseFunctionCall(String)0111
Parser.parseParenthesizedExpression()0111
Parser.parsePower()1122
Parser.parseRecursiveFunctionCall(String)0111
Parser.parseTerOp()0111
Parser.parseTerm()1122
Parser.parseUnary()3233
Parser.parseUnaryFunctionCall(String)0111
Poly.Poly()0111
Poly.Poly(int)0111
Poly.add(Poly)0111
Poly.addTerm(TermKey, BigInteger)3213
Poly.appendAllTerms(StringBuilder, List<Entry<TermKey, BigInteger>>, int)4334
Poly.appendExpPart(StringBuilder, Poly)0111
Poly.appendTerm(StringBuilder, Entry<TermKey, BigInteger>, boolean)7279
Poly.appendVariable(StringBuilder, String, BigInteger)1122
Poly.appendVariablePart(StringBuilder, TreeMap<String, Variable>, boolean, boolean, boolean)7156
Poly.compareTermEntries(Entry<TermKey, BigInteger>, Entry<TermKey, BigInteger>)2313
Poly.compareVariableExponent(Entry<TermKey, BigInteger>, Entry<TermKey, BigInteger>, String)4$\color{red}{\text{4}}$25
Poly.computeExpInsideGcd(Poly)3323
Poly.derivative(String)$\color{red}{\text{18}}$177
Poly.divideExpInside(Poly, BigInteger)1122
Poly.equals(Object)2313
Poly.findFirstPositiveIndex(List<Entry<TermKey, BigInteger>>)3323
Poly.getSortedEntries()0111
Poly.getTerms()0111
Poly.grad()0111
Poly.hashCode()0111
Poly.isComplex()8$\color{red}{\text{5}}$48
Poly.isZero()0111
Poly.mul(Poly)0111
Poly.pow(BigInteger)8$\color{red}{\text{4}}$57
Poly.pow(int)8$\color{red}{\text{4}}$57
Poly.renderExpLiteral(Poly)2212
Poly.renderExpOptimized(Poly)5135
Poly.renderGroupedExp(Poly)7256
Poly.sub(Poly)0111
Poly.substitute(Poly)5144
Poly.toString()2313
RecursiveFunc.RecursiveFunc(RecursiveFuncStructure)0111
RecursiveFunc.getFunction(int)1222
RecursiveFuncStructure.RecursiveFuncStructure(RuleBuilder)0111
RecursiveFuncStructure.getRoot(int, RecursiveFunc)0111
TerOpNode.TerOpNode(Node[], String)0111
TerOpNode.evaluate()3233
TerOpNode.getSon(int)0111
TermKey.addVariable(Variable)0111
TermKey.equals(Object)4335
TermKey.getExpInside()0111
TermKey.getVariables()0111
TermKey.hashCode()0111
TermKey.mul(TermKey)0111
TermKey.pow(BigInteger)0111
TermKey.setExpInside(Poly)0111
TermKey.toString()6155
TermKey.valueOf(Poly)0111
TermKey.valueOf(String)0111
UnaryOpNode.UnaryOpNode(String, Node)0111
UnaryOpNode.evaluate()4126
UnaryOpNode.getExpr()0111
UnaryOpNode.getOp()0111
Variable.Variable(BigInteger, String)0111
Variable.equals(Object)4335
Variable.getVariableName()0111
Variable.getXexp()0111
Variable.hashCode()0111
Variable.mul(Variable)1212
Variable.pow(BigInteger)0111
Variable.toString()0111

Parser Lexer 内部方法复杂度高符合期望,且改进空间小,因为通常这部分代码不会过多的修改

但是值得注意的一点是 Poly.derivative 的设计较差,没有较好的分离逻辑(这是因为 Poly 设计有一定的问题,没有很好的分层结构)

有很多代码的 ev(G) 较高,这类问题会增加维护负担,值得注意


OCavg Average Operation Complexity (平均操作复杂度)类中所有方法的平均复杂度,即 $WMC \div 方法数量$。它反映了该类方法的平均逻辑密集度。

OCmax Maximum Operation Complexity (最大操作复杂度)类中逻辑最复杂(分支最多)的那个方法的复杂度分值。

WMC Weighted Methods per Class (类加权方法数)该类中所有方法的复杂度(通常指循环复杂度)之和。它代表了维护该类所需的总体工作量。

ClassOCavgOCmaxWMC
BinOpNode2.00610
FuncNode1.0012
FunctionManager1.0016
FunctionParser2.60313
FunctionParser.FunctionDefinition1.0013
FunctionParser.ParsedHead1.0011
FunctionParser.RecursiveFunctionDefinition1.0013
Lexer2.86720
Main3.0033
NumberNode1.0013
Parser2.08827
Poly$\color{red}{\text{3.03}}$7$\color{red}{\text{94}}$
RecursiveFunc1.5023
RecursiveFuncStructure1.0012
TerOpNode2.0046
TermKey1.45416
UnaryOpNode2.50710
Variable1.38311

Poly 类经过多次重构,在设计上有很严重的问题,逻辑链条过长,没有很好的分离

接下来在架构设计体验中会详细说明 Poly 的设计以及我是如何给自己挖了一个巨大的天坑的

总的来说,多项式计算和自定义函数属于耦合较高的代码,维护起来相当麻烦

我比较喜欢的是AST树的设计,代码维护简单,结构清晰

img

通过观察类图我们发现, AST 树的部分基本遵循高内聚低耦合的性质。但是 Poly 类内部方法太复杂,应当分离对象。Function 的类大致为一个独立整体。

架构设计体验

题外话:发现博客应当每周写点,不然写这部分会忘掉当初的思路

HW1

第一次作业主要难度在表达式的解析,于是我设计了 Poly 类用于计算,设计了 Node 接口用于实现不同种类的 AST 语法树节点,设计了 Parser 和 Lexer 用于解析。

其中 Node、Parser 和 Lexer 一直用到最后,但是 Poly 在后续进行了 2 次重构。

原因是设计 Poly 时偷懒,并没有抽象出合理的结构,导致每次增加新功能需要对这个类所有代码修改。

HW2

这次加入的选择式因子和函数并没有费太多的力气,因为 Node 高度抽象,我可以很轻松的组建不同的计算节点。

但是 exp 的加入使得我开始时设计的 HashMap<Integer, BigInteger> 跟不上时代,其实当时就应该抽象出 Term 。

于是我换成了 HashMap<TermKey, BigInteger> 进行管理,这种改变相当麻烦,因为我一开始没有抽象出 GetExponent 这样的接口。

都是直接对 HashMap 查询的。导致涉及到查询的地方代码都要更改。

但是我的 TermKey 由于设计不够优雅导致后面加入多个变量时又有很多麻烦。

HW3

这次加入的递推函数设计起来我感到有点吃力,因为我希望代码能够在递推层数足够多的时候或者表达式足够复杂的时候都能保证高效性。不过最终引入了结构接口和新的 FunctionParser 使得问题得到解决。扩展变量导致对 TermKey 进行了一定的修改,同时我模仿 BigInteger 的代码修改了各个类的构造函数使得其他地方用到构造函数的时候能够更加方便。扩展计算就是新增了一种计算节点。总之这次迭代还算比较轻松。

从这几次迭代来看,每次重构引起的麻烦都是没有设计好 Getter 和 Setter 导致的,出现了跨级管理内部数据的情况,使得重构代码不能限制在小的范围内。应当引起注意。

分析自己程序的bug

只有一个 BUG 就是被人卡了提取公因式的优化

代码中有两个方法会互相调用,经过多层 exp 嵌套将会形成庞大的搜索树

其实交之前发现了,但是没来得及加缓存并且有侥幸心理以为不会出事(

这个 BUG 很好找,你只需要随便搞个边界数据,然后使用 Profile 分析,看火焰图或者调用树就能发现异常

出现 BUG 的代码往往是在代码迭代的时候加入的代码,因为会更加缺少全局的考虑。而且直接塞到原来方法的代码风险更高,因为会更难进行 Junit 测试。

分析自己发现别人 BUG 所采用的策略

首先盲测一些有概率出错的数据,例如

// 卡没去符号的
0
0
exp(x)^+02
// 卡选择式短路和字符串替换展开
1
f(x)=exp(exp(exp(exp(exp(exp(x)^2)^2)^2)^2)^2)^3
0
[x==x?0:f(f(f(f(f(f(f(f(f(f(f(f(f(f(x))))))))))))))]
// 卡提取公因式优化
1
f(x)=exp(exp(exp(exp(exp(exp(x)^2)^2)^2)^2)^2)^3
0
f(f(f(f(x))))

然后再针对代码检查

在这次作业中主要检查 ParserPoly.toString()

比如我就卡掉了一个在 $n$ 较小的时候进行 dp 优化的,但是他的复杂度为 $2^n$,嵌套两层他就爆炸了

0
0
(1+x+x^2+x^3+x^4+x^5+x^6+x^7+x^8+y+y^2)*exp((x+x^2+x^3+x^4+x^5+x^6+x^7+x^8+y+y^2+y^3+y^4+y^5+y^6))

有很多人的代码没人叉成功可能是因为没有细看

所以针对代码检查是有必要的,同时构造样例可以使用 AI 帮助,虽然 AI 不见得能发现这些位置的 BUG ,但是经过提醒就可以快速构造合法且针对的高强度样例。

关于优化

HW2 中对输出做了优化,简单提取公因式优化输出长度

HW2 BUG 修复对程序性能做了优化,加入了缓存。

HW3 对输出做了优化,加入分组公因式优化输出长度。

可以保证简洁性和正确性,因为原本设计中遵循了低耦合的原则,在优化的过程中仅仅改变了一些方法的内部实现,优化仅仅需要测试更改的方法即可。

大模型使用

使用大模型辅助了 FuncManager 的架构,这部分业务需求实在是有点混乱邪恶。

性能优化上,AI 能够提出一些小巧思,感觉让 AI 设计搜索的估价函数会很有说法,不过我没尝试,实在是不放心 AI 代码的运行效率。

使用了大模型进行代码的检查,结果差强人意,能够看出一些明显的错误:查出来了一个 exp(x*y) 的错误,根据互测中大模型的表现,我认为大模型在该题目中检查代码有一定的局限性。

评测机搭建非常完美,几分钟搞出来一个带测试数据合法性用 numpy 检查结果的评测机。(不过用 numpy 有点变态了)在批量测试中有很大的帮助。

心得体会

本次作业感觉做的有点糙了(

在 HW1 中本来想要第一次编写代码时就加入对多变量的支持,还有 Poly 类的扩展性,由于时间紧张就没加入,结果在后续的作业吃了很多亏,基本上每次代码都需要进行或大或小规模的重构。

可以发现一个好的框架是好的作业的基础。

不过由于笔者代码底子尚可,代码基本都是一边过,强测也没出过问题,仅仅在 HW2 因为程序性能被叉了一次。

但正是一边过让我在互测阶段很难拿到一些易错的样本进行批量测试,笔者基本上都是对着代码设计的叉人数据(好费时间)。以至于在互测阶段总是对着某个 **星上来交了一发 Hack 一穿四百思不得其解。

互测非常有趣味,但是太费时间,本人也没看太多。不过让 AI 帮忙看代码基本都看不出问题,还得手搓。

以后可以在设计的时候就想好可能会出现的问题来方便进行互叉。

一个比较好的互测流程:

  1. 盲测一些易错的数据
  2. 读代码进行针对性的构造

未来方向

conclusion: CAS 是一个不错的课题,我认为目前的题目过于简单,对一些方面的涉及有些过于浅显,可以多加一点东西。

  • 就目前来看,代码整体难度体现在 toString 的设计上,我认为 toString 其实是本单元不太优雅的地方,性能分的要求是输出字符串最短,这个东西的性质太少,且与多项式本身相关性太差,我认为性能值部分应当重新设计,应当给出标准的 AST 树构建方法,并设计性能值为一些树上和 如点权和、size ,这样设计的性能值我认为会更加优雅。
  • 在代码互测环节中,Cost 的限制应当适当放宽,很多时候能够发现对方代码中出现了很明显的性能问题,但是限于 Cost 没办法构造出卡掉他的代码,事实上在整个互测阶段,我只找到两三个很离谱的性能问题然后叉掉,只有一次成功叉掉了一个比较普通的性能问题,目前来看 Cost 的限制严重限制了叉人的想象力。
  • 还有一点就是,数据范围的规定实在是模糊不清,我很难在强测结果出现之前知道:我这次优化到底是狠狠地让我因为超时被肘击死还是让我爽吃一波性能分。
  • 文法定义也有些不太符合直觉。例如 exp 中只能放因子,如果我想要放表达式要加括号转化为因子,实在是有些画蛇添足,并且为代码编写带来了很多麻烦。
  • 经过上面的调整,我们发现这样作业将会变得简单很多。我认为去年的三角函数是不错的思路,应当大力加入。
  • 目前的作业中对于自定义函数的部分仅仅涉及到 1 个函数,过于简单,在设计自定义函数尤其是递推函数时,不难发现兼顾程序性能可扩展性的代码设计是一个充满挑战的任务,并且可以从中得到一些面向对象设计的思路(笔者尝试扩展了不限数量的函数,但是在这里设计的代码就不太优雅,不过感觉收获很多)。
  • 多项式分解和多项式长除也是一个值得考察的点,能够学到一些经典算法。
  • 总的来说,这几次迭代有一种意犹未尽的感觉,希望能多加点需求,现在的太短,不太能暴露设计缺陷(比如我现在的 Poly

思考题

如何检查输入是否符合要求?包括空格,连续符号等。

严格按照文法做递归下降即可

如何精准计算一个合法输入的Cost?

可以发现此题中的 Cost 计算和原本的符号计算差不多,只需要更改 AST 树节点中 Interface 内的 evaluate 方法支持 Cost 计算即可。

搞了个 GoLang 的

package main

import (
    "bufio"
    "fmt"
    "math"
    "os"
    "strconv"
    "strings"
    "unicode"
)

const CostLimit = 5000

func sAdd(a, b int) int {
    if a >= CostLimit || b >= CostLimit || a+b >= CostLimit {
        return CostLimit + 1
    }
    return a + b
}

func sMul(a, b int) int {
    if a == 0 || b == 0 {
        return 0
    }
    if a >= CostLimit || b >= CostLimit || a*b >= CostLimit {
        return CostLimit + 1
    }
    return a * b
}

func sPow(base, exp int) int {
    if exp == 0 {
        return 1
    }
    if base >= CostLimit {
        return CostLimit + 1
    }
    res := math.Pow(float64(base), float64(exp))
    if res >= float64(CostLimit) {
        return CostLimit + 1
    }
    return int(res)
}

type TermKey struct{ X, Y int }
type Poly map[TermKey]int64

func NewPoly() Poly { return make(Poly) }
func (p Poly) Add(q Poly) Poly {
    res := NewPoly()
    for k, v := range p {
        res[k] += v
    }
    for k, v := range q {
        res[k] += v
    }
    return res
}
func (p Poly) Mul(q Poly) Poly {
    res := NewPoly()
    for k1, v1 := range p {
        for k2, v2 := range q {
            res[TermKey{k1.X + k2.X, k1.Y + k2.Y}] += v1 * v2
        }
    }
    return res
}
func (p Poly) Equals(q Poly) bool {
    p1, q1 := NewPoly(), NewPoly()
    for k, v := range p {
        if v != 0 {
            p1[k] = v
        }
    }
    for k, v := range q {
        if v != 0 {
            q1[k] = v
        }
    }
    if len(p1) != len(q1) {
        return false
    }
    for k, v := range p1 {
        if q1[k] != v {
            return false
        }
    }
    return true
}

type Node interface {
    Cost() int
    EvalPoly() Poly
    String() string
    Substitute(repl Node) Node
}

type VarNode struct{ Name string }

func (n *VarNode) Cost() int { return 1 }
func (n *VarNode) EvalPoly() Poly {
    p := NewPoly()
    if n.Name == "x" {
        p[TermKey{1, 0}] = 1
    } else {
        p[TermKey{0, 1}] = 1
    }
    return p
}
func (n *VarNode) String() string { return n.Name }
func (n *VarNode) Substitute(repl Node) Node {
    if n.Name == "x" {
        return repl
    }
    return n
}

type NumNode struct {
    Raw string
    Val int64
}

func (n *NumNode) Cost() int {
    s := strings.TrimLeft(n.Raw, "0")
    if s == "" {
        return 1
    }
    return len(s)
}
func (n *NumNode) EvalPoly() Poly {
    p := NewPoly()
    p[TermKey{0, 0}] = n.Val
    return p
}
func (n *NumNode) String() string            { return n.Raw }
func (n *NumNode) Substitute(repl Node) Node { return n }

type BinNode struct {
    Op   string
    L, R Node
}

func (n *BinNode) Cost() int {
    if n.Op == "*" {
        return sMul(n.L.Cost(), n.R.Cost())
    }
    return sAdd(n.L.Cost(), n.R.Cost())
}
func (n *BinNode) EvalPoly() Poly {
    if n.Op == "+" {
        return n.L.EvalPoly().Add(n.R.EvalPoly())
    }
    if n.Op == "-" {
        neg := n.R.EvalPoly()
        for k := range neg {
            neg[k] *= -1
        }
        return n.L.EvalPoly().Add(neg)
    }
    return n.L.EvalPoly().Mul(n.R.EvalPoly())
}
func (n *BinNode) String() string { return fmt.Sprintf("(%s%s%s)", n.L, n.Op, n.R) }
func (n *BinNode) Substitute(repl Node) Node {
    return &BinNode{n.Op, n.L.Substitute(repl), n.R.Substitute(repl)}
}

type PowNode struct {
    Base Node
    Exp  int
}

func (n *PowNode) Cost() int {
    if _, isVar := n.Base.(*VarNode); isVar {
        return 1
    }
    if _, isExp := n.Base.(*ExpNode); isExp {
        return sAdd(sPow(2, n.Exp), n.Base.Cost())
    }
    return sPow(max(n.Base.Cost(), 2), max(n.Exp, 1))
}
func (n *PowNode) EvalPoly() Poly {
    res := NewPoly()
    res[TermKey{0, 0}] = 1
    base := n.Base.EvalPoly()
    for i := 0; i < n.Exp; i++ {
        res = res.Mul(base)
    }
    return res
}
func (n *PowNode) String() string            { return fmt.Sprintf("%s^%d", n.Base, n.Exp) }
func (n *PowNode) Substitute(repl Node) Node { return &PowNode{n.Base.Substitute(repl), n.Exp} }

type ExpNode struct{ Inner Node }

func (n *ExpNode) Cost() int                 { return sAdd(n.Inner.Cost(), 1) }
func (n *ExpNode) EvalPoly() Poly            { return NewPoly() }
func (n *ExpNode) String() string            { return fmt.Sprintf("exp(%s)", n.Inner) }
func (n *ExpNode) Substitute(repl Node) Node { return &ExpNode{n.Inner.Substitute(repl)} }

type SelectNode struct{ A, B, C, D Node }

func (n *SelectNode) Cost() int {
    var choose int
    if n.A.EvalPoly().Equals(n.B.EvalPoly()) {
        choose = n.C.Cost()
    } else {
        choose = n.D.Cost()
    }
    return sAdd(sAdd(n.A.Cost(), n.B.Cost()), sAdd(choose, 5))
}
func (n *SelectNode) EvalPoly() Poly {
    if n.A.EvalPoly().Equals(n.B.EvalPoly()) {
        return n.C.EvalPoly()
    }
    return n.D.EvalPoly()
}
func (n *SelectNode) String() string { return fmt.Sprintf("[(%s==%s)?%s:%s]", n.A, n.B, n.C, n.D) }
func (n *SelectNode) Substitute(repl Node) Node {
    return &SelectNode{n.A.Substitute(repl), n.B.Substitute(repl), n.C.Substitute(repl), n.D.Substitute(repl)}
}

var funcTable = make(map[string]Node)

type FuncCallNode struct {
    Name string
    Arg  Node
}

func (n *FuncCallNode) Cost() int {
    def, ok := funcTable[n.Name]
    if !ok {
        return CostLimit + 1
    }
    if n.Arg.Cost() > 500 {
        return CostLimit + 1
    }
    return sAdd(sAdd(n.Arg.Cost(), sMul(def.Substitute(n.Arg).Cost(), 2)), 5)
}
func (n *FuncCallNode) EvalPoly() Poly { return NewPoly() }
func (n *FuncCallNode) String() string { return fmt.Sprintf("%s(%s)", n.Name, n.Arg) }
func (n *FuncCallNode) Substitute(repl Node) Node {
    return &FuncCallNode{n.Name, n.Arg.Substitute(repl)}
}

type Parser struct {
    tokens []string
    pos    int
}

func (p *Parser) parseExpr() Node {
    var node Node
    sign := 1
    if p.peek() == "+" || p.peek() == "-" {
        if p.next() == "-" {
            sign = -1
        }
    }
    node = p.parseTerm()
    if sign == -1 {
        node = &BinNode{"-", &NumNode{"0", 0}, node}
    }
    for p.peek() == "+" || p.peek() == "-" {
        op := p.next()
        node = &BinNode{op, node, p.parseTerm()}
    }
    return node
}

func (p *Parser) parseTerm() Node {
    var node Node
    sign := 1
    if p.peek() == "+" || p.peek() == "-" {
        if p.next() == "-" {
            sign = -1
        }
    }
    node = p.parseFactor()
    if sign == -1 {
        node = &BinNode{"-", &NumNode{"0", 0}, node}
    }

    for p.peek() == "*" {
        p.next()
        node = &BinNode{"*", node, p.parseFactor()}
    }
    return node
}

func (p *Parser) parseFactor() Node {
    var node Node
    curr := p.next()
    if curr == "" {
        panic("Unexpected EOF")
    }

    switch {
    case curr == "x" || curr == "y":
        node = &VarNode{curr}
    case unicode.IsDigit(rune(curr[len(curr)-1])):
        v, _ := strconv.ParseInt(curr, 10, 64)
        node = &NumNode{curr, v}
    case curr == "(":
        node = p.parseExpr()
        p.expect(")")
    case curr == "exp":
        p.expect("(")
        node = &ExpNode{p.parseFactor()}
        p.expect(")")
    case curr == "[":
        p.expect("(")
        a := p.parseExpr()
        p.expect("==")
        b := p.parseExpr()
        p.expect(")")
        p.expect("?")
        c := p.parseFactor()
        p.expect(":")
        d := p.parseFactor()
        p.expect("]")
        node = &SelectNode{a, b, c, d}
    case curr == "f":
        name := "f"
        if p.peek() == "{" {
            p.next()
            name = "f" + p.next()
            p.expect("}")
        }
        p.expect("(")
        arg := p.parseFactor()
        p.expect(")")
        node = &FuncCallNode{name, arg}
    default:
        panic("Invalid Factor at: " + curr)
    }

    if p.peek() == "^" {
        p.next()
        if p.peek() == "+" {
            p.next()
        }
        e, _ := strconv.Atoi(p.next())
        node = &PowNode{node, e}
    }
    return node
}

func (p *Parser) expect(s string) {
    if p.next() != s {
        panic("Expected " + s + " but got something else")
    }
}
func (p *Parser) peek() string {
    if p.pos < len(p.tokens) {
        return p.tokens[p.pos]
    }
    return ""
}
func (p *Parser) next() string {
    r := p.peek()
    p.pos++
    return r
}

func tokenize(s string) []string {
    s = strings.ReplaceAll(s, " ", "")
    s = strings.ReplaceAll(s, "\t", "")
    ops := []string{"==", "exp", "f", "{", "}", "(", ")", "[", "]", "?", ":", "^", "*", "+", "-"}
    for _, op := range ops {
        s = strings.ReplaceAll(s, op, " "+op+" ")
    }
    return strings.Fields(s)
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func main() {
    scanner := bufio.NewScanner(os.Stdin)

    scanner.Scan()
    n, _ := strconv.Atoi(scanner.Text())
    for i := 0; i < n; i++ {
        scanner.Scan()
        line := scanner.Text()
        parts := strings.Split(line, "=")
        funcTable["f"] = (&Parser{tokens: tokenize(parts[1])}).parseExpr()
    }

    scanner.Scan()
    m, _ := strconv.Atoi(scanner.Text())
    for i := 0; i < 3*m; i++ {
        scanner.Scan()
        line := scanner.Text()
        fName := "f" + line[strings.Index(line, "{")+1:strings.Index(line, "}")]
        funcTable[fName] = (&Parser{tokens: tokenize(line[strings.Index(line, "=")+1:])}).parseExpr()
    }

    scanner.Scan()
    exprStr := scanner.Text()

    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("\n>>> [REJECT] 文法不合规: %v\n", r)
        }
    }()

    p := &Parser{tokens: tokenize(exprStr)}
    ast := p.parseExpr()

    cost := ast.Cost()
    clean := strings.ReplaceAll(strings.ReplaceAll(exprStr, " ", ""), "\t", "")

    fmt.Printf("\n--- 校验结果 ---\n")
    fmt.Printf("解析结果: %s\n", ast.String())
    fmt.Printf("有效长度: %d / 100\n", len(clean))
    fmt.Printf("计算代价: %d / 5000\n", cost)

    if len(clean) <= 100 && cost <= 5000 {
        fmt.Println(">>> [SUCCESS] 数据合法")
    } else {
        fmt.Println(">>> [FAIL] 数据超标")
    }
}
...全文
42 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

302

社区成员

发帖
与我相关
我的任务
社区描述
2026年北航面向对象设计与构造
java 高校
社区管理员
  • 孙琦航
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧