11.5 使用嵌套循环

    那么我们能够用嵌套循环做些什么呢?嗯,嵌套循环最擅长的工作就是得出一系列决定的所有可能的排列和组合。

    术语箱
    排列(permutation)是一个数学概念,表示结合一组事物的唯一方式。组合(combination)与它很类似。它们的区别在于,对于组合,顺序并不重要,而排列中顺序会有影响。
    如果要从 1 到 20 选择 3 个数,可以选择
    • 5, 8, 14
    2, 12, 20
    等等。如果想建立一个列表,列出从 1 到 20 选择 3 个数的所有排列,下面这两项是不同的:
    • 5, 8, 14
    8, 5, 14
    这是因为,对于排列,元素出现的顺序很重要。如果建立一个包含所有组合的列表,下面这些都会视为一项:
    • 5, 8, 14
    8, 5, 14
    • 8, 14, 5
    这是因为对于组合来说,顺序并不重要。

    要解释这个问题,最好的办法就是举一个例子。下面假设你要在学校开春季交易会期间开个热狗店,你想做个广告海报,用数字显示如何订购热狗、小面包、番茄酱、芥末酱和洋葱的所有可能的组合。所以我们需要得出总共有多少种可能的组合。

    考虑这个问题的一种方法就是使用决策树(decision tree)。下面的图显示了这个热狗问题的决策树。

    空标题文档 - 图1

    每个决策点都有两种选择,是(Y)或者否(N)。这棵树的每一条不同的路径分别描述了热狗各部分的不同的组合。这里突出显示的路径是这样选择的:热狗选择 Y,小面包选择 N,芥末酱选择 Y,番茄酱选择 Y 。

    现在我们使用嵌套循环来列出所有组合,也就是这棵决策树的所有路径。由于这里有 5 个决策点,所以在我们的决策树中有 5 层,相应地,在程序中就会有 5 个嵌套循环。(上图只显示了决策树的前 4 层。)

    在 IDLE 编辑器窗口中键入代码清单 11-6 中的代码,保存为 hotdog1.py。

    代码清单 11-6 热狗组合

    空标题文档 - 图2

    看到这些循环是如何一个套一个的了吗?这正是嵌套循环,即一个循环放在另一个循环中。

    • 外循环(热狗循环)运行两次。

    • 对热狗循环的每一次迭代,小面包循环运行两次,所以它会运行 2 × 2 = 4 次。

    • 对小面包循环的每一次迭代,番茄酱循环运行两次,所以它会运行 2 × 2 × 2 = 8 次。

    依此类推。

    最内层循环(嵌套最深的循环,也就是洋葱循环)会运行 2 × 2 × 2 × 2 × 2 = 32 次。这就涵盖了所有可能的组合。因此共有 32 种可能的组合。

    如果运行代码清单 11-6 中的程序,会得到下面的结果:

    >>> =========================== RESTART ===========================
    >>>
    Dog Bun Ketchup Mustard Onions
    # 1 0 0 0 0 0
    # 2 0 0 0 0 1
    # 3 0 0 0 1 0
    # 4 0 0 0 1 1
    # 5 0 0 1 0 0
    # 6 0 0 1 0 1
    # 7 0 0 1 1 0
    # 8 0 0 1 1 1
    # 9 0 1 0 0 0
    # 10 0 1 0 0 1
    # 11 0 1 0 1 0
    # 12 0 1 0 1 1
    # 13 0 1 1 0 0
    # 14 0 1 1 0 1
    # 15 0 1 1 1 0
    # 16 0 1 1 1 1
    # 17 1 0 0 0 0
    # 18 1 0 0 0 1
    # 19 1 0 0 1 0
    # 20 1 0 0 1 1
    # 21 1 0 1 0 0
    # 22 1 0 1 0 1
    # 23 1 0 1 1 0
    # 24 1 0 1 1 1
    # 25 1 1 0 0 0
    # 26 1 1 0 0 1
    # 27 1 1 0 1 0
    # 28 1 1 0 1 1
    # 29 1 1 1 0 0
    # 30 1 1 1 0 1
    # 31 1 1 1 1 0
    # 32 1 1 1 1 1

    这 5 个嵌套循环可以得到热狗、小面包、番茄酱、芥末酱和洋葱的所有可能的组合。

    代码清单 11-6 中,我们使用了制表符来实现对齐,也就是符号 \t。我们还没有讨论到打印格式,不过如果你想了解更多,可以先看看第 21 章。

    这里使用了一个名为 count 的变量对各个组合编号。例如,一个带小面包和芥末酱的热狗就是 #27。当然,这 32 个组合中有些组合并没有实际意义。(如果没有小面包,但有番茄酱和芥末酱,这样的热狗肯定会弄得一团糟。)要知道有句话是这么说的:“顾客就是上帝!”

    空标题文档 - 图3

    计算卡路里

    因为如今所有人都很关心营养问题。下面为菜单上的每个组合增加一个卡路里计算。(你可能不太关心卡路里,不过我打赌你的爸爸妈妈一定很关心!)我们可以利用这个机会使用 Python 的一些数学功能(这在第 3 章学过)。

    我们已经知道了每个组合里有哪些项。现在需要的就是每一项的卡路里数。然后可以在最内层循环中把各项的卡路里数加起来。

    下面的代码设置了每一项有多少卡路里:

    dog_cal = 140
    bun_cal = 120
    mus_cal = 20
    ket_cal = 80
    onion_cal = 40

    现在只需要把它们加起来。我们知道每个菜单组合中各项要么是 0 要么是 1。所以可以直接将数量乘以每一项的卡路里,像这样:

    tot_cal = (dog dog_cal) + (bun bun_cal) + \
    (mustard mus_cal) + (ketchup ket_cal) + \
    (onion * onion_cal)

    空标题文档 - 图4 由于运算的先后顺序是先算乘法再算加法,所以这里原本不需要加括号。之所以加括号是为了更容易地看出这是怎么做的。

    长代码行
    注意到以上代码中行末的反斜线(\)字符了吗?如果有一个很长的语句,在一行里放不下,就可以使用反斜线字符告诉 Python,“这一行还没有结束。下一行的内容也是这一行的一部分”。这里使用了两个反斜线把一个长代码行分成了 3 个小代码行。反斜线也称为行联接符(line continuation character),很多编程语言都有这种行联接符。
    还可以在整个表达式前后两边额外加一对小括号,这样不必使用反斜线也可以把语句分为多行,就像下面这样:

    tot_cal = ((dog dog_cal) + (bun bun_cal) +
    (mustard mus_cal) + (ketchup ket_cal) +
    (onion * onion_cal))

    综合上面的内容,增加卡路里计算的热狗程序版本如代码清单 11-7 所示。

    代码清单 11-7 能计算卡路里的热狗程序

    空标题文档 - 图5

    在 IDLE 中运行代码清单 11-7 中的程序,应该能得到这样的输出:

    >>> =========================== RESTART ===========================
    >>>
    Dog Bun Ketchup Mustard Onions Calories
    # 1 0 0 0 0 0 0
    # 2 0 0 0 0 1 40
    # 3 0 0 0 1 0 20
    # 4 0 0 0 1 1 60
    # 5 0 0 1 0 0 80
    # 6 0 0 1 0 1 120
    # 7 0 0 1 1 0 100
    # 8 0 0 1 1 1 140
    # 9 0 1 0 0 0 120
    # 10 0 1 0 0 1 160
    # 11 0 1 0 1 0 140
    # 12 0 1 0 1 1 180
    # 13 0 1 1 0 0 200
    # 14 0 1 1 0 1 240
    # 15 0 1 1 1 0 220
    # 16 0 1 1 1 1 260
    # 17 1 0 0 0 0 140
    # 18 1 0 0 0 1 180
    # 19 1 0 0 1 0 160
    # 20 1 0 0 1 1 200
    # 21 1 0 1 0 0 220
    # 22 1 0 1 0 1 260
    # 23 1 0 1 1 0 240
    # 24 1 0 1 1 1 280
    # 25 1 1 0 0 0 260
    # 26 1 1 0 0 1 300
    # 27 1 1 0 1 0 280
    # 28 1 1 0 1 1 320
    # 29 1 1 1 0 0 340
    # 30 1 1 1 0 1 380
    # 31 1 1 1 1 0 360
    # 32 1 1 1 1 1 400

    想想看,如果你自己手工计算所有这些组合的卡路里该是多么枯燥,即使用计算器来完成数学运算也会很乏味。编写一个程序,让它帮你把这些都算出来就有意思多了。运用循环和 Python 中的一点数学运算,这个任务对它来说简直是小菜一碟。

    你学到了什么

    在这一章,你学到了以下内容。

    • 嵌套循环。

    • 可变循环。

    • 排列和组合。

    • 决策树。

    测试题

    1. Python 中如何建立可变循环?

    2. Python 中如何建立嵌套循环?

    3. 下面的代码总共会打印出多少星号:

    for i in range(5):
    for j in range(3):
    print '*',
    print

    1. 第 3 题中的代码会得到什么输出?

    2. 如果一个决策树有 4 层,每层有两个选择,共有多少种可能的选择(决策树有多少条路径)?

    动手试一试

    1. 还记得第 8 章创建的倒计时定时器程序吗?在这儿呢,提醒你一下:
    import time
    for i in range (10, 0, -1):
    print i
    time.sleep(1)
    print "BLAST OFF!"

    使用一个可变循环修改程序。这个程序要询问用户向下计数应当从哪里开始,比如:

    Countdown timer: How many seconds? 4
    4
    3
    2
    1
    BLAST OFF!

    1. 根据第 1 题写的程序,让它除了打印各个数之外还要打印一行星号,如下:
    Countdown timer: How many seconds? 4
    4
    3
    2

    1

    BLAST OFF!

    (提示:可能需要使用一个嵌套循环。)