Разница между fold и foldLeft или foldRight?

ПРИМЕЧАНИЕ. Я использую Scala 2.8 — может ли это быть проблемой?

Почему я не могу использовать функцию fold так же, как foldLeft или foldRight?

В Set scaladoc сказано, что:

Результат свертки может быть только супертипом параметра типа этой параллельной коллекции T.

Но я не вижу параметра типа T в сигнатуре функции:

def fold [A1 >: A] (z: A1)(op: (A1, A1) ⇒ A1): A1

В чем разница между foldLeft-Right и fold и как использовать последний?

РЕДАКТИРОВАТЬ: Например, как мне написать складку, чтобы добавить все элементы в список? С foldLeft это будет:

val foo = List(1, 2, 3)
foo.foldLeft(0)(_ + _)

// now try fold:
foo.fold(0)(_ + _)
>:7: error: value fold is not a member of List[Int]
  foo.fold(0)(_ + _)
    ^

person Andriy Drozdyuk    schedule 06.06.2011    source источник
comment
«T» Скаладока — это A в подписи, которую вы скопировали.   -  person Jean-Philippe Pellet    schedule 06.06.2011
comment
Какую версию Scala вы используете? IIRC fold появился в версии 2.9.   -  person Jean-Philippe Pellet    schedule 06.06.2011
comment
См. scala-lang.org/api/2.8. .0/scala/collection/immutable/List.html. fold появился в версии 2.9 с параллельными коллекциями.   -  person Aaron Novstrup    schedule 06.06.2011
comment
Д'о. Спасибо, у меня были проблемы с поиском документов 2.8 :-(   -  person Andriy Drozdyuk    schedule 06.06.2011
comment
@drozzy Я знаю, что это старый вопрос, но люди все еще приходят к нему. Не могли бы вы рассмотреть вопрос об изменении ответа апокалиспа на правильный - он точен и все еще актуален.   -  person akauppi    schedule 08.01.2017
comment
@akauppi Извините, но мой вопрос был конкретно об отсутствии функции fold в библиотеке Scala (это было из-за старой версии Scala). Таким образом, я не хотел, чтобы это был теоретический вопрос, как его воспринимали многие люди. Так что ответ меня удовлетворил. Я чувствую, что изменение принятого ответа также будет означать изменение самого вопроса.   -  person Andriy Drozdyuk    schedule 09.01.2017


Ответы (7)


Вы правы в том, что старая версия Scala является проблемой. Если вы посмотрите на страницу scaladoc для Scala 2.8.1 вы не увидите там определенной складки (что согласуется с вашим сообщением об ошибке). Судя по всему, fold появился в Scala 2.9.

person exlevan    schedule 08.06.2011

Короткий ответ:

foldRight ассоциируется справа. т.е. элементы будут накапливаться в порядке справа налево:

List(a,b,c).foldRight(z)(f) = f(a, f(b, f(c, z)))

foldLeft ассоциируется слева. т.е. аккумулятор будет инициализирован, и элементы будут добавлены в аккумулятор в порядке слева направо:

List(a,b,c).foldLeft(z)(f) = f(f(f(z, a), b), c)

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

person Apocalisp    schedule 06.06.2011
comment
foldLeft и foldRight также некоммутативны (отсюда и названия), а fold может быть, а может и не быть; Я не могу отделаться от мысли, что это странный выбор соглашения об именах. - person Rex Kerr; 06.06.2011
comment
Извините, но я не понимаю, что такое моноид, а также почему я не могу использовать fold так же, как foldLeft. Смотрите мой вопрос для дополнительного примера. - person Andriy Drozdyuk; 06.06.2011
comment
Моноид — это ассоциативная бинарная функция вместе с элементом идентичности для этой функции. Например, (0)(_+_) является моноидом. (1)(_*_) — еще один пример. Я думаю, вы понимаете, что это такое, просто не под этим названием. - person Apocalisp; 09.06.2011
comment
Рекс: Коммутативность здесь не очень актуальна. - person Apocalisp; 09.06.2011
comment
Моноиды без слез: fsharpforfunandprofit.com/posts/monoids-without-tears - person Richard Gomes; 03.04.2014

fold, в отличие от foldRight и foldLeft, не дает никаких гарантий относительно порядка обработки элементов коллекции. Вы, вероятно, захотите использовать fold с его более ограниченной сигнатурой с параллельными коллекциями, где отсутствие гарантированного порядка обработки помогает параллельной коллекции реализовать свертывание параллельным образом. Причина изменения подписи аналогична: с дополнительными ограничениями проще сделать параллельную складку.

person Jean-Philippe Pellet    schedule 06.06.2011
comment
Не могли бы вы привести пример добавления всех элементов в список? Я исправил свой вопрос! - person Andriy Drozdyuk; 06.06.2011

Для вашего конкретного примера вы должны кодировать его так же, как и с foldLeft.

val ns = List(1, 2, 3, 4)
val s0 = ns.foldLeft (0) (_+_) //10
val s1 = ns.fold (0) (_+_) //10
assert(s0 == s1)
person Garrett Rowe    schedule 06.06.2011
comment
Хм.. Я пробовал, не работает. Строка с сгибом выдает ошибку (см. мой вопрос). Я использую scala 2.8 - может быть, это проблема? - person Andriy Drozdyuk; 06.06.2011

Согласен с другими ответами. думал привести простой иллюстративный пример:

 object MyClass {
 def main(args: Array[String]) {
val numbers = List(5, 4, 8, 6, 2)
 val a =  numbers.fold(0) { (z, i) =>
 {
     println("fold val1 " + z +" val2 " + i)
  z + i

 }
}
println(a)
 val b =  numbers.foldLeft(0) { (z, i) =>
 println("foldleft val1 " + z +" val2 " + i)
  z + i

}
println(b)
   val c =  numbers.foldRight(0) { (z, i) =>
   println("fold right val1 " + z +" val2 " + i)
  z + i

}
println(c)
 }
}

Результат говорит сам за себя:

fold val1 0 val2 5
fold val1 5 val2 4
fold val1 9 val2 8
fold val1 17 val2 6
fold val1 23 val2 2
25
foldleft val1 0 val2 5
foldleft val1 5 val2 4
foldleft val1 9 val2 8
foldleft val1 17 val2 6
foldleft val1 23 val2 2
25
fold right val1 2 val2 0
fold right val1 6 val2 2
fold right val1 8 val2 8
fold right val1 4 val2 16
fold right val1 5 val2 20
25
person Ram Ghadiyaram    schedule 20.04.2017

Существует два способа решения задач: итеративный и рекурсивный. Давайте разберемся на простом примере. Напишем функцию суммирования до заданного числа.

Например, если я даю ввод как 5, я должен получить 15 в качестве вывода, как указано ниже.

Ввод: 5

Вывод: (1+2+3+4+5) = 15

Итеративное решение.

перебрать от 1 до 5 и суммировать каждый элемент.

  def sumNumber(num: Int): Long = {
    var sum=0
    for(i <- 1 to num){
      sum+=i
    }
    sum
  }

Рекурсивное решение

разбивайте большую проблему на более мелкие и решайте их.

  def sumNumberRec(num:Int, sum:Int=0): Long = {
    if(num == 0){
      sum
    }else{
      val newNum = num - 1
      val newSum = sum + num
      sumNumberRec(newNum, newSum)
    }
  }

FoldLeft: итеративное решение.

FoldRight: это рекурсивное решение. Я не уверен, есть ли у них мемоизация для повышения сложности.

Итак, если вы запустите foldRight и FoldLeft в маленьком списке, оба дадут вам результат с одинаковой производительностью.

Однако, если вы попытаетесь запустить FoldRight для Длинного списка, это может привести к ошибке StackOverFlow (зависит от вашей памяти).

Посмотрите на следующий снимок экрана, где foldLeft выполняется без ошибок, однако foldRight в том же списке выдает OutofMemmory ошибку.

введите здесь описание изображения

person Gaurang Shah    schedule 07.02.2020

fold() выполняет параллельную обработку, поэтому не гарантирует порядок обработки. где foldLeft и foldRight обрабатывают элементы последовательно слева направо (в случае foldLeft) или справа налево (в случае foldRight)

Примеры суммы список -

val numList = List(1, 2, 3, 4, 5)

val r1 = numList.par.fold(0)((acc, value) => {
  println("adding accumulator=" + acc + ", value=" + value + " => " + (acc + value))
  acc + value
})
println("fold(): " + r1)
println("#######################")
/*
 * You can see from the output that,
 * fold process the elements of parallel collection in parallel
 * So it is parallel not linear operation.
 * 
 * adding accumulator=0, value=4 => 4
 * adding accumulator=0, value=3 => 3
 * adding accumulator=0, value=1 => 1
 * adding accumulator=0, value=5 => 5
 * adding accumulator=4, value=5 => 9
 * adding accumulator=0, value=2 => 2
 * adding accumulator=3, value=9 => 12
 * adding accumulator=1, value=2 => 3
 * adding accumulator=3, value=12 => 15
 * fold(): 15
 */

val r2 = numList.par.foldLeft(0)((acc, value) => {
  println("adding accumulator=" + acc + ", value=" + value + " => " + (acc + value))
  acc + value
})
println("foldLeft(): " + r2)
println("#######################")
/*
 * You can see that foldLeft
 * picks elements from left to right.
 * It means foldLeft does sequence operation
 * 
 * adding accumulator=0, value=1 => 1
 * adding accumulator=1, value=2 => 3
 * adding accumulator=3, value=3 => 6
 * adding accumulator=6, value=4 => 10
 * adding accumulator=10, value=5 => 15
 * foldLeft(): 15
 * #######################
 */

// --> Note in foldRight second arguments is accumulated one.
val r3 = numList.par.foldRight(0)((value, acc) => {
 println("adding value=" + value + ", acc=" + acc + " => " + (value + acc))
  acc + value
})
println("foldRight(): " + r3)
println("#######################")

/*
 * You can see that foldRight
 * picks elements from right to left.
 * It means foldRight does sequence operation.
 * 
 * adding value=5, acc=0 => 5
 * adding value=4, acc=5 => 9
 * adding value=3, acc=9 => 12
 * adding value=2, acc=12 => 14
 * adding value=1, acc=14 => 15
 * foldRight(): 15
 * #######################
 */
person thedevd    schedule 18.10.2019