列表和迭代

在前面的章节中我们已经大量使用了变量,在变量里我们存储过整数、浮点数、字符串等这些数据类型的值,这些数据类型被称为基本类型,它们的特点是只能存储一个值,比如你没办法把1和2同时存在变量a里面。如果想要往变量里存储多个值的话,就需要用到本章的主角了:列表!学习列表之后,我们就可以写出比较复杂的程序了,不过也不用担心列表很难,先通过一个趣味案例来体验一下列表。

趣味短语-列表初体验

在这个程序中,我们从3个列表中随机挑选出了3个词语,然后把它们组合成了一个句子,因为是随机挑选的,所以有时候会有奇怪的有趣的短语出现,来试试吧。

import random
#verbs: 动词  
verbs = ['吃','踢','跑','走','写','看','逛']
# adjectives: 形容词 
adjectives = ['漂亮的','明亮的','绿色的','白色的','光滑的','椭圆的','萌萌的']
# nouns: 名词
nouns = ['饭','球','路','作业','电视','商场','房子']
verb = random.choice(verbs)
adjective = random.choice(adjectives)
noun = random.choice(nouns)
# phrase: 短语
phrase ='屏幕前的你 ' + verb + ' '+ adjective + ' ' + noun
print(phrase)

以上这个程序就用到了3个列表:

  • 存储动词的列表
  • 存储形容词的列表
  • 存储名词的列表 然后通过random.choice()这个函数从列表中随机挑选了一个元素,再通过加号把他们连接在一起就组成了一个短语。怎么样,列表的基本用法还是比较简单的吧?

列表基本操作

接下来我们以班级里同学名字作为案例来详细讲一下列表的用法。 首先, 列出班里所有同学的名字,就可以用一个list表示:

>>> names = ['张三', '李四', '王五']
>>> names
 ['张三', '李四', '王五']

变量names就是一个list。用len()函数可以获得list元素的个数:

>>> len(names)
3

用索引来访问list中每一个位置的元素,记得索引是从0开始的:

>>> names[0]
'张三'
>>> names[1]
'李四'
>>> names[2]
'王五'
>>> names[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

当索引超出了范围时,Python会报一个IndexError错误,所以,要确保索引不要越界,记得最后一个元素的索引是len(names) - 1。如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素:

>>> names[-1]
'王五'

以此类推,可以获取倒数第2个、倒数第3个:

>>> names[-2]
'李四'
>>> names[-3]
'张三'
>>> names[-4]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

当然,倒数第4个就越界了。list是一个可变的有序表,所以,可以往list中追加元素到末尾:

>>> names.append('赵六')
>>> names
['张三', '李四', '王五','赵六']

也可以把元素插入到指定的位置,比如索引号为1的位置:

>>> names.insert(1, '孙七')
>>> names
['张三', '孙七', '李四', '王五','赵六']

要删除list末尾的元素,用pop()方法:

>>> names.pop()
'赵六'
>>> names
['张三', '孙七', '李四', '王五']

要删除指定位置的元素,用pop(i)方法,其中i是索引位置:

>>> names.pop(1)
'孙七'
>>> names
['张三', '李四', '王五']

要把某个元素替换成别的元素,可以直接赋值给对应的索引位置:

>>> names[1] = '周八'
>>> names
['张三', '周八', '王五']

列表list里面的元素的数据类型也可以不同,比如:

>>> L = ['Apple', 123, True]

list元素也可以是另一个list,比如:

>>> s = ['python', 'java', ['asp', 'php'], 'scratch']
>>> len(s)
4

要注意s只有4个元素,其中s[2]又是一个list,如果拆开写就更容易理解了:

>>> p = ['asp', 'php']
>>> s = ['python', 'java', p, 'scratch']

要拿到'php'可以写p[1]或者s[2][1],因此s可以看成是一个二维数组,类似的还有三维、四维……数组,不过很少用到。 如果一个list中一个元素也没有,就是一个空的list,它的长度为0:

>>> L = []
>>> len(L)
0

如果对列表做乘法的话,会发生什么事情呢?我们来尝试一下:

a = ['hello']*5

输出一下a,对比一下我们之前学过的字符串乘法,记住两者的不同。

a = 'hello'*5

迭代列表

接下来我们通过一个泡泡公司的案例来深入学习列表,泡泡公司通过不同的配方研制了不同的泡泡液,每种泡泡液能吹出的泡泡数量是不一样的,因为有很多种方案所以要通过列表来存储,代码如下:

scores = [60,50,60,58,54,54,58,50,52,54,48,69,34,55,51,52,44,51,69,64,66,55,52,61,46,31,57,52,44,18,41,53,55,61,51,44]

我们要实现的功能是把每一种泡泡方案的编号和得分以此输出,然后输出泡泡方案的总个数、最高分以及最高分对应的方案编号是哪个。 我们的第一个小任务是把列表中的每一个元素依次取出来,这个过程我们也称为迭代列表,我们可以利用之前学过的索引把其中一个元素取出来:

print('泡泡方案#1的得分为:',scores[0])

如果要依次全部取出来的话,就需要把所有的编号都写出来,虽然可以利用复制功能,但是那样依然很麻烦。

while循环

仔细分析每次输出得分的代码,其实只需要把索引每次增加1就可以了,我们可以使用之前学过的while循环来做,代码如下:

#回顾while循环的用法
scores = [60,50,60,58,54,54,58,50,52,54,48,69,34,55,51,52,44,51,69,64,66,55,52,61,46,31,57,52,44,18,41,53,55,61,51,44]
i = 0
length = len(scores)
while i < length:
    print('泡泡方案#',i,'的得分为:',scores[i])
    i= i + 1

输出的结果很正确,但是有一个小问题你注意到了吗?输出中#后面有一个多余的空格,导致文字并不连续:

泡泡方案# 35 的得分为: 44

之所以会有这个问题,是因为print函数虽然可以通过增加逗号的形式将多个内容一起输出,但是会在各个内容中间添加一个空格。我们可以通过加号链接的方式来解决这个问题,但是注意加号只能把两个字符串连接在一起,所以需要把整数类型的i转换为字符串之后再进行连接:

scores = [60,50,60,58,54,54,58,50,52,54,48,69,34,55,51,52,44,51,69,64,66,55,52,61,46,31,57,52,44,18,41,53,55,61,51,44]
i = 0
length = len(scores)
while i < length:
    print('泡泡方案#'+ str(i),'的得分为:',scores[i])
    i= i + 1

for循环-迭代的首选方法

虽然使用while循环是可以迭代处理列表额,不过首选的方法其实是使用一个for循环。可以把for循环想成是while循环的兄弟,这两个循环实际上做的是同样的事情,只不过,如果要基于某个条件循环时通常使用while循环,如果要迭代处理一个列表时往往会使用for循环。以上面的同学名字为例:

names = ['张三', '李四', '王五']
for name in names:
    print(name)

执行这段代码,会依次打印names的每一个元素:

张三
李四
王五

所以for i in ...循环就是把每个元素代入变量i,然后执行缩进块的语句。再比如我们想计算1-10的整数之和,可以用一个sum变量做累加:

sum = 0
for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
    sum = sum + i
print(sum)

如果要计算1-100的整数之和,从1写到100有点困难,幸好Python提供一个range()函数,可以生成一个整数序列,再通过list()函数可以转换为list。比如range(5)生成的序列是从0开始小于5的整数:

>>> list(range(5))
[0, 1, 2, 3, 4]

回到计算1-100的整数之和这道题,通过for语句就可以这么写了:

sum = 0
for i in range(101):
    sum = sum + i
print(sum)

运行上述代码,结果是不是也是5050呢。 另外,for循环的写法经常用在重复多少次的场景中,比如重复说5遍“我学会了!”:

for i in range(5):
    print('我学会了!')

是不是比while语句写出重复执行几次更简单一些呢?

range进一步学习

range函数可以生成从0到某个数的序列,也可以创建任意类型的数字区间。下面给出几个例子:

增加起始数:

range函数支持填入两个参数,第一个为起始数,第二个为结束数,第一个不填的话则默认为0,所以range(5)就相当于range(0,5):

range(5,10)
# 创建一个从5开始到10结束,包含5不包含10的序列,也就是5,6,7,8,9,10

增加步长

range函数也支持填入三个参数,第三个参数为步长,不填的话默认为1,所以range(5)相当于range(0,5),也相当于range(0,5,1):

range(3,10,2)
# 创建一个从3开始到10结束,包含5不包含10的步长为2的序列,也就是3,5,7,9

支持倒数

range(10,0,-1)
# 创建一个从10开始到0结束,包含10不包含0的步长为-1的序列,也就是10,9,8,7,6,5,4,3,2,1

找最高分方案与编号

学过了for循环之后,我们迭代泡泡方案的方法就可以这么写了:

scores = [60,50,60,58,54,54,58,50,52,54,48,69,34,55,51,52,44,51,69,64,66,55,52,61,46,31,57,52,44,18,41,53,55,61,51,44]
length = len(scores)
for i in range(length):
    print('泡泡方案#'+ str(i),'的得分为:',scores[i])

找最高分

迭代输出泡泡方案的编号和得分之后就该输出方案的个数和最高分了,个数比较简单,用len函数求一下列表的长度就可以,最高分怎么求呢? 我们的思路是这样的,新建一个变量high_score专门用来记录最高分,一开始把它设置为0,然后在迭代泡泡列表的时候检查一下当前得分是不是大于high_score,如果确实大于,就把当前得分存到high_score中。然后迭代结束的时候high_score中存储的就是列表中所有元素的最高分了! 代码如下:

# 求列表里面的最高分
scores = [60,50,60,58,54,54,58,50,52,54,48,69,34,55,51,52,44,51,69,64,66,55,52,61,46,31,57,52,44,18,41,53,55,61,51,44]
high_score = 0
length = len(scores)
for i in range(length):
    print('泡泡方案#'+ str(i),'得分为:',scores[i])
    if scores[i] > high_score:
        high_score = scores[i]
print("泡泡方案的个数为:",length)
print("泡泡方案的最高分为:",high_score)

有了最高分的思路,你能举一反三求出来最低分吗?尝试一下,后面我们来讲解。

找最高分方案编号

有了最高分之后,我们还得进一步找到哪个方案是最高分,这时候就得注意了,是不是有可能并列最高分呢? 所以找最高分编号的时候就不能想找最高分一样用变量来存储了,必须得使用列表以解决有可能有多个编号是最高分的情况。 我们的思路是这样的,新建一个列表类型的变量best专门用来记录最高分方案编号,一开始把它设置为空,找到编号之后把它添加到列表中,然后再迭代泡泡列表的时候检查一下当前得分是不是等于最高分,如果等于的话就把它的编号添加到best列表中。然后迭代结束的时候我们就找到了所有具有最高分的方案编号。代码如下:

# 求列表里面的最高分
scores = [60,50,60,58,54,54,58,50,52,54,48,69,34,55,51,52,44,51,69,64,66,55,52,61,46,31,57,52,44,18,41,53,55,61,51,44]
high_score = 0
length = len(scores)
for i in range(length):
    print('泡泡方案#'+ str(i),'得分为:',scores[i])
    if scores[i] > high_score:
        high_score = scores[i]
print("泡泡方案的个数为:",length)
print("泡泡方案的最高分为:",high_score)
# 求最高分的编号
best_solutions = []
for i in range(length):
    if scores[i] == high_score:
        best_solutions.append(i)
print('最优方案的编号是:',best_solutions)

更近一步考虑成本

求最低分程序分析:

scores = [60,50,60,58,54,54,58,50,52,54,48,69,34,55,51,52,44,51,69,64,66,55,52,61,46,31,57,52,44,18,41,53,55,61,51,44]
high_score = 0
low_score = 99
length = len(scores)
for i in range(length):
    print('泡泡方案编号'+ str(i),'得分为:',scores[i])
    if scores[i] > high_score:
        high_score = scores[i]
    if scores[i] < low_score:
        low_score = scores[i]
print("泡泡方案的个数为:",length)
print("泡泡方案的最高分为:",high_score)
print("泡泡方案的最低分为:",low_score)

回到泡泡方案,目前我们已经有了寻找得分最高的方案编号的程序,如果有很多方案都是最高分的话,我们还得进一步优化程序,考虑到不同方案的成本不同,我们最终要找出的方案是成本效益最好的方案:也就是得分最高的同时成本最低。 以下是我们的成本列表:

costs = [.25,.27,.25,.25,.25,.25,
         .33,.31,.25,.29,.27,.22,
         .31,.25,.25,.33,.21,.25,
         .25,.25,.28,.25,.24,.22,
         .20,.25,.30,.25,.24,.25,
         .25,.25,.27,.25,.26,.29]

成本列表costs跟得分列表时并行列表,因为对于每个分数,在相同的索引上会有一个对应的成本,比如索引0上的成本是索引0位置上的泡泡方案的成本,列表中其他成本和得分也是如此。

我们的思路是创建一个变量cost来保存成本效益最好的泡泡方案的成本,开始时要让它大于costs中的所有元素,创建一个变量most_effective用来保存成本效益最好的泡泡方案的索引,然后迭代处理每个泡泡方案:如果一个泡泡方案得分等于最高分并且它的成本比之前的泡泡方案成本低,则记录当前泡泡方案的索引和成本。

scores = [60,50,60,58,54,54,
          58,50,52,54,48,69,
          34,55,51,52,44,51,
          69,64,66,55,52,61,
          46,31,57,52,44,18,
          41,53,55,61,51,44]
costs = [.25,.27,.25,.25,.25,.25,
         .33,.31,.25,.29,.27,.22,
         .31,.25,.25,.33,.21,.25,
         .25,.25,.28,.25,.24,.22,
         .20,.25,.30,.25,.24,.25,
         .25,.25,.27,.25,.26,.29]
high_score = 0
length = len(scores)
for i in range(length):
    print('泡泡方案编号'+ str(i),'得分为:',scores[i])
    if scores[i] > high_score:
        high_score = scores[i]
print("泡泡方案的个数为:",length)
print("泡泡方案的最高分为:",high_score)
best_solutions = []
for i in range(length):
    if high_score == scores[i]:
        best_solutions.append(i)
print('最优方案的编号是:',best_solutions)
cost = 100.0
most_effective = 0
for i in range(length):
    if scores[i] == high_score and costs[i] < cost:
        most_effective = i
        cost = costs[i]
print('编号',most_effective,'是最优方案,它的成本是', costs[most_effective])

细心的同学可能会有疑问:我们已经知道了分数最高的泡泡方案,他们就存在best_solutions列表中,为什么还需要再次迭代检查每一个方案分数呢?确实是没有必要的,我们可以进一步优化我们的程序。 也有同学可能会问:这有很大区别吗?这重要吗?反正python运行的很快,都是一瞬间的事情。不!这很重要,这个涉及到程序的效率,对于一个比较小的列表来说,这确实没有太大的差别,但是如果有一个庞大的数据列表,如果能有更高效的方法的话,是会节省很多的时间的。 顺着这个思路,我们来分析以下优化后的程序:

for i in best_solutions:
    if costs[i] < cost:
        most_effective = i
        cost = costs[i]

这一次我们要迭代处理best_solutions列表而不是scores列表,我们使用每个best_solutions元素作为索引来比较costs列表中的元素,也就是这个方案对应的成本,比较完best_solutions里所有元素之后,也就得到了成本最低的方案。 仔细对比一下这个方法跟之前方法的不同,考虑这两个代码分别如何执行,你能看出这个版本在计算成本效益最好的方案时少做了多少工作吗?可能需要花些时间才能发现这两个代码的区别,耐心思考一下!

冒泡排序

依然是泡泡公司的需求,这次我们需要找出所有方案中最好的5个并且由高到低进行排序,以便公司可以对这些方案的发明人进行奖励。为此我们需要学习如何对列表中的元素进行排序。

普通排序方法

我们首先用一种相对比较笨但是很容易理解的方法来做,思路是这样的:

  1. 每次找到一个最小值;
  2. 把它存入新列表;
  3. 把最小值从原始列表中删除;
  4. 再次从原始列表中找最小值;
  5. 把它存入新列表;
  6. 依次类推 代码如下:
scores_old=[3,6,1,8,4,9]
scores_new=[]
a=len(scores_old)
for i in range(a):
    lowest=100
    number = 0
    b=len(scores_old)  # 重新计算原始列表的长度
    for j in range(b):
        if scores_old[j]<lowest:
            lowest=scores_old[j]
            number = j
    scores_new.append(lowest)
    del scores_old[number]
print(scores_new)

冒泡排序-初步

通过这个算法可视化网站https://visualgo.net/zh/sorting?slide=1-1可以查看冒泡排序执行的过程,几个核心要点:

  1. 嵌套循环:两个循环套在一起;
  2. 内部循环:迭代处理列表中的每一个元素(除了最后一个元素),将它与下一个元素进行比较,如果这个元素较大,就交换这两个值,等这个循环结束则找到了本次循环中的最大值并且放到了列表的最后;
  3. 外部循环:多次进行第2步的内部循环,每次都找到最大值并且放到最后,最终外部循环结束时整个列表就完成了排序;
def bubble_sort(scores):
    length = len(scores)
    for j in range(0,length):
        for i in range(0,length-1):
            if scores[i] > scores[i+1]:
                temp = scores[i]
                scores[i] = scores[i+1]
                scores[i+1] = temp
        print('第'+str(j+1)+'趟:')
        print(scores)
scores = [6,2,5,3,9]
bubble_sort(scores)
print(scores)
'''
smoothies = ['coconut','strawberry','banana','pineapple']
bubble_sort(smoothies)
print(smoothies)
'''

在这个过程中可以发现,如果列表提前完成了排序则很多比较的步骤是可以省略的,为此我们可以进一步优化冒泡排序这个程序。

冒泡排序-优化

优化的思路:设定一个变量用来标记是否要进行一趟比较,默认一开始是需要的,进入迭代之前设定成False,也就是说如果在一趟迭代之后,没有进行过两两交换,则下一次不再进行冒泡,这样就有可能提前结束冒泡的过程,代码如下。

def bubble_sort(scores):
    swapped = True
    j = 1
    while (swapped):
        swapped = False
        for i in range(0,len(scores)-1):
            if scores[i] > scores[i+1]:
                temp = scores[i]
                scores[i] = scores[i+1]
                scores[i+1] = temp
                swapped = True
        print('第'+str(j)+'趟:')
        print(scores)
        j = j + 1
scores = [6,2,5,3,9]
bubble_sort(scores)
print(scores)
smoothies = ['coconut','strawberry','banana','pineapple']
bubble_sort(smoothies)
print(smoothies)

最终方案

利用前面学到的冒泡排序方法实现泡泡公司的需求:计算得分排名前5的泡泡方案并依次输出:

def bubble_sort(scores,numbers):
    swapped = True
    while (swapped):
        swapped = False
        for i in range(0,len(scores)-1):
            if scores[i] < scores[i+1]:
                temp = scores[i]
                scores[i] = scores[i+1]
                scores[i+1] = temp
                temp = numbers[i]
                numbers[i] = numbers[i+1]
                numbers[i+1] = temp
                swapped = True
scores = [60,50,60,58,54,54,58,50,52,54,48,69,34,55,51,52,44,51,69,64,66,55,52,61,46,31,57,52,44,18,41,53,55,61,51,44]
numbers_of_scores = len(scores)
solution_numbers = list(range(numbers_of_scores))
bubble_sort(scores, solution_numbers)
print("排名最好的几个方案:")
for i in range(0,5):
    print(str(i+1) + ')',
        '最优方案编号#' + str(solution_numbers[i]),
        '得分:', scores[i])

嵌套练习

练习1

for i in range(1,3):
    for j in range(1,4):
        print((i+j)*"*")

练习2

for word in ['公牛','猫','狮子','老虎','山猫']:
    for i in range(2,9):
        letters = len(word)
        if(letters % i) == 0:
            print(i, word)

练习3

full = False
donations = []
full_load = 45
toys = ['机器人', '洋娃娃', '球', '乐高积木']
while not full:
    for toy in toys:
        donations.append(toy)
        size = len(donations)
        if(size >= full_load):
            full = True
            #continue
            #break
print("满了,一共", len(donations), '个玩具')
print(donations)
Last Updated:
Contributors: aibokalv, houlinsen