F#入門-第三章 功能性-第三節 高階函數(fold)

    本節介紹高階函數fold。在英語中,fold單詞是指折起來,摺疊的意思,在F#中,所謂“摺疊”,是指對集合中的各元素進行統一運算,最後返回一個值的意思。

    下面的介紹中,用列表舉例代替集合進行說明。例如,拿“列表求和”舉例,輸入:[1; 2; 3; 4; 5; 6; 7; 8; 9; 10],輸出:55。原來的int值的集合(int list),變成了單一的int值。因此,這種抽象化計算的時候,就使用到了fold函數。另外,有時統一運算後返回的值也可就是集合,所以輸出值的類型並不一定比輸入值的類型簡單。(例如後例中的rev就是(int list -> int list))

    fold函數,根據計算順序分爲兩種。

 List.fold_left
    ('state -> 'a -> 'state) -> 'state -> 'a list -> 'state
    如果將第一個參數稱爲f,第二個參數稱爲init,第三個參數稱爲[l1;l2;..;ln]
    則fold_left執行如下運算
    f (... (f (f (f init l1) l2) l3) ... ln)

    例:計算fold_left (fun x y->x y) 0 [1..3]爲((0 1) 2) 3

 

 List.fold_right
    ('a -> 'state -> 'state) -> 'a list -> 'state -> 'state
    如果將第一個參數稱爲f,第二個參數稱爲[l1;..;ln-1;ln],第三個參數稱爲init
    則fold_right執行如下運算
    f l1 (... (f ln-1 (f ln init)) ...)

    例:計算fold_right (fun x y->x y) [1..3] 0爲1 (2 (3 0))


    要想熟練使用fold函數,必須深刻理解並記住fold函數的第一個函數型參數的意義。第一個參數爲帶兩個參數的函數,這兩個參數爲“到現在爲止的fold計算的累計值”和“列表的元素”。暫且將累計值稱爲acc,列表元素稱爲x,init是從acc方向傳遞給x方向。同時,在fold_left與fold_right中acc與x的放置順序不一致,fold_left的第一個參數爲f acc x,fold_right的第一個參數爲f x acc,請注意到這一不同。

    fold是使用起來非常方便,理解起來比較困難的函數,爲了讓大家對fold的使用場合有個印象,我們拿使用fold可以替代的函數進行舉例。

想要用fold進行替代的函數
//返回列表長度
let rec count = function
    | [] -> 0
    | x::xs -> 1 (count xs);;
//返回列表中各元素之總和
let rec sum = function
     | [] -> 0
    | x::xs -> x (sum xs);;
//將列表逆序。
//重點是沒有使用::而使用@
let rec rev = function
    | [] -> []
    | x::xs -> (rev xs) @ [x];;


    如您所見,以上函數都使用相同形式進行定義的.這些固定形式的函數都可以用fold進行替代。

用fold進行替代的例子
//照原代碼進行逐字替代
let count ls = List.fold_left (fun acc x -> 1 acc) 0 ls;;
let sum ls = List.fold_left (fun acc x -> x acc) 0 ls;;
let rev ls = List.fold_right (fun x acc -> acc @ [x]) ls [];;
//如果使用fold,也可以用::進行定義
let rev ls = fold_left (fun acc x -> x::acc) [] ls;;


    以下是fold_left和fold_right的使用實例。

fold_left和fold_right
List.fold_right (fun x y -> x^y) ["a";"b";"c"] "R";;
List.fold_right (fun x y -> y^x) ["a";"b";"c"] "R";;
List.fold_left (fun x y -> x^y) "L" ["a";"b";"c"];;
List.fold_left (fun x y -> y^x) "L" ["a";"b";"c"];;

List.fold_left (fun x y -> y::x) [0] [1..3];;
List.fold_right (fun x y-> x::y) [1..3] [0];;
//List.fold_left (fun x y -> x::y) [0] [1..3];; //這樣是錯的
//List.fold_right (fun x y-> y::x) [1..3] [0];; //這樣是錯的

解釋器上的運行結果(從上往下順序)
val it : string = "abcR"
val it : string = "Rcba"
val it : string = "Labc"
val it : string = "cbaL"
val it : int list = [3; 2; 1; 0]
val it : int list = [1; 2; 3; 0]

 

    事實上也可以用fold定義map。

用fold定義map
//用fold定義map
let mmap f ls = rev <| fold_left (fun acc x -> (f x)::acc) [] ls;;
//從尾端開始執行的map
let mmapr f ls = fold_right (fun x acc -> (f x)::acc) ls [];;
//執行測試
let f x = printfn "%d" x;x in
let ls = [1..3] in
List.map f ls;mmap f ls;mmapr f ls;;

 

    讓我們來看個更爲複雜的例子。

更爲複雜的例子
//組合排列(重複時不執行)
let permutation lst =
    let rec p a b = function
        | [] -> a::b
        | ls -> fold_left (fun x y -> p (y::a) x (filter ((<>)y) ls)) b ls in
    p [] [] lst;;
permutation [1..3];;


    最後看一個fold的封裝例子。fold_left是末尾遞歸的形式,fold_right不是這種形式,所以執行時fold_left的性能會更好一些。

fold的封裝例子
let rec foldl f init = function
    | [] -> init
    | x::xs -> foldl f (f init x) xs;;
let rec foldr f ls init = match ls with
    | [] -> init
    | x::xs -> f x (foldr f xs init);;


 

發佈了55 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章