九月初请了四天假去海边,行李箱里塞了一本最不适合度假的书:Donald Knuth 的《计算机程序设计艺术》第一卷。朋友看到以后笑了很久,问我是不是打算在沙滩上推公式。答案是:是的,而且推得很开心。这篇是那四天的读书笔记,也是一点关于“慢书”的感想。

为什么是这本书

《计算机程序设计艺术》(The Art of Computer Programming,下文简称 TAOCP)的故事本身就很像一个关于“慢”的寓言。Knuth 在 1962 年开始写它,最初只打算写一本讲编译器的书;写着写着,大纲膨胀成了七卷。第一卷《基本算法》在 1968 年出版;从动笔算起,六十多年过去了,这套书仍然没有写完。

我书架上这本第三版已经放了好几年,每次翻开都卡在第一章的数学准备里。平时读不下去的原因很简单:它要求你停下来。读一页,算一页,有时一个习题就是一整个下午。而海边的四天,恰好什么都不缺,就缺需要停下来的东西。

习题的等级

TAOCP 最有名的设计之一,是给每道习题标上难度等级。我在沙滩上抄了一份,贴在书的扉页上:

  • 00:看完题目就能回答;
  • 10:简单题,想一分钟左右;
  • 20:中等难度,大概要十五到二十分钟;
  • 30:有一定难度,可能要花上几个小时;
  • 40:相当难,足够当一学期的课程项目;
  • 50:研究问题,写书的时候还没有人完全解决。

等级前面还可能有个 M,表示偏数学;HM 表示需要高等数学;有些题前面画着一个小三角,表示特别推荐。这套等级最打动我的地方,是它的诚实:它不假装每道题都同样重要,也不假装每个读者都该做完所有的题。它只是告诉你每道题的“价钱”,然后让你自己决定花多少。

那四天里,我做完的最难的一道题是 22 级。很普通的成绩,但做出来的那一刻,海风都好像更凉快了一些。

潮汐与算法 E

第一卷的第一个算法是欧几里得算法,Knuth 叫它算法 E。它只有三步:求余数;余数为零就结束;否则把除数和余数挪到前面,回到第一步。用 Python 写出来是这样:

euclid.pypython
def gcd(m: int, n: int) -> int:
    """TAOCP 第一卷的算法 E:欧几里得算法,要求 m、n 都是正整数。"""
    while True:
        r = m % n  # E1:求余数
        if r == 0:  # E2:余数为零?
            return n
        m, n = n, r  # E3:互换,回到 E1

紧接着,Knuth 列出了一个算法应当具备的五个性质:有穷性、确定性、输入、输出、可行性。坐在海边读这一段的时候,潮水正在退。我忍不住想,潮汐算不算一个算法?它有输入(月亮和太阳的位置),有确定的规则,每一步都“可行”,却没有有穷性——它永远不会停。在 Knuth 的定义里,这样的过程只能叫“计算方法”,不能叫算法。

一只渡鸦掠过浪花拍打的礁石海岸
图 1 · 退潮的时候,正好读完 1.2.1 节的数学归纳法Illustration · AstreoX

我很喜欢这个区分。一个算法之所以是算法,是因为它会结束;而读书这件事,大概更接近潮汐。

一张没有拿到的支票

Knuth 有个流传很广的习惯:谁第一个发现他书里的错误,就会收到一张 2.56 美元的支票。按他的说法,这是“一个十六进制美元”。很多收到支票的人从来没有兑现过,而是把它装裱起来挂在墙上。

我当然没有找到任何错误。回程的高铁上,我数了数书页边上的铅笔痕迹,四天一共读了不到四十页。按这个速度,读完第一卷也要两个多月——前提是每天都在海边。可我并不着急:这套书 Knuth 写了六十多年,读者花上几年慢慢读,好像也说得过去。