1. 列表迭代¶
1.1. 列表迭代¶
要理解算法如何操纵大规模的现实世界抽象,我们需要理解两个概念的组合:
列表——它保存待操纵的数据元素,以及
迭代——处理列表元素的算法手段。
这两个概念的组合—我们称之为"列表迭代"—意味着对列表中的每个元素逐一重复相同的处理步骤。
在现实生活中,人们创建并使用各种各样的列表:购物清单、愿望清单、待办清单等等。 这些列表有两个共同点:
列表中包含若干性质相似的项目(即购物清单里是商店中能买到的东西,待办清单里是需要完成的任务),以及
对于列表中的每个项目,我们想采取某种行动(即购买购物清单上的一项,完成待办清单上的一项任务)。
类似地,计算用的列表包含同一类型的数据项,而列表迭代用于对列表中的每个项目施加某些计算操作。
列表迭代对于那些分析拥有许多—也许是极大量—实例的抽象的算法至关重要。 想象一下用美国人口普查数据计算家庭平均收入。 家庭这一抽象包含有关家庭收入的信息,但这种抽象有数千万个实例。 在这种情况下列表迭代至关重要,因为它允许对每个实例重复相同的步骤。
列表的概念还帮助我们开始回答一个重要而实际的问题: 我们如何把抽象"放进"计算机内部? 要回答这个问题,我们需要更多地了解"数据结构"。 数据结构是在计算机中组织数据的一种手段。 我们将学习两种重要的数据结构—现在学列表,稍后学词典。 我们还会看到,这两种数据结构是构建块,因为我们可以把这些简单的数据结构组合起来,形成更复杂的数据组织—其复杂程度堪比我们所要处理的现实世界现象。
1.1.1. 列表¶
列表就是同一类型的各个元素一个接一个排列而成的序列。
下图展示了 BlockPy 中列表的一个例子。
图中的箭头显示了如何组装这些积木块,以形成一个包含三个数字的列表,并将其设为某个属性的值。
create list with 块位于 Lists 菜单中。
数字位于 Values 菜单中,而 set 块位于 Property 菜单中。
Figure 0.1.2: 创建列表¶
默认情况下, create list with 块会创建一个带有三个元素槽位的列表。
当然,有时可能需要元素槽位更多或更少的列表。
因此,可以编辑 create list with 块来增加或删除槽位。
下图展示了编辑的一个例子。
Figure 0.1.3: 编辑 create list with 块¶
要开始添加新的列表元素,请点击 create list with 块上单词"create"前面的蓝色齿轮符号。
这会弹出上图左侧所示的编辑菜单。
标有 property_name 的块可以从灰色区域拖到列表分组中。
上图左侧显示了一个高亮的 property_name 块正在被这样拖动。
当 property_name 块插入到列表组中时, create list with 块就会增加一个新槽位。
通过反复将 property_name 块从灰色区域拖到列表组中,可以按需添加任意数量的槽位。
添加完新的列表元素后,点击蓝色齿轮符号即可折叠编辑菜单。
要删除列表元素,首先点击蓝色齿轮图标。
在编辑菜单中,将 property_name 块从列表组移到其左侧的灰色区域。
这会缩短列表组,并从 create list with 块中移除相应的槽位。
可以以文本形式或折线图可视化形式查看列表。
下面是一个实时 BlockPy 画布,用于演示这两种选项。
在 BlockPy 算法中,创建了一个包含四个元素的列表,并将其设为属性 number-list 的值。
number-list 属性先使用 print 块以文本形式显示,然后以标题为"List of Numbers"的折线图显示。
用于打印和绘制折线图的块位于 Output 菜单中。
运行这个程序,观察 Printer 框中(紧挨着 BlockPy 画布上方)显示的输出。
注意 Printer 框有一个滚动条,右下角还有一个用于垂直调整显示区域大小的控件。
待处理
- type: BlockPy
在此处放置第一个 BlockPy 练习。
print 块显示的文本如下:
[2, 7, 10, 5]
其中方括号包围列表,列表中的每一项与下一项之间用逗号分隔。 这是 Python 书写列表的方式。 按照惯例,列表从左到右读取,因此最左边的项是列表中的第一项,最右边的项是列表中的最后一项。 在上面的例子中,数字 2 是第一项,数字 5 是最后一项。
折线图同样显示了列表中的四个值。 注意,这些值在列表中是按从左到右的顺序打印并绘制的(即 2 是第一个打印并绘制的数字,5 是最后一个打印并绘制的数字)。
使用上面的画布来:
在列表中添加和删除元素,
修改列表中数字的值,
修改列表的标题,以及
在打印输出之前生成图表。
解决你在使用这个简单列表时遇到的任何疑问或问题。
虽然 create list with 块是处理小型列表的一种简单方式,但它显然过于局限,无法应对我们期望在"大数据"集合中看到的那些长列表。
我们很快就会看到一组表示此类"大数据"列表的块。
1.1.2. 对列表进行迭代¶
一般来说,迭代的概念是指重复执行一组给定的步骤,直到达到某个既定目标。 例如,许多迷宫算法重复一组步骤(感知环境、转动和/或移动角色),直到达到既定目标(迷宫出口)。
列表迭代是操纵以列表形式组织的数据时所使用的一种迭代形式。 列表迭代的一般形式通常表示为:
- for each <element> in <some list>
do <these steps using element>
其中属性"element"在迭代的每一轮中都指向列表中不同的元素。 这个属性通常称为迭代变量。 循环会对列表中的每个项目重复一次。 需要注意的是,属性"element"(即迭代变量)在迭代的每一轮中取不同的值。 这是因为在每一轮中,属性"element"都指向列表上不同的项目。
列表迭代可以从实用角度定义为:
列表迭代(实用定义):对列表中的每个元素逐一执行一组操作。
下面是一个 BlockPy 工作区,其中有一个演示列表迭代的简单算法。 在这个例子中,我们要输出列表中的每个元素,并找出列表中哪些元素严格大于某个阈值,本例中是值 5。 在更现实的情形中,我们可能会用这样的算法来找出所有震级最大的地震,或者找出犯罪率高于某个水平的所有年份。
运行这个示例算法,观察它在工作区顶部的 Printer 区域生成的输出。
待处理
- type: BlockPy
在此处放置第二个 BlockPy 练习。
如下表所示,这个算法经历四次迭代。 注意,每次迭代时迭代变量的值都会改变。 每次迭代时,迭代变量的值都与列表上的某个项目相同。
迭代的关键重要性在于它对任意长度的列表都适用。 使用上面的工作区向列表中添加更多元素,观察迭代无需改动即可适用于这个更长的列表。
1.1.3. 迭代变量与初始化¶
"一次一个元素"这一说法意味着,算法的设计必须考虑到这样一个事实:迭代的各个步骤只能直接访问迭代变量的值(即"当前"列表元素的值)。 在某些情况下,只需要当前值。 上面那个判断每个列表元素是否高于阈值的算法就是这种情况。 然而,当算法需要知道它先前见过的某个元素的信息时,算法就必须在其状态中"记住"这一事实。 能够定义算法的状态以适应迭代的这一方面,是一项重要的技能。
考虑一个在全部为正数的数字列表中查找最大值的算法。 这样的算法有助于回答诸如以下的问题: 震级最大的地震是哪次?或者犯罪率最高的是多少? 使用列表迭代时,整个列表不会一次性全部可见—我们所能"看到"的只是迭代变量所揭示的那个列表值。 例如,在第二次迭代时,列表 [2, 7, 10, 5] 会显示为 [--, 7, --, --, ...]。 列表中的第一个数字(第一次迭代时看到的那个)不再可见,而当前数字(数字 7)之后的数字则尚未看到。
那么,如果我们一次只能看到一个数字,怎么可能找到最大值呢? 算法需要一个额外的属性来帮助记住它在迭代中到目前为止所见过的内容。 由于我们要找的是最大值,这个额外的属性只需记录一个数字:到目前为止见过的最大数字。 查看下面工作区中的算法,了解它是如何工作的。
待处理
- type: BlockPy
在此处放置第三个 BlockPy 练习。
对于示例列表,如下表所示,该算法经历四次迭代。
属性 maximum 记录列表中到目前为止见过的最大值。 跟踪一遍这个算法,确信这张表是正确的。
这类(以及许多类似)迭代算法的一个重要方面是需要 初始化 。
表中的第一行表明,在迭代开始之前,属性 maximum 被赋予值零。
这一点可以在 Blockly 算法中看到。
给属性 maximum 赋予这个初始值称为初始化。
这种初始化是必要的,这样在第一次迭代时,迭代变量(属性 item )与属性 maximum 的比较才有意义。
如果不对 maximum 进行初始化,就无法判断比较的结果是真还是假,因为我们不知道属性 maximum 具有什么值—这显然不是我们想要编写优秀算法的方式。
试着移除或禁用初始化 maximum 属性的块,观察运行算法时会发生什么。
1.1.4. 列表、迭代、大数据与抽象¶
迭代能处理任意长度的列表,这一事实自然与"大数据"世界相关联,因为"大数据"列表不过是一个项目数量极大的列表。 迭代能够对任意数量的项目施加同一组操作,这种能力赋予计算以"力量"。 许多机器通过重复执行某种机械动作来产生物理动力:内燃机中活塞的往复运动产生推动车辆所需的物理动力。 以此类推,算法使用迭代对列表项目进行重复处理,产生回答关于大型数据集的问题所需的信息处理能力。
列表和迭代的思想也与更宏大的抽象概念相关联。 我们把抽象画成一张表。 表中的每一行都是该抽象所建模的某个现实世界实体的一个实例。 实例的集合可以组织成一个列表—列表的每个元素就是一个实例。 为了操纵抽象,可以使用迭代来重复处理列表的每个元素(即每个实例)。
由此得到列表迭代的概念性定义:
列表迭代(概念定义):对抽象的每个实例逐一执行一组操作。
要充分实现这种处理抽象的思想,我们还需要再学一点—但不会太多。
