list 是一个可变的集合,其中的元素按_顺序_排列。和大多数集合一样(参见内置的 tuple、dict 和 set),数组可以存放对任意一种(或多种)数据类型的引用,其中也包括其他数组。和任何序列一样,可以用从左侧开始的 0-based index 编号或从右侧开始的 -1-based index 编号来访问元素。数组可以通过切片写法或<list>.copy()整体或部分复制。
数组同时支持通用和可变两类序列操作,例如min()/max()、<list>.index()、<list>.append()和<list>.reverse()。使用for item in <list>结构可以遍历数组中的元素。当同时需要元素下标和元素值时,可以使用for index, item in enumerate(<list>)。
在底层,lists 以动态数组实现,类似于 Java 的ArrayList类型,最常用于存储长度未知、成组的相似数据(字符串、数字、集合等)。数组是一种极其灵活、实用的数据结构,Python 中许多内置方法和操作都会以数组作为输出。
list 可以用方括号[]写成_字面量_,元素之间用逗号分隔:
>>> no_elements = []
>>> no_elements
[]
>>> one_element = ["Guava"]
>>> one_element
['Guava']
>>> elements_separated_with_commas = ["Parrot", "Bird", 334782]
>>> elements_separated_with_commas
['Parrot', 'Bird', 334782]
为了提高可读性,当 list 中元素很多或含有嵌套数据结构时,可以换行书写:
>>> lots_of_entries = [
"Rose",
"Sunflower",
"Poppy",
"Pansy",
"Tulip",
"Fuchsia",
"Cyclamen",
"Lavender"
]
>>> lots_of_entries
['Rose', 'Sunflower', 'Poppy', 'Pansy', 'Tulip', 'Fuchsia', 'Cyclamen', 'Lavender']
# Each data structure is on its own line to help clarify what they are.
>>> nested_data_structures = [
{"fish": "gold", "monkey": "brown", "parrot": "grey"},
("fish", "mammal", "bird"),
['water', 'jungle', 'sky']
]
>>> nested_data_structures
[{'fish': 'gold', 'monkey': 'brown', 'parrot': 'grey'}, ('fish', 'mammal', 'bird'), ['water', 'jungle', 'sky']]
list() 构造函数可以不带参数调用,也可以接收一个_可迭代对象_作为参数。构造函数会依次遍历该可迭代对象中的元素,并按顺序加入 list:
>>> no_elements = list()
>>> no_elements
[]
# The tuple is unpacked and each element is added.
>>> multiple_elements_from_tuple = list(("Parrot", "Bird", 334782))
>>> multiple_elements_from_tuple
['Parrot', 'Bird', 334782]
# The set is unpacked and each element is added.
>>> multiple_elements_from_set = list({2, 3, 5, 7, 11})
>>> multiple_elements_from_set
[2, 3, 5, 7, 11]
用 list 构造函数处理 string 或 dict 时,结果可能会出人意料:
# String elements (Unicode code points) are iterated through and added *individually*.
>>> multiple_elements_string = list("Timbuktu")
>>> multiple_elements_string
['T', 'i', 'm', 'b', 'u', 'k', 't', 'u']
# Unicode separators and positioning code points are also added *individually*.
>>> multiple_code_points_string = list('अभ्यास')
>>> multiple_code_points_string
['अ', 'भ', '्', 'य', 'ा', 'स']
# The iteration default for dictionaries is over the keys, so only key data is inserted into the list.
>>> source_data = {"fish": "gold", "monkey": "brown"}
>>> multiple_elements_dict_1 = list(source_data)
['fish', 'monkey']
由于 list 构造函数只接受_可迭代对象_(或不接受任何参数)作为参数,_不可迭代_的对象会抛出类型错误。因此,用字面量方式创建只含一个元素的 list 要容易得多。
# Numbers are not iterable, and so attempting to create a list with a number passed to the constructor fails.
>>> one_element = list(16)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
# Tuples *are* iterable, so passing a one-element tuple to the constructor does work, but it's awkward
>>> one_element_from_iterable = list((16,))
>>> one_element_from_iterable
[16]
数组中的元素(以及其他序列类型 str 和 tuple 中的元素)可以通过 0-based index 和_方括号写法_来访问。下标可以从**left** --> right(从零开始),也可以从**right** --> left(从 -1 开始)。
| 从左数下标 ⟹ |
|
⟸ 从右数下标 |
>>> breakfast_foods = ["Oatmeal", "Fruit Salad", "Eggs", "Toast"]
# Oatmeal is at index 0 or index -4.
>>> breakfast_foods[0]
'Oatmeal'
>>> breakfast_foods[-4]
'Oatmeal'
# Eggs are at index -2 or 2
>>> breakfast_foods[-2]
'Eggs'
>>> breakfast_foods[2]
'Eggs'
# Toast is at -1
>>> breakfast_foods[-1]
'Toast'
数组内部的一段元素可以通过_切片写法_(<list>[start:stop])访问。_切片_定义为位置 index 上的元素序列,满足 start <= index < stop。_切片_返回被“切”出来的元素的副本,不会修改原 list。
还可以使用 step 参数 [start:stop:step] 来“跳过”或筛选数组元素(例如,step 为 2 时会每隔一个元素选取一个):
>>> colors = ["Red", "Purple", "Green", "Yellow", "Orange", "Pink", "Blue", "Grey"]
# If there is no step parameter, the step is assumed to be 1.
>>> middle_colors = colors[2:6]
>>> middle_colors
['Green', 'Yellow', 'Orange', 'Pink']
# If the start or stop parameters are omitted, the slice will
# start at index zero, and will stop at the end of the list.
>>> primary_colors = colors[::3]
>>> primary_colors
['Red', 'Yellow', 'Blue']
对数组使用内置的 sum() 函数,会返回数组中所有数字之和:
>>> number_list = [1, 2, 3, 4]
>>> sum(number_list)
10
也可以用 len() 函数获取数组的_长度_:
>>> long_list = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]
>>> len(long_list)
10
数组还可以用多种方式组合:
# Using the plus + operator unpacks each list and creates a new list, but it is not efficient.
>>> new_via_concatenate = ["George", 5] + ["cat", "Tabby"]
>>> new_via_concatenate
['George', 5, 'cat', 'Tabby']
# Likewise, using the multiplication operator * is the equivalent of using + n times.
>>> first_group = ["cat", "dog", "elephant"]
>>> multiplied_group = first_group * 3
>>> multiplied_group
['cat', 'dog', 'elephant', 'cat', 'dog', 'elephant', 'cat', 'dog', 'elephant']
数组带有_迭代器_,可以像其他_序列类型_一样被遍历。
# Looping through the list and printing out each element.
>>> colors = ["Orange", "Green", "Grey", "Blue"]
>>> for item in colors:
... print(item)
...
Orange
Green
Grey
Blue
想更深入地了解 loops 和 iterators,请完成 loops 概念练习。
Elyse 非常期待在即将到来的拉斯维加斯之旅中玩上几局扑克(还有其他纸牌游戏)。她是个热衷于“自我追踪”的人,想写几个小函数来帮她完成各种追踪任务,于是请你帮忙一起理清思路。
Elyse 特别喜欢扑克,她想记录自己玩了多少轮,以及_具体是哪几轮_。每一轮都有自己的编号,每张牌桌都会显示当前正在进行的轮次编号。Elyse 选了一张牌桌坐下,开始玩她的第一轮。她打算玩三轮。
实现一个函数 get_rounds(<round_number>),它接收当前轮次编号,并返回一个list,其中包含该轮次以及_接下来两轮_:
>>> get_rounds(27)
[27, 28, 29]
Elyse 在第一张牌桌上玩了几轮,然后休息了一下,又到第二张牌桌上玩了几轮……但每张桌子得到的数组都不一样!她想把这两个数组合并到一起,这样就能在同一个地方追踪所有的扑克轮次。
实现一个函数 concatenate_rounds(<rounds_1>, <rounds_2>),它接收两个数组,并返回一个list,其中包含第一个list中的所有轮次,后面跟着第二个list中的所有轮次:
>>> concatenate_rounds([27, 28, 29], [35, 36])
[27, 28, 29, 35, 36]
在聊到之前的一些扑克轮次时,另一位玩家提到其中有两轮打得非常相似。Elyse 不确定自己有没有玩过那两轮。
实现一个函数 list_contains_round(<rounds>, <round_number>),它接收两个实参:一个记录已玩轮次的数组,以及一个轮次编号。如果该轮次在这个已玩轮次的数组中,函数返回True,否则返回False:
>>> list_contains_round([27, 28, 29, 35, 36], 29)
True
>>> list_contains_round([27, 28, 29, 35, 36], 30)
False
Elyse 想试玩一款叫 Black Joe 的新游戏。它和 Black Jack 类似,目标都是让手中的牌加起来等于某个目标点数,但在 Black Joe 里,目标是让牌面值的_平均值_等于 7。把所有牌的面值相加,再除以手中牌的张数,就能得到平均值。
实现一个函数 card_average(<hand>),返回一手 Black Joe 牌的平均值。
>>> card_average([5, 6, 7])
6.0
在 Black Joe 里,速度很重要。Elyse 想试着找到一种更快求平均值的方法。
她想到了两种得到_近似平均值_的方法:
实现函数 approx_average_is_average(<hand>),其中hand是一个数组,包含你手中各张牌的面值。
如果上述策略中_有一种_or_两种_得到的结果与_实际平均值_相等,就返回True。
注意:所有手牌的张数都是奇数,这样更容易找到中位数。
>>> approx_average_is_average([1, 2, 3])
True
>>> approx_average_is_average([2, 3, 4, 8, 8])
True
>>> approx_average_is_average([1, 2, 3, 5, 9])
False
Elyse 对自己的平均值实验结果很感兴趣,她想知道取_偶数_位置牌面值的平均值,与取_奇数_位置牌面值的平均值,结果是否相同。该再写一个测试函数了!
实现一个函数 average_even_is_average_odd(<hand>),返回一个布尔值,表示偶数下标位置上牌面值的平均值是否与奇数下标位置上牌面值的平均值相同。
>>> average_even_is_average_odd([1, 2, 3])
True
>>> average_even_is_average_odd([1, 2, 3, 4])
False
在 Black Joe 里,每第 11 手牌都是带有奖励规则的奖励手牌:如果你抽到的最后一张牌是 J,就把它的面值翻倍。
实现一个函数 maybe_double_last(<hand>),它接收一手牌,检查最后一张牌是不是 J(11)。如果最后一张牌是 J(11),就在返回这手牌之前把它的面值翻倍。
>>> hand = [5, 9, 11]
>>> maybe_double_last(hand)
[5, 9, 22]
>>> hand = [5, 9, 10]
>>> maybe_double_last(hand)
[5, 9, 10]