第十六章 约束
前三天,一切顺利。
林知行在宿舍里搭了个简易的工作台——两张桌子拼在一起,左边放笔记本电脑,右边放方小满帮他借来的显示器。显示器上开着代码编辑器,笔记本上开着ChatGPT的对话窗口。
排课规则被他拆成了约束条件:教师可用时间段、教室容量、课程时长、教师资质要求。每个约束条件都是一个变量,变量之间用逻辑关系连接。他用Python写了一个简单的约束满足模型,把第一批测试数据——一个校区、二十名老师、三十六个时间段——导进去,跑了一遍。
结果看起来不错。
屏幕上显示出一张排课表,每个时间段都填满了,没有明显的冲突。他检查了几遍,发现两个小问题:一个是某位老师的课被排到了午休时间,另一个是同一间教室在同一时间段被安排了两节课。他调整了权重参数,重新跑了一遍,问题解决了。
方小满从上铺探下头来,看了一眼屏幕:“跑通了?”
“跑通了。”林知行说,“但这是最简单的情况。一个校区,二十个老师,三十六个时间段。变量数量不到一千,约束关系不到五千。”
“那真实数据是多少?”方小满问。
林知行靠在椅背上,眼睛盯着天花板:“三十七个校区,上千名老师,每位老师有不同的时间偏好、资质限制、通勤距离。变量数量……”他顿了一下,“至少几十万。”
方小满缩回脑袋:“操,那你搞不搞得定?”
“不知道。”林知行说,“但先把框架搭起来。”
第四天和第五天,他继续搭建框架。把模型从单校区扩展到多校区,加入了教室分配模块、教师通勤距离约束、课程依赖关系约束。代码行数从三百行涨到八百行,ChatGPT的对话记录翻了二十多页。每写一个模块,他都会用小规模数据测试一遍,确认没有明显的逻辑错误。
两天下来,框架看起来挺像回事了。
他拍了一张截图,发给方小满看。方小满回了一个大拇指的表情,然后问:什么时候要真实数据?
林知行回复:越快越好。
第六天,方小满拉来了第一批真实数据。
是张副总发的一个Excel文件,里面有三个校区的完整信息:一百二十三名老师,每人有姓名、年龄、职称、教授科目、可授课时间段、所在校区、通勤距离、联系方式。还有教室信息、课程安排、学生选课情况。
林知行打开文件的时候,手有点抖。
这是他第一次看到真实的商业数据。不是他编造的测试用例,不是网上下载的示例数据,而是一家真正运营中的连锁教育机构的完整信息。
他深吸一口气,开始做数据清洗。
Excel文件的格式不统一。有的校区用"上午/下午"标注时间段,有的用具体的小时数;有的老师的姓名后面带空格,有的不带;有的教室信息在单独的表格里,有的混在教师信息里。他花了一个下午,才把数据格式统一起来,导入到系统里。
然后,他点击了"运行"按钮。
进度条开始走动。
一分钟。两分钟。五分钟。
进度条卡在37%的位置,不动了。
林知行皱起眉头,打开任务管理器,发现CPU占用率已经飙到98%,内存也快爆了。他等了十分钟,进度条还是不动。他只好强制关闭程序,检查代码。
问题出在约束求解器上。
三个校区、一百二十三名老师、几千个时间段。约束变量的数量不是几十万,而是几百万。约束关系不是几千,而是几千万。他的求解器用的是暴力搜索算法,时间复杂度是O(n³),面对这个规模的数据,计算量直接爆表。
他换了一个更高效的求解算法——回溯搜索加剪枝优化,重新跑了一遍。
这次进度条跑得快了一点,但卡在了72%的位置。又等了半个小时,程序终于输出了结果。
他看了一眼结果,心凉了半截。
排课表上有明显的冲突:同一个老师在同一时间段被安排了两节课,同一间教室在同一时间段被安排了三个班,某些老师的时间偏好被完全忽略,某些课程的依赖关系被打破。
他检查了代码,发现约束条件的优先级设置有问题。某些约束条件应该有更高的优先级,但他没有区分。他调整了优先级参数,重新跑了一遍。
结果好了一点,但还是有问题。
他再调整,再跑。
再调整,再跑。
一个晚上,他跑了十几遍,每次都调一点参数,每次结果都不一样,但每次都有新的问题。
凌晨两点,他靠在椅背上,盯着屏幕,眼睛有点发酸。
方小满从上铺爬下来,揉着眼睛:“还没搞定?”
“没。”林知行说,“约束条件太多了,优先级不好设。有些约束是硬约束,必须满足;有些是软约束,尽量满足。但软约束之间也有冲突——比如张老师希望周二上午没课,但李老师希望周二上午有课,他们俩的偏好就冲突了。怎么权衡?”
方小满听不懂,但看出来林知行很累:“要不先睡一觉?明天再搞。”
林知行摇头:“还有八天。三个校区的数据就搞成这样,后面还有三十四个校区。”
方小满拍了拍他的肩膀:“你先睡,明天我帮你整理数据。你告诉我格式,我来弄。”
林知行看着他,犹豫了一下,点了点头。
第七天到第九天,是林知行最崩溃的三天。
方小满帮他整理了剩下三十四个校区的数据。但数据量一上来,系统直接跑不动了。他把暴力搜索算法换成启发式搜索算法,把约束条件的优先级重新调整了一遍,把代码优化了三遍,系统才勉强能运行。
但运行一次要四十分钟。
而且输出的结果漏洞百出。
他检查了一遍又一遍,发现问题出在数据本身。有些老师的时间偏好没有被录入系统,有些教室的容量信息是错的,有些课程的依赖关系没有被标记。数据不完整,模型就不可能给出正确的结果。
他只好一边跑模型,一边手动补数据。白天补数据,晚上跑模型。模型跑完了,检查结果,发现问题,调整参数,再跑一遍。
三天下来,他只睡了不到十个小时。
第九天晚上,凌晨三点,他靠在椅背上,盯着屏幕,脑子里一片空白。
屏幕上是一张约束关系图。每个节点是一个变量——老师、教室、时间段、课程。每条边是一个约束——老师的时间偏好、教室的容量限制、课程的依赖关系。节点有几千个,边有几万条,密密麻麻,像一团乱麻。
他盯着那张图,忽然想起方小满说的那句话。
"有些问题不是算法问题。"
他闭上眼睛,深吸一口气。
也许这个问题真的超出了他的能力边界。
也许一个大专生,真的做不了这么复杂的商业项目。
也许他应该跟张副总说,做不了,对不起。
他睁开眼睛,看着屏幕,手指放在键盘上,不知道该敲什么。
第十天,他动摇了。
早上起来,他没有打开电脑。他躺在床上,盯着上铺的床板,脑子里转的不是代码,而是一个问题:我为什么要接这个项目?
为了钱?张副总没说给多少钱,只是说"做出来带你见张老板"。
为了证明自己?证明给谁看?张副总?张老板?还是他自己?
为了积累经验?什么经验?失败的经验?
他翻了个身,把脸埋在枕头里。
方小满从洗手间出来,看到他还在床上,愣了一下:"你今天不写代码了?"
"写不动了。"林知行闷闷地说。
方小满走到他床边,坐下来:"咋了?"
林知行没说话。
"是不是数据的问题?"方小满问,"我昨天又帮你整理了一批,你看没看?"
林知行还是没说话。
方小满沉默了一会儿,然后说:"你要是不想做,就算了。我跟张叔说一声,道个歉。"
林知行翻过身来,看着他:"你不怪我?"
"怪你干啥?"方小满笑了,"你又不是神,做不了就做不了呗。大不了再找别的项目。"
林知行看着他,心里有点感动。
他从大一认识方小满到现在,三年了。这三年里,方小满从来不会因为他做不好什么事情而嘲笑他或者责怪他。他只会说"大不了再找别的",或者"你要是不想做就算了"。
这种无条件的支持,有时候比技术上的帮助更重要。
他从床上坐起来,深吸一口气:"我再试试。"
方小满拍了拍他的肩膀:"行,那我先去上课了。你要是需要数据,跟我说。"
林知行点点头。
第十一天,他换了一个思路。
不再试图一次性求解所有约束,而是把约束分成三类:硬约束、强软约束、弱软约束。硬约束必须满足——比如同一个老师不能在同一时间段上两节课。强软约束尽量满足——比如老师的时间偏好。弱软约束尽量满足——比如老师的通勤距离。
他把优先级设置成:硬约束 > 强软约束 > 弱软约束,然后重新跑了一遍模型。
结果好了一些,但还是有问题。
强软约束之间有冲突——比如张老师希望周二上午没课,但李老师希望周二上午有课。他的模型处理不了这种冲突,只能随机选择一个满足,另一个忽略。
他想了一下午,想不出怎么解决。
晚上,他跟方小满去食堂吃饭。方小满看他脸色不好,问他怎么了。
林知行把问题说了一遍。
方小满听完,想了想:"那你就别让机器选啊。让老师自己选。"
林知行愣了一下:"什么意思?"
"我的意思是,"方小满说,"如果两个老师的偏好冲突,你就把这个冲突列出来,发给他们,让他们自己商量。商量好了,你再录入系统。"
林知行盯着他,忽然反应过来。
对啊。
为什么要让机器做所有的决定?
有些约束冲突,机器解决不了,但人可以解决。机器的强项是处理海量数据和复杂计算,人的强项是处理人际关系和主观偏好。把这两者结合起来,不就行了?
他放下筷子,掏出手机,开始写代码。
方小满在旁边看着他,无奈地笑了:"你能不能先吃饭?"
林知行没理他,继续敲代码。
第十二天夜里,他盯着屏幕上那张约束关系图,脑子里忽然冒出一个想法。
他一直在试图用一个大模型解决整个问题——把所有约束条件都输入进去,让模型一次性输出最优解。但这个思路本身就是错的。因为约束变量太多、约束关系太复杂,一个大模型根本处理不了。
为什么不把问题拆成几个小问题?
他拿起笔,在笔记本上画了一个流程图。
排课分成三个阶段:
第一阶段:初排。把硬约束输入模型,输出一个初步的排课方案。这个方案不考虑软约束,只保证基本的时间和教室不冲突。
第二阶段:调整。把强软约束输入模型,对初排方案进行调整。如果有冲突,列出来,发给相关的老师,让他们自己商量。
第三阶段:评价。把弱软约束输入模型,对调整后的方案进行评价。如果有问题,微调;如果没有问题,输出最终方案。
三个阶段,每个阶段用不同的策略,每个阶段处理不同类型的约束。
这个思路不是最优解——分阶段处理肯定会损失一些全局最优性。但它能把复杂度从O(n³)压到O(n² log n)。
他盯着那个流程图,心跳开始加速。
这个思路,能行。
他关掉电脑,出门走了两圈。
十二月底的夜风很冷,吹在脸上有点刺痛。他裹紧外套,沿着学校的小路慢慢走。路灯把他的影子拉得很长,投在地上,像一个移动的感叹号。
他想起方小满说的那句话:"有些问题不是算法问题。"
有些问题,需要拆成几个小问题。
有些约束,需要人来解决而不是机器。
有些决定,需要让利害相关方自己做而不是替他们做。
他走了两圈,回到宿舍,重新打开电脑。
第十三天,他重写了整个模型。
新的模型分成三个模块:初排模块、调整模块、评价模块。每个模块都有独立的输入和输出,模块之间通过接口连接。
初排模块用回溯搜索算法,只处理硬约束,输出一个基本可行的排课方案。
调整模块用启发式搜索算法,处理强软约束。如果有冲突,生成冲突报告,发给相关的老师。
评价模块用评分函数,处理弱软约束,输出一个0-100的评分。评分越高,方案越好。
他花了一天时间写代码,一天时间调试,一天时间测试。
第十三天晚上,凌晨两点,他点击了"运行"按钮。
进度条开始走动。
一分钟。两分钟。五分钟。十分钟。
进度条走到了100%。
屏幕上显示出结果:
初排完成率:98.7%。
调整完成率:94.2%。
综合评分:87.3分。
冲突报告:23条。
他检查了冲突报告,发现大部分是老师时间偏好的冲突——张老师希望周二上午没课,但李老师希望周二上午有课。这些冲突需要人工处理,但数量不多,完全可以接受。
他靠在椅背上,长出一口气。
勉强跑通了。
方小满从上铺探下头来,看到他的表情,问:"搞定了?"
"勉强。"林知行说,"还有23条冲突需要人工处理。"
"23条算个屁啊。"方小满笑了,"之前几千条冲突呢,你这不就搞定了?"
林知行没接话,但嘴角翘了一下。
他看了看屏幕右下角的时间:凌晨两点十七分。
距离张副总给的期限,还有二十六个小时。
他关掉电脑,爬上床,闭上眼睛。
脑子里还在转,但这次转的不是代码,而是一个问题:
这个demo,够不够好?
他不知道。
但他知道,他已经尽力了。
剩下的,交给张副总来判断吧。
他翻了个身,把脸埋在枕头里,很快就睡着了。
这是十几天来,他睡得最踏实的一觉。