ความแตกต่างพื้นฐานระหว่างการพับและลดใน Kotlin คืออะไร? ควรใช้เมื่อใด


134

ฉันค่อนข้างสับสนกับทั้งสองฟังก์ชั่นนี้fold()และreduce()ใน Kotlin ใครช่วยยกตัวอย่างที่เป็นรูปธรรมที่ทำให้ทั้งสองอย่างเห็นได้ชัด


2
พับและลด
Zoe

4
ดูที่นี่สำหรับการสนทนาพื้นฐานที่ลึกซึ้งของหัวข้อนี้
GhostCat

2
@LunarWatcher ฉันเห็นเอกสารเหล่านั้น แต่ไม่ได้รับนั่นคือคำถามที่โพสต์แล้วคุณสามารถยกตัวอย่างได้ไหม
TapanHP

1
@MattKlein done
Jayson Minard

คำตอบ:


284

fold รับค่าเริ่มต้นและการเรียกใช้ lambda ครั้งแรกที่คุณส่งผ่านไปจะได้รับค่าเริ่มต้นนั้นและองค์ประกอบแรกของคอลเล็กชันเป็นพารามิเตอร์

ตัวอย่างเช่นใช้รหัสต่อไปนี้ที่คำนวณผลรวมของรายการจำนวนเต็ม:

listOf(1, 2, 3).fold(0) { sum, element -> sum + element }

การเรียกแลมบ์ดาครั้งแรกจะมีพารามิเตอร์0และ1.

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

listOf(1, 6, 4).fold(10) { max, element ->
    if (element > max) element else max
}

reduceไม่ใช้ค่าเริ่มต้น แต่เริ่มต้นด้วยองค์ประกอบแรกของคอลเล็กชันเป็นตัวสะสม (เรียกsumในตัวอย่างต่อไปนี้)

ตัวอย่างเช่นลองหาผลรวมของจำนวนเต็มอีกครั้ง:

listOf(1, 2, 3).reduce { sum, element -> sum + element }

การเรียกแลมบ์ดาครั้งแรกที่นี่จะเป็นพารามิเตอร์1และ2.

คุณสามารถใช้ได้reduceเมื่อการดำเนินการของคุณไม่ขึ้นอยู่กับค่าอื่นใดนอกจากค่าในคอลเล็กชันที่คุณใช้


48
คำอธิบายที่ดี! ฉันจะบอกด้วยว่าคอลเลกชันที่ว่างเปล่าไม่สามารถลดลง แต่สามารถพับได้
Miha_x64

ดู m ในระดับเริ่มต้นมากใน Kotlin ตัวอย่างแรกที่คุณให้คุณสามารถอธิบายเพิ่มเติมพร้อมขั้นตอนบางอย่างและคำตอบสุดท้ายได้หรือไม่? จะช่วยได้มาก
TapanHP

3
@TapanHP emptyList<Int>().reduce { acc, s -> acc + s }จะสร้างข้อยกเว้น แต่emptyList<Int>().fold(0) { acc, s -> acc + s }ก็โอเค
Miha_x64

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

4
@andresp: เป็นบันทึกเพื่อความสมบูรณ์: ไม่จำเป็นต้องเป็นประเภทเดียวกัน สมาชิกรายชื่อยังสามารถเป็นประเภทย่อยของตัวสะสมได้: ใช้งานได้listOf<Int>(1, 2).reduce { acc: Number, i: Int -> acc.toLong() + i }(ประเภทรายการคือ Int ในขณะที่ประเภทตัวสะสมถูกประกาศเป็น Number และเป็นแบบยาว)
บอริส

11

ความแตกต่างของฟังก์ชันที่สำคัญที่ฉันจะเรียก (ซึ่งกล่าวถึงในความคิดเห็นเกี่ยวกับคำตอบอื่น ๆ แต่อาจยากที่จะเข้าใจ) คือreduce จะทำให้เกิดข้อยกเว้นหากดำเนินการกับคอลเล็กชันที่ว่างเปล่า

listOf<Int>().reduce { x, y -> x + y }
// java.lang.UnsupportedOperationException: Empty collection can't be reduced.

เนื่องจาก.reduceไม่ทราบว่าจะส่งคืนค่าใดในกรณีที่ "ไม่มีข้อมูล"

ตรงกันข้ามกับสิ่งนี้.foldซึ่งคุณต้องระบุ "ค่าเริ่มต้น" ซึ่งจะเป็นค่าเริ่มต้นในกรณีที่คอลเลกชันว่างเปล่า:

val result = listOf<Int>().fold(0) { x, y -> x + y }
assertEquals(0, result)

ดังนั้นแม้ว่าคุณจะไม่ต้องการรวมคอลเลกชันของคุณเป็นองค์ประกอบเดียวของประเภทอื่น (ที่ไม่เกี่ยวข้อง) (ซึ่ง.foldจะให้คุณทำเท่านั้น) หากคอลเล็กชันเริ่มต้นของคุณอาจว่างเปล่าคุณต้องตรวจสอบคอลเล็กชันของคุณ ขนาดก่อนแล้ว.reduceหรือเพียงแค่ใช้.fold

val collection: List<Int> = // collection of unknown size

val result1 = if (collection.isEmpty()) 0
              else collection.reduce { x, y -> x + y }

val result2 = collection.fold(0) { x, y -> x + y }

assertEquals(result1, result2)

0

ลด - มีreduce()วิธีการเปลี่ยนให้คอลเลกชันเป็นผลเดียว

val numbers: List<Int> = listOf(1, 2, 3)
val sum: Int = numbers.reduce { acc, next -> acc + next }
//sum is 6 now.

พับ - จะเกิดอะไรขึ้นในกรณีก่อนหน้านี้ของรายการว่าง ? ที่จริงแล้วไม่มีค่าที่ถูกต้องที่จะส่งคืนดังนั้นจึงreduce()โยน aRuntimeException

ในกรณีfoldนี้เป็นเครื่องมือที่มีประโยชน์ คุณสามารถใส่ค่าเริ่มต้นได้ -

val sum: Int = numbers.fold(0, { acc, next -> acc + next })

ที่นี่เราได้ระบุค่าเริ่มต้น ในทางตรงกันข้ามไปreduce()ถ้าคอลเลกชันนี้เป็นที่ว่างเปล่า , RuntimeExceptionค่าเริ่มต้นจะถูกส่งกลับซึ่งจะป้องกันไม่ให้คุณจาก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.