ความแตกต่างระหว่างพับและลด?


121

พยายามเรียนรู้ F # แต่มีสับสนเมื่อพยายามที่จะแยกแยะความแตกต่างระหว่างการพับและลด การพับดูเหมือนจะทำสิ่งเดียวกันแต่ใช้พารามิเตอร์พิเศษ มีเหตุผลอันชอบธรรมที่ทำให้ฟังก์ชันทั้งสองนี้มีอยู่หรือมีขึ้นเพื่อรองรับผู้ที่มีภูมิหลังที่แตกต่างกัน? (เช่น: สตริงและสตริงใน C #)

นี่คือข้อมูลโค้ดที่คัดลอกมาจากตัวอย่าง:

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
คุณสามารถเขียนลดและพับในแง่ของแต่ละอื่น ๆ เช่นสามารถเขียนเป็นfold f a l reduce f a::l
นีล

9
@Neil - การใช้งานfoldในแง่ของreduceมันซับซ้อนกว่านั้น - ประเภทของตัวสะสมfoldไม่จำเป็นต้องเหมือนกับประเภทของสิ่งต่างๆในรายการ!
Tomas Petricek

@TomasPetricek ความผิดพลาดของฉันตอนแรกฉันตั้งใจจะเขียนในทางกลับกัน
นีล

คำตอบ:


171

Foldรับค่าเริ่มต้นที่ชัดเจนสำหรับตัวสะสมในขณะที่reduceใช้องค์ประกอบแรกของรายการอินพุตเป็นค่าตัวสะสมเริ่มต้น

ซึ่งหมายความว่าตัวสะสมและประเภทผลลัพธ์จะต้องตรงกับประเภทองค์ประกอบรายการในขณะที่อาจแตกต่างกันได้foldเนื่องจากตัวสะสมมีให้แยกต่างหาก สิ่งนี้สะท้อนให้เห็นในประเภท:

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

นอกจากนี้ยังreduceมีข้อยกเว้นในรายการอินพุตว่าง


โดยพื้นฐานแล้วแทนที่จะทำfoldคุณสามารถเพิ่มค่าเริ่มต้นนั้นในจุดเริ่มต้นของรายการและทำreduceอย่างไร แล้วประเด็นfoldคืออะไร?
Pacerier

2
@Pacerier - ฟังก์ชันตัวสะสมสำหรับการพับมีประเภทที่แตกต่างกัน: 'state -> 'a -> 'stateสำหรับการพับเทียบกับ'a -> 'a -> 'aการลดเพื่อลดข้อ จำกัด ประเภทผลลัพธ์ให้เหมือนกับประเภทขององค์ประกอบ ดูคำตอบของ Tomas Petricekด้านล่าง
Lee

178

นอกจากสิ่งที่ลีกล่าวว่าคุณสามารถกำหนดreduceในแง่ของfoldแต่ไม่ได้ (ง่าย) รอบวิธีอื่น ๆ :

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

ความจริงที่foldใช้ค่าเริ่มต้นอย่างชัดเจนสำหรับตัวสะสมยังหมายความว่าผลลัพธ์ของfoldฟังก์ชันอาจมีประเภทที่แตกต่างจากประเภทของค่าในรายการ ตัวอย่างเช่นคุณสามารถใช้ตัวสะสมประเภทstringเพื่อเชื่อมตัวเลขทั้งหมดในรายการเข้าด้วยกันในการแสดงข้อความ:

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

เมื่อใช้reduceประเภทของตัวสะสมจะเหมือนกับประเภทของค่าในรายการซึ่งหมายความว่าหากคุณมีรายการตัวเลขผลลัพธ์จะต้องเป็นตัวเลข ในการใช้ตัวอย่างก่อนหน้านี้คุณจะต้องแปลงตัวเลขเป็นstringอันดับแรกจากนั้นจึงสะสม:

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

2
เหตุใดจึงกำหนดลดขนาดที่อาจเกิดข้อผิดพลาดขณะรันไทม์
Fresheyeball

+1 สำหรับหมายเหตุทั่วไปของการfold' & its ability to express ลด ' บางภาษามีแนวคิดเกี่ยวกับโครงสร้างแบบ chirality (Haskell ฉันกำลังมองหาคุณ) คุณสามารถพับซ้ายหรือขวาตามภาพในวิกินี้ได้ ( en.wikipedia.org/wiki/Fold_%28higher-order_function ) ด้วยการสร้างเอกลักษณ์ตัวดำเนินการ FP 'พื้นฐาน' อีกสองตัว (ตัวกรองและ fmap) สามารถใช้งานได้กับโครงสร้างภาษาชั้นหนึ่งที่มีอยู่ `` พับ '' (ซึ่งเป็นโครงสร้างแบบไอโซมอร์ฟิกทั้งหมด) ( cs.nott.ac.uk/~pszgmh/fold.pdf ) ดู: HoTT, Princeton (ส่วนความคิดเห็นนี้เล็กเกินไปที่จะมี .. )
Andrew

ด้วยความอยากรู้อยากเห็น .. สิ่งนี้จะทำให้ประสิทธิภาพของการลดเร็วกว่าการพับหรือไม่เพราะมันอยู่ภายใต้สมมติฐานเกี่ยวกับประเภทและข้อยกเว้นน้อยกว่าหรือไม่?
sksallaj

19

มาดูลายเซ็นของพวกเขากัน:

> 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>

มีความแตกต่างที่สำคัญบางประการ:

  • แม้ว่าจะreduceทำงานกับองค์ประกอบประเภทเดียวเท่านั้นตัวสะสมและองค์ประกอบในรายการfoldอาจอยู่ในประเภทต่างๆกัน
  • ด้วยreduceคุณใช้ฟังก์ชันfกับทุกองค์ประกอบรายการโดยเริ่มจากองค์ประกอบแรก:

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

    ด้วยfoldคุณสมัครโดยfเริ่มจากตัวสะสมs:

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

ดังนั้นreduceผลลัพธ์ในArgumentExceptionรายการว่างเปล่า นอกจากนี้ยังfoldเป็นทั่วไปมากขึ้นกว่าreduce; คุณสามารถใช้foldเพื่อใช้งานreduceได้อย่างง่ายดาย

ในบางกรณีการใช้reduceจะรวบรัดกว่า:

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

หรือสะดวกกว่าหากไม่มีตัวสะสมที่สมเหตุสมผล:

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

โดยทั่วไปfoldมีประสิทธิภาพมากกว่าด้วยตัวสะสมประเภทโดยพลการ:

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

18

foldreduceเป็นฟังก์ชั่นที่มีคุณค่ามากขึ้นกว่า คุณสามารถกำหนดฟังก์ชันต่างๆได้มากมายในรูปแบบfold.

reducefoldเป็นเพียงส่วนย่อยของ

ความหมายของการพับ:

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

ตัวอย่างของฟังก์ชันที่กำหนดในรูปแบบของการพับ:

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
คุณกำหนดของคุณfoldแตกต่างจากList.foldชนิดของList.foldเป็นแต่ในกรณีของคุณ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a ('a -> 'b -> 'b) -> 'b -> 'a list -> 'bเพียงเพื่อให้ชัดเจน นอกจากนี้การติดตั้งภาคผนวกของคุณไม่ถูกต้อง มันจะใช้ได้ถ้าคุณเพิ่มการผูกเข้าไปเช่นList.collect id (fold (fun x y -> x :: y) [] [xs;ys])หรือแทนที่ข้อเสียด้วยตัวดำเนินการผนวก ดังนั้นการผนวกไม่ใช่ตัวอย่างที่ดีที่สุดในรายการนี้
jpe
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.