Сложить и метод использовать-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 ответов:
как определено 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
, а не 2Int
значения.для ситуации, когда эти типы отличаются, используйте
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
, anint
является подтипом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)) }