[2017.02.21-22] 《Haskell趣學指南 —— Learning You a Haskell for Great Good!》

{-
2017.02.21-22 《Haskell趣學指南 —— Learning You a Haskell for Great Good!》
學習了Haskell的基本語法,並實現了一些模塊和程序,學習的時候,應該注意GHCI模式和文本模式時的一些語法的差別
[官網](http://learnyouahaskell.com)
shell > ghc xxx.hs        # 編譯文件
shell > runhaskell xxx.hs # 直接運行hs文件
shell > ghci              # 進入Haskell解釋器
ghci  > :l test.hs        -- 載入test.hs文件
ghci  > :r                -- 從新載入文件

注意
1. ghci中定義變量須要使用let關鍵字,而hs文件中不須要
2. ghci中,冒號(:)開頭標示命令,相似於vi。如 :? 表示查找助,:l 表示加載文件。
-}

----------------------------------------------------------------------------------------
-- ch00 HelloHaskell
import System.IO
main = do
    putStr $ "Hello,"           -- Hello,
    putStrLn $ "Haskell"        -- Haskell\n
    print $ "Hello,Haskell"     -- "Hello,Haskell"

----------------------------------------------------------------------------------------
-- ch01 Introduction
doubleMe x = x + x            -- 前綴函數: doubleMe 3
doubleUs x y = x*2 + y*2      -- 中綴函數: doubleUs 3 4 或 3 `doubleUs` 4

-- Haskell中程序是一系列的函數的集合,函數取數據做爲參數,並將它們轉爲想要的結果。
-- 每一個函數都會返回一個結果,能夠做爲其餘函數的子結構。
-- Hasekill中if返回的是表達式(expression)不是語句(statement)。
operatedIfSmall x = (if x>=50 then x else x*2) + 1

-- 1.3 introduction to list
-- 列表list是一種單類型的數據結構,用來存儲同構元素
let vec  = [1] ++ 2:[3,4,5] ++ 6:[7,8,9] ++ [10]  --語法糖
print vec -- [1,2,3,4,5,6,7,8,9,10]

let mat = [[1,2,3],[4,5,6],[7,8,9]]  --嵌套列表
mat!!2!!2       -- 使用雙感嘆號來索引列表中的元素,注意從0開始

let vec = [1,2,3,4,5]
vec!!0      -- 1,訪問列表中的元素
vec!!2      -- 3
head vec    -- 1
tail vec    -- [2,3,4,5]
init vec    -- [1,2,3,4]
last vec    -- 5
length vec  -- 5
null   vec  -- False
null   []   -- True
reserve vec -- [5,4,3,2,1]
take 3 vec  -- [1,2,3]
drop 3 vec  -- [4,5]
maximum vec -- 5
minimum vec -- 1
sum vec       -- 15
product vec   -- 120
3 `elem` vec  -- True
13 `elem` vec -- False

-- 生成列表區間
print [1..10]   -- [1,2,3,4,5,6,7,8,9,10]
print [2,4..10] -- [2,4,6,8,10]
-- 生成無限列表
take 10 (cycle [1,2,3,4])
take 10 (repeat 5)
replicate 10 5

-- 列表推導 list compression
print [x*2 | x<- [1..10]]   --[2,4,6,8,10,12,14,16,18,20]
-- 增長謂詞(predicate),過濾(filter)
print [x*2 | x<- [1..10], x*2>10]   --[12,14,16,18,20]

-- 判斷一個數是否爲奇數,奇數返回True,偶數返回False。
let boomBangs xs = [ if x < 6 then "BOOM!" else "BANG!" | x <- xs, odd x]
boomBangs [1..10]

-- 對兩個列表組合,使用逗號隔開的多個謂詞
print [x*y| x<-[3,4,5],y<-[8,9,10]]     -- [24,27,30,32,36,40,40,45,50]
print [x*y| x<-[3,4,5],y<-[8,9,10],x*y>28, x* y<45] -- [30,32,36,40,40]

-- 使用一組名詞和一組形容詞構成列表推導式子
let nouns = ["hobo","frog","pope"]
let adjectives = ["lazy","grouchy","scheming"]
[adjective ++ " " ++ noun | adjective <- adjectives, noun <- nouns]
-- ["lazy hobo","lazy frog","lazy pope","grouchy hobo","grouchy frog",
-- "grouchy pope","scheming hobo","scheming frog","scheming pope"]

-- 自定義length函數,下劃線(_)表示可有可無的臨時變量
let mylength xs = sum [1| _ <- xs]
mylength [2,4..10]

-- 去除字符串中非大寫字母
let removeNonUppercase xs = [c |c<-xs, c `elem` ['A'..'Z']]
removeNonUppercase "Hello,World!"         -- "HW"

-- 嵌套處理嵌套列表(列表的列表)
let xxs = [[1,2,3,4],[5,6,7,8]]
let myevens xxs = [[x|x<-xs, even x] | xs <- xxs]
myevens xxs


-- 元組tuple,又稱有序對pair,容許存儲多個異構的值
let tt =(1,2.0,'3',"four")

-- 使用元組的列表存儲座標
let pts = [(x,y)|x<-[1..5],y<-[5,4..1]]
pts!!3        -- (4,1)
fst (pts!!3)  -- 4
snd (pts!!3)  -- 1

-- zip交叉配對
let tbs = zip [1..] ["one","two","three","four","five"]
print tbs

-- 查找邊長是整數且周長爲24的直角三角形
let rightTriangle24 = [(a,b,c)|c<-[1..(24 `div` 2)],b<-[1..c],a<-[1..b],a^2+b^2==c^2, a+b+c==24]
rightTriangle24



----------------------------------------------------------------------------------------
-- ch02 type reference
{-
Haskell常見類型有 Bool,Int,Float,Double,Num,Char,[],()等,凡是類型,首字母必須大寫。
使用小寫字母表示的類型變量,表示能夠是任意類型。
:type True -- True :: Bool
:type fst  -- fst :: (a, b) -> a

類型類定義行爲的抽象接口(相似於純虛基類),若是某個類型是某個類型類的實例(instance),那麼它必須實現該類型所描述的行爲。
具體來講,類型類就是一組函數集合,若是某類型實現某類型類的實例,就須要爲這一類類型提供這些函數的實現。
:type (==) -- (==) :: (Eq a) => a -> a -> Bool

常見類型類有Eq等性測試、Ord比較大小、Show轉化爲字符串、Read從字符串讀取參數並轉爲爲種類型、Enum連續枚舉、Bounded上下界限、Num數值類、Floating浮點類、Integeral實數和整數。一個類型能夠有多個類型類實例,一個類型類能夠包含多個類型做爲實例。有時候某些類型是另外類型的先決條件(prerequisite),如Eq是Ord實例的先決條件。
Eq:必須定義了==和/=兩個函數
Ord:包含了全部標準類型的比較函數,如<,>,<=,>=等。compare函數取兩個Ord中的相同類型的值做爲參數,返回一個Ordering類型的值(有GT、LT、EQ三種值)。
Show:常見的是show函數
Read:常見的是read函數
Enum:連續枚舉,每一個值都有前驅predecessor和後繼successer,能夠分別經過pred和succ函數獲得。
Bounded:反應的是實例類型的上下限,經過maxBound和minBound得到,這兩個函數類型都是多態函數,原型爲 (Bounded a)=>a。
-}
let tt =(True,1,2.0,'3',"four")
:type tt    -- tt :: (Fractional t1, Num t) => (Bool, t, t1, Char, [Char])
let tf = (head,tail,init,last)
:type tf    -- tf :: ([a] -> a, [a1] -> [a1], [a2] -> [a2], [a3] -> a3)

:t (==)       -- (==) :: Eq a => a -> a -> Bool
5 == 5        -- True
5 /= 5        -- False

:t (>)        -- (>) :: Ord a => a -> a -> Bool
5 > 5         -- False
5 `compare` 5 -- EQ

:t show       -- show :: Show a => a -> String
show 3.14     -- "3.14"

:t read       -- read :: Read a => String -> a
read "True" || False -- True
[read "True",False]  -- [True, False]
(read "3.14"::Double) + 2.4       -- 5.54
read "[1,2,3,4,5]" ++ [6]          -- [1,2,3,4,5,6]
(read "[1,2,3,4,5]"::[Int]) ++[6] -- [1,2,3,4,5,6]

[1..5]     --[1,2,3,4,5]
[LT ..GT] --[LT,EQ,GT]
succ 'D'   -- 'E'
pred 'D'   -- 'C'

minBound::(Bool,Int,Char)  -- (False,-9223372036854775808,'\NUL')
maxBound::(Bool,Int,Char) -- (True,9223372036854775807,'\1114111')

:t fromIntegral       -- fromIntegral :: (Integral a, Num b) => a -> b
length [x|x<-[1..100],x `mod` 2 == 0, x `mod` 3 == 0]  --16
fromIntegral (length [x|x<-[1..100],x `mod` 2 == 0, x `mod` 3 == 0]) + 3.14  --19.14


-- ch03 函數語法
-- 模式匹配(pattern matching)是經過檢查數據是否符合特定的結構來判斷是否匹配,並從模式中解析數據。
-- 在模式中給出小寫字母的名字而不是具體的值,那麼就是一個萬能模式(catchal pattern),它總能匹配輸入參數,並將其綁定到模式中的名字供咱們引用。
-- 模式用來檢查參數的結構是否匹配,而哨兵(guard)用來檢查參數的性質時候威震,相似於if條件判斷語句。

-- 萬能匹配模式,替代if-then-else決策樹
-- 辨別1-4並輸出相應的單詞,不然令作處理
sayMe :: Int -> String
sayMe 1 = "One"
sayMe 2 = "Two"
sayMe 3 = "Three"
sayMe 4 = "Four"
sayMe x = "Not between 1 and 4"
-- print [sayMe x| x<- [1..5]]   -- ["One","Two","Three","Four","Not between 1 and 4"]


-- 階乘函數 product[1..n]
factorial :: Int -> Int
factorial 0 = 1
factorial n = factorial(n-1) * n
-- factorial 10    -- 3628800


-- 元組的模式匹配
addTuples :: (Double,Double) ->  (Double,Double) ->  (Double,Double)
addTuples (x1,y1) (x2,y2) = (x1+x2,y1+y2)
-- addTuples (1,2) (3,4) -- (4.0,6.0)


-- 獲取三元組第三個元素
thirdItem :: (a,b,c) -> c
thirdItem (_,_,z) = z
-- thirdItem (1,2,3) -- 3


-- 列表推導模式匹配,使用運行時錯誤來指明錯誤
myfirst :: [a] -> a
myfirst [] = error "Cannot call myfirst on an empty list, dummy!"
myfirst (x:_) = x


-- 列表匹配,綁定多個變量
tell :: (Show a) => [a] -> String
tell [] = "The list is empty"
tell (x:[]) = "The list has one element: " ++ show x
tell (x:y:[]) = "The list has two elements: " ++ show x ++ " and " ++ show y
tell (x:y:_) = "This list is long. The first two elements are: " ++ show x ++ " and " ++ show y
-- tell [1..5] -- "This list is long. The first two elements are: 1 and 2"


-- 使用as模式,將模式的值分割爲多個項的同時保持對總體的引用。
firstLetter :: String -> String
firstLetter "" = "Empty string, whoops!"
firstLetter all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]
-- firstLetter "Hello" -- "The first letter of Hello is H"


-- 使用哨兵,哨兵是一個布爾表達式,跟在豎線(|)右邊,計算爲真的時候就選擇對應的函數體,不然向後查找。
tellBMI :: (RealFloat a) => a -> String
tellBMI bmi
    | bmi <= 18.5 = "You're underweight, you emo, you!"
    | bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"
    | bmi <= 30.0 = "You're fat! Lose some weight, fatty!"
    | otherwise   = "You're a whale, congratulations!"
-- tellBMI 21

-- 關於計算時數值的一些問題,參考https://hackage.haskell.org/package/base-4.9.1.0/docs/Numeric.html
calcBMI :: (RealFloat a, Show a) => a -> a -> String
calcBMI weight height
    | bmi <= skinny = info++ "You're underweight, you emo, you!"
    | bmi <= normal = info++ "You're supposedly normal. Pffft, I bet you're ugly!"
    | bmi <= fat    = info++ "You're fat! Lose some weight, fatty!"
    | otherwise     = info++ "You're a whale, congratulations!"
    where bmi =  weight / height **2.5 *1.3
          (skinny,normal,fat) = (18.5, 25.0, 30.0)  -- man24,woman22
          info= "You are " ++ show weight ++" kgs, " ++show height ++ " m, and your bmi is " ++ show bmi ++ "! \n"
-- tellBMI 63 172

--列表生成器generator,let語句,謂詞等構造列表推導式
calcBMI2 :: [(Double,Double)] ->[Double]
calcBMI2 xs = [bmi|(w,h)<-xs, let bmi = w/h**2.5 *1.3,bmi>15.0]


-- 使用case語句,其實模式匹配不過是case表達式的語法糖而已
describeList :: [a] -> String
describeList xs = "The list is " ++ case xs of [] -> "empty."
                                                [x] -> "a singleton list."
                                                xs -> "a longer list."

-- 使用where what語句替換case語句
describeList2 :: [a] -> String
describeList2 xs = "The list is " ++ what xs
    where  what [] = "empty."
            what [x] = "a singleton list."
            what xs = "a longer list."


----------------------------------------------------------------------------------------
-- ch4 遞歸
-- 按照遞歸的基本思想,遞歸傾向於將問題展開爲一樣的子問題,並不斷對子問題進行展開、求解,直達到達問題的基準條件(base case)爲止。基準條件中問題沒必要再進行分解,必須有程序員知名一個非遞歸結果。
-- 編寫函數的時候,首先肯定基準條件,也就是對應特殊輸入的簡單非遞歸解(好比空列表排序結果仍然爲空);而後將問題分解爲一個或者多個子問題,並遞歸地調用自身,最後基於子問題裏獲得的結果,組合成爲最終的解(好比快速排序中,將列表分解爲兩個列表和一個主元,並對這兩個列表分別應用同一個函數進行排序,最後獲得的結果從新組合起來,就是最終排序後的列表了。
-- 遞歸在Haskell中很重要,命令式語言中傾向於「告知如何計算」(how),而Haskell傾向於「聲明問題是什麼」(what)。這也就是說Haskell編程是,重要的不是給出解題步驟,而是定義問題和解的描述。此時遞歸將大顯身手,一旦肯定了正確的基準條件和正確的子問題,Haskell就會根據子問題的求解結果組合成最終的結果。

-- 遞歸計算最大值,注意何時用方括號,何時用圓括號
mymax :: (Ord a) => [a] -> a
mymax [] = error "empty list"      -- 基準模式1,空列表拋出錯誤
mymax [x] = x                      -- 基準模式2,單元數列表返回元素自己
mymax (x:xs) = max x (mymax xs)    -- 模式3,列表形式,遞歸調用自己
-- mymax [1,2,5,3,4] --5


-- 複製n份,取一個整數和一個元素,返回一個包含該整數個重複元素的列表。
-- 由於是布爾判斷,因此使用了哨兵而不是模式匹配。
myreplicate :: Int -> a -> [a]
myreplicate n x
    | n<=0      = []
    | otherwise = x:myreplicate (n-1) x
-- myreplicate 4 3 -- [3,3,3,3]


-- 從列表中選取前n個元素
mytake :: (Num i, Ord i) => i -> [a] ->[a]
mytake n _
    | n<=0 = []          -- 基準模式1,若是要獲取不大於0個元素,則返回空列表;注意這裏的哨兵沒有otherwise,能夠轉到其餘的模式。
mytake _ [] = []         -- 基準模式2,若是列表爲空,返回空列表
mytake n (x:xs) = x: mytake (n-1) xs -- 迭代模式,將列表切片,並遞歸操做
-- mytake 3 [1..5]   -- [1,2,3]

-- 翻轉列表
myreverse :: [a] -> [a]
myreverse [] = []
myreverse (x:xs) = myreverse xs ++ [x]
-- myreverse [1..5] -- [5,4,3,2,1]

-- 生成無限列表
myrepeat :: a -> [a]
myrepeat x = [x] ++ myrepeat x
-- mytake 4 (myrepeat 3) -- [3,3,3,3]

-- 打包
myzip :: [a]->[b]->[(a,b)]
myzip [] _ = []
myzip _ [] = []
myzip (x:xs) (y:ys) = (x,y):myzip xs ys
-- myzip [1..5] [5..1]  -- []
-- myzip [1,2,3,4,5] [5,4,3,2,1] -- [(1,5),(2,4),(3,3),(4,2),(5,1)]


-- 查看是否子元素
myelem :: (Eq a) => a -> [a] -> Bool
myelem a [] = False
myelem a (x:xs)
    | a == x = True
    | otherwise = a `myelem` xs
--  3 `myelem` [1..5] -- True
-- 13 `myelem` [1..5] -- False


-- 快速排序
-- 使用謂詞和列表推導
myqsort :: (Ord a) => [a] -> [a]
myqsort [] = []              -- 基準模式是空列表
myqsort (x:xs) =             -- 迭代模式是,將小於當前元素的放在左邊,大於當前元素的放在右邊,分別對左右兩部分進行快排
    myqsort [a|a<-xs, a<=x] ++ [x] ++ myqsort [a|a<-xs, a>x]

-- 使用filter函數
myqsort2 :: (Ord a) => [a] -> [a]
myqsort2 [] = []              -- 基準模式是空列表
myqsort2 (x:xs) =             -- 迭代模式是,將小於當前元素的放在左邊,大於當前元素的放在右邊,分別對左右兩部分進行快排
    let smallerOrEqual = filter (<=x) xs
        larger = filter (>x) xs
    in myqsort2 smallerOrEqual ++ [x] ++ myqsort2 larger

-- myqsort [1,5,3,2,7,6,4,9,8]  -- [1,2,3,4,5,6,7,8,9]


----------------------------------------------------------------------------------------
-- ch05 高階函數
-- Haskell中能夠把函數用作參數和返回值,這樣的特定成爲高階函數。
-- C++14支持函數返回自動類型推導,能夠返回一個函數內部的lambda,也就是支持高階函數,好BUG啊。

{-
// g++ -std=c++14 xxx.cpp
auto fun(){
    auto myfun = [](int a, int b){return a>b?a:b;};
    return myfun;
}

auto myfun = fun();
cout << myfun(3,4)<<endl;
-}

-- 只要在類型簽名中看到了箭頭符號(->),就意味着它是一個將箭頭左邊視爲參數類型並將箭頭右側部分做爲返回類型的函數。
-- 若是遇到a->(a->a)這樣的類型簽名,說明它是一個函數,取類型爲a的值做爲參數,返回一個函數一樣取類型a爲參數,而且返回值類型是a。
-- 這樣作,能夠以部分參數調用函數,獲得一個部分應用(Partial application)函數,該函數所接受的參數是一個函數,和以前少傳入的參數類型一致。

multThree :: Int -> (Int -> (Int -> Int))
multThree x y z = x*y*z
-- multiThree 3 4 5 -- 60

-- 使用部分應用
multTwoWithThree :: Int -> Int -> Int
multTwoWithThree x y = multThree 3 x y
-- multiTwoWithThree 4 5 -- 60

-- 在ghc中使用部分應用
multTwoWithThree2 = multThree 3   -- hs文件中不用let,可是ghc中須要使用let
-- multiTwoWithThree2 4 5 -- 60

-- 對中綴函數使用截斷,除以10
dividedByTen :: (Floating a) => a -> a
dividedByTen = (/10)
-- dividedByTen 200   -- 20.0

-- 對中綴函數使用截斷
subtractFour :: Int -> Int
subtractFour = (subtract 4)
-- [subtractFour x | x<-[1..5]] -- [-3,-2,-1,0,1]


-- 對中綴函數使用截斷
isUpperCase :: Char -> Bool
isUpperCase = (`elem` ['A'..'Z'])
-- [isUpperCase x | x<-"Hello"] -- [True,False,False,False,False]

-- 取另外一個函數做爲參數,並返回函數
applyTwice :: (a->a) -> a -> a
applyTwice f x = f (f x)
-- applyTwice (+3) 10           -- 16
-- applyTwice (++[3]) [10]      -- [10,3,3]
-- applyTwice ([3]++) [10]      -- [3,3,10]


-- 實現zipWith,取一個函數和兩個列表做爲輸入,使用函數調用從兩個列表中取出的相應元素
-- 在編寫高階函數時,若是拿不許類型,能夠先省略類型聲明,而後在使用:t命令查看Haskell的類型推導結果
myzipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
myzipWith _ [] _ = []
myzipWith _ _ [] = []
myzipWith f (x:xs) (y:ys) = f x y : myzipWith f xs ys
-- myzipWith (+) [1,2,3,4,5] [11,12,13,14,15]  -- [12,14,16,18,20]
-- myzipWith (*) (replicate 5 2) [1..]         -- [2,4,6,8,10]


-- 實現flip函數,以一個函數爲參數,返回參數順序顛倒的函數
myflip :: (a->b->c) -> (b -> a -> c)
myflip f x y = f y x
-- zip [1..5] "hello"                       -- [(1,'h'),(2,'e'),(3,'l'),(4,'l'),(5,'o')]
-- myflip zip [1..5] "hello"                -- [('h',1),('e',2),('l',3),('l',4),('o',5)]
-- myzipWith div (repeat 2)  [1..5]         -- [2,1,0,0,0]
-- flip (myzipWith div) (repeat 2) [1..5]   -- [0,1,1,2,2]


-- 函數式程序員工具箱
-- 身爲函數式程序員,不多須要對單個值求解花費太多心思。更多狀況下是,處理一系列的數值、字母或者其餘類型的數據
-- 經過轉換了這一類集合來求取最後的結果。經常使用的工具函數有:map、filter

-- map接受一個取a返回b的函數和一組a值的列表爲參數,返回一組b值的列表
mymap:: (a->b) -> [a] -> [b]
mymap _ [] = []
mymap f (x:xs) = f x : mymap f xs

-- filter 去一個謂詞(predicate)和一個列表做爲參數 ,返回由列表全部符合該條件的元素組成的列表。
-- 謂詞特指判斷事物是True或者False的函數,也就是返回布爾值的函數
myfilter :: (a->Bool) -> [a] -> [a]
myfilter _ [] = []
myfilter f (x:xs)
    | f x = x : myfilter f xs
    | otherwise = myfilter f xs

-- mymap (+2) [1..10]    -- [3,4,5,6,7,8,9,10,11,12]
-- myfilter (>5) [1..10] -- [6,7,8,9,10]
-- myfilter (`elem` ['a'..'z']) "Hi, my name is Ausk!"   -- "imynameisusk"
-- myfilter (`elem` ['A'..'Z']) "Hi, my name is Ausk!"   -- "HA"

-- myfilter (<15) (myfilter even [1..20])     -- [2,4,6,8,10,12,14]
-- [x|x<-[1..20], even x,x<15]                -- [2,4,6,8,10,12,14]

-- 計算小於1000的全部奇數的平方的和,分別使用括號、列表生成器、函數組合和函數應用符
sum (takeWhile (<1000) (filter odd (map (^2) [1..])))   -- 5456,函數組合
sum (takeWhile (<1000) [x^2| x<-[1..],odd x])           -- 5456,列表生成器
sum . takeWhile (<1000) . filter odd $map (^2) $[1..]   -- 5456,函數組合和函數應用符

-- 將天然數的平方根相加,會在何處超過1000?
sqrtSum :: Int
sqrtSum = length ( takeWhile(<1000) ( scanl1 (+) (map sqrt [1..]) ) ) + 1
-- sum(map sqrt [1..130]) -- 993.6486803921487
-- sum(map sqrt [1..131]) -- 1005.0942035344083


-- 克拉茲序列,定義以下
-- 從任意天然數開始,若是是1則中止;若是是偶數則除以2;若是是奇數則乘以3而後加上1。取得結果並重復過程。
chain :: Integer -> [Integer]
chain 1=[1]     -- 基準條件
chain n         -- 哨兵選擇
    | even n = n:chain (n `div` 2)
    | odd n  = n:chain (n*3 + 1)

-- chain 1   -- [1]
-- chain 10  -- [10,5,16,8,4,2,1]


-- 映射帶有多個參數的函數
let listOfFuncs = map (*) [10..]   -- (*10) (*11) (*12) (*13) (*14) (*15) ...
(listOfFuncs !!4) 4      -- (*14) 4 = 56

-- Haskell中的Lambda是一次性的匿名函數,以右斜槓開頭,後面緊跟着函數的參數列表,參數之間使用空格分割,->以後是函數體,一般使用圓括號括起來。
map (+3) [1..5]                               -- [4,5,6,7,8]
map (\x -> x+3) [1..5]                        -- [4,5,6,7,8]
map (\(a,b) -> a+b) (zip [1..5] [11..15])     -- [12,14,16,18,20]
zipWith (\a b -> a*10 + b) [1..] [5,4,3,2,1]  -- [15,24,33,42,51]

-- 處理列表大多數具備篤定模式。一般會將基準條件設置爲空列表,而後引入 (x:xs)模式,再對單個元素和餘下的列表作一些事情。
-- 由於這一模式比較常見,Haskell中提供了摺疊(fold)函數來使之簡化。經過摺疊函數將一個數據結構(如列表)歸約爲單個值。
-- 一個摺疊能夠從左邊或者右邊開始摺疊,一般取一個二元函數、一個待摺疊的列表(和一個初始值),獲得歸約後的單值。
-- 左摺疊(foldl、foldl1)從左側開始,右摺疊(foldr、foldr1)從右側開始。
-- 使用lambda表達式和foldl進行摺疊
suml :: (Num a) => [a] -> a
suml xs = foldl (\acc x-> acc + x) 0 xs
-- 使用foldl進行摺疊
suml2 :: (Num a) => [a] -> a
suml2 xs = foldl (+) 0 xs
-- 使用lambda表達式和foldr進行摺疊
sumr :: (Num a) => [a] -> a
sumr xs = foldr (\x acc-> acc + x) 0 xs
-- 使用foldr進行摺疊
sumr2 :: (Num a) => [a] -> a
sumr2 xs = foldr (+) 0 xs


-- 使用左摺疊實現map
mapl :: (a->b) -> [a] -> [b]
mapl  f xs = foldl(\acc x -> acc ++ [f x]) [] xs
-- mapl (+1) [1..5] -- [2,3,4,5,6]

-- 使用右摺疊實現map
mapr :: (a->b) -> [a] -> [b]
mapr  f xs = foldr(\x acc -> f x : acc) [] xs
-- mapr (+1) [1..5] -- [2,3,4,5,6]

-- 使用摺疊實現一些函數
mymax_foldr1 ::(Ord a) => [a] -> a
mymax_foldr1 = foldr1 max  -- 使用foldr1函數和max組合,獲得求最值的函數
-- mymax [2,4,10,7,3,8,6,1,5,9] -- 10


-- 掃描scan,相似於摺疊,不過會將變更記錄到一個列表中
mymax_scanr1 :: (Ord a) => [a] ->[a]
mymax_scanr1 = scanr1 max
-- mymax_scanr1 [2,4,10,7,3,8,6,1,5,9] -- [10,10,10,9,9,9,9,9,9,9]


-- 掃描scan,相似於摺疊,不過會將變更記錄到一個列表中
mymax_scanl1 :: (Ord a) => [a] ->[a]
mymax_scanl1 = scanl1 max
-- mymax_scanl1 [2,4,10,7,3,8,6,1,5,9] --[2,4,10,10,10,10,10,10,10,10]


-- 無限列表摺疊,當二元函數不老是須要對第二個參數進行求值如邏輯操做(&& ||)的短路求值
-- 這時,能夠利用Haskell的惰性求值操做無限列表
myand ::[Bool] -> Bool
myand xs = foldr (&&) True xs
myand (repeat False)


-- 使用空格的函數調用符是左結合的,如 f a b c 等價於 ((f a b) c)
-- 而函數應用符(function application operator)即$的調用是右結合的
-- 函數應用符號能夠減小括號,也能夠將函數應用轉化爲函數
-- 即 f $ g $ x 等價於 f$(g$x), 如 sum $ a + b + c 等價於 (sum (a + b + c))
:t ($)     -- ($) :: (a -> b) -> a -> b
sum $3+4+5 -- 12
map ($3) [ (4+), (5*),(6/),(^2),sqrt] -- [7.0,15.0,2.0,9.0,1.7320508075688772]

-- 函數組合function composition,定義是 (f.g)(x) = f(g(x))
-- 也便是先拿參數調用一個函數,而後取結果做爲參數調用另外一個函數
-- 進行函數組合的函數是 (.)
:t (.)    --  (.) :: (b -> c) -> (a -> b) -> a -> c
map (\x -> negate $sqrt $ abs x) [-4,16,-9]  -- [-2.0,-4.0,-3.0]
map (negate . sqrt . abs) [-4,16,-9]          -- [-2.0,-4.0,-3.0]

sum (replicate 5 (max 3 4) )  -- 20
(sum . replicate 5) (max 3 4) -- 20
sum $ replicate 5 $ max 3 4   -- 20

-- 經過使用函數組合哈數來省略表達式中的大量括號,能夠首先找到最裏面的函數和參數,
-- 在它們前面加上一個$,接着省略其他函數的最後一個參數,經過 (.)組合在一塊兒。
replicate 2 ( product ( map (*3) ( zipWith max [1,4,7,9] [3,2,6,10])))
replicate 2 . product . map (*3) $ zipWith max [1,4,7,9] [3,2,6,10]


-- 使用函數組合PointFree風格,即省略共同的參數,使得代碼更加簡潔易讀。
-- 讓程序員更加傾向於思考函數的組合方式,而不是數據的攢肚和變化。
mysum ::(Num a) => [a] -> a
mysum   xs = foldr (+) 0 xs
-- 等價的point-free風格
mysum ::(Num a) => [a] -> a
mysum = foldr (+) 0

-- 原始函數
func :: (Floating a, Integral b, RealFrac a) => a -> b
func x = log(sqrt (max 50 x))
-- 等價的point-free風格
func :: (Floating a, Integral b, RealFrac a) => a -> b
func = log.sqrt.max 50


----------------------------------------------------------------------------------------
-- ch06 模塊
-- Haskell 中的模塊,相似與Python中的module,C++中的namespace,Java中的Package。
-- 模塊中能夠包含類型和函數的定義,而且容許導出(export)其中的部分定義。
-- 將模塊代碼分解到模塊中,使得代碼相互之間較少過多的依賴,也就是使得模塊鬆耦合(loosely coupled),方便管理和重用。

-- 在任意函數定義以前導入模塊,import ModuleName (FunctionName) 或者 :m ModuleName
-- 要檢查函數的定義或者查找函數所在的模塊,使用[Hoogle](http://www.haskell.org/hoogle/)
-- 學習Haskell的最好方式是查找函數和模塊的文檔,也能夠閱讀Haskell各個模塊的源代碼。

-- 注意當點號位於限定導入的模塊名和函數之間且沒有空格時標示對模塊中函數的引用;不然視爲函數的組合。
import qualified Data.Map as M -- 限定導入 Data.Map,定義別名爲M,必須使用M.xxx來引用名字
import Data.List hiding (nub)   -- 避免導入模塊中的某些函數
import Data.List (sort,group,words) -- 導入模塊中的特定函數

import Data.List (sort,group,words)
wordNums :: String ->[(String,Int)]
wordNums = map (\ws ->(head ws, length ws)) . group . sort . words
-- wordNums xs = map (\ws ->(head ws, length ws)) (group(sort(words xs)
wordNums "it is a good idea, it is very good !"   -- [("!",1),("a",1),("good",2),("idea,",1),("is",2),("it",2),("very",1)]


-- 乾草堆中的縫紉針:查找一個列表(縫紉針,needle)是否在另外一個列表(乾草堆,haystack)中
-- 等價的函數是 Data.List.isInPrefixOf
import Data.List
isIn :: Eq a => [a] -> [a] -> Bool
needle `isIn` haystack = any (needle `isPrefixOf`) $tails haystack
-- isIn "how" "Hello,how are you ?" -- True
-- isIn "hei" "Hello,how are you ?" -- False


----------------------------------------------------------------------------------------
-- 凱撒加密與解密
-- 加密,把字符映射到另外一個字符。首先把字符轉化爲數字,增長/減小偏移量,再從數字映射到字母。
import Data.Char
-- 加密,map (\c -> chr $ ord c + offset) msg
myencode :: Int -> String -> String
myencode offset = map ( chr . (+offset) . ord )
-- 解密,map (\c -> chr $ ord c + offset) msg
mydecode :: Int -> String -> String
mydecode offset = map ( chr . (-offset) . ord )

let msg = "how are you"
let offset = 10
let encoded = myencode offset msg
let decoded = mydecode offset encoded
decoded == msg      -- True


----------------------------------------------------------------------------------------
-- 實現嚴格左摺疊,不使用惰性求值,防止大量的延遲計算佔據內存而致使棧溢出
--myfoldl::  (b -> a -> b) -> b -> [a] -> b
myfoldl g initval [] = error "empty list"
myfoldl g initval [x] = g initval x
--myfoldl g initval all@(x:xs)= myfoldl g (g initval x)  xs
myfoldl g initval all@(x:xs)= myfoldl g (g initval $head all)  $tail all
-- myfoldl (+) 0 [1..100000] -- 5000050000


-- Maybe類型: Maybe a類型標示能夠爲空,能夠只含有一個元素
:t Just      -- Just :: a -> Maybe a
:t Just "Hi" -- Just "Hi" :: Maybe [Char]


----------------------------------------------------------------------------------------
import Data.Char
import Data.List
-- 計算數字的各位之和
digitSum :: Int -> Int
digitSum = sum . map digitToInt . show
-- digitSum 49999 -- 40

-- 查找一個數,這個數的各位數字之和等於n
findFirstTo :: Int -> Maybe Int
findFirstTo n = find (\x -> digitSum x == n )  [1..]
-- findFirstTo 40 -- Just 49999


-- 有些數據結構不關心數據的存儲順序,而是將數據按照鍵值對的形式進行存儲,而後按鍵索值,查找指定鍵的值。
-- 這樣的數據結構可使用關聯列表(association list)或者映射map實現,於Python中的dict,C++中的map/unordered_map
idBook = [("jj","M72340"),("ff","M72341")]

-- 取一個鍵和一個鍵值列表做爲參數,過濾列表,僅僅保留與鍵相匹配的元素,取首個鍵值對,返回其中的值。
-- 一個標準的處理列表的遞歸函數,基準條件、分隔條件、遞歸調用都齊全了,這也是典型的摺疊模式。
findKey :: (Eq k) => k -> [(k,v)] -> Maybe v
findKey key [] = Nothing
findKey key ((k,v):xs)
    | key == x = Just v
    | otherwise  findKey key xs
-- [findKey key idBook| key <- ["ff", "ff","ll"] ]  -- [Just "M72340",Just "M72341",Nothing]

-- 基於摺疊模式,替換遞歸函數。摺疊模式簡潔明瞭,一目瞭然。
findKey :: (Eq k) => k -> [(k,v)] -> Maybe v
findKey key xs = foldr (\(k,v) acc -> if key == k then Just v else acc) Nothing xs
-- [findKey key idBook| key <- ["ff", "ff","ll"] ]  -- [Just "M72340",Just "M72341",Nothing]


-- 使用Data.Map數據結構
import qualified Data.Map as M -- 限定導入 Data.Map,定義別名爲M,必須使用M.xxx來引用名字
phoneBookToMap ::(Ord k) => [(k,String)] -> M.Map k String
phoneBookToMap xs = M.fromListWith add xs
    where add number1 number2 = number1 ++ ", " ++ number2

phoneBookList = [("jj","M72340"),("jj","Q33361"),("ff","M72341")]
phoneBookMap = phoneBookToMap phoneBookList   -- fromList [("ff","M72341"),("jj","Q33361, M72340")]
newPhoneBookMap = M.insert "ff" "XX-XXX" phoneBookMap -- fromList [("ff","XX-XXX"),("jj","Q33361, M72340")]
M.size phoneBookMap  -- 2
M.lookup "jj" phoneBookMap  --Just "Q33361, M72340"
M.lookup "jia" phoneBookMap  --Nothing


-- Set
import qualified Data.Set as Set
let text1 = "Are they so different?"
let text2 = "Yeah, they are so different!"
let set1 = Set.fromList text1  -- fromList " ?Adefhinorsty"
let set2 = Set.fromList text2  -- fromList " !,Yadefhinorsty
Set.intersection set1 set2      -- fromList " defhinorsty"
Set.difference set1 set2        -- fromList "?A"
Set.difference set2 set1        -- fromList "!,Ya"
Set.union set1 set2             -- fromList " !,?AYadefhinorstyset1
Set.insert 4 $ Set.fromList [9,3,8,1]          -- fromList [1,3,4,8,9]
Set.delete 4 $ Set.fromList [3,4,5,4,3,4,5]    -- fromList [3,5]
Set.fromList [2,3,4] `Set.isSubsetOf` Set.fromList [1,2,3,4,5]     -- True


----------------------------------------------------------------------------------------
-- 自定義數據類型和類型別名

-- 汽車Car,帶類型說明的數據結構,及相應的操做
data Car = Car{ company::String, model::String, year::Int } deriving (Show)

tellCar :: Car -> String
tellCar (Car {company = c, model = m, year = y}) = "This " ++ c ++ " " ++ m ++ " was made in " ++ show y


-- 一週七天,繼承類型
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
            deriving (Eq, Ord, Show, Read, Bounded, Enum)


-- 遞歸數據結構
-- 樹的構造和操做
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)

singleton :: a -> Tree a
singleton x = Node x EmptyTree EmptyTree

treeInsert :: (Ord a) => a -> Tree a -> Tree a
treeInsert x EmptyTree = singleton x
treeInsert x (Node a left right)
    | x == a = Node x left right
    | x < a  = Node a (treeInsert x left) right
    | x > a  = Node a left (treeInsert x right)

treeElem :: (Ord a) => a -> Tree a -> Bool
treeElem x EmptyTree = False
treeElem x (Node a left right)
    | x == a = True
    | x < a  = treeElem x left
    | x > a  = treeElem x right


test = do
    let stang =  Car{company = "Ford", model = "Mustang", year=1967}
    print $ tellCar stang

    {-
        show Wednesday
        read "Saturday" :: Day
        Monday `compare` Wednesday
        Monday == Monday
        [minBound .. maxBound] :: [Day]
        succ Monday
        pred Saturday
    -}

    let nums = [8,6,4,1,7,3,5]
    let numsTree = foldr treeInsert EmptyTree nums
    print $ numsTree
    print $ 3 `treeElem` numsTree
    print $ 13 `treeElem` numsTree

----------------------------------------------------------------------------------------
{-
-- hello.hs
-- 功能:學習輸入和輸出操做
shell > ghc --make ./hello.hs
shell > ./hello
-}

import System.IO

main = do
    print "What's your name?"
    name <- getLine
    putStrLn $ "Hey " ++ name ++ ", you rock!"
    sequence $ map print [3..5]
    mapM print [3..5]   -- mapM取一個函數和一個列表做爲參數,而後將函數映射到列表上,最後將結果應用與sequence。
    mapM_ print [3..5]  -- mapM_與mapM相似,不過不保存結果。

    -- 使用 openFile打開文件,hGetContents讀取文件,hClose關閉文件
    infile_handle <- openFile "hello.hs" ReadMode
    contents <- hGetContents infile_handle
    putStr contents
    hClose infile_handle

    -- 使用withFile確保文件操做後,文件的句柄必定被關閉
    withFile "hello.hs" ReadMode (\infile_handle -> do
        contents <- hGetContents infile_handle
        putStr contents)

----------------------------------------------------------------------------------------
-- ch10 函數式地解決問題
-- 經常使用的中綴表達式,須要用括號改變優先級別。而使用逆波蘭表達式(reverse polish natation, RPN),
-- 操做符在操做數的後面。解析表達式子的時候,遇到數字時將數字壓入棧,
-- 遇到符號時彈出兩個數字並使用符號操做,而後將結果入棧。表達式結尾時,棧裏面僅剩下一個數字,也就是表達式的值。
import Data.List

solveRPN :: String -> Float
solveRPN = head . foldl foldingFunction [] . words
    where   foldingFunction (x:y:ys) "*" = (x * y):ys
            foldingFunction (x:y:ys) "+" = (x + y):ys
            foldingFunction (x:y:ys) "-" = (y - x):ys
            foldingFunction (x:y:ys) "/" = (y / x):ys
            foldingFunction (x:y:ys) "^" = (y ** x):ys
            foldingFunction (x:xs) "ln" = log x:xs
            foldingFunction xs "sum" = [sum xs]
            foldingFunction xs numberString = read numberString:xs

{-
-- http://learnyouahaskell.com/for-a-few-monads-more

readMayBe ::(Read a) => String -> Maybe a
readMayBe stmt = case reads stmt of [(x,"")] -> Just x
                                     _ -> Nothing


foldingFunction2 :: [Double] -> String -> Maybe [Double]
foldingFunction2 (x:y:ys) "+" = (x + y):ys
foldingFunction2 (x:y:ys) "-" = (y - x):ys
foldingFunction2 (x:y:ys) "/" = (y / x):ys
foldingFunction2 (x:y:ys) "^" = (y ** x):ys
foldingFunction2 (x:xs) "ln" = log x:xs
foldingFunction2 xs "sum" = [sum xs]
foldingFunction xs numberString = liftM (:xs) $ readMayBe numberString


solveRPN2 :: String -> Maybe Double
solveRPN2 stmt = do
    [result] <- foldM foldingFunction2 [] $ words stmt
    return result
-}


-----------------------------------------------------------------------------------
{-
- 計算最快的路線
# A === 50 == A1 === 5  === A2 === 40 === A3 === 10 === A4
#             ||            ||            ||            ||
#             30            20            25            00
#             ||            ||            ||            ||
# B === 10 == B1 === 90 === B2 === 2  === B3 === 8  === B4

求分別從A和B出發到達A4和B4的最短路徑

解決方案:
每次檢查三條路線的通行時間:A路上一段路程,B路上一段路程,以及鏈接兩個路口的C小路。
稱它們爲一個地段section。所以將道路標示爲4個地段:
> 50 30 10
> 5  20 90
> 40 25 2
> 10 0  8
定義道路數據類型
    data Section = Section { getA :: Int, getB :: Int, getC :: Int } deriving (Show)
    type RoadSystem = [Section]
引入標籤Label表示一個枚舉類型(A、B或者C),以及一個類型別名Path。
    data Label = A | B | C deriving (Show)
    type Path = [(Label, Int)]
在求解時,處理的列表同時維護抵達當前地段A的最佳路徑和抵達B的最佳路徑。遍歷的時候累加這兩條最佳路徑,是一個左摺疊。
摺疊的步驟是,根據抵達當前地段A路和B路的最佳路線和下一個地段的信息,分別計算到達下一個地段A和下一個地段B的最佳路線。
初始地段A、B路的最佳路線都是[],檢查地段 Section 50 10 30,推斷出到達A1和B1的最佳路線分別是[(B,10),(C,30)]和[(B,10)]。
重複這一計算步驟,直處處理完畢。寫成函數的形式,參數是一對路徑和一個地段,返回到達下一個地段的一條新的最佳路徑。
    roadStep :: (Path, Path) -> Section -> (Path, Path)

-}

import Data.List --在任何函數開始以前道路模塊
import System.IO -- 導入 IO 模塊

-- 自定義數據類型和類型別名
data Section = Section { getA :: Int, getB :: Int, getC :: Int } deriving (Show)
type RoadSystem = [Section]
data Label = A | B | C deriving (Show)
type Path = [(Label, Int)]

-- 構建道路
heathrowToLondon :: RoadSystem
heathrowToLondon = [Section 50 10 30, Section 5 90 20, Section 40 2 25, Section 10 8 0]

-- 根據當前路段A和B路的最佳路線以及下一個Section的信息,求解獲得下一個路段A路和B路的最佳路線。
roadStep :: (Path, Path) -> Section -> (Path, Path)
roadStep (pathA, pathB) (Section a b c) =
    let priceA = sum $ map snd pathA
        priceB = sum $ map snd pathB
        forwardPriceToA = priceA + a
        crossPriceToA = priceB + b + c
        forwardPriceToB = priceB + b
        crossPriceToB = priceA + a + c
        newPathToA = if forwardPriceToA <= crossPriceToA
                        then (A,a):pathA
                        else (C,c):(B,b):pathB
        newPathToB = if forwardPriceToB <= crossPriceToB
                        then (B,b):pathB
                        else (C,c):(A,a):pathA
    in  (newPathToA, newPathToB)

-- 求解最佳路徑
optimalPath :: RoadSystem -> Path
optimalPath roadSystem =
    let (bestAPath, bestBPath) = foldl roadStep ([],[]) roadSystem
    in  if sum (map snd bestAPath) <= sum (map snd bestBPath)
            then reverse bestAPath
            else reverse bestBPath

-- 將一個列表分割成爲一些等長度的列表
groupsOf :: Int -> [a] -> [[a]]
groupsOf 0 _ = undefined
groupsOf _ [] = []
groupsOf n xs = take n xs : groupsOf n (drop n xs)

-- 輸入和輸出操做
shell > ghc --make ./hello.hs
shell > ./hello


testCalcBestPath = do
    print "The default patch:" ++ show heathrowToLondon
    print "The optimal path:" ++ show (optimalPath heathrowToLondon)

    {-
    echo -e "50\n10\n30\n5\n90\n20\n40\n2\n25\n10\n8\n0" > path.txt
    runhaskell haskell.hs <path.txt
    ghc --make -O xxx.hs
    shell > ./haskell < path.txt
    print "Get path from file (runhaskell XXX.hs < path.txt):"
    -}

    -- 直接從文件中讀取路徑
    withFile "path.txt" ReadMode (\infile_handle -> do
        contents <- hGetContents infile_handle
        putStr contents
        let threeTupleList = groupsOf 3 (map read $ lines contents)
            roadSystem = map (\[a,b,c] -> Section a b c) threeTupleList
            path = optimalPath roadSystem
        putStrLn $ "The best path to take is: " ++ show path
        putStrLn $ "The best path to take is: " ++ (concat $ map (show . fst) path )
        putStrLn $ "The price of the path is: " ++ show (sum $ map snd path)
        )

main = do
    -- -- 調用測試函數
    -- testCalcBestPath

    print $ "Hello, Haskell!"
    {-
    print "What's your name?"
    name <- getLine
    putStrLn $ "Hey " ++ name ++ ", you rock!"
    sequence $ map print [3..5]
    mapM print [3..5]   -- mapM取一個函數和一個列表做爲參數,而後將函數映射到列表上,最後將結果應用與sequence。
    mapM_ print [3..5]  -- mapM_與mapM相似,不過不保存結果。

    -- 使用 openFile打開文件,hGetContents讀取文件,hClose關閉文件
    infile_handle <- openFile "hello.hs" ReadMode
    contents <- hGetContents infile_handle
    putStr contents
    hClose infile_handle

    -- 使用withFile確保文件操做後,文件的句柄必定被關閉
    withFile "hello.hs" ReadMode (\infile_handle -> do
        contents <- hGetContents infile_handle
        putStr contents)
    -}
相關文章
相關標籤/搜索