08.常用数据结构之列表-1
常用数据结构之列表-1
在开始本节课的内容之前,我们先给大家一个编程任务,将一颗色子掷 6000 次,统计每种点数出现的次数。这个任务对大家来说应该是非常简单的,我们可以用 1 到 6 均匀分布的随机数来模拟掷色子,然后用 6 个变量分别记录每个点数出现的次数,相信通过前面的学习,大家都能比较顺利的写出下面的代码。
"""
将一颗色子掷6000次,统计每种点数出现的次数
Author: XX
Version: 1.0
"""
import random
f1 = 0
f2 = 0
f3 = 0
f4 = 0
f5 = 0
f6 = 0
for _ in range(6000):
face = random.randrange(1, 7)
if face == 1:
f1 += 1
elif face == 2:
f2 += 1
elif face == 3:
f3 += 1
elif face == 4:
f4 += 1
elif face == 5:
f5 += 1
else:
f6 += 1
print(f'1点出现了{f1}次')
print(f'2点出现了{f2}次')
print(f'3点出现了{f3}次')
print(f'4点出现了{f4}次')
print(f'5点出现了{f5}次')
print(f'6点出现了{f6}次')上面的代码非常有多么“丑陋”相信就不用我多说了。当然,更为可怕的是,如果我们要掷两颗或者掷更多的色子,然后统计每种点数出现的次数,那就需要定义更多的变量,写更多的分支结构,大家想想都会感到恶心。讲到这里,相信大家心中已经有一个疑问了:有没有办法用一个变量来保存多个数据,有没有办法用统一的代码对多个数据进行操作?答案是肯定的,在 Python 语言中我们可以通过容器型变量来保存和操作多个数据,我们首先为大家介绍列表(list)这种新的数据类型。
创建列表
在 Python 中,列表是由一系元素按特定顺序构成的数据序列,这就意味着如果我们定义一个列表类型的变量,可以用它来保存多个数据。在 Python 中,可以使用[]字面量语法来定义列表,列表中的多个元素用逗号进行分隔,代码如下所示。
items1 = [35, 12, 99, 68, 55, 35, 87]
items2 = ['Python', 'Java', 'Go', 'Kotlin']
items3 = [100, 12.3, 'Python', True]
print(items1) # [35, 12, 99, 68, 55, 35, 87]
print(items2) # ['Python', 'Java', 'Go', 'Kotlin']
print(items3) # [100, 12.3, 'Python', True]说明:列表中可以有重复元素,例如
items1中的35;列表中可以有不同类型的元素,例如items3中有int类型、float类型、str类型和bool类型的元素,但是我们通常并不建议将不同类型的元素放在同一个列表中,主要是操作起来极为不便。
我们可以使用type函数来查看变量的类型,有兴趣的小伙伴可以自行查看上面的变量items1到底是什么类型。因为列表可以保存多个元素,它是一种容器型的数据类型,所以我们在给列表类型的变量起名字时,变量名通常用复数形式的单词。
除此以外,还可以通过 Python 内置的list函数将其他序列变成列表。准确的说,list并不是一个普通的函数,它是创建列表对象的构造器,后面的课程会为大家介绍对象和构造器这些概念。
items4 = list(range(1, 10))
items5 = list('hello')
print(items4) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(items5) # ['h', 'e', 'l', 'l', 'o']说明:
range(1, 10)会产生1到9的整数序列,给到list构造器中,会创建出由1到9的整数构成的列表。字符串是字符构成的序列,上面的list('hello')用字符串hello的字符作为列表元素,创建了列表对象。
列表的运算
我们可以使用+运算符实现两个列表的拼接,拼接运算会将两个列表中的元素连接起来放到一个列表中,代码如下所示。
items5 = [35, 12, 99, 45, 66]
items6 = [45, 58, 29]
items7 = ['Python', 'Java', 'JavaScript']
print(items5 + items6) # [35, 12, 99, 45, 66, 45, 58, 29]
print(items6 + items7) # [45, 58, 29, 'Python', 'Java', 'JavaScript']
items5 += items6
print(items5) # [35, 12, 99, 45, 66, 45, 58, 29]我们可以使用运算符实现列表的重复运算,运算符会将列表元素重复指定的次数,我们在上面的代码中增加两行,如下所示。
print(items6 * 3) # [45, 58, 29, 45, 58, 29, 45, 58, 29]
print(items7 * 2) # ['Python', 'Java', 'JavaScript', 'Python', 'Java', 'JavaScript']我们可以使用in或not in运算符判断一个元素在不在列表中,我们在上面的代码代码中再增加两行,如下所示。
print(29 in items6) # True
print(99 in items6) # False
print('C++' not in items7) # True
print('Python' not in items7) # False由于列表中有多个元素,而且元素是按照特定顺序放在列表中的,所以当我们想操作列表中的某个元素时,可以使用[]运算符,通过在[]中指定元素的位置来访问该元素,这种运算称为索引运算。需要说明的是,[]的元素位置可以是0到N - 1的整数,也可以是-1到-N的整数,分别称为正向索引和反向索引,其中N代表列表元素的个数。对于正向索引,[0]可以访问列表中的第一个元素,[N - 1]可以访问最后一个元素;对于反向索引,[-1]可以访问列表中的最后一个元素,[-N]可以访问第一个元素,代码如下所示。
items8 = ['apple', 'waxberry', 'pitaya', 'peach', 'watermelon']
print(items8[0]) # apple
print(items8[2]) # pitaya
print(items8[4]) # watermelon
items8[2] = 'durian'
print(items8) # ['apple', 'waxberry', 'durian', 'peach', 'watermelon']
print(items8[-5]) # 'apple'
print(items8[-4]) # 'waxberry'
print(items8[-1]) # watermelon
items8[-4] = 'strawberry'
print(items8) # ['apple', 'strawberry', 'durian', 'peach', 'watermelon']在使用索引运算的时候要避免出现索引越界的情况,对于上面的items8,如果我们访问items8[5]或items8[-6],就会引发IndexError错误,导致程序崩溃,对应的错误信息是:list index out of range,翻译成中文就是“数组索引超出范围”。因为对于只有五个元素的列表items8,有效的正向索引是0到4,有效的反向索引是-1到-5。
如果希望一次性访问列表中的多个元素,我们可以使用切片运算。切片运算是形如[start:end:stride]的运算符,其中start代表访问列表元素的起始位置,end代表访问列表元素的终止位置(终止位置的元素无法访问),而stride则代表了跨度,简单的说就是位置的增量,比如我们访问的第一个元素在start位置,那么第二个元素就在start + stride位置,当然start + stride要小于end。我们给上面的代码增加下面的语句,来使用切片运算符访问列表元素。
print(items8[1:3:1]) # ['strawberry', 'durian']
print(items8[0:3:1]) # ['apple', 'strawberry', 'durian']
print(items8[0:5:2]) # ['apple', 'durian', 'watermelon']
print(items8[-4:-2:1]) # ['strawberry', 'durian']
print(items8[-2:-6:-1]) # ['peach', 'durian', 'strawberry', 'apple']提醒:大家可以看看上面代码中的最后一行,想一想当跨度为负数时,切片运算是如何访问元素的。
如果start值等于0,那么在使用切片运算符时可以将其省略;如果end值等于N,N代表列表元素的个数,那么在使用切片运算符时可以将其省略;如果stride值等于1,那么在使用切片运算符时也可以将其省略。所以,下面的代码跟上面的代码作用完全相同。
print(items8[1:3]) # ['strawberry', 'durian']
print(items8[:3:1]) # ['apple', 'strawberry', 'durian']
print(items8[::2]) # ['apple', 'durian', 'watermelon']
print(items8[-4:-2]) # ['strawberry', 'durian']
print(items8[-2::-1]) # ['peach', 'durian', 'strawberry', 'apple']事实上,我们还可以通过切片操作修改列表中的元素,例如我们给上面的代码再加上一行,大家可以看看这里的输出。
items8[1:3] = ['x', 'o']
print(items8) # ['apple', 'x', 'o', 'peach', 'watermelon']两个列表还可以做关系运算,我们可以比较两个列表是否相等,也可以给两个列表比大小,代码如下所示。
nums1 = [1, 2, 3, 4]
nums2 = list(range(1, 5))
nums3 = [3, 2, 1]
print(nums1 == nums2) # True
print(nums1 != nums2) # False
print(nums1 <= nums3) # True
print(nums2 >= nums3) # False说明:上面的
nums1和nums2对应元素完全相同,所以==运算的结果是True。nums2和nums3的比较,由于nums2的第一个元素1小于nums3的第一个元素3,所以nums2 >= nums3比较的结果是False。两个列表的关系运算在实际工作并不那么常用,如果实在不理解就跳过吧,不用纠结。
元素的遍历
如果想逐个取出列表中的元素,可以使用for-in循环的,有以下两种做法。
方法一:在循环结构中通过索引运算,遍历列表元素。
languages = ['Python', 'Java', 'C++', 'Kotlin']
for index in range(len(languages)):
print(languages[index])输出:
Python
Java
C++
Kotlin说明:上面的
len函数可以获取列表元素的个数N,而range(N)则构成了从0到N-1的范围,刚好可以作为列表元素的索引。
方法二:直接对列表做循环,循环变量就是列表元素的代表。
languages = ['Python', 'Java', 'C++', 'Kotlin']
for language in languages:
print(language)输出:
Python
Java
C++
Kotlin知识点总结
一、列表的概念和特点
- 列表是由一系列元素按特定顺序组成的数据序列,类型名称为
list。 - 列表可以在一个变量中保存多个数据,元素之间使用逗号分隔,整体使用方括号
[]表示。 - 列表允许出现重复元素,也可以保存不同类型的元素;实际编程中通常建议一个列表中的元素具有相近的数据类型,便于统一处理。
- 列表是可变序列,可以通过索引或切片修改其中的元素。
- 因为列表通常保存多个同类数据,列表变量名常使用复数形式,例如
languages、scores和counters。
二、创建列表
items1 = [35, 12, 99]
items2 = ['Python', 'Java', 'Go']
items3 = list(range(1, 10))
items4 = list('hello')- 使用
[]可以直接创建列表,也可以创建空列表items = []。 - 使用
list构造器可以把其他序列转换为列表,例如list(range(1, 10))和list('hello')。 range(1, 10)产生1到9,转换成列表后得到包含这些整数的列表。- 字符串是字符序列,
list('hello')会把每个字符作为一个列表元素。 - 可以使用
type(items)查看变量的类型,使用len(items)获取列表元素个数。
三、列表的常用运算
- 拼接:
list1 + list2,把两个列表的元素连接起来,结果仍然是列表。 - 重复:
items * n,把列表中的元素整体重复n次。 - 成员判断:
value in items判断元素是否存在,value not in items判断元素是否不存在。 - 增量赋值:
items += other_items可以把另一个列表拼接到原列表中。 - 列表关系运算会按元素顺序进行比较,常见的是判断两个列表是否相等;列表大小比较在实际开发中不常用,理解基本规则即可。
四、索引和元素修改
- 列表索引从0开始,长度为
N的列表有效正向索引是0到N - 1。 - 反向索引从
-1开始,-1表示最后一个元素,-N表示第一个元素。 - 通过
items[index]访问元素,也可以直接给索引位置赋新值,例如items[2] = 'durian'。 - 列表是可变对象,修改元素不会改变列表的长度。
- 索引不能超出有效范围,否则会产生
IndexError: list index out of range。 - 使用索引前应确认列表长度,尤其要注意空列表不能访问任何索引位置。
五、切片运算
items[start:end:stride]start表示起始位置,包含该位置;end表示终止位置,不包含该位置;stride表示步长。items[1:3]表示访问索引1和2;切片结果仍然是列表。start省略时默认为列表开头,end省略时默认为列表结尾,步长省略时默认为1。- 步长为正数时通常从左向右取值,步长为负数时从右向左取值;反向切片时起点和终点的方向要匹配。
- 常见写法:
items[:]复制全部元素,items[::2]隔一个元素取一个,items[::-1]将元素倒序取出。 - 切片不会因为起止位置接近边界而像索引那样轻易产生越界错误,但仍要确认切片方向和步长是否符合预期。
六、切片修改列表
- 可以把一个列表赋值给切片,用一次操作替换多个连续元素,例如
items[1:3] = ['x', 'o']。 - 切片赋值的右侧必须是可迭代对象,替换元素的数量可以与原切片长度不同,因此也可以实现插入或删除多个元素。
- 修改列表时要区分
items[index] = value和items[start:end] = values:前者修改一个元素,后者替换一个切片。
七、列表遍历
- 需要同时使用索引时,可以通过索引遍历:
for index in range(len(languages)):
print(languages[index])- 只需要逐个处理元素时,推荐直接遍历列表:
for language in languages:
print(language)len得到元素个数,range(len(items))产生有效索引范围。- 直接遍历代码更简洁;只有在需要使用元素位置、修改指定位置或同时处理索引时,才优先考虑索引遍历。
- 遍历过程中不要随意改变列表长度,否则可能造成跳过元素或逻辑混乱。
八、综合复习示例:用列表统计骰子点数
讲义开头使用6个变量分别统计骰子点数,列表可以把这6个计数值统一保存和处理:
"""
将一颗色子掷6000次,统计每种点数出现的次数
Author: XX
Version: 1.1
"""
import random
counters = [0] * 6
# 模拟掷色子记录每种点数出现的次数
for _ in range(6000):
face = random.randrange(1, 7)
counters[face - 1] += 1
# 输出每种点数出现的次数
for face in range(1, 7):
print(f'{face}点出现了{counters[face - 1]}次')上面的代码中,我们用counters列表中的六个元素分别表示 1 到 6 点出现的次数,最开始的时候六个元素的值都是 0。接下来,我们用 1 到 6 均匀分布的随机数模拟掷色子,如果摇出 1 点,counters[0]的值加 1,如果摇出 2 点,counters[1]的值加 1,以此类推。大家感受一下,由于使用了列表类型加上循环结构,我们对数据的处理是批量性的,这就使得修改后的代码比之前的代码要简单优雅得多。
九、复习时的易错点
- 把列表的第一个元素误认为索引1;Python 列表的第一个元素索引是0。
- 混淆正向索引和反向索引,尤其是把
-1误认为倒数第二个元素。 - 忘记切片的终止位置不包含,例如
items[1:3]不包含索引3。 - 误以为切片会返回单个元素;索引通常返回一个元素,切片返回一个列表。
- 对空列表或超出范围的索引进行访问,导致
IndexError。 - 用
[0] * 6创建计数列表时,适合保存数字等不可变值;以后处理嵌套列表等可变对象时要特别注意对象引用问题。 - 使用
face - 1把骰子点数1到6转换为列表索引0到5,这是计数示例中最关键的下标映射。