缺省模塊
Prelude 模塊是缺省模塊,會被自動載入
載入模塊
用 import 可以載入模塊,模塊載入后,其中的名字就都在命名空間中了。
比如 Data.List 模塊中有一個 nub 函數(shù),可以篩選掉列表中的重復元素,下面我們用 nub 和 length 組合起來,寫一個計算列表中非重復元素個數(shù)的函數(shù)
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 函數(shù),可以向下面這樣
import Data.List (nub,sort)
也可以用 hiding 指令排除掉模塊中的某些名字,比如排除掉 nub 函數(shù)
import Data.List hiding (nub)
限定載入模塊
如果要載入的模塊中有和當前命名空間中重復的名字,則需要用 qualified 指令限定載入的模塊。這時候再使用限定載入模塊中的名字,就必須使用全限定名
比如 Data.Map 中就有和缺省模塊中同樣名字的 filter 函數(shù),要使用它就必須進行限定
import qualified Data.Map
Data.Map.filter
使用全限定名字實在是太啰嗦了,所以我們可以用 as 指令給被限定的模塊起個簡短的別名
import qualified Data.Map as M
M.filter
Data.List
該模塊提供了一組非常有用的 List 處理函數(shù)。出于方便起見,幾個常用的函數(shù)(如 map 和 filter)被引入到了 Prelude 模塊中,可以直接使用。下面我們來看看幾個以前沒見過的函數(shù)
intersperse
取一個元素與 List 作參數(shù),并將該元素置于 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 作參數(shù)。它會將第一個 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
轉(zhuǎn)置 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'
它們是各自惰性實現(xiàn)的嚴格版本,如果用惰性 fold 時經(jīng)常遇到溢出錯誤,就應換用它們的嚴格版。
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 作參數(shù)。只有其中的值全為 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 更靈活,可以自定義判斷真值的函數(shù),使用 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
迭代調(diào)用,他會用一個函數(shù)調(diào)用初值,得到的結果再次調(diào)用該函數(shù),迭代下去,產(chǎn)生一個無限的 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
根據(jù)條件從 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 的數(shù)的和,因為 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)]
--這里的匿名函數(shù)用到了模式匹配
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 中的出現(xiàn)次數(shù)
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 相似,只是它們會遞歸地調(diào)用自身直到什么都不剩
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 實現(xiàn)一個在列表中搜索子列的函數(shù)
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
在列表中查找并返回首個符合該條件的元素,返回的時候會將其轉(zhuǎn)換為 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 就會引發(fā)一個錯誤。
把它改成 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 的反函數(shù),它取一組字串的 List,并將其通過 '\n'合并到一塊
ghci> unlines ["first line","second line","third line"]
"first line\nsecond line\nthird line\n"
words 和 unwords
可以把一個字串分為一組單詞或執(zhí)行相反的操作,很有用
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 作參數(shù),會刪掉該 List 中首次出現(xiàn)的這一元素
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]
整數(shù)通用版函數(shù)
length,take,drop,splitAt,!! 和 replicate 這些函數(shù)涉及到整數(shù)的時候使用的是 Int 類型,通用性很差,但出于歷史原因,無法修改以前的代碼。
在 Data.List 中包含了更通用的替代版,如: genericLength,genericTake,genericDrop,genericSplitAt,genericIndex 和 genericReplicate。
比如 genericLength 的型別聲明則為 genericLength :: (Num a) => [b] -> a,Num 既可以是整數(shù)又可以是浮點數(shù),let xs = [1..6] in sum xs / genericLength xs 就不會有問題了
條件通用版函數(shù)
nub, delete, union, intsect 和 group 等函數(shù)都是用相等性作為測試條件,通用替代版 nubBy,deleteBy,unionBy,intersectBy 和 groupBy 可以自己定義一個函數(shù)作為測試條件
比如,用 groupBy 將符號相同的數(shù)分在一組
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 函數(shù)
使用 on 函數(shù)可以進一步精簡代碼, on 函數(shù)定義如下:
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
f `on` g = \x y -> f (g x) (g y)
因此 (==) 'on' (> 0) 得到的函數(shù)就與 \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]]
比較通用版函數(shù)
sort,insert,maximum 和 min 都有各自的通用版本。sortBy,insertBy,maximumBy 和 minimumBy,可以自定義比較函數(shù)
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 模塊包含了一組用于處理字符的函數(shù)。
過濾函數(shù)
Data.Char 模塊中含有一系列用于判定字符范圍的函數(shù),可以進行過濾操作:
| 函數(shù) | 說明 |
|---|---|
isControl |
判斷一個字符是否是控制字符 |
isSpace |
判斷一個字符是否是空格字符,包括空格,tab,換行符等 |
isLower |
判斷一個字符是否為小寫 |
isUper |
判斷一個字符是否為大寫 |
isAlpha |
判斷一個字符是否為字母 |
isAlphaNum |
判斷一個字符是否為字母或數(shù)字 |
isPrint |
判斷一個字符是否是可打印的 |
isDigit |
判斷一個字符是否為數(shù)字 |
isOctDigit |
判斷一個字符是否為八進制數(shù)字 |
isHexDigit |
判斷一個字符是否為十六進制數(shù)字 |
isLetter |
判斷一個字符是否為字母 |
isMark |
判斷是否為 unicode 注音字符,你如果是法國人就會經(jīng)常用到的 |
isNumber |
判斷一個字符是否為數(shù)字 |
isPunctuation |
判斷一個字符是否為標點符號 |
isSymbol |
判斷一個字符是否為貨幣符號 |
isSeperater |
判斷一個字符是否為 unicode 空格或分隔符 |
isAscii |
判斷一個字符是否在 unicode 字母表的前 128 位 |
isLatin1 |
判斷一個字符是否在 unicode 字母表的前 256 位 |
isAsciiUpper |
判斷一個字符是否為大寫的 ascii 字符 |
isAsciiLower |
判斷一個字符是否為小寫的 ascii 字符 |
以上函數(shù)配合 Data.List 模塊的 all 過濾字符串非常好用
ghci> all isAlphaNum "bobby283"
True
ghci> all isAlphaNum "eddy the fish!"
False
下面自定義一個 words 函數(shù)
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]
轉(zhuǎn)換函數(shù)
toUpper 將一個字符轉(zhuǎn)為大寫字母,若該字符不是小寫字母,就按原值返回
toLower 將一個字符轉(zhuǎn)為小寫字母,若該字符不是大寫字母,就按原值返回
toTitle 將一個字符轉(zhuǎn)為 title-case,對大多數(shù)字元而言,title-case 就是大寫
digitToInt 將一個字符轉(zhuǎn)為 Int 值,而這一字符必須得在 '1'..'9','a'..'f'或'A'..'F' 的范圍之內(nèi)
ghci> map digitToInt "34538"
[3,4,5,3,8]
ghci> map digitToInt "FF85AB"
[15,15,8,5,10,11]
intToDigit 是 digitToInt 的反函數(shù)。它取一個 0 到 15 的 Int 值作參數(shù),并返回一個小寫的字符
ghci> intToDigit 15
'f'
ghci> intToDigit 5
'5'
ord 與 char 函數(shù)可以將字符與其對應的數(shù)字相互轉(zhuǎn)換
ghci> ord 'a'
97
ghci> chr 97
'a'
ghci> map ord "abcdefgh"
[97,98,99,100,101,102,103,104]
Data.Map
鍵值對對象
在 Haskell 中,鍵值對就是序?qū)Φ牧斜?,如下就是一個鍵值對對象
phoneBook = [("betty","555-2938") ,
("bonnie","452-2928") ,
("patsy","493-2928") ,
("lucille","205-2928") ,
("wendy","939-8282") ,
("penny","853-2492") ]
可以自己實現(xiàn)一個鍵值對查找函數(shù)
findKey :: (Eq k) => k -> [(k,v)] -> v
findKey key xs = snd . head . filter (\(k,v) -> key == k) $ xs
如果要查找的鍵不再關聯(lián)列表中,filter 函數(shù)就會返回一個空列表, head 函數(shù)就會出現(xiàn)錯誤,因此應該用 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
這其實是一個經(jīng)典的 fold 模式。 看看用 fold怎樣實現(xiàn)吧
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
我們實現(xiàn)的這個函數(shù)其實模塊中已經(jīng)存在了,就是 Data.List 模塊中的 lookup 函數(shù)。這個函數(shù)的算法需要遍歷整個 List,性能稍差,Data.Map 模塊提供了更高效的方式(通過樹實現(xiàn)),并提供了一組好用的函數(shù)。
注意:由于
Data.Map中的一些函數(shù)與Prelude和Data.List模塊存在命名沖突,所以我們使用qualified import
Map 對象
之前我們一直使用關聯(lián)列表作為鍵值對,其實還有專門的 Data.Map.Map 對象,他的鍵必須是可排序的,因為它會按照某順序?qū)⑵浣M織在一棵樹中。
在處理鍵值對時,只要鍵的型別屬于 Ord 型別類,就應該盡量使用Data.Map
fromList
這個函數(shù)可以將一個關聯(lián)列表轉(zhuǎn)換成等價的 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 做參數(shù),給這個 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)]
--用部分函數(shù)、組合函數(shù)連續(xù)調(diào)用
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
取一個鍵值對做參數(shù),并返回一個只含有一個映射的 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
是個判斷函數(shù),它取一個鍵與 map 做參數(shù),并返回該鍵是否存在于該 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 的反函數(shù)
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 很像,只是它不會直接忽略掉重復鍵,而是交給一個函數(shù)來處理它們。假設一個姑娘可以有多個號碼,而我們有個像這樣的關聯(lián)列表:
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"
一旦出現(xiàn)重復鍵,這個函數(shù)會將不同的值組在一起,同樣,也可以缺省地將每個值放到一個單元素的 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 若已經(jīng)包含這個鍵,就問問這個函數(shù)該怎么辦
ghci> Map.insertWith (+) 3 100 $ Map.fromList [(3,4),(5,103),(6,339)]
fromList [(3,104),(5,103),(6,339)]
Data.Set
Data.Set 模塊提供了對數(shù)學中集合的處理
- 里面的每個元素都是唯一的,內(nèi)部由一棵樹來組織
- 必須是可排序的
- 插入,刪除,判斷從屬關系之類的操作,要比 List 快得多
- 對一個集合而言,最常見的操作莫過于并集,判斷從屬或是將集合轉(zhuǎn)為 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 作參數(shù)并將其轉(zhuǎn)為一個集合,所有元素會被排序,重復元素將被取出
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
這幾個函數(shù)就跟你想的差不多啦
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 轉(zhuǎn)回去。盡管 Data.List 模塊的 nub 函數(shù)完全可以完成這一工作,但使用集合則會快很多
注意:
nub函數(shù)只需 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 模塊,模塊中聲明了對球體,立方體和立方體的面積和體積相關解法的函數(shù)的名字
module Geometry
( sphereVolume
,sphereArea
,cubeVolume
,cubeArea
,cuboidArea
,cuboidVolume
) where
下面繼續(xù)定義函數(shù)體
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
注意:模塊中定義了一個輔助函數(shù) rectangleArea。他可以計算矩形面積。因為我們這個模塊只與三維圖形打交道,因此這個輔助函數(shù)不會被導出
使用模塊
要使用我們的模塊,只需將模塊文件 Geometry.hs 文件至于調(diào)用進程文件的同一目錄之下
import Geometry
組織模塊
每個模塊都可以含有多個子模塊。而子模塊還可以有自己的子模塊。我們可以把 Geometry 分成三個子模塊。
首先,建立一個 Geometry 文件夾,注意首字母要大寫,在里面新建三個文件
如下就是各個文件的內(nèi)容:
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