ความแตกต่างระหว่าง foldLeft และลด leeft ใน Scala


196

ฉันได้เรียนรู้ความแตกต่างพื้นฐานระหว่างfoldLeftและreduceLeft

foldLeft:

  • ค่าเริ่มต้นจะต้องถูกส่งผ่าน

reduceLeft:

  • ใช้องค์ประกอบแรกของการรวบรวมเป็นค่าเริ่มต้น
  • พ่นข้อยกเว้นถ้าคอลเลกชันว่างเปล่า

มีความแตกต่างอื่น ๆ อีกไหม?

เหตุผลใดที่มีสองวิธีที่มีฟังก์ชั่นคล้ายกัน


แนะนำให้คุณดูstackoverflow.com/questions/25158780/…
samthebest

จะดีมากถ้าคุณแก้ไขคำถามให้เป็น "ความแตกต่างระหว่างการพับและการย่อในสกาล่า"
pedram bashiri

คำตอบ:


303

มีบางสิ่งที่ต้องพูดถึงที่นี่ก่อนที่จะให้คำตอบจริง:

  • คำถามของคุณไม่มีอะไรเกี่ยวข้องกับleftมันค่อนข้างเกี่ยวกับความแตกต่างระหว่างการลดและการพับ
  • ความแตกต่างไม่ใช่การใช้งานเลยเพียงแค่ดูที่ลายเซ็น
  • คำถามไม่มีส่วนเกี่ยวข้องกับ Scala โดยเฉพาะมันค่อนข้างเกี่ยวกับสองแนวคิดของการเขียนโปรแกรมเชิงฟังก์ชัน

กลับไปที่คำถามของคุณ:

นี่คือลายเซ็นของfoldLeft(อาจเป็นfoldRightสำหรับจุดที่ฉันจะทำด้วย):

def foldLeft [B] (z: B)(f: (B, A) => B): B

และนี่คือลายเซ็นต์ของreduceLeft(อีกทิศทางไม่สำคัญที่นี่)

def reduceLeft [B >: A] (f: (B, A) => B): B

ทั้งสองดูคล้ายกันมากและทำให้เกิดความสับสน reduceLeftเป็นกรณีพิเศษของfoldLeft(ซึ่งโดยวิธีการที่บางครั้งคุณสามารถแสดงสิ่งเดียวกันโดยใช้อย่างใดอย่างหนึ่ง)

เมื่อคุณเรียกใช้reduceLeftSay บนList[Int]ตัวอักษรมันจะลดรายการทั้งหมดของจำนวนเต็มเป็นค่าเดียวซึ่งจะเป็นประเภทInt(หรือประเภทของIntดังนั้น[B >: A])

เมื่อคุณโทรfoldLeftพูดบนList[Int]มันจะพับรายการทั้งหมด (ลองนึกภาพม้วนกระดาษ) เป็นค่าเดียว แต่ค่านี้ไม่จำเป็นต้องเกี่ยวข้องแม้แต่กับInt(ดังนั้น[B])

นี่คือตัวอย่าง:

def listWithSum(numbers: List[Int]) = numbers.foldLeft((List.empty[Int], 0)) {
   (resultingTuple, currentInteger) =>
      (currentInteger :: resultingTuple._1, currentInteger + resultingTuple._2)
}

วิธีการนี้จะใช้เวลาList[Int]และผลตอบแทนหรือTuple2[List[Int], Int] (List[Int], Int)มันคำนวณผลรวมและส่งกลับ tuple ด้วยรายการจำนวนเต็มและผลรวม โดยวิธีการที่รายการจะถูกส่งกลับไปข้างหลังเพราะเราใช้แทนfoldLeftfoldRight

ดูOne Fold เพื่อควบคุมทั้งหมดสำหรับคำอธิบายเชิงลึก


คุณสามารถอธิบายได้ว่าทำไมBเป็น supertype ของA? ดูเหมือนว่าBจริง ๆ แล้วควรเป็นประเภทย่อยAไม่ใช่ซุปเปอร์ชนิด ตัวอย่างเช่นสมมติว่าBanana <: Fruit <: Foodถ้าเรามีรายการFruits ดูเหมือนว่าอาจมีบางรายการBananaแต่ถ้ามีรายการใด ๆFoodแล้วประเภทจะเป็นFoodถูกต้องหรือไม่ ดังนั้นในกรณีนี้ถ้าBเป็นของ supertype Aและมีรายการที่มีทั้งสองBและAของรายการที่ควรจะเป็นประเภทไม่B Aคุณอธิบายความคลาดเคลื่อนนี้ได้ไหม?
socom1880

ฉันไม่แน่ใจว่าฉันเข้าใจคำถามของคุณถูกต้องหรือไม่ สิ่งที่คำตอบอายุ 5 ปีของฉันกำลังพูดถึงเกี่ยวกับฟังก์ชั่นการลดคือ a List[Banana]สามารถลดลงเป็นแบบเดี่ยวBananaหรือเดี่ยวFruitหรือเดี่ยวFoodได้ เพราะFruit :> Bananaและอาหาร:> Banana '
agilesteel

ใช่ ... มันสมเหตุสมผลจริง ๆ ขอบคุณ เดิมทีฉันแปลมันเป็น "รายการประเภทBananaอาจมีFruit" ซึ่งไม่สมเหตุสมผล คำอธิบายของคุณสมเหตุสมผล - fฟังก์ชั่นที่ส่งผ่านไปreduce()อาจส่งผลให้ a Fruitหรือ a Foodซึ่งหมายความว่าBในลายเซ็นควรเป็นซูเปอร์คลาสไม่ใช่คลาสย่อย
socom1880

193

reduceLeftเป็นเพียงวิธีการอำนวยความสะดวก มันเทียบเท่ากับ

list.tail.foldLeft(list.head)(_)

11
ดีคำตอบที่รัดกุม :) อาจต้องการแก้ไขการสะกดคำreducelftว่า
Hanxue

10
คำตอบที่ดี. นอกจากนี้ยังเน้นว่าทำไมfoldทำงานในรายการที่ว่างเปล่าในขณะที่reduceไม่
Mansoor Siddiqui

44

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

List(1,3,5).foldLeft(0) { _ + _ }
List(1,3,5).foldLeft(List[String]()) { (a, b) => b.toString :: a }

foldLeftจะใช้ปิดที่มีผลพับสุดท้าย (ครั้งแรกที่ใช้ค่าเริ่มต้น) และความคุ้มค่าต่อไป

reduceLeftในทางกลับกันก่อนจะรวมสองค่าจากรายการและนำไปใช้กับการปิด ถัดไปมันจะรวมค่าที่เหลือกับผลสะสม ดู:

List(1,3,5).reduceLeft { (a, b) => println("a " + a + ", b " + b); a + b }

หากรายการว่างเปล่าfoldLeftสามารถแสดงค่าเริ่มต้นเป็นผลทางกฎหมาย reduceLeftในทางกลับกันไม่มีค่าทางกฎหมายหากไม่สามารถหาค่าอย่างน้อยหนึ่งค่าในรายการ


5

เหตุผลพื้นฐานที่ทั้งคู่อยู่ในห้องสมุดมาตรฐานของสกาล่าอาจเป็นเพราะพวกเขาทั้งคู่อยู่ในห้องสมุดมาตรฐานของแฮสเคลล์ (เรียกว่าfoldlและfoldl1) ถ้าreduceLeftไม่เป็นเช่นนั้นก็มักจะถูกกำหนดให้เป็นวิธีการอำนวยความสะดวกในโครงการต่างๆ


5

สำหรับการอ้างอิงreduceLeftจะเกิดข้อผิดพลาดหากนำไปใช้กับภาชนะที่ว่างเปล่าที่มีข้อผิดพลาดดังต่อไปนี้

java.lang.UnsupportedOperationException: empty.reduceLeft

ทำใหม่รหัสที่จะใช้

myList foldLeft(List[String]()) {(a,b) => a+b}

เป็นหนึ่งในตัวเลือกที่มีศักยภาพ อีกวิธีหนึ่งคือการใช้reduceLeftOptionชุดตัวเลือกที่ส่งกลับผลลัพธ์เป็นทางเลือก

myList reduceLeftOption {(a,b) => a+b} match {
  case None    => // handle no result as necessary
  case Some(v) => println(v)
}

2

จากหลักการเขียนโปรแกรมเชิงหน้าที่ใน Scala (Martin Odersky):

ฟังก์ชั่นที่reduceLeftกำหนดไว้ในแง่ของฟังก์ชั่นทั่วไปมากขึ้น, foldLeft.

foldLeftเป็นเหมือนreduceLeftแต่ใช้แอคคูมูเล zเตอร์เป็นพารามิเตอร์เพิ่มเติมซึ่งจะถูกส่งคืนเมื่อfoldLeftถูกเรียกบนรายการว่าง:

(List (x1, ..., xn) foldLeft z)(op) = (...(z op x1) op ...) op x

[ตรงข้ามกับreduceLeftข้อผิดพลาดเมื่อเรียกใช้ในรายการว่าง]

หลักสูตร (ดูการบรรยาย 5.5) ให้คำจำกัดความที่เป็นนามธรรมของฟังก์ชั่นเหล่านี้ซึ่งแสดงให้เห็นถึงความแตกต่างของพวกเขาแม้ว่าพวกเขาจะคล้ายกันมากในการใช้การจับคู่รูปแบบและการเรียกซ้ำ

abstract class List[T] { ...
  def reduceLeft(op: (T,T)=>T) : T = this match{
    case Nil     => throw new Error("Nil.reduceLeft")
    case x :: xs => (xs foldLeft x)(op)
  }
  def foldLeft[U](z: U)(op: (U,T)=>U): U = this match{
    case Nil     => z
    case x :: xs => (xs foldLeft op(z, x))(op)
  }
}

โปรดทราบว่าfoldLeftคืนค่าประเภทUซึ่งไม่จำเป็นต้องเป็นชนิดเดียวกันList[T]แต่ลดค่าระดับต่ำคืนค่าประเภทเดียวกันเป็นรายการ)


0

หากต้องการทำความเข้าใจกับสิ่งที่คุณทำด้วยการย่อ / ลดให้ตรวจสอบสิ่งนี้: http://wiki.tcl.tk/17983 คำอธิบายที่ดีมาก เมื่อคุณได้รับแนวคิดการพับการลดจะมาพร้อมกับคำตอบข้างต้น: list.tail.foldLeft (list.head) (_)

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