10.2.2.1 添加元素到列表

到目前为止,我们已经看到如何追加元素到已有(函数式)列表的前面;如果我们想在列表的末尾追加元素,又该如何做呢?这要求听起来是合理的,那么,我们尝试实现它。清单 10.10 显示了在列表的前面和在后面插入na?ve 企图之间性能的差别。

清单10.10 在列表中添加元素(F# Interactive)

> let prepend el list = el::list;;     [1]

val prepend : ‘a -> ‘a list -> ‘alist

> let rec append el list =     [2]

match listwith

| [] ->[el]     <-- 追加到空列表

| x::xs ->x::(append el xs)     <-- 递归追加到其余部分

val append : ‘a -> ‘a list -> ‘a list

> #time;;     [3]

> let l = [ 1 .. 30000 ];;

val l : int list

> for i = 1 to 100 do ignore(prepend 1l);;   |

Real: 00:00:00.000, CPU: 00:00:00.000    |

| 追加的性能更慢

> for i = 1 to 100 do ignore(append 1l);;   |

Real: 00:00:00.434, CPU: 00:00:00.421    |

prepend 的实现非常简单[1],因为可以使用 cons 运算符(::),直接构造新的列表单元;而追加元素到列表的末尾,则需要写递归函数[2],遵循通常的递归列表处理模式,一个分支处理空列表,另一个处理 cons cell。

接下来,我们输入一个非常有用的 F# Interactive 命令,#time,打开记时器(timing)。在这种模式下,对我们输入的命令,F# 会自动输出执行所用的时间。可以看到,对于大型列表,在末尾追加元素要慢得多。我们在 for 循环中运行一百次,在前面追加所需的时间为零,但在后面追加元素,需要一个相当长的时间。任何“简单”的操作,那怕只需要半秒钟,迭代一百次都值得关注。

我们的追加函数不是尾递归,但在这里不是问题。尾递归能够避免堆栈溢出,但对性能的影响轻微。问题是函数式列表并不适合我们要尝试执行的操作。

图 10.5 显示了为什么这个操作对于函数式列表,不能有效地实现,而在前面追加元素却很容易。因为列表是不可变数据结构,我们可以创建一个单元,并指向原始列表。不可变性保证以后不会有人能改变原始列表,背着我们改变的是“新”列表的内容。与此相比,在后面追加元素,需要改变最后一个元素。以前,最后一个元素“知道”它自己在最后,而我们需要在它的后面有新的元素。列表是不可变的,所以,不能改变保存在最后元素中的信息。相反,必须克隆最后一个元素,这样,还要克隆前面的元素(这样,它就知道,后面跟着的是克隆最后一个元素),等等。

图 10.5 在前面追加元素,我们创建了新的 cons cell 和对原始列表的引用;在后面追加元素,需要遍历并克隆整个列表。

当然,有不同的数据结构,每一种数据结构都有不同的可以高效执行的操作。总要有一个权衡,这就是为什么对于不同的问题,选择正确的数据结构,是非常重要的。

算法的复杂性

计算机科学家使用非常精确的数学术语来讨论算法的复杂性,但这些术语背后的概念更重要的,即使当我们非正式使用时。在一般情况下,操作的复杂性告诉我们,算法需要的“原始”步骤数,依赖于输入的大小。它并不预测步骤数,只与输入的大小有关系。

让我们来分析一下前面的例子。追加一个元素到列表前面,总是涉及到一个单独的步骤:创建一个新的列表 cons cell。在正规表示法中,写成 O(1),这意味着,步数是不变的,不管列表如何大。在有一百万个元素的列表的前面添加元素,与在只有一个元素的列表前面添加元素花费一样!

在列表的后面追加元素是棘手。开始的时候,如果列表中有 N 个元素,我们需要处理和重复 N 个 cons cell。这可写成 O(N),表示步骤数大致与列表的大小是成正比:在有 1000 个元素的列表后面添加元素大约是为在有 1000 个元素的列表后面添加元素的两倍。

例如,如果我们想追加 M 个新元素列表中的,其复杂性应该乘以 M。这意味着,在前面追加将需要 O(M) 步,因为,1*M = M。使用类似的道理,追加到后面将需要 O(N * M) 步,这可能是一个更大的数量级。

到目前为止,我们已经讨论了函数式列表,在函数编程中是最重要的集合。让我们来一个大的飞跃,看一看在几乎所有命令式编程语言都存在的集合:不起眼的数组。F# 是一种 .NET 语言,所以,它也可以使用正常 .NET 数组。

时间: 2024-12-18 05:29:06

10.2.2.1 添加元素到列表的相关文章

jQuery演示10种不同的切换图片列表动画效果

经常用到的图片插件演示jQuery十种不同的切换图片列表动画效果 在线演示 下载地址 实例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <!DOCTYPE html> <html lang="en" class="no-

10.迭代器/生成器/协程函数/列表生成器

迭代器为什么要用迭代器?小结:生成器为什么要使用生成器,什么是生成器?如何创建一个生成器举个栗子:斐波拉契数列用yield返回结果的执行流程作业代码以及注释:协程函数面向过程编程作业以及代码注解:典型范例以及代码解析:列表生成式生成器表达式作业和练习 迭代器 为什么要用迭代器? 提供了一种不依赖索引的取值方式,使一些不具有索引属性的对象也能遍历输出 相比列表,迭代器的惰性计算更加节约内存. 但是它无法有针对性地指定取某个值,并且只能向后取值. >>> from collections i

10天精通Sass 之 Sass列表函数

列表函数主要有: length($list):返回一个列表的长度值: nth(list,n):返回一个列表中指定的某个标签值 join(list1,list2, [$separator]):将两个列给连接在一起,变成一个列表: append(list1,val, [$separator]):将某个值放在列表的最后: zip($lists-):将几个列表结合成一个多维的列表: index(list,value):返回一个值在列表中的位置值. length($list)返回一个列表的长度值 .spa

列表操作被访问最多的10个问答

1.迭代列表时如何访问列表下标索引 普通版: items = [8, 23, 45]for index in range(len(items)):    print(index, "-->", items[index]) >>>0 --> 81 --> 232 --> 45 优雅版: for index, item in enumerate(items):    print(index, "-->", item) &g

Python中最常见的10个问题(列表)

列表是Python中使用最多的一种数据结果,如何高效操作列表是提高代码运行效率的关键,这篇文章列出了10个常用的列表操作,希望对你有帮助. 注意:很多人学Python过程中会遇到各种烦恼问题,没有人帮答疑.为此小编建了个Python全栈免费答疑交流.裙 :一久武其而而流一思(数字的谐音)转换下可以找到了,不懂的问题有老司机解决里面还有最新Python教程项目可拿,,一起相互监督共同进步! 1.迭代列表时如何访问列表下标索引 普通版: items = [8, 23, 45] for index i

python的列表,元组和字典简单介绍

引 入 java                                   python 存取多个值:数组或list集合 ------------------------> 列表,元组 key-value格式:    Map        ------------------------>    字典 自己学习发现,java跟python这两门面向对象语言在数据类型的定义上,很多思想都是互通的,这里不说java,简单介绍一下python的列表,元组和字典. 一.列表 List: 最通

python课程第二周 内置数据结构——列表和元组

5种内置数据结构:列表.元组.字典.集合.字符串.列表.字典.字符串三种被称为线性结构. 针对线性结构的操作有:切片.封包和解包.成员运算符.迭代. 针对数据结构的操作有解析式:解析式分为列表解析.生成器解析.集合解析和字典解析. 后面三种是Python3.x特有的. 基本框架如下: 一.列表:Python中最具灵活性的有序集合对象类型 列表可包含任何种类的对象:数字.字符串.字典.集合甚至其他列表,这个特性称为异构.还具有可变长度和任意嵌套的特性,属于可变长度序列. (1)列表的初始化,有两种

Python列表 元组 字典 集合

元组 Python中的元组(Tuple)类似于Java中的数组,一旦创建了一个 tuple,就不能以任何方式改变它.这点与Python中的字符串类似,所以我们说元组和字符串都是不可变的序列.元组也支持索引和分片操作. 定义一个元组使用一对小(圆)括号” ( ) “. #定义一个元组 tuple1 = (1, 2, '3', 4, '5') # 定义了一个元组之后就无法再添加或修改元组中的元素 print tuple1[0] # 元组的元素都有确定的顺序.元组的索引也是以0为基点的 print t

python全栈开发从入门到放弃之列表的内置方法

1.列表切片 1 l=['a','b','c','d','e','f'] 2 print(l[1:5]) # 根据索引号来切片,但顾头不顾尾 3 ['b', 'c', 'd', 'e'] 4 print(l[1:5:2]) # 根据索引号来切片,顾头不顾尾,后面加个haxi值每两个空一个 5 ['b', 'd'] 6 print(l[2:5] ) 7 ['c', 'd', 'e'] 8 print(l[-1]) #-1代表从后往前取 -1代表最后一个 9 f 10 11 12 l=['a','b