1.3 列表入门
在Haskell中,列表是一种单类型的(homogeneous)数据结构,可以用来存储多个类型相同的元素。我们可以在里面装一组数字或者一组字符,但不能把字符和数字装在一起。
列表由方括号括起,其中的元素用逗号分隔开来:
ghci> let lostNumbers = [4,8,15,16,23,42]
ghci> lostNumbers
[4,8,15,16,23,42]
注意:
在GHCi中,可以使用let关键字来定义一个常量。在GHCi中执行let a = 1与在脚本中编写a=1随后用:l装载的效果是等价的。
1.3.1 拼接列表
拼接两个列表可以算是最常见的操作之一了,在Haskell中这可以通过++运算符实现:
ghci> [1,2,3,4] ++ [9,10,11,12]
[1,2,3,4,9,10,11,12]
ghci> "hello" ++ " " ++ "world"
"hello world"
ghci> ['w','o'] ++ ['o','t']
"woot"
注意:
Haskell中的字符串实际上就是一组字符组成的列表。"hello"只是['h','e','l','l','o']的语法糖而已。因此,我们可以使用处理列表的函数来对字符串进行操作。
在使用++运算符处理长字符串时要格外小心(对长的列表也是同样),Haskell会遍历整个第一个列表(++符号左边的列表)。在处理较短的字符串时问题还不大,但要是在一个长度为5000万的列表上追加元素,那可得执行好一会儿了。
不过,仅仅将一个元素插入列表头部的成本几乎为零,这里使用: 运算符(被称作Cons1运算符)即可:
ghci> 'A':" SMALL CAT"
"A SMALL CAT"
ghci> 5:[1,2,3,4,5]
[5,1,2,3,4,5]
可以留意,第一个例子中,取一个字符和一个字符构成的列表(即字符串)作为参数;第二个例子很相似,取一个数字和一个数字组成的列表作为参数。:运算符的第一个参数的类型一定要与第二个参数中列表中元素的类型保持一致。
而++运算符总是取两个列表作为参数。若要使用++运算符拼接单个元素到一个列表的最后,必须用方括号把它括起来,使之成为单元素的列表。
ghci> [1,2,3,4] ++ [5]
[1,2,3,4,5]
这里如果写成[1,2,3,4] ++ 5就错了,因为++运算符的两边都必须是列表才行,这里的5是个数,不是列表。
有趣的是,[1,2,3]实际上是1:2:3:[]的语法糖。[]表示一个空列表,若从前端插入3,它就成了[3],再插入2,它就成了[2,3],依此类推。
注意:
[]、[[]]和[[], [], []]是不同的。第一个是一个空的列表,第二个是含有一个空列表的列表,第三个是含有三个空列表的列表。
1.3.2 访问列表中的元素
若是要按照索引取得列表中的元素,可以使用!!运算符,下标从0开始。
ghci> "Steve Buscemi" !! 6
'B'
ghci> [9.4,33.2,96.2,11.2,23.25] !! 1
33.2
但你如果想在一个只含有4个元素的列表中取它的第6个元素,就会得到一个错误。要小心!
1.3.3 嵌套列表
列表同样也可以以列表为元素,甚至是列表的列表的列表:
ghci> let b = [[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]
ghci> b
[[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]
ghci> b ++ [[1,1,1,1]]
[[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3],[1,1,1,1]]
ghci> [6,6,6]:b
[[6,6,6],[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]
ghci> b !! 2
[1,2,2,3,4]
列表中的列表可以是不同长度的,但类型必须相同。与不能在同一个列表中混合放置字符和数组一样,混合放置数的列表和字符的列表也是不可以的。
1.3.4 比较列表
只要列表内的元素是可以比较的,那就可以使用<、>、>= 和 <= 来比较两个列表的大小。 它会按照字典顺序,先比较两个列表的第一个元素,若它们的值相等,则比较二者的第二个元素,如果第二个仍然相等,再比较第三个,依此类推,直至遇到不同为止。两个列表的大小以二者第一个不等的元素的大小为准。
比如,执行[3, 4, 2] < [3, 4, 3]时,Haskell就会发现3与3相等,随后比较4与4,依然相等,再比较2与3,这时就得出结论了:第一个列表比第二个列表小。>、>=与<=也是同样的道理。
ghci> [3,2,1] > [2,1,0]
True
ghci> [3,2,1] > [2,10,100]
True
ghci> [3,4,2] < [3,4,3]
True
ghci> [3,4,2] > [2,4]
True
ghci> [3,4,2] == [3,4,2]
True
此外,非空列表总认为比空列表更大。这样即可保证两个列表总是可以顺利地做比较,即使其中一个列表完全等同于另一个列表的开头部分。
1.3.5 更多列表操作
下面是几个有关列表的常用函数,稍带用途与示例。
head函数返回一个列表的头部,也就是列表的第一个元素。
ghci> head [5,4,3,2,1]
5
tail函数返回一个列表的尾部,也就是列表除去头部之后的部分:
ghci> tail [5,4,3,2,1]
[4,3,2,1]
last函数返回一个列表的最后一个元素。
ghci> last [5,4,3,2,1]
1
init函数返回一个列表除去最后一个元素的部分。
ghci> init [5,4,3,2,1]
[5,4,3,2]
要更直观地看这些函数,我们可以把列表想象为一头怪兽,下面就是它的样子:
试一下,若是取一个空列表的头部又会怎样?
ghci> head []
*** Exception: Prelude.head: empty list
天哪,它翻脸了!如果怪兽压根就不存在,head又从何而来?在使用head、tail、last和init时要小心,不要用到空列表上。这个错误不会在编译时被捕获,所以说做些工作以防止Haskell从空列表中取值是一个好的习惯。
length函数返回一个列表的长度:
ghci> length [5,4,3,2,1]
5
null函数检查一个列表是否为空。如果是,则返回True;否则返回False:
ghci> null [1,2,3]
False
ghci> null []
True
reverse函数将一个列表反转:
ghci> reverse [5,4,3,2,1]
[1,2,3,4,5]
take函数取一个数字和一个列表作为参数,返回列表中指定的前几个元素,如下:
ghci> take 3 [5,4,3,2,1]
[5,4,3]
ghci> take 1 [3,9,3]
[3]
ghci> take 5 [1,2]
[1,2]
ghci> take 0 [6,6,6]
[]
如上,若是试图取超过列表长度的元素个数,只能得到原先的列表。若取0个元素,就会得到一个空列表。
drop函数的用法大体相同,不过它是删除一个列表中指定的前几个元素:
ghci> drop 3 [8,4,2,1,5,6]
[1,5,6]
ghci> drop 0 [1,2,3,4]
[1,2,3,4]
ghci> drop 100 [1,2,3,4]
[]
maximum函数取一个列表作为参数,并返回最大的元素,其中的元素必须可以做比较。minimum函数与之相似,不过是返回最小的元素。
ghci> maximum [1,9,2,3,4]
9
ghci> minimum [8,4,2,1,5,6]
1
sum函数返回一个列表中所有元素的和。product函数返回一个列表中所有元素的积。
ghci> sum [5,2,1,6,3,2,5,7]
31
ghci> product [6,2,1,2]
24
ghci> product [1,2,5,6,7,9,2,0]
0
elem函数可用来判断一个元素是否包含于一个列表,通常以中缀函数的形式调用它,这样更加清晰。
ghci> 4 elem [3,4,5,6]
True
ghci> 10 elem [3,4,5,6]
False