Sự khác biệt giữa gấp và giảm?


121

Đang cố gắng học F # nhưng lại nhầm lẫn khi cố gắng phân biệt giữa gấpgiảm . Fold dường như làm điều tương tự nhưng có thêm một tham số. Có lý do chính đáng nào để hai chức năng này tồn tại hay chúng ở đó để chứa những người có hoàn cảnh khác nhau? (Ví dụ: Chuỗi và chuỗi trong C #)

Đây là đoạn mã được sao chép từ mẫu:

let sumAList list =
    List.reduce (fun acc elem -> acc + elem) list

let sumAFoldingList list =
    List.fold (fun acc elem -> acc + elem) 0 list

printfn "Are these two the same? %A " 
             (sumAList [2; 4; 10] = sumAFoldingList [2; 4; 10])

1
Bạn có thể viết giảm và gấp theo nghĩa của nhau, ví dụ fold f a lcó thể được viết như reduce f a::l.
Neil

9
@Neil - Việc triển khai foldvề mặt reducephức tạp hơn thế - loại tích lũy của foldkhông nhất thiết phải giống với loại thứ trong danh sách!
Tomas Petricek

@TomasPetricek Sai lầm của tôi, ban đầu tôi định viết nó theo cách khác.
Neil

Câu trả lời:


171

Foldnhận một giá trị ban đầu rõ ràng cho bộ tích lũy trong khi reducesử dụng phần tử đầu tiên của danh sách đầu vào làm giá trị bộ tích lũy ban đầu.

Điều này có nghĩa là bộ tích lũy và do đó loại kết quả phải khớp với loại phần tử danh sách, trong khi chúng có thể khác nhau foldkhi bộ tích lũy được cung cấp riêng. Điều này được phản ánh trong các loại:

List.fold : ('State -> 'T -> 'State) -> 'State -> 'T list -> 'State
List.reduce : ('T -> 'T -> 'T) -> 'T list -> 'T

Ngoài ra, reduceném một ngoại lệ vào danh sách đầu vào trống.


Vì vậy, về cơ bản thay vì làm fold, bạn có thể chỉ cần thêm giá trị ban đầu đó vào đầu danh sách và làm gì reduce? Mục đích của việc foldsau đó là gì?
Pacerier

2
@Pacerier - Hàm tích lũy cho nếp gấp có một kiểu khác: 'state -> 'a -> 'statecho nếp gấp và 'a -> 'a -> 'acho giảm, do đó, giảm ràng buộc kiểu kết quả giống với kiểu phần tử. Hãy xem câu trả lời của Tomas Petricek bên dưới.
Lee

178

Ngoài những gì Lee đã nói, bạn có thể xác định reducetheo cách fold, nhưng không (dễ dàng) theo cách khác:

let reduce f list = 
  match list with
  | head::tail -> List.fold f head tail
  | [] -> failwith "The list was empty!"

Thực tế là foldlấy giá trị ban đầu rõ ràng cho bộ tích lũy cũng có nghĩa là kết quả của foldhàm có thể có kiểu khác với kiểu giá trị trong danh sách. Ví dụ: bạn có thể sử dụng loại tích lũy stringđể nối tất cả các số trong danh sách thành một biểu diễn văn bản:

[1 .. 10] |> List.fold (fun str n -> str + "," + (string n)) ""

Khi sử dụng reduce, loại bộ tích lũy giống với loại giá trị trong danh sách - điều này có nghĩa là nếu bạn có một danh sách các số, kết quả sẽ phải là một số. Để triển khai mẫu trước, bạn phải chuyển đổi các số thành stringtrước rồi tích lũy:

[1 .. 10] |> List.map string
          |> List.reduce (fun s1 s2 -> s1 + "," + s2)

2
Tại sao xác định giảm như vậy mà nó có thể lỗi trong thời gian chạy?
Fresheyeball

+1 cho lưu ý về tính tổng quát của fold' & its ability to express giảm '. Một số ngôn ngữ có khái niệm về sự chirality cấu trúc (Haskell tôi đang nhìn bạn), bạn có thể gập sang trái hoặc phải được mô tả trực quan trong wiki này ( en.wikipedia.org/wiki/Fold_%28higher-order_ Chức năng ). Với cấu trúc nhận dạng, hai toán tử FP 'cơ bản' khác (bộ lọc và fmap) cũng có thể triển khai được với cấu trúc ngôn ngữ hạng nhất `` nếp gấp '' hiện có (tất cả chúng đều là cấu trúc đẳng hình). ( cs.nott.ac.uk/~pszgmh/fold.pdf ) Xem: HoTT, Princeton (Phần bình luận này quá nhỏ để chứa ..)
Andrew

Vì tò mò .. điều này sẽ làm cho hiệu suất của giảm nhanh hơn gấp vì nó ít giả định hơn về các loại và ngoại lệ?
sksallaj

19

Hãy xem chữ ký của họ:

> List.reduce;;
val it : (('a -> 'a -> 'a) -> 'a list -> 'a) = <fun:clo@1>
> List.fold;;
val it : (('a -> 'b -> 'a) -> 'a -> 'b list -> 'a) = <fun:clo@2-1>

Có một số khác biệt quan trọng:

  • Mặc dù chỉ reducehoạt động trên một loại phần tử, bộ tích lũy và liệt kê các phần tử trong foldcó thể ở các loại khác nhau.
  • Với reduce, bạn áp dụng một hàm fcho mọi phần tử danh sách bắt đầu từ phần tử đầu tiên:

    f (... (f i0 i1) i2 ...) iN.

    Với fold, bạn áp dụng fbắt đầu từ bộ tích lũy s:

    f (... (f s i0) i1 ...) iN.

Do đó, reducedẫn đến một ArgumentExceptiondanh sách trống. Hơn nữa, foldlà chung chung hơn reduce; bạn có thể sử dụng foldđể thực hiện reducemột cách dễ dàng.

Trong một số trường hợp, việc sử dụng reducesẽ ngắn gọn hơn:

// Return the last element in the list
let last xs = List.reduce (fun _ x -> x) xs

hoặc thuận tiện hơn nếu không có bất kỳ bộ tích lũy hợp lý nào:

// Intersect a list of sets altogether
let intersectMany xss = List.reduce (fun acc xs -> Set.intersect acc xs) xss

Nói chung, foldmạnh hơn với bộ tích lũy thuộc loại tùy ý:

// Reverse a list using an empty list as the accumulator
let rev xs = List.fold (fun acc x -> x::acc) [] xs

18

foldlà một chức năng có giá trị hơn nhiều so với reduce. Bạn có thể xác định nhiều chức năng khác nhau về mặt fold.

reducechỉ là một tập hợp con của fold.

Định nghĩa về nếp gấp:

let rec fold f v xs =
    match xs with 
    | [] -> v
    | (x::xs) -> f (x) (fold f v xs )

Ví dụ về các chức năng được xác định theo dạng gấp:

let sum xs = fold (fun x y -> x + y) 0 xs

let product xs = fold (fun x y -> x * y) 1 xs

let length xs = fold (fun _ y -> 1 + y) 0 xs

let all p xs = fold (fun x y -> (p x) && y) true xs

let reverse xs = fold (fun x y -> y @ [x]) [] xs

let map f xs = fold (fun x y -> f x :: y) [] xs

let append xs ys = fold (fun x y -> x :: y) [] [xs;ys]

let any p xs = fold (fun x y -> (p x) || y) false xs 

let filter p xs = 
    let func x y =
        match (p x) with
        | true -> x::y
        | _ -> y
    fold func [] xs

1
Bạn định nghĩa của bạn foldkhác List.foldvới loại List.fold('a -> 'b -> 'a) -> 'a -> 'b list -> 'a, nhưng trong trường hợp của bạn ('a -> 'b -> 'b) -> 'b -> 'a list -> 'b. Chỉ để làm cho nó rõ ràng. Ngoài ra, việc triển khai append của bạn là sai. Nó sẽ hoạt động nếu bạn thêm một ràng buộc vào nó, ví dụ List.collect id (fold (fun x y -> x :: y) [] [xs;ys]), hoặc thay thế khuyết điểm bằng toán tử nối thêm. Vì vậy, append không phải là ví dụ tốt nhất trong danh sách này.
jpe
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.