різниця між foldLeft та reduLeft у Scala


196

Я дізнався основну різницю між foldLeftтаreduceLeft

foldLeft:

  • початкове значення має бути передано

reduLeft:

  • приймає перший елемент колекції як початкове значення
  • викидає виняток, якщо колекція порожня

Чи є якась інша різниця?

Якась конкретна причина мати два способи з подібною функціональністю?


Рекомендуємо Вам ознайомитись з stackoverflow.com/questions/25158780 / ...
samthebest

Було б чудово, якби ви відредагували питання "різниця між складанням і зменшенням у Scala".
pedram bashiri

Відповіді:


302

Тут потрібно згадати кілька речей, перш ніж дати відповідь:

  • Ваше запитання не має нічого спільного 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(який, до речі, означає, що ви можете іноді висловити те саме, використовуючи будь-який з них).

Коли ви зателефонуєте reduceLeftсказати на a, List[Int]це буквально зведе весь список цілих чисел в єдине значення, яке буде типу Int(або Int, таким чином, і супертипом [B >: A]).

Коли ви телефонуєте foldLeft сказати на a, List[Int]це складе весь список (уявіть, як закотити аркуш паперу) в одне значення, але це значення не повинно бути навіть пов'язане Int(отже [B]).

Ось приклад:

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

Цей метод приймає a List[Int]і повертає a Tuple2[List[Int], Int]або(List[Int], Int) . Він обчислює суму і повертає кортеж зі списком цілих чисел і це сума. До речі, список повертається назад, тому що ми використовували foldLeftзамість цього foldRight.

Дивіться один раз, щоб правити їх усі для більш глибокого пояснення.


Чи можете ви пояснити, чому Bтаке супертип A? Здається, що Bнасправді має бути підтипом A, а не супертипом. Наприклад, якщо припустити Banana <: Fruit <: Food, якби у нас був список Fruits, здається, що він може містити деякий Bananas, але якщо він містив Foods, то тип був би Food, правильно? Так що в цьому випадку, якщо Bє супертип Aі є список, що містить і Bs, і As, список повинен бути тип B, а не A. Чи можете ви пояснити цю невідповідність?
socom1880

Я не впевнений, чи правильно я розумію ваше запитання. Що моя 5-річна відповідь говорить про функцію зменшення, це те, що a List[Banana]можна зменшити до одиничного, Bananaодиничного Fruitабо одиничного Food. Тому що Fruit :> 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

Основна причина, що вони обидва в стандартній бібліотеці Scala, ймовірно, тому, що вони обидва є у стандартній бібліотеці Haskell (називається 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 (Мартін Одерський):

Функція 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], але ReduLeft повертає значення того ж типу, що і список).


0

Щоб дійсно зрозуміти, що ви робите зі складанням / зменшенням, перевірте це: http://wiki.tcl.tk/17983 дуже вдале пояснення. як тільки ви отримаєте концепцію складання, зменшення зійде разом з відповіддю вище: list.tail.foldLeft (list.head) (_)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.