Сложить и метод использовать-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): A1vs
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): BThe
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)) }