缺省模块
Prelude
模块是缺省模块,会被自动载入
载入模块
用 import
可以载入模块,模块载入后,其中的名字就都在命名空间中了。
比如 Data.List
模块中有一个 nub
函数,可以筛选掉列表中的重复元素,下面我们用 nub
和 length
组合起来,写一个计算列表中非重复元素个数的函数
import Data.List
numUniques :: (Eq a) => [a] -> Int
numUniques = length . nub
在 ghci 中载入模块
用 :m
命令可以在 ghci 中载入模块
ghci> :m Data.List
可以一次载入多个模块
ghci> :m Data.List Data.Map Data.Set
部分载入
如果我们想只载入模块中的部分名字,可以用括号将其列出来,比如只载入 Data.List
模块中的 nub
和 sort
函数,可以向下面这样
import Data.List (nub,sort)
也可以用 hiding
指令排除掉模块中的某些名字,比如排除掉 nub
函数
import Data.List hiding (nub)
限定载入模块
如果要载入的模块中有和当前命名空间中重复的名字,则需要用 qualified
指令限定载入的模块。这时候再使用限定载入模块中的名字,就必须使用全限定名
比如 Data.Map
中就有和缺省模块中同样名字的 filter
函数,要使用它就必须进行限定
import qualified Data.Map
Data.Map.filter
使用全限定名字实在是太啰嗦了,所以我们可以用 as
指令给被限定的模块起个简短的别名
import qualified Data.Map as M
M.filter
Data.List
该模块提供了一组非常有用的 List 处理函数。出于方便起见,几个常用的函数(如 map
和 filter
)被引入到了 Prelude 模块中,可以直接使用。下面我们来看看几个以前没见过的函数
intersperse
取一个元素与 List 作参数,并将该元素置于 List 中每对元素的中间。
ghci> intersperse '.' "MONKEY"
"M.O.N.K.E.Y"
ghci> intersperse 0 [1,2,3,4,5,6]
[1,0,2,0,3,0,4,0,5,0,6]
intercalate
取两个 List 作参数。它会将第一个 List 交叉插入第二个 List 中间,并返回一个 List.
ghci> intercalate " " ["hey","there","guys"]
"hey there guys"
ghci> intercalate [0,0,0] [[1,2,3],[4,5,6],[7,8,9]]
[1,2,3,0,0,0,4,5,6,0,0,0,7,8,9]
transpose
转置 2D 的矩阵
ghci> transpose [[1,2,3],[4,5,6],[7,8,9]]
[[1,4,7],[2,5,8],[3,6,9]]
ghci> transpose ["hey","there","guys"]
["htg","ehu","yey","rs","e"]
foldl' 和 foldl1'
它们是各自惰性实现的严格版本,如果用惰性 fold
时经常遇到溢出错误,就应换用它们的严格版。
concat
把一组 List 连接为一个 List,相当于移除一级嵌套
ghci> concat ["foo","bar","car"]
"foobarcar"
ghci> concat [[3,4,5],[2,3,4],[2,1,1]]
[3,4,5,2,3,4,2,1,1]
concatMap
相当于先 map
,然后对结果在 concat
移除一级嵌套
ghci> concatMap (replicate 4) [1..3]
[1,1,1,1,2,2,2,2,3,3,3,3]
and
取一组布林值 List 作参数。只有其中的值全为 True 的情况下才会返回 True。
ghci> and $ map (>4) [5,6,7,8]
True
ghci> and $ map (==4) [4,4,4,3,4]
False
or
与 and 相似,一组布林值 List 中若存在一个 True 它就返回 True.
ghci> or $ map (==4) [2,3,4,5,6,1]
True
ghci> or $ map (>4) [1,2,3]
False
any 和 all
比 and 或 or 更灵活,可以自定义判断真值的函数,使用 any 或 all 会更多些
ghci> any (==4) [2,3,5,6,1,4]
True
ghci> all (>4) [6,9,10]
True
ghci> all (`elem` ['A'..'Z']) "HEYGUYSwhatsup"
False
ghci> any (`elem` ['A'..'Z']) "HEYGUYSwhatsup"
True
iterate
迭代调用,他会用一个函数调用初值,得到的结果再次调用该函数,迭代下去,产生一个无限的 List.
ghci> take 10 $ iterate (*2) 1
[1,2,4,8,16,32,64,128,256,512]
ghci> take 3 $ iterate (++ "haha") "haha"
["haha","hahahaha","hahahahahaha"]
splitAt
将一个列表在指定的的位置断开
ghci> splitAt 3 "heyman"
("hey","man")
ghci> splitAt 100 "heyman"
("heyman","")
ghci> splitAt (-3) "heyman"
("","heyman")
ghci> let (a,b) = splitAt 3 "foobar" in b ++ a
"barfoo"
takeWhile
根据条件从 List 中取元素
注意:一旦遇到不符合条件的元素就停止,即使后面还有符合条件的元素
ghci> takeWhile (>3) [6,5,4,3,2,1,2,3,4,5,4,3,2,1]
[6,5,4]
ghci> takeWhile (/=' ') "This is a sentence"
"This"
求所有三次方小于 1000
的数的和,因为 filter
无法处理无限 List ,所以应该这样:
ghci> sum $ takeWhile (<10000) $ map (^3) [1..]
53361
dropWhile
扔掉符合条件的元素。一旦限制条件返回 False,就返回 List 的余下部分
ghci> dropWhile (/=' ') "This is a sentence"
" is a sentence"
ghci> dropWhile (<3) [1,2,2,2,3,4,5,4,3,2,1]
[3,4,5,4,3,2,1]
由一组 Tuple 组成的 List,Tuple 的第一个分量表示股票价格,第二三四分量分别表示年,月,日。我们想知道它是在哪天首次突破 $1000
的!
ghci> let stock = [(994.4,2008,9,1),(995.2,2008,9,2),(999.2,2008,9,3),(1001.4,2008,9,4),(998.3,2008,9,5)]
--这里的匿名函数用到了模式匹配
ghci> head (dropWhile (\(val,y,m,d) -> val < 1000) stock)
(1001.4,2008,9,4)
span
与 takeWhile
有点像,只是它返回一个包含两个 List 分量的元组。第一个 List 是符合条件的列表,第二个 List 是余下的部分
ghci> let (fw,rest) = span (/=' ') "This is a sentence" in "First word:" ++ fw ++ ",the rest:" ++ rest
"First word: This,the rest: is a sentence"
break
span
是在条件首次为 False
时断开列表,而 break
则是在条件首次为 True
时断开 列表。
break p
与 span (not . p)
是等价的
ghci> break (==4) [1,2,3,4,5,6,7]
([1,2,3],[4,5,6,7])
ghci> span (/=4) [1,2,3,4,5,6,7]
([1,2,3],[4,5,6,7])
sort
排序一个 List
注意:只有能够作比较的元素才可以被排序,元素必须属于 Ord 型别类
ghci> sort [8,5,3,2,1,6,4,2]
[1,2,2,3,4,5,6,8]
ghci> sort "This will be sorted soon"
" Tbdeehiillnooorssstw"
group
将列表中相邻并相等的元素归类,组成一个个子 List
ghci> group [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7]
[[1,1,1,1],[2,2,2,2],[3,3],[2,2,2],[5],[6],[7]]
如果在 group
列表之前先给它排序就可以得到每个元素在该 List 中的出现次数
ghci> map (\l@(x:xs) -> (x,length l)) . group . sort $ [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7]
[(1,4),(2,7),(3,2),(5,1),(6,1),(7,1)]
inits 和 tails
与 init 和 tail 相似,只是它们会递归地调用自身直到什么都不剩
ghci> inits "w00t"
["","w","w0","w00","w00t"]
ghci> tails "w00t"
["w00t","00t","0t","t",""]
ghci> let w = "w00t" in zip (inits w) (tails w)
[("","w00t"),("w","00t"),("w0","0t"),("w00","t"),("w00t","")]
我们用 fold
配合 tails
实现一个在列表中搜索子列的函数
search :: (Eq a) => [a] -> [a] -> Bool
search needle haystack =
let nlen = length needle
in foldl (\acc x -> if take nlen x == needle then True else acc) False (tails haystack)
运行效果
ghci> "cat" `isInfixOf` "im a cat burglar"
True
ghci> "Cat" `isInfixOf` "im a cat burglar"
False
ghci> "cats" `isInfixOf` "im a cat burglar"
False
isPrefixOf 与 isSuffixOf
分别检查一个 List 是否以某子 List 开头或者结尾
ghci> "hey" `isPrefixOf` "hey there!"
True
ghci> "hey" `isPrefixOf` "oh hey there!"
False
ghci> "there!" `isSuffixOf` "oh hey there!"
True
ghci> "there!" `isSuffixOf` "oh hey there"
False
elem 与 notElem
检查一个 List 是否包含某元素
partition
将一个列表分为两部分,一部分中包含所有符合条件的元素,另一部分包含余下的元素
ghci> partition (`elem` ['A'..'Z']) "BOBsidneyMORGANeddy"
("BOBMORGAN","sidneyeddy")
ghci> partition (>3) [1,3,5,6,3,2,1,0,3,7]
([5,6,7],[1,3,3,2,1,0,3])
与 span
和 break
不同,span
和 break
会在遇到第一个符合或不符合条件的元素处断开,而 partition
则会遍历整个 List。
ghci> span (`elem` ['A'..'Z']) "BOBsidneyMORGANeddy"
("BOB","sidneyMORGANeddy")
find
在列表中查找并返回首个符合该条件的元素,返回的时候会将其转换为 Maybe
类型类
ghci> find (>4) [1,2,3,4,5,6]
Just 5
ghci> find (>9) [1,2,3,4,5,6]
Nothing
ghci> :t find
find :: (a -> Bool) -> [a] -> Maybe a
前面那段找股票的代码,head (dropWhile (\(val,y,m,d) -> val < 1000) stock)
,因为 dropWhile
可能会返回一个空 List,而对空 List 取 head
就会引发一个错误。
把它改成 find (\(val,y,m,d) -> val > 1000) stock
就安全多啦
elemIndex
与 elem
相似,只是它返回的不是布林值,它只是'可能' (Maybe)
返回我们找的元素的索引,若这一元素不存在,就返回 Nothing
。
ghci> :t elemIndex
elemIndex :: (Eq a) => a -> [a] -> Maybe Int
ghci> 4 `elemIndex` [1,2,3,4,5,6]
Just 3
ghci> 10 `elemIndex` [1,2,3,4,5,6]
Nothing
elemIndices
与 elemIndex
相似,他会返回所有匹配位置组成的列表,而不仅仅是第一个匹配的位置
ghci> ' ' `elemIndices` "Where are the spaces?"
[5,9,13]
findIndex
与 find
相似,但它返回的是首个符合条件元素的索引
findIndices
会返回所有符合条件的索引.
ghci> findIndex (==4) [5,3,2,1,6,4]
Just 5
ghci> findIndex (==7) [5,3,2,1,6,4]
Nothing
ghci> findIndices (`elem` ['A'..'Z']) "Where Are The Caps?"
[0,6,10,14]
zip3 和 zipWith3
前面讲过 zip
和 zipWith
,它们只能将两个列表,要处理三个以上的列表可以使用 zip3
, zip4
... zipWith3
, zipWith4
... 直到 7
。
ghci> zipWith3 (\x y z -> x + y + z) [1,2,3] [4,5,2,2] [2,2,3]
[7,9,8]
ghci> zip4 [2,3,3] [2,2,2] [5,5,3] [2,2,2]
[(2,2,5,2),(3,2,5,2),(3,2,3,2)]
lines
lines
在处理来自文件或其它地方的输入时 会非常有用
ghci> lines "first line\nsecond line\nthird line"
["first line","second line","third line"]
unlines
是 lines 的反函数,它取一组字串的 List,并将其通过 '\n'合并到一块
ghci> unlines ["first line","second line","third line"]
"first line\nsecond line\nthird line\n"
words 和 unwords
可以把一个字串分为一组单词或执行相反的操作,很有用
ghci> words "hey these are the words in this sentence"
["hey","these","are","the","words","in","this","sentence"]
ghci> words "hey these are the words in this\nsentence"
["hey","these","are","the","words","in","this","sentence"]
ghci> unwords ["hey","there","mate"]
"hey there mate"
nub
将一个 List 中的重复元素全部筛掉
ghci> nub [1,2,3,4,3,2,1,2,3,4,3,2,1]
[1,2,3,4]
ghci> nub "Lots of words and stuff"
"Lots fwrdanu"
delete
取一个元素和 List 作参数,会删掉该 List 中首次出现的这一元素
ghci> delete 'h' "hey there ghang!"
"ey there ghang!"
ghci> delete 'h' . delete 'h' $ "hey there ghang!"
"ey tere ghang!"
ghci> delete 'h' . delete 'h' . delete 'h' $ "hey there ghang!"
"ey tere gang!"
\\
求列表的差集,与集合的差集很相似,它会从左边 List 中的元素扣除存在于右边 List 中的元素一次
ghci> [1..10] \\ [2,5,9]
[1,3,4,6,7,8,10]
ghci> "Im a big baby" \\ "big"
"Im a baby"
union
返回两个 List 的并集,将第二个列表中不属于第一个列表的元素则追加到第一个 List中
ghci> "hey man" `union` "man what's up"
"hey manwt'sup"
ghci> [1..7] `union` [5..10]
[1,2,3,4,5,6,7,8,9,10]
intersection
它返回两个 List 的相同部分
ghci> [1..7] `intersect` [5..10]
[5,6,7]
insert
将一个元素插入一个可排序的 List,并将其置于首个大于等于它的元素之前
ghci> insert 4 [1,2,3,5,6,7]
[1,2,3,4,5,6,7]
ghci> insert 'g' $ ['a'..'f'] ++ ['h'..'z']
"abcdefghijklmnopqrstuvwxyz"
ghci> insert 3 [1,2,4,3,2,1]
[1,2,3,4,3,2,1]
整数通用版函数
length
,take
,drop
,splitAt
,!!
和 replicate
这些函数涉及到整数的时候使用的是 Int
类型,通用性很差,但出于历史原因,无法修改以前的代码。
在 Data.List 中包含了更通用的替代版,如: genericLength
,genericTake
,genericDrop
,genericSplitAt
,genericIndex
和 genericReplicate
。
比如 genericLength
的型别声明则为 genericLength :: (Num a) => [b] -> a,Num
既可以是整数又可以是浮点数,let xs = [1..6] in sum xs / genericLength xs
就不会有问题了
条件通用版函数
nub
, delete
, union
, intsect
和 group
等函数都是用相等性作为测试条件,通用替代版 nubBy
,deleteBy
,unionBy
,intersectBy
和 groupBy
可以自己定义一个函数作为测试条件
比如,用 groupBy
将符号相同的数分在一组
ghci> let values = [-4.3,-2.4,-1.2,0.4,2.3,5.9,10.5,29.1,5.3,-2.4,-14.5,2.9,2.3]
ghci> groupBy (\x y -> (x > 0) == (y > 0)) values
[[-4.3,-2.4,-1.2],[0.4,2.3,5.9,10.5,29.1,5.3],[-2.4,-14.5],[2.9,2.3]]
on 函数
使用 on
函数可以进一步精简代码, on
函数定义如下:
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
f `on` g = \x y -> f (g x) (g y)
因此 (==) 'on' (> 0)
得到的函数就与 \x y -> (x > 0) == (y > 0)
等价,上面的代码可以改成这样
ghci> groupBy ((==) `on` (> 0)) values
[[-4.3,-2.4,-1.2],[0.4,2.3,5.9,10.5,29.1,5.3],[-2.4,-14.5],[2.9,2.3]]
比较通用版函数
sort
,insert
,maximum
和 min
都有各自的通用版本。sortBy
,insertBy
,maximumBy
和 minimumBy
,可以自定义比较函数
ghci> let xs = [[5,4,5,4,4],[1,2,3],[3,5,4,3],[],[2],[2,2]]
ghci> sortBy (compare `on` length) xs
[[],[2],[2,2],[1,2,3],[3,5,4,3],[5,4,5,4,4]]
Data.Char
Data.Char
模块包含了一组用于处理字符的函数。
过滤函数
Data.Char 模块中含有一系列用于判定字符范围的函数,可以进行过滤操作:
函数 | 说明 |
---|---|
isControl |
判断一个字符是否是控制字符 |
isSpace |
判断一个字符是否是空格字符,包括空格,tab,换行符等 |
isLower |
判断一个字符是否为小写 |
isUper |
判断一个字符是否为大写 |
isAlpha |
判断一个字符是否为字母 |
isAlphaNum |
判断一个字符是否为字母或数字 |
isPrint |
判断一个字符是否是可打印的 |
isDigit |
判断一个字符是否为数字 |
isOctDigit |
判断一个字符是否为八进制数字 |
isHexDigit |
判断一个字符是否为十六进制数字 |
isLetter |
判断一个字符是否为字母 |
isMark |
判断是否为 unicode 注音字符,你如果是法国人就会经常用到的 |
isNumber |
判断一个字符是否为数字 |
isPunctuation |
判断一个字符是否为标点符号 |
isSymbol |
判断一个字符是否为货币符号 |
isSeperater |
判断一个字符是否为 unicode 空格或分隔符 |
isAscii |
判断一个字符是否在 unicode 字母表的前 128 位 |
isLatin1 |
判断一个字符是否在 unicode 字母表的前 256 位 |
isAsciiUpper |
判断一个字符是否为大写的 ascii 字符 |
isAsciiLower |
判断一个字符是否为小写的 ascii 字符 |
以上函数配合 Data.List
模块的 all
过滤字符串非常好用
ghci> all isAlphaNum "bobby283"
True
ghci> all isAlphaNum "eddy the fish!"
False
下面自定义一个 words
函数
ghci> words "hey guys its me"
["hey","guys","its","me"]
ghci> groupBy ((==) `on` isSpace) "hey guys its me"
["hey"," ","guys"," ","its"," ","me"]
ghci>
再加入过滤空格的功能
ghci> filter (not . any isSpace) . groupBy ((==) `on` isSpace) $ "hey guys its me"
["hey","guys","its","me"]
generalCategory
可以判断一个字符所属的分类,一共有 31
个分类
ghci> generalCategory ' '
Space
ghci> generalCategory 'A'
UppercaseLetter
ghci> generalCategory 'a'
LowercaseLetter
ghci> generalCategory '.'
OtherPunctuation
ghci> generalCategory '9'
DecimalNumber
ghci> map generalCategory " \t\nA9?|"
[Space,Control,Control,UppercaseLetter,DecimalNumber,OtherPunctuation,MathSymbol]
转换函数
toUpper
将一个字符转为大写字母,若该字符不是小写字母,就按原值返回
toLower
将一个字符转为小写字母,若该字符不是大写字母,就按原值返回
toTitle
将一个字符转为 title-case,对大多数字元而言,title-case 就是大写
digitToInt
将一个字符转为 Int 值,而这一字符必须得在 '1'..'9','a'..'f'或'A'..'F' 的范围之内
ghci> map digitToInt "34538"
[3,4,5,3,8]
ghci> map digitToInt "FF85AB"
[15,15,8,5,10,11]
intToDigit
是 digitToInt
的反函数。它取一个 0 到 15 的 Int 值作参数,并返回一个小写的字符
ghci> intToDigit 15
'f'
ghci> intToDigit 5
'5'
ord
与 char
函数可以将字符与其对应的数字相互转换
ghci> ord 'a'
97
ghci> chr 97
'a'
ghci> map ord "abcdefgh"
[97,98,99,100,101,102,103,104]
Data.Map
键值对对象
在 Haskell 中,键值对就是序对的列表,如下就是一个键值对对象
phoneBook = [("betty","555-2938") ,
("bonnie","452-2928") ,
("patsy","493-2928") ,
("lucille","205-2928") ,
("wendy","939-8282") ,
("penny","853-2492") ]
可以自己实现一个键值对查找函数
findKey :: (Eq k) => k -> [(k,v)] -> v
findKey key xs = snd . head . filter (\(k,v) -> key == k) $ xs
如果要查找的键不再关联列表中,filter
函数就会返回一个空列表, head
函数就会出现错误,因此应该用 Maybe 型别修改代码如下
findKey :: (Eq k) => k -> [(k,v)] -> Maybe v
findKey key [] = Nothing
findKey key ((k,v):xs) =
if key == k then
Just v
else
findKey key xs
这其实是一个经典的 fold
模式。 看看用 fold
怎样实现吧
findKey :: (Eq k) => k -> [(k,v)] -> Maybe v
findKey key = foldr (\(k,v) acc -> if key == k then Just v else acc) Nothing
试用一下,看看效果
ghci> findKey "penny" phoneBook
Just "853-2492"
ghci> findKey "betty" phoneBook
Just "555-2938"
ghci> findKey "wilma" phoneBook
Nothing
我们实现的这个函数其实模块中已经存在了,就是 Data.List 模块中的 lookup
函数。这个函数的算法需要遍历整个 List,性能稍差,Data.Map 模块提供了更高效的方式(通过树实现),并提供了一组好用的函数。
注意:由于
Data.Map
中的一些函数与Prelude
和Data.List
模块存在命名冲突,所以我们使用qualified import
Map 对象
之前我们一直使用关联列表作为键值对,其实还有专门的 Data.Map.Map
对象,他的键必须是可排序的,因为它会按照某顺序将其组织在一棵树中。
在处理键值对时,只要键的型别属于 Ord 型别类,就应该尽量使用Data.Map
fromList
这个函数可以将一个关联列表转换成等价的 Map,其类型声明为
Map.fromList :: (Ord k) => [(k,v)] -> Map.Map k v
测试一下
ghci> Map.fromList [("betty","555-2938"),("bonnie","452-2928"),("lucille","205-2928")]
fromList [("betty","555-2938"),("bonnie","452-2928"),("lucille","205-2928")]
ghci> Map.fromList [(1,2),(3,4),(3,2),(5,5)]
fromList [(1,2),(3,2),(5,5)]
注意:重复的键会被忽略
empty
返回一个空 map
ghci> Map.empty
fromList []
insert
取一个键,一个值和一个 map 做参数,给这个 map 插入新的键值对,并返回一个新的 map
ghci> Map.empty
fromList []
ghci> Map.insert 3 100 Map.empty
fromList [(3,100)]
ghci> Map.insert 5 600 (Map.insert 4 200 ( Map.insert 3 100 Map.empty))
fromList [(3,100),(4,200),(5,600)]
--用部分函数、组合函数连续调用
ghci> Map.insert 5 600 . Map.insert 4 200 . Map.insert 3 100 $ Map.empty
fromList [(3,100),(4,200),(5,600)]
通过 empty
,insert
与 fold
,我们可以编写出自己的 fromList
fromList' :: (Ord k) => [(k,v)] -> Map.Map k v
fromList' = foldr (\(k,v) acc -> Map.insert k v acc) Map.empty
null
检查一个 map 是否为空
ghci> Map.null Map.empty
True
ghci> Map.null $ Map.fromList [(2,3),(5,5)]
False
size
返回一个 map 的大小
ghci> Map.size Map.empty
0
ghci> Map.size $ Map.fromList [(2,4),(3,3),(4,2),(5,4),(6,4)]
5
singleton
取一个键值对做参数,并返回一个只含有一个映射的 map
ghci> Map.singleton 3 9
fromList [(3,9)]
ghci> Map.insert 5 9 $ Map.singleton 3 9
fromList [(3,9),(5,9)]
lookup
与 Data.List 的 lookup 很像,只是它的作用对象是 map,如果它找到键对应的值。就返回 Just something,否则返回 Nothing
member
是个判断函数,它取一个键与 map 做参数,并返回该键是否存在于该 map
ghci> Map.member 3 $ Map.fromList [(3,6),(4,3),(6,9)]
True
ghci> Map.member 3 $ Map.fromList [(2,5),(4,5)]
False
map 与 filter
与其对应的 List 版本很相似
ghci> Map.map (*100) $ Map.fromList [(1,1),(2,4),(3,9)]
fromList [(1,100),(2,400),(3,900)]
ghci> Map.filter isUpper $ Map.fromList [(1,'a'),(2,'A'),(3,'b'),(4,'B')]
fromList [(2,'A'),(4,'B')]
toList
是 fromList 的反函数
ghci> Map.toList . Map.insert 9 2 $ Map.singleton 4 3
[(4,3),(9,2)]
keys 与 elems
各自返回一组由键或值组成的 List
-
keys
与map fst . Map.toList
等价 -
elems
与map snd . Map.toList
等价
fromListWith
它与 fromList
很像,只是它不会直接忽略掉重复键,而是交给一个函数来处理它们。假设一个姑娘可以有多个号码,而我们有个像这样的关联列表:
phoneBook =
[("betty","555-2938")
,("betty","342-2492")
,("bonnie","452-2928")
,("patsy","493-2928")
,("patsy","943-2929")
,("patsy","827-9162")
,("lucille","205-2928")
,("wendy","939-8282")
,("penny","853-2492")
,("penny","555-2111")
]
如果用 fromList
来生成 map
,我们会丢掉许多号码! 如下才是正确的做法:
phoneBookToMap :: (Ord k) => [(k, String)] -> Map.Map k String
phoneBookToMap xs = Map.fromListWith (\number1 number2 -> number1 ++ ", " ++ number2) xs
ghci> Map.lookup "patsy" $ phoneBookToMap phoneBook
"827-9162, 943-2929, 493-2928"
ghci> Map.lookup "wendy" $ phoneBookToMap phoneBook
"939-8282"
ghci> Map.lookup "betty" $ phoneBookToMap phoneBook
"342-2492,555-2938"
一旦出现重复键,这个函数会将不同的值组在一起,同样,也可以缺省地将每个值放到一个单元素的 List 中,再用 ++ 将他们都连接在一起。
phoneBookToMap :: (Ord k) => [(k,a)] -> Map.Map k [a]
phoneBookToMap xs = Map.fromListWith (++) $ map (\(k,v) -> (k,[v])) xs
ghci> Map.lookup "patsy" $ phoneBookToMap phoneBook
["827-9162","943-2929","493-2928"]
很简洁! 它还有别的玩法,例如在遇到重复元素时,单选最大的那个值
ghci> Map.fromListWith max [(2,3),(2,5),(2,100),(3,29),(3,22),(3,11),(4,22),(4,15)]
fromList [(2,100),(3,29),(4,22)]
或是将相同键的值都加在一起.
ghci> Map.fromListWith (+) [(2,3),(2,5),(2,100),(3,29),(3,22),(3,11),(4,22),(4,15)]
fromList [(2,108),(3,62),(4,37)]
insertWith
如 fromListWith
之于 fromList
。它会将一个键值对插入一个 map 之中,而该 map 若已经包含这个键,就问问这个函数该怎么办
ghci> Map.insertWith (+) 3 100 $ Map.fromList [(3,4),(5,103),(6,339)]
fromList [(3,104),(5,103),(6,339)]
Data.Set
Data.Set 模块提供了对数学中集合的处理
- 里面的每个元素都是唯一的,内部由一棵树来组织
- 必须是可排序的
- 插入,删除,判断从属关系之类的操作,要比 List 快得多
- 对一个集合而言,最常见的操作莫过于并集,判断从属或是将集合转为 List
使用的时候同样要限定名称
import qualified Data.Set as Set
假定我们有两个字串,要找出同时存在于两个字串的字符
text1 = "I just had an anime dream. Anime... Reality... Are they so different?"
text2 = "The old man left his garbage can out and now his trash is all over my lawn!"
fromList
取一个 List 作参数并将其转为一个集合,所有元素会被排序,重复元素将被取出
ghci> let set1 = Set.fromList text1
ghci> let set2 = Set.fromList text2
ghci> set1
fromList " .?AIRadefhijlmnorstuy"
ghci> set2
fromList " !Tabcdefghilmnorstuvwy"
intersection
取交集
ghci> Set.intersection set1 set2
fromList " adefhilmnorstuy"
difference
取差集得到存在于第一个集合但不在第二个集合的元素
ghci> Set.difference set1 set2
fromList ".?AIRj"
ghci> Set.difference set2 set1
fromList "!Tbcgvw"
union
取并集
ghci> Set.union set1 set2
fromList " !.?AIRTabcdefghijlmnorstuvwy"
null,size,member,empty,singleton,insert,delete
这几个函数就跟你想的差不多啦
ghci> Set.null Set.empty
True
ghci> Set.null $ Set.fromList [3,4,5,5,4,3]
False
ghci> Set.size $ Set.fromList [3,4,5,3,4,5]
3
ghci> Set.singleton 9
fromList [9]
ghci> Set.insert 4 $ Set.fromList [9,3,8,1]
fromList [1,3,4,8,9]
ghci> Set.insert 8 $ Set.fromList [5..10]
fromList [5,6,7,8,9,10]
ghci> Set.delete 4 $ Set.fromList [3,4,5,4,3,4,5]
fromList [3,5]
isSubsetOf 和 isProperSubsetOf
判断子集与真子集
ghci> Set.fromList [2,3,4] `Set.isSubsetOf` Set.fromList [1,2,3,4,5]
True
ghci> Set.fromList [1,2,3,4,5] `Set.isSubsetOf` Set.fromList [1,2,3,4,5]
True
ghci> Set.fromList [1,2,3,4,5] `Set.isProperSubsetOf` Set.fromList [1,2,3,4,5]
False
ghci> Set.fromList [2,3,4,8] `Set.isSubsetOf` Set.fromList [1,2,3,4,5]
False
map 和 filter
ghci> Set.filter odd $ Set.fromList [3,4,5,6,7,2,3,4]
fromList [3,5,7]
ghci> Set.map (+1) $ Set.fromList [3,4,5,6,7,2,3,4]
fromList [3,4,5,6,7,8]
toList
集合有一常见用途,那就是先 fromList
删掉重复元素后再 toList
转回去。尽管 Data.List 模块的 nub
函数完全可以完成这一工作,但使用集合则会快很多
注意:
nub
函数只需 List 中的元素属于 Eq 型别类就行了,而若要使用集合,它必须得属于 Ord 型别类
ghci> let setNub xs = Set.toList $ Set.fromList xs
ghci> setNub "HEY WHATS CRACKALACKIN"
" ACEHIKLNRSTWY"
ghci> nub "HEY WHATS CRACKALACKIN"
"HEY WATSCRKLIN"
可以从中看出,nub
保留了 List 中元素的原有顺序,而 setNub
不。
建立自己的模块
定义模块
文件名和模块名称必须相同吗,在在模块的开头定义模块中的名称
下面,我们在 Geometry.hs
中定义一个 Geometry
模块,模块中声明了对球体,立方体和立方体的面积和体积相关解法的函数的名字
module Geometry
( sphereVolume
,sphereArea
,cubeVolume
,cubeArea
,cuboidArea
,cuboidVolume
) where
下面继续定义函数体
module Geometry
( sphereVolume
,sphereArea
,cubeVolume
,cubeArea
,cuboidArea
,cuboidVolume
) where
sphereVolume :: Float -> Float
sphereVolume radius = (4.0 / 3.0) * pi * (radius ^ 3)
sphereArea :: Float -> Float
sphereArea radius = 4 * pi * (radius ^ 2)
cubeVolume :: Float -> Float
cubeVolume side = cuboidVolume side side side
cubeArea :: Float -> Float
cubeArea side = cuboidArea side side side
cuboidVolume :: Float -> Float -> Float -> Float
cuboidVolume a b c = rectangleArea a b * c
cuboidArea :: Float -> Float -> Float -> Float
cuboidArea a b c = rectangleArea a b * 2 + rectangleArea a c * 2 + rectangleArea c b * 2
rectangleArea :: Float -> Float -> Float
rectangleArea a b = a * b
注意:模块中定义了一个辅助函数 rectangleArea
。他可以计算矩形面积。因为我们这个模块只与三维图形打交道,因此这个辅助函数不会被导出
使用模块
要使用我们的模块,只需将模块文件 Geometry.hs 文件至于调用进程文件的同一目录之下
import Geometry
组织模块
每个模块都可以含有多个子模块。而子模块还可以有自己的子模块。我们可以把 Geometry 分成三个子模块。
首先,建立一个 Geometry 文件夹,注意首字母要大写,在里面新建三个文件
如下就是各个文件的内容:
sphere.hs
module Geometry.Sphere
( volume
,area
) where
volume :: Float -> Float
volume radius = (4.0 / 3.0) * pi * (radius ^ 3)
area :: Float -> Float
area radius = 4 * pi * (radius ^ 2)
cuboid.hs
module Geometry.Cuboid
( volume
,area
) where
volume :: Float -> Float -> Float -> Float
volume a b c = rectangleArea a b * c
area :: Float -> Float -> Float -> Float
area a b c = rectangleArea a b * 2 + rectangleArea a c * 2 + rectangleArea c b * 2
rectangleArea :: Float -> Float -> Float
rectangleArea a b = a * b
cube.hs
module Geometry.Cube
( volume
,area
) where
import qualified Geometry.Cuboid as Cuboid
volume :: Float -> Float
volume side = Cuboid.volume side side side
area :: Float -> Float
area side = Cuboid.area side side side
导入的时候就要通过子模块导入了
import Geometry.Sphere
因为各个子模块当中有重名,因此导入的时候可能需要限定
import qualified Geometry.Sphere as Sphere
import qualified Geometry.Cuboid as Cuboid
import qualified Geometry.Cube as Cube