разница между foldLeft и reduLeft в Scala

196

Я узнал основную разницу между foldLeftиreduceLeft

foldLeft:

  • начальное значение должно быть передано

reduceLeft:

  • принимает первый элемент коллекции в качестве начального значения
  • выдает исключение, если коллекция пуста

Есть ли другая разница?

Есть какая-то конкретная причина иметь два метода с похожей функциональностью?

Раджеш Питти
источник
Рекомендуем Вам посмотреть stackoverflow.com/questions/25158780/...
samthebest
Было бы здорово, если бы вы отредактировали вопрос так: «Разница между сгибом и уменьшением в Scala».
Педрам Башири

Ответы:

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

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

Когда вы вызываете foldLeftsay для a, 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). Он вычисляет сумму и возвращает кортеж со списком целых чисел и его суммой. Кстати, список возвращается в обратном порядке, потому что мы использовали foldLeftвместо foldRight.

Смотрите One Fold, чтобы править ими все для более глубокого объяснения.

agilesteel
источник
Можете ли вы объяснить, почему Bэто супертип A? Похоже, на Bсамом деле должен быть подтип A, а не супертип. Например, если предположить Banana <: Fruit <: Food, что если бы у нас был список Fruits, кажется, что он может содержать некоторые Bananas, но если бы он содержал какие-либо Foods, то тип был бы Food, правильно? Таким образом, в этом случае, если Bесть супертип Aи существует список, содержащий как Bs, так и As, список должен иметь тип B, а не A. Можете ли вы объяснить это несоответствие?
socom1880
Я не уверен, правильно ли я понимаю ваш вопрос. Мой 5-летний ответ говорит о том, что функция сокращения 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как нет.
Мансур Сиддики
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с другой стороны, не имеет допустимого значения, если не может найти хотя бы одно значение в списке.

thoredge
источник
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 возвращает значение того же типа, что и список).

C8H10N4O2
источник
0

Чтобы действительно понять, что вы делаете со сложением / уменьшением, проверьте это: http://wiki.tcl.tk/17983 очень хорошее объяснение. как только вы поймете концепцию сгиба, сокращение придет вместе с ответом выше: list.tail.foldLeft (list.head) (_)

Генри Х.
источник