发布者:raccoon
(上述三行本应存在数字标号,但是实际并未存在)
发布者:raccoon
由于笨人水平有限,今天来 水 写一篇QM-Algorithm的编写过程。
QM-Algorithm(Quine-McCluskey Algorithm,奎因 - 麦克拉斯基算法),是在布尔代数中用于化简布尔函数的系统方法,比卡诺图更适合多变量(通常 4 个及以上变量)布尔函数的化简,是数字逻辑电路设计中的核心工具。
QM 算法是一种系统化、可机械化的布尔函数最小化方法,通过逐步合并具有相同变量但相反取值的最小项(或最大项),消去冗余变量,最终得到布尔函数的最简与-或表达式(或者 或-与表达式),广泛应用于数字电路设计、逻辑化简等场景,可手动计算,也可通过程序实现自动化化简。
卡诺图作为化简布尔代数函数的基石方法,尽管有其可视化强(图形方法)、速度快、错误率低等一系列优点,但在处理变量大于4的布尔函数时可视化程度迅速降低。其核心原因是格雷码相邻两数只相差一位二进制码的特性不再易于观察,以及高维图形的直观性较差。同时,自动化化简布尔函数的工业要求也冲击了卡诺图的使用范围。如何寻找质蕴含项和实质蕴含项很难用机器易于理解的编程语言实现,这也阻碍了机器通过机械化重复步骤实现多变量函数的快速化简。
在这种情况下,不依赖图形化、操作步骤具有可重复性的QM-Algorithm成为了工业化化简布尔函数的首选方法。
注:尽管QM-Algorithm的核心操作具有可重复性,但是在人工使用QM-Algorithm化简时非常容易发生错误,且操作步数随着变量的增加指数级提升。这导致了人工化简的时长高达卡诺图化简相同变量数布尔函数的3-4倍。
本算法由C++实现。C++支持OOP,故使用类和对象构建算法中的数据结构Minterm(最小项布尔代数式)和MergedTerm(算法合并过程中产生的已合并的布尔代数式)。同时将QM-Algorithm的4步核心步骤以及相关辅助函数封装在一个名为QMAlgorithm的类中,定义算法执行函数QMAlgorithm::run()以顺序执行调用核心步骤对应成员函数。
Minterm数据结构构造:
class Minterm {
friend class QMAlgorithm;
int dec_value; // 十进制值
int binary[MAX_BIN_BITS]; // MAX_BIN_BITS位二进制
bool is_used; // 是否被合并
bool is_dont_care; // 是否为无关项
void decimal_binary(int num,int *binary);
public:
Minterm(int dec=0, bool dc=false);
};
MergedTerm数据结构构造:
class MergedTerm {
friend class QMAlgorithm;
int merged_bin[MAX_BIN_BITS]; // 合并后的二进制串(如1-0----...)(-用2表示)
set<int> covered_minterms; // 覆盖的最小项集合
bool is_essential; // 是否为实质蕴涵项
public:
MergedTerm();
MergedTerm(const int* bin, const set<int>& mins);
};
QMAlgorithm数据结构构造:
class QMAlgorithm {
vector<Minterm> minterms_; // 原始最小项列表(含无关项)
vector<MergedTerm> merged_terms_; // 合并后的项列表
vector<MergedTerm> essential_terms_; // 实质蕴涵项列表
set<int> required_minterms_; // 需要覆盖的最小项(不含无关项)
void mergeMinterms(); // 合并相邻最小项
int isOneBitDiff(const int* bin1, const int* bin2) const; // 判断相邻最小项
void findEssentialTerms(); // 寻找实质蕴涵项
void printTerm(const int* merged_bin) const; // 打印项
bool areTermsEqual(const int* bin1, const int* bin2) const;
public:
QMAlgorithm(const vector<int>& minterms, const vector<int>& dont_cares);
void run(); // 执行QM算法的主流程
void printResult() const; // 打印最简布尔表达式
};
整个编写过程基本都是机械化操作,只需要按照核心步骤编写即可。
由于笨人实力有限,在编写过程中遇到了一堆疑难和bug(哭)。
对于新手,我认为AI Assistance是很有必要的。但是AI不应该完全替代你在编程中的主导地位。死磕困难也是不可取的,因为不会的知识、语法,很难随便灵机一动便习得。
以笨人编写该程序的经验为例:笨人对<vector>是什么作用的略有耳闻,但到底怎么使用这个内置的类笨人知之甚少。因此在面临不知道使用什么数据类型存储Minterm、MergedTerm等创建出的新类型对象时,Copilot跟笨人说可以使用vector存储。于是笨人又继续追问vector的成员函数都有哪些,有什么作用,并要求Copilot写一个文件展示vector的各种语法和功能。最后笨人又询问了Copilot关于迭代器的知识以完成vector对象的迭代任务(这也是本算法中经常出现的重要逻辑)。于是笨人在任务编写过程中学习并训练到了关于vector的应用......
以上为笨人编写QM-Algorithm的一些心得体验。这是笨人第一次发帖子,如有任何问题,还请各位大神指教。(mol)
发布者:SweetGargamel
如果大家有用过Claude Code 或者 Codex 或者 Opencode之类的CLI的Coding工具的话,你会发现他不能像cursor那样选择性的apply或者reject代码,只能全盘接受他的所有修改(或者拒绝)
同时也没有什么很好的可视化的方式来看他修改了哪些代码(部分vscode插件可以做到预览,但是预览的也比较麻烦,可能就是这样类似的效果)
所以我开始尝试用git管理ai修改的代码。我可以用Pycharm自带的diff工具来查看不同的区别,同时选择性的选择应用。
主工作区继续干主线,AI 的改动单独丢进一个 git worktree 里。
这个做法最直接的好处,不是”Git 更高级了”,而是脑子终于不用频繁换上下文了。Git 官方对 git worktree 的定义,本来就是 同一个仓库可以挂多个工作目录,并且同时检出不同分支 。这件事一旦放到日常开发里,体验上的变化其实非常朴素:主仓库保持稳定,AI 在旁边大胆试,试完我再决定哪些要吸收,哪些直接删掉。
这个思路其实和我原来笔记里的几句土话是同一个意思:以前切分支像灵魂出窍,现在更像肉身分离;主仓库是责任区,实验区只是候选方案区。
以前我也不是不会用分支。
功能分支、修 bug 分支、临时实验分支,这些都很正常。问题不在 Git,问题在”切换”这件事本身。你正在写一半的代码、开着一堆终端、dev server 跑着、脑子里还挂着当前任务的上下文,这时候突然来一个 hotfix,或者突然想让 AI 试一个重构方案,你虽然知道”切个分支就行”,但整个人还是会断一下。
尤其是 AI 参与之后,这个断裂感更明显。
因为 AI 最擅长的是”快速给一个候选答案”,而不是替你承担最后的责任。它特别适合去写样板、铺测试、试重构、改脚手架、补胶水代码;但如果你让它直接在你当前主工作区里折腾,最后常见的结果不是它写错了,而是 你看不清了 。哪些文件是我刚改的,哪些是 AI 改的,哪些改动是为了验证思路,哪些改动其实根本不该留下来,全都搅在一起。
所以我后来不再问”AI 能不能直接帮我写”,我问的是: 怎么让 AI 的输出和我的责任边界分开。
git worktree 刚好就是干这个的。一个仓库,多开几个工作目录;主线在这边,实验在那边,谁也别打扰谁。(Git)
我现在的目录通常会长这样:
~/projects/my-saas/ # 主仓库,稳定开发 ~/projects/my-saas/worktrees/my-saas-ai/ # AI 草稿区 ~/projects/my-saas/worktrees/my-saas-hotfix/ # 紧急修复区
这个结构和我原来记下来的工作模式差不多:主仓库负责稳定开发,旁边挂几个不同用途的 worktree,像几个平行宇宙。
实际操作很简单。我一般会先把主线停在一个干净状态,然后给 AI 开一个短命分支,再单独挂一个 worktree 出去:
git switch main git commit -am "checkpoint before ai draft" git worktree add -b ai/draft-order-pricing ./worktrees/my-saas-ai main
git worktree add 本来就支持在指定目录创建一个新的工作树,也可以顺手基于某个起点新建分支。
做完以后,我通常会开两个 VS Code 窗口。
左边那个是主仓库,我继续写真正要进主线的东西。右边那个是 AI 草稿区,我让它在那里大胆尝试。它写坏了没关系,反正坏的是草稿区,不是我手上的主工作区。
这个感觉和”切分支”最大的差别,不在 Git 命令,而在人的注意力。切分支是逻辑切换;worktree 更像物理隔离。你看见的是两个目录,两个窗口,两套终端,两个开发上下文。脑子会轻松很多。
一开始我以为自己会最常拿它干 AI 实验,后来发现真正让我离不开 worktree 的,反而是紧急修 bug。
场景很常见:你正在主仓库写新功能,写到一半,线上突然炸了。以前我的做法一般是先 stash,或者硬切分支过去修,修完再切回来。技术上当然也能干,但节奏会断,编辑器状态会断,终端上下文也会断。
现在我直接开一个 hotfix worktree:
git worktree add -b hotfix/critical-bug ./worktrees/my-saas-hotfix main cd ./worktrees/my-saas-hotfix
然后在这个目录里修、提、推,修完以后把它关掉。主仓库那边原封不动,dev server 继续跑,写到一半的文件还停在那儿,我回来接着写就行。
我原来在笔记里写过一句话:以前切分支像”灵魂出窍”,现在是”肉身分离”。 这句话虽然土,但很准。因为 worktree 真正解决的不是 Git 技巧问题,而是上下文切换成本。
你可以这样看差异
git diff main...ai/draft-order-pricing
或者只看某个文件:
git diff main...ai/draft-order-pricing -- src/domain/order_pricing.ts
你会发现,一旦 AI 的改动被隔离到一个独立 worktree 里,review 这件事会轻松很多。因为你面对的不是一坨混在当前工作区里的脏改动,而是一个清清楚楚的”候选答案”。
如果我觉得这份候选答案整体还行,但不想保留它那个分支历史,我会直接 squash 进当前分支:
git switch main git merge --squash ai/draft-order-pricing
这个方式特别适合 AI 协作。因为 AI 的分支历史很多时候没有保留价值,我真正想要的只是”把这个候选实现铺到眼前,然后我自己再挑、再改、再提交”。
当然,如果你是大面积的修改,命令行看起来可能就有点不舒服了
让AI在worktree写完后commit到 ai-fix 分支上【或者我们可以直接开一个worktree叫ai_playground然后每次ai写完了先Review和merge,然后再把 ai_fix 直接挪到merge后的结果上即可。这样就是开两个Vscode窗口就可以了】,然后用
# 1) 切到目标分支 main
git switch main
# 2) 确保工作区干净(很重要)
git status
# 3) 合并 ai-fix,但不提交
# --no-ff表示不用fastforward
git merge --no-commit --no-ff ai-fix
# 或者使用 把 ai-fix 的全部差异压扁应用到当前分支,但不提交
git merge --squash ai-fix
接下来打开vscode的change面板会有两个change,左侧是 HEAD 提交(也就是我们在 git merge --no-commit --no-ff ai-fix 前的提交)
这里会分 staged change 【在staged区】和普通 change 【在working区】,这里我点开的是普通的 change 。中间的按钮你可以revert或者把他staged了
【刚跑完上面的命令一般只会有 staged change 我们可以参考图二取消`staged】
在 staged 里面,我们能做的比较少,一般只能取消 staged ,但是如果我们放到普通的 change 里面就可以直接对文件进行编辑。
可以用这个命令一键全部从 stage 覆盖到 working area
git restore . # 注意,--staged是用HEAD提交来覆盖index区域
AI 草稿分支这东西,最适合短命。
它的价值是”候选方案”,不是长期维护。你今天让它试一个重构,明天让它铺一层测试,后天再让它改个脚本,这些都适合用临时分支和临时 worktree 做。事完删掉,世界清净。
我现在会定期清理:
git worktree list git worktree remove ./worktrees/my-saas-ai git branch -D ai/draft-order-pricing git worktree prune
git worktree remove 和 git worktree prune 都是官方支持的清理方式。(Git)
虽然是多个目录,但本质上它们还是挂在同一个仓库上。官方文档也明确说了,主工作树之外还可以有零个或多个 linked working tree。(Git)
所以最怕的不是”不会用”,而是”乱用”:
最后不是 Git 出问题,是你自己已经不知道哪份改动该信谁了。
我的经验很简单:一块逻辑同一时刻只让一个 worktree 负责。主仓库负责主线,AI 区负责提议,hotfix 区只干救火。井水不犯河水,事情就会简单很多。
发布者:小核桃
大家下午好!
最近顺手把 NJUTIC 论坛(tic-forum)的 UI 给汉化了。
之前那个全英文界面确实有点儿违和,看着不够顺眼。趁着这两天有空,我把涉及到 Navbar、PostCard 还有各种交互文案的 12 个核心文件都过了一遍。现在全站已经基本实现中文化适配了。
中间稍微折腾了一下仓库权限和 PR 关联(GitHub 的 patch 分支逻辑确实有点绕),不过好在小许给开了 Org 权限,最后 PR #33 已经顺利 Merge 了。
汉化后的界面现在已经上线,大家登录论坛就能看到。如果在使用中发现哪里的翻译不够地道,或者排版还有瑕疵,欢迎直接去仓库开 Issue 指正。
希望大家可以合作共建一个更完善的NJUTIC论坛!
发布者:selflo
很多人刚开始写代码时,流程其实都很简单:一条 `main` 用到底,有功能就直接写,有 bug 就直接改。 在单人开发阶段,这种方式通常没什么问题。项目只有一个维护者,改了什么、影响到哪里、后面准备怎么收尾,基本都在掌控之中。
但一旦进入多人协作,事情就不一样了。 开发不再只是“把功能做完”,还要考虑改动是否清晰、是否方便 review、会不会影响别人,以及出了问题之后能不能快速追踪。
也是在这个过程中,才会慢慢意识到:多人开发真正需要转变的,不只是 Git 的用法,而是对“开发流程”本身的理解。
---
一条main用到底,本身并不是什么错误习惯。
在个人项目、课程作业或者小型 demo 里,这种方式反而很直接:
流程足够轻,开发节奏自然也更快。 所以在单人场景下,关注点往往是“尽快做完”,而不是“如何协作”。
但这里其实有一个隐含前提:只有一个人在维护这份代码。

---
## 多人开发时,主分支就不再只是“自己的工作台”
进入多人协作之后,`main` 的角色会发生变化。
单人开发时,`main` 更像当前工作的延长线,想到什么就可以直接改。 但在多人开发里,`main` 往往意味着团队共享的主线,它最好保持稳定、清晰、可追踪,很多时候甚至还承担着“随时可发布”的职责。
这时候如果还像以前一样直接在 `main` 上开发,就很容易出现一连串问题:
* 不同人的改动互相影响 * 写到一半的代码提前进入主线 * 无关改动混在一起,难以 review * 后续排查问题时,提交历史十分混乱 * 合并时冲突集中爆发
所以多人开发里最先变化的,其实不是写代码的动作,而是对主分支的认知。 **它不再只是某个人的工作台,而是整个团队共享的主干。**
---
## 分支的意义,是把改动隔离成清晰的单元
既然 `main` 需要保持稳定,那么最基本的做法就是:**从 `main` 拉出自己的分支,在分支上完成当前任务。**
比如要做一个登录功能,可以先切出一条分支:
```bash git checkout -b feature/user-login ```
表面上看,这只是“多开一条线”; 但实际上,它真正解决的是**改动隔离**的问题。
在分支上开发,意味着:
* 未完成的功能不会直接影响主线 * 改动范围更集中,方便 review * 方案如果推翻,可以直接放弃这条分支 * 每个人都能在相对独立的空间里推进自己的任务
所以分支不是为了让 Git 看起来更复杂,而是为了让协作更可控。 它本质上是在回答一个问题:**这次改动,能不能先和团队主线隔离开来,再以清晰的形式合并回去。**

---
## PR 的价值,不只是“申请合并”
刚接触多人开发时,很容易把 PR 理解成一个固定流程:代码写完,发起 PR,等别人看过,再 merge。
但 PR 真正的价值,其实远不止“申请合并”。 它更像是在告诉团队:**这里有一批边界清晰的改动,值得被一起看清楚。**
一个好的 PR,至少有三层作用。
第一,它明确了改动边界。 review 的人不需要猜“最近到底改了什么”,因为这次变更已经被打包成了一个清晰的单元。
第二,它给讨论提供了上下文。 问题可以直接落在具体文件、具体代码行上,而不是停留在笼统的描述里。
第三,它会沉淀协作过程。 为什么这么改、为什么不用另一种方案、哪些边界已经考虑过,这些信息往往都会留在 PR 的讨论记录里。
所以 PR 并不只是“把代码合进去”,而是让团队在进入主线之前,先把这次改动的边界、质量和影响面看清楚。
---
## Code Review 不是挑刺,而是在提前暴露问题
Review 很容易让人产生一种“被检查”的感觉,尤其是之前一直习惯单人开发时。 但在团队协作里,review 更重要的意义其实是:**尽可能把问题挡在 merge 之前。**
很多问题如果能在进入主分支之前发现,处理成本通常很低; 但如果已经合进主线,甚至进入测试环境或线上环境,再返工就会麻烦很多。
Review 常见会发现的问题包括:
* 逻辑漏洞 * 边界情况遗漏 * 命名不清晰 * 抽象层次不合理 * 改动影响面超出预期 * 测试覆盖不足
除此之外,review 还有一个很容易被低估的作用:**帮助团队逐渐形成一致的工程习惯。**
单人开发时,风格统一几乎是天然成立的。 但在多人协作中,如果没有 review 这种机制,项目很容易在命名、结构、提交习惯上越走越散,最后谁都能看懂一点,但谁都看不顺手。

---
## 真正需要改变的,不只是 Git 命令,而是协作视角
多人开发当然离不开 Git,但比起记住多少命令,更重要的是先接受一个事实:
> 每一次改动,都不再只是“我把功能做完了”,而是“我提交了一个需要被团队理解、评估和接纳的变更单元”。
一旦换到这个视角,很多规范都会变得很自然。
为什么要切分支? 因为需要隔离改动。
为什么要控制 PR 大小? 因为过大的改动很难 review。
为什么要规范 commit 命名? 因为提交历史本来就是团队协作的一部分。
为什么不要把修 bug、重构、顺手改样式混成一坨? 因为这样既不利于 review,也不利于后续追踪和回滚。
很多时候,这些看起来像“流程要求”的东西,本质上都在做同一件事:**降低协作摩擦。**
---
## 分支命名和 commit 命名,看起来是细节,其实很重要
单人开发时,分支名和 commit message 往往怎么顺手怎么来。 像 `test`、`tmp`、`update`、`fix bug` 这种名字,自己当下未必看不懂。
但放到多人协作里,这种命名方式的信息量就太低了。 它可能在提交当下还能勉强理解,过几周再回头看,基本只剩下“好像改过东西”,却看不出到底改了什么。
### 分支命名
比较常见的分支命名方式有:
* `feature/xxx` * `fix/xxx` * `refactor/xxx` * `docs/xxx` * `chore/xxx`
例如:
* `feature/user-login` * `fix/order-timeout` * `docs/api-usage`
这样的好处很直接:别人看到分支名时,几乎立刻就能知道这条分支在做什么。
### commit 命名
提交记录本质上是项目历史的一部分。 如果 commit message 太随意,后续排查问题会很痛苦。
像下面这种方式就清楚很多:
```text feat: add user login API fix: handle null response in order service docs: update setup instructions refactor: simplify auth middleware test: add unit tests for payment module ```
规范命名的意义,不只是“看起来整齐”,而是让历史记录真正变得可读、可追踪、可回溯。

---
## 一个比较实用的多人开发流程
如果用最常见的方式来概括,一个比较实用的多人开发流程大概是这样:
### 1. 从 `main` 拉取最新代码
```bash git checkout main git pull origin main ```
### 2. 为当前任务创建独立分支
```bash git checkout -b feature/user-profile ```
### 3. 在分支上开发,并保持提交清晰
```bash git add . git commit -m "feat: add user profile page" ```
### 4. 推送远程并发起 PR
```bash git push origin feature/user-profile ```
### 5. 接受 review,并继续修改
### 6. review 通过后,再合并回 `main`
这套流程看起来比单人开发麻烦一些, 但换来的,是更清晰的改动边界、更低的冲突成本,以及更稳定的团队协作体验。
换句话说,它确实增加了一点流程成本,但也显著减少了后续的沟通成本、排查成本和返工成本。

---
## 结语
从单人开发转向多人开发,表面上看是在学习分支、PR 和 review; 但更深一层,其实是在适应一种新的工程思维。
单人开发更关注“怎么尽快做完”; 多人开发则更强调“怎么让改动以一种清晰、可协作、可追踪的方式进入项目”。
所以真正需要改变的,往往不是某条 Git 命令怎么写, 而是开始意识到:**写代码这件事,从来不只是把功能实现出来,还包括如何让它被他人理解、接住,并长期维护下去。**
发布者:asimov
hello,njutic!
发布者:Insouciant21
hh
发布者:admin
1919810
发布者:selflo
目前论坛还在开发和测试简短,有新功能的想法或者发现了bug可以来https://github.com/NJU-TIC/forum/issues 来反馈!
(发现新bug,插入url后会导致该行后续字符均被识别为url,只有换行后才正常)
发布者:selflo
some~~ ~~content
无高亮,为bug
#include <iostream>
using namespace std;
int main() {
cout << "hello world!" << endl;
return 0;
}
无高亮,为bug
https://images.selflo06.xyz/GeminiBalance/8.jpg
Image:

发布者:selflo
cover image test
发布者:selflo

发布者:selflo
111
发布者:selflo
test
