Сложить и метод использовать-foldleft разностей


Я не уверен, в чем разница между fold и foldLeft в Scala.

вопрос разницу между створкой и метод использовать-foldleft и foldRight? есть ответ, который говорит о заказе. Это вполне понятно. Но я до сих пор не понимаю, почему это работает (от REPL):

scala> Array("1","2","3").foldLeft(0)(_ + _.toInt)
res6: Int = 6

но это не:

scala> Array("1","2","3").fold(0)(_ + _.toInt)
<console>:8: error: value toInt is not a member of Any
              Array("1","2","3").fold(0)(_ + _.toInt)
                                               ^

что означает это сообщение об ошибке означает?

эта строка из документации также сбивает с толку мне.

z - нейтральный элемент для деятельности створки; смогите быть добавлено к результат произвольное количество раз, и не должен изменять результат (например, Nil для объединения списков, 0 для добавления или 1 для мультипликационный.)

Почему он будет добавлен произвольное количество раз? Я думал, что складывание работает по-другому.

6 51

6 ответов:

как определено Scala,foldLeft является линейной операцией в то время как fold допускается операция с деревом. Например:

List(1,2,3,4,5).foldLeft(0)(_ + _)
// This is the only valid order of operations
0+1 = 1
      1+2 = 3
            3+3 = 6
                  6+4 = 10
                        10 + 5 = 15
                                 15  // done

List(1,2,3,4,5).fold(0)(_ + _)
// This is valid
0+1 = 1             0+3 = 3           0+5 = 5
      1+2 = 3             3+4 = 7           5
            3         +         7=10        5
                                  10    +   5 = 15
                                                15  // done

чтобы разрешить произвольные разложения дерева последовательного списка, у вас должен быть ноль, который ничего не делает (поэтому вы можете добавить его туда, где вам это нужно в дереве), и вы должны создать такую же вещь, которую вы принимаете в качестве своих двоичных аргументов, чтобы типы не менялись в зависимости от того, как вы разлагаете список. дерево.

(возможность оценивать как дерево хороша для распараллеливания. Если вы хотите иметь возможность преобразовывать время вывода по мере прохождения, вам нужен как оператор комбинации и стандартные пуск-значение-преобразования-последовательности-элемент-к-желать-тип функции как foldLeft есть. Скала имеет это и называет его aggregate, но в некотором смысле это больше похоже foldLeft чем fold есть.)

я не знаком с Scala, но библиотека коллекции Scala имеет аналогичный дизайн с Haskell, этот ответ основан на Haskell и, вероятно, точен для Scala.

, потому что foldLeft обрабатывает свои входные сигналы слева направо, оно может иметь различные типы входного сигнала и выхода. С другой стороны, fold смогите обрабатывать свои входные сигналы в различных заказах и поэтому входные сигналы и выход должны все иметь такой же тип. Это проще всего увидеть, развернув выражения сгиба. foldLeft работает в определенном порядке:

Array("1","2","3").foldLeft(0)(_ + _.toInt)
= ((0 + "1".toInt) + "2".toInt) + "3".toInt

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

fold не гарантирует конкретный заказ. Он может делать различные вещи, такие как:

Array("1","2","3").fold(0)(_ + _.toInt)
=  ((0 + "1".toInt) + "2".toInt) + "3".toInt
or (0 + "1".toInt) + ("2" + "3".toInt).toInt
or "1" + ("2" + ("3" + 0.toInt).toInt).toInt

элементы массива могут отображаться в любом параметре функции объединения. Но ваша комбинирующая функция ожидает, что ее первый аргумент будет int. Если вы не уважайте это ограничение, вы в конечном итоге добавляете строки в ints! Эта ошибка перехватывается системой типов.

нейтральный элемент может быть введен несколько раз, потому что, как правило, параллельный сгиб реализуется путем разделения ввода и выполнения нескольких последовательных сгибов. Последовательная складка вводит нейтральный элемент один раз. Представьте себе одно конкретное исполнение Array(1,2,3,4).fold(0)(_ + _) где массив разделен на два отдельных массива, и они складываются последовательно в два потока. (Конечно, настоящий fold функция не плюет массив в несколько массивов.) Один поток выполняет Array(1,2).fold(0)(_ + _), вычислительная техника 0 + 1 + 2. Другой поток выполняет Array(3,4).fold(0)(_ + _), вычислительная техника 0 + 3 + 4. Наконец, частичные суммы из двух потоков суммируются вместе. Обратите внимание, что нейтральный элемент 0 появляется дважды.

примечание: Я мог бы быть совершенно неправильно здесь. Моя скала не идеальна.

Я думаю, что разница в сигнатуре методов:

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

vs

def foldLeft[B](z: B)(op: (B, T) ⇒ B): B

короче говоря, fold определяется как работа с некоторым типом A1, который является супертипом типа массива, который для вашего строкового массива компилятор определяет как "любой" (вероятно, потому, что ему нужен тип, который может хранить вашу строку или int-обратите внимание, что метод объединителя переданный в fold Fold принимает два параметра одного типа?) Это также то, что означает документация, когда она говорит о z-реализация Fold может быть такой, что она объединяет ваши входы параллельно, например:

"1" + "2" --\
             --> 3 + 3 -> 6
"3" + *z* --/

С другой стороны, foldLeft работает с типом B (без ограничений) и только просит предоставить метод combiner, который принимает параметр типа B и другой тип вашего массива (String, в вашем случае) и создает B.

ошибка. вы получаете ошибку времени компиляции, потому что подпись fold только позволяет складывать значения типа, который является супертипом типа значений в коллекции, и единственный супертип String (ваш тип коллекции) и Int (типа вашего нулевого элемента)Any. Таким образом, тип результата сгиба выводится как Any и Any не имеет метода toInt.

обратите внимание, что две версии fold имеют разные подписи:

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

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

почему у них разные подписи? Это потому что fold может быть реализовано параллельно,как и в случае с параллельными коллекциями. Когда несколько процессоров складывают значения в коллекциях, каждый из процессоров принимает подмножество элементов типа A и производит сложенное значение типа A1, последовательно применяя op. Результаты, полученные этими процессорами, должны быть объединены вместе в окончательное значение складывания - это делается с помощью op функция, которая делает именно это.

теперь, обратите внимание, что это не может быть сделано с помощью f на foldLeft, потому что каждый из процессоров производит сложить значение типа B. Несколько значений типа B не могут быть объединены с помощью f, потому что f только совместная стоимостью B С другим значением типа A - нет соответствия между типами A и B.

пример. в вашем примере Предположим, что 1-й процессор принимает элементы "1", "2" и второй принимает элемент "3". Первый из них будет производить сложенное значение 3, и второй произведет другое сложенное значение 3. Теперь они должны объединить свои результаты, чтобы получить окончательное сложенное значение - это невозможно, потому что закрытие _ + _.toInt только знает, как объединить Int и String, а не 2 Int значения.

для ситуации, когда эти типы отличаются, используйте aggregate, в которой вы должны определить, как объединить два значения типа B:

def aggregate[B](z: B)(seqop: (B, A) => B, combop: (B, B) => B): B

The combop выше определяется, как сделать последний шаг, когда результат сгиба и элементы в коллекции имеют разные типы.

нейтральный элемент. как описано выше, несколько процессоров могут складываться над подмножествами элементов в коллекции. Каждый из них начнет свое сложенное значение, добавив нейтральный элемент.

в следующем примере:

List(1, 2, 3).foldLeft(4)(_ + _)

всегда возвращает 10 = 4 + 1 + 2 + 3.

, 4 не следует использовать fold, так как это не нейтральный элемент:
List(1, 2, 3).fold(4)(_ + _)

выше может возвратить (4 + 1 + 2) + (4 + 3) = 14 или (4 + 1) + (4 + 2) + (4 + 3) = 18. Если вы не используете нейтральный элемент fold результаты являются недетерминированными. Таким же образом, вы можете использовать Nil как нейтральный элемент, но не пустой список.

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

case class TrackInt(v: Int) {
  val log = collection.mutable.Buffer.empty[Int]
  def plus(that: TrackInt) = {
    this.log += that.v
    that.log += this.v
    new TrackInt(this.v + that.v)
  }
}

Далее мы можем создать параллельную коллекцию этих вещей и элемент личности:

val xs = (1 to 10).map(TrackInt(_)).par
val zero = TrackInt(0)

сначала попробуем foldLeft:

scala> xs.foldLeft(zero)(_ plus _)
res0: TrackInt = TrackInt(55)

scala> zero.log
res1: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1)

так что наше нулевое значение используется только один раз, как и следовало ожидать, так как foldLeft выполняет последовательный раз. Далее мы можем очистить журнал и попробовать fold:

scala> zero.log.clear()

scala> xs.fold(zero)(_ plus _)
res2: TrackInt = TrackInt(55)

scala> zero.log
res3: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1, 6, 2, 7, 8)

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

общие разницу

вот прототипы методов

fold[A1 >: A](z: A1)(op: (A1, A1) ⇒ A1): A1
foldLeft[B](z: B)(f: (B, A) ⇒ B): B

Итак, для сгиба результат имеет тип A1 >: Aвместо B. Кроме того, как указано в документе, для fold заказ не

о ошибка

при вводе scala> Array("1","2","3").fold(0)(_ + _.toInt) вы предполагаете, что 0, an int является подтипом String. Вот почему компилятор выдает ошибку.

о странном z в раза

здесь мы должны увидеть реализация на fold чтобы понять, что происходит. Вот что мы получаем:

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

так что в основном,fold реализация foldleft С ограничением на тип продукции. Теперь мы видим, что z на практике будет использоваться так же, как и в foldleft. Таким образом, мы можем просто заключить, что этот комментарий был сделан, потому что ничто не гарантирует такое поведение в будущих реализациях. Мы уже видим это сейчас, с параллели:

def fold[U >: T](z: U)(op: (U, U) => U): U = {
  executeAndWaitResult(new Fold(z, op, splitter))
}