“yh-calc”表格程序的设计,如同一座刚刚打好地基的建筑,蓝图宏伟,但真正开始砌墙架梁时,才发现内部结构的复杂远超想象。软件组在实现了最基本的网格显示、数字和文本输入后,便一头撞上了坚硬的“算法瓶颈”——公式的解析与依赖关系的动态管理。
最初的方案简单粗暴:每当任何一个单元格的值发生变化时,就遍历整个表格的所有单元格,检查其公式中是否引用了发生变化的单元格,如果有,则重新计算该公式。这个方法在谢明华看来,无异于用高射炮打蚊子,效率低下得令人发指。在一个10x10的微小网格上尚可勉强运行,但一旦表格规模扩大,比如到100x100,计算量将呈指数级增长,足以拖垮目前原型机那有限的性能。
调试间里,徐工和几位内核软件工程师围在原型机前,屏幕上显示着那个10x10的测试网格。徐工在a1单元格输入数字10,在b1单元格输入数字20,然后在c1单元格输入公式“=a1+b1”,屏幕上立刻显示出正确的结果30。
“现在,改变a1的值为5。”谢明华在一旁说道。
徐工照做。只见屏幕短暂地凝固了一下,虽然时间很短,但能明显感觉到迟滞,然后c1单元格的值才刷新为25。
“感觉到了吗?这仅仅是三个单元格。”谢明华指着屏幕,“如果我们有一百个、一千个单元格,其中几十个单元格都含有复杂的公式,并且相互引用,这样的全局遍历重算,会让系统瞬间卡死。”
众人沉默。他们都明白问题的严重性。这不仅仅是速度问题,更关乎程序的可用性。
“我们必须找到一种方法,只重新计算那些‘真正受到影响’的单元格。”谢明华走到旁边的小黑板前,画了一个简单的示意图,“我们需要创建一套‘依赖关系图’。”
他画出几个节点,代表单元格,用箭头表示引用关系。“比如,c1 依赖于 a1 和 b1,d1 的公式是 ‘=c12’,那么 d1 就依赖于 c1。当我们改变 a1 的值时,受影响的单元格是 a1 -> c1 -> d1,是一条链。我们不需要去检查与 a1 无关的 e1、f1 等单元格。”
“谢主任,这个思路我们讨论过,”一位负责内核算法的工程师面露难色,“难点在于,如何动态地创建和维护这个依赖关系图?公式是可以随时编辑修改的,可能这一刻c1引用a1,下一刻用户就把公式改成引用e1了。依赖关系是在不断变化的。”
“还有循环引用的问题,”,系统必须能检测到这种循环依赖,并报错,而不是陷入死循环导致崩溃。”
问题一个接一个,如同纠缠在一起的线团,找不到那个可以轻易抽出的线头。软件组尝试了几种不同的数据结构来存储依赖关系,但在处理动态更新和循环检测时,要么逻辑过于复杂容易出错,要么计算开销依然很大。
连续几天的讨论和尝试,进展甚微。实验室里弥漫着一种熟悉的焦灼感,与之前攻克磁盘格式化难题时如出一辙。徐工等人的脸上又挂上了熬夜后的疲惫和苦苦思索的痕迹。
谢明华的压力同样巨大。他拥有超越时代的见识,知道后世成熟的电子表格软件是如何处理这些问题的,比如使用拓扑排序来安排计算顺序,使用脏位标记(dirty fg)来避免不必要的计算。但如何用当下这个时代有限的程序语言和硬件资源,优雅而高效地实现这些思想,却是一个巨大的挑战。他不能直接给出答案,那样太过惊世骇俗,他必须引导团队自己去思考,去发现。
这天晚上,他带着一身的疲惫和满脑子的依赖关系图回到家中。已是华灯初上,四合院里飘散着各家各户的饭菜香气。
推开家门,温暖的灯光和一股淡淡的奶香扑面而来。王桂英正在厨房炒菜,林婉抱着小致远坐在外屋的椅子上,手里拿着一个红色的、会发出轻微响声的布制摇铃,逗弄着孩子。小致远快两个月了,脖子比之前硬实了不少,能短暂地抬起头,乌溜溜的大眼睛追随着母亲手中的摇铃,小嘴里发出“咿咿呀呀”的声音,小手试图去抓。
看到谢明华回来,林婉抬起头,温柔一笑:“回来了?今天好象比昨天更晚点。”
“恩,有点难题卡住了。”谢明华放下公文包,很自然地走过去,先俯身看了看儿子。小致远看到父亲的脸,注意力立刻从摇铃转移过来,小嘴巴咧开,露出一个无齿的笑容,可爱极了。
谢明华心中的烦闷瞬间被这笑容驱散了大半。他伸出手指,小家伙立刻用他那小而柔软的手紧紧抓住。
“咱们致远今天好象又精神了。”他看着儿子亮晶晶的眼睛,对林婉说。
“是啊,下午睡了挺久,这会儿正有劲呢。”林婉看着父子俩的交互,眼里满是幸福。
就在这时,晓婷拿着作业本从里屋出来,看到哥哥,立刻跑过来:“哥,你回来了!快帮我看看这道题,老师说有点超纲,我想了半天没想明白。”
谢明华接过本子,是一道关于逻辑推理的题目,涉及到几个元素的相互关系和排序。他抱着儿子,坐了下来,开始给妹妹讲解。他画出示意图,用箭头表示关系,引导晓婷一步步分析。
“……你看,a 排在 b 前面,c 排在 a 后面但在 d 前面,那么 b 和 c 之间,谁前谁后不一定,但 b 和 d 之间,一定是 b 在 d 前面,因为 a 在 b 后,c 在 a 后,d 在 c 后,所以这条链是 b -> a -> c -> d ……”
他讲解着,脑海中却仿佛有什么东西被触动了。箭头……链条……前后顺序……这不正象单元格之间的依赖关系吗?b 依赖于 a,a 依赖于 c,c 依赖于 d……要确定 b 的值,必须按顺序先确定 a、c、d 的值?不,好象反了……应该是被依赖的先计算……
他猛地停住,盯着草稿纸上自己画出的那条关系链,眼睛骤然亮了起来。拓扑排序!关键是计算顺序!必须保证在计算一个单元格之前,它所依赖的所有单元格都已经被计算过了!这不仅能解决依赖问题,还能在构建依赖图的过程中,自然检测出循环引用——如果存在循环,就无法进行拓扑排序!
“哥?你怎么了?”晓婷看着突然愣住、眼神发直的哥哥,疑惑地推了推他。
林婉也关切地望过来。
谢明华回过神,深吸一口气,将怀里咿咿呀呀的儿子交给林婉,用力揉了揉晓婷的头发,脸上露出了这几天来第一个真正轻松的笑容:“晓婷,你真是哥哥的福星!你又帮了哥哥一个大忙!”
他拿起那份画着关系链的草稿纸,如获至宝。
困扰团队数日的算法迷雾,竟在辅导妹妹功课的寻常瞬间,被一道简单的逻辑题照亮了方向。他仿佛已经看到,那条清淅的计算串行,正从错综复杂的依赖关系中,被一步步提炼出来。