矩形版 81 个式子,三角版 45 个,
多出来的 36 个全是重复(3*4 和 4*3 各算一遍)。
单个式子的最大显示宽度是 6 格,下三角最宽那一行占 70 格。
上一节算数列,这一节练排版。 乘法表本身谁都会打,但「四种版式的行数、个数、宽度各是多少」很少有人真算过。
一、四种版式
Demo 打了四种:矩形、下三角、上三角、右对齐。 - 矩形:九行九列,每行 9 个,共 81 个式子。 - 下三角:第 i 行有 i 个,共 45 个。 - 上三角:第 i 行从 i 数到 9,也是 45 个,只是顺序反了。 - 右对齐:把数值补到固定宽度,位数不齐也能对齐。 前三种是「打多少」的差别,第四种是「怎么对齐」的差别。两类问题得分开处理。
二、81 减 45 等于 36,这 36 个是重复
乘法交换律告诉我们 3*4 和 4*3 是一回事。矩形版把它们各算一遍,所以多出 36 个。
45 是怎么来的?1+2+…+9 = 45。81 - 45 = 36,正好是「重复的个数」。
别小看这个数。做报表、做特征交叉、做 A/B 对比,都涉及「组合数」。
n 个东西两两组合是 n(n-1)/2,本例 36;允许自己跟自己组合就是 n(n+1)/2,本例 45。
这两个公式记混,算出来的样本量会差一倍——而样本量差一倍,成本差一倍。
三、对齐:%-2d 与显示宽度
乘法表要好看,关键是让每个式子的宽度一样。9*9=81 是 6 个字符,1*1=1 只有 5 个。
Demo 用 %-2d 把乘积补到两格宽,于是 1*8= 8 和 9*9=81 都是 6 格。
这就是那张表能对齐的原因。
然后算总宽:下三角最宽的一行是第 9 行,9 个 6 格的式子加 8 个两格的分隔,一共 70 格。
70 格这个数是有用的:终端一般 80 格,70 格刚好放得下;再宽就会折行,一折行整个表就散了。
四、为什么这件事值得较真
有人会说:打个乘法表而已,费这么大劲干什么。 但「输出对齐」是所有日志、报表、命令行工具的基本功。 对齐的背后是「先算宽度、再排版」这个两步动作,少了它就只能靠肉眼挪空格。 而且本例用到的三个数——81、45、36——全是算出来的,不是数出来的。 能用公式算的东西,就不要靠眼睛数。 本书所有 Demo 的表格输出,用的都是同一套「先算宽度再排」的思路。 你看它们齐,不是手工凑的,是代码算的。
五、动手 Demo:四种排版与它的真实宽度
按四种版式打出乘法表的前几行并统计行数;用 %-2d 做右对齐版;统计矩形与三角版的式子个数;
最后算出单个式子的最大显示宽度,以及下三角最宽那一行的总宽度。
def block(rows, title):
lines = [title]
for i, js in rows:
lines.append(' '.join('%d*%d=%-2d' % (j, i, i * j)
for j in js).rstrip())
return lines
lower = [(i, list(range(1, i + 1))) for i in range(1, 10)]
upper = [(i, list(range(i, 10))) for i in range(1, 10)]
full = [(i, list(range(1, 10))) for i in range(1, 10)]
cnt_full = sum(len(js) for _, js in full) # 81
cnt_low = sum(len(js) for _, js in lower) # 45
wid = max(len('%d*%d=%2d' % (j, i, i * j))
for i in range(1, 10) for j in range(1, i + 1))
实跑输出:
--------------------------------------------------------------------
实例 4:九九乘法口诀表,四种排版与它的真实宽度
--------------------------------------------------------------------
【一】九行九列矩形(81 个式子)
1*1=1 2*1=2 3*1=3 4*1=4 5*1=5 6*1=6 7*1=7 8*1=8 9*1=9
1*2=2 2*2=4 3*2=6 4*2=8 5*2=10 6*2=12 7*2=14 8*2=16 9*2=18
1*3=3 2*3=6 3*3=9 4*3=12 5*3=15 6*3=18 7*3=21 8*3=24 9*3=27
…… 共 9 行
【二】下三角(45 个式子)
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
…… 共 9 行
【三】上三角(也是 45 个式子,只是顺序反了)
1*1=1 2*1=2 3*1=3 4*1=4 5*1=5 6*1=6 7*1=7 8*1=8 9*1=9
2*2=4 3*2=6 4*2=8 5*2=10 6*2=12 7*2=14 8*2=16 9*2=18
……
矩形式个数 81
三角式个数 45
省下的个数 36 个
矩形版重复了 36 个式子(3*4 和 4*3 各算一遍)。
【四】右对齐版——数字位数不齐时,靠 %-2d 一类的宽度补出来:
1*8= 8 | 2*8=16 | 3*8=24 | 4*8=32 | 5*8=40 | 6*8=48 | 7*8=56 | 8*8=64
1*9= 9 | 2*9=18 | 3*9=27 | 4*9=36 | 5*9=45 | 6*9=54 | 7*9=63 | 8*9=72 | 9*9=81
单个式子的最大显示宽度 6 格
下三角最宽那一行 70 格(第 9 行)
四种版式打出来的前三行很清楚:矩形每行 9 个,下三角第 2 行 2 个,上三角第 2 行从 2 数到 9。
个数统计:矩形 81、三角 45、多出 36 个。36 正好是「两两组合」的个数。
右对齐版看最后两行最明显:1*9= 9 和 9*9=81 都是 6 格,小数点位置对得整整齐齐。
单个式子最大宽度 6 格;下三角最宽那一行 70 格(9 个 6 格 + 8 个两格分隔)。
70 格在 80 格的终端里刚好不折行。
30 秒实验
- 把乘法表改成 12×12,重新算一遍四个数(81/45/36/70 会怎么变)。
- 把
%-2d换成%-3d,量一下下三角最宽那行变成多少格。 - 想一想:为什么「两两组合」的个数是
n(n-1)/2而不是n²?
常见误区
- 「乘法表随便打打就行」——80 格终端一折行,整个表就散了。
- 「81 就是 9×9,没别的数」——三角版只有 45,多出来的 36 是重复。
- 「对齐靠手动加空格」——宽度一变就全歪,得先算再排。
自测三问(点开对答案)
1. 矩形版和三角版差多少个式子?为什么? —— 差 36 个,因为 `3*4` 和 `4*3` 在矩形版里各算了一遍。 2. 下三角最宽那一行占多少格? —— 70 格:9 个 6 格式子加 8 个两格分隔。 3. `%-2d` 在这个例子里起什么作用? —— 把乘积补到 2 格宽,让 `1*1=1` 和 `9*9=81` 都是 6 格,才能对齐。最后一个实例最好玩:写个猜数字游戏,顺便看看两种策略差多少。
- ☐ (本节完,下一节 3.3.5 实例5:使用Python制作猜数字游戏)