- 编程风格:程序设计与系统构建的艺术(原书第2版)
- (美)克里斯蒂娜·维代拉·洛佩斯
- 1597字
- 2025-04-15 17:59:02
3.3 评注
此风格最明显的元素是数组:一个固定大小的元素集合。所有数据都存放在数组中,这些数组的大小是固定的,且必须是确定的。数组可以有一个或多个维度。一维数组称为向量,而N维数组称为N维矩阵。当数据数量小于数组中分配的插槽时,通常会在数组末尾用一些类似零的值填充。
当然,数组是每个程序员都非常熟悉的数据结构。但是仅仅使用数组并不构成数组风格的编程,事实上,远非如此。此风格第二个更重要的约束是:没有显式的迭代遍历。不像在命令式编程语言中那样显式地遍历数组中的每个元素,而是使用应用于整个数组的、高级声明性操作来访问(每个)数组元素。针对数组的操作通过高级(编程语言中的)数学抽象隐藏了底层的实现细节,这使这些操作非常适合高度并发的实现,例如被图形处理器(Graphical Processing Unit,GPU)所支持的那些。
例如,考虑以下用命令式伪代码编写的代码片段:

此代码片段使用数组来放置数据(cars),但它不是用数组编程风格编写的。然而,以下代码则使用了数组编程风格:

前一个代码片段使用显式的迭代遍历,而后者则使用针对数组的高级声明性操作。如果没有这些操作,我们可能使用了数组,但并没有使用数组编程风格。
在Python语言中,数据集合通常被存放在可变大小的列表、元组或字典中。Python语言还通过array模块支持数组,但是,这些数组仅仅是基本的数据结构,并不支持数组风格的编程。Python语言缺乏对高级数组操作的支持,这使得它的某些应用程序,尤其是科学计算方面的应用程序,非常有限。而第三方库填补了这一空白。此类库中最流行的是numpy库,它不仅支持数组,还支持强大的数组操作。示例程序使用numpy库。我们来详细分析一下。
在高级的编程语言层面,通过数组风格的编程解决词频问题意味着将所有文本数据放在一个数组中,然后通过执行一些数组操作得到单词和它们的频率。在此实现中,我们从第5行的原始数据(字符数组)开始。为了简化某些操作,数组的第一个和最后一个位置都空白。第10行和第11行展示了numpy库中可用的第一个高级数组操作。通过这些高级数组操作将所有非字母、非数字字符替换为空格,将所有字符转换为小写等,以规范字符数组。这些高级搜索和替换操作的具体实现,可能会在多方面被优化,以便于被并行处理,但这些优化对我们不可见。
接着,我们需要标记字符串,即我们需要识别字符数组中的(每个)单词。为了忠实于数组编程风格,这需要我们用一种不同的方式来思考问题——这种方式不同于在使用其他数据结构时的方式。这里,我们采用的方法如下:找到空格的索引,单词就是两个索引之间的字符序列。我们希望得到一个二维矩阵,其中每一行都是一对开始、结束索引。为了实现这种方法,第16行寻找空格的索引,第19行复制每个索引,为构建二维矩阵做准备,第22行中的操作reshape将复制的索引的向量转换为二维矩阵,最后,第28行只选择结束索引和开始索引之间差值大于2的行,这意味着该单词至少有两个字符。在程序那部分的末尾,在第28行,w_ranges包含所有单词的开始、结束索引对。
在程序的这一点上,我们打破了数组编程风格,生成了一个可变大小的单词列表(第33行)。这是因为我们无法预测有多少个单词,所以不能使用数组(除非假设数组具有默认的最大值)。第33行中列表words仍然是一个numpy字符数组。但是,从这里开始,我们要对单词而不是字符进行操作。因此,在第37行,我们创建了一个新的numpy数组,这次使用的是字符串元素(单词),而不是字符元素。第41行将停用词加载到字符串数组中,第42行使用强大的数组操作选择存在于swords数组中、但不在停用词数组stop_words中的单词。
最后,在第45行,使用另一个强大的数组操作来返回单词及其频率。第46行的排序操作以传统的非数组编程风格来完成。
为了便于读者理解数组操作的含义,示例程序中夹杂着运行输入数据示例的注释。因此,该程序看起来比实际要长。如果没有注释,示例程序将非常简洁:


通常,以数组编程风格编写的数据密集型程序往往小巧而简洁,一旦我们熟悉了数组操作,这类程序就很容易阅读。