Пример агрегатной функции Scala
Я искал и не могу найти пример или обсуждение aggregate
функция в Scala, которую я могу понять. Это кажется довольно мощным.
можно ли использовать эту функцию для уменьшения значений кортежей для создания коллекции типа multimap? Например:
val list = Seq(("one", "i"), ("two", "2"), ("two", "ii"), ("one", "1"), ("four", "iv"))
после нанесения заполнителя:
Seq(("one" -> Seq("i","1")), ("two" -> Seq("2", "ii")), ("four" -> Seq("iv"))
кроме того, вы можете привести пример параметров z
, segop
и combop
? Я не понимаю, что делают эти параметры.
7 ответов
агрегатная функция этого не делает (за исключением того, что это очень общая функция, и ее можно использовать для этого). Вы хотите groupBy
. По крайней мере, близко. Как вы начинаете с Seq[(String, String)]
, и вы группируете, принимая первый элемент в кортеж (который является (String, String) => String)
, он вернет Map[String, Seq[(String, String)]
). Затем вам нужно отбросить первый параметр в значениях Seq[String, String)].
так
list.groupBy(_._1).mapValues(_.map(_._2))
там вы получите Map[String, Seq[(String, String)]
. Если вы хотите Seq
вместо Map
, называют toSeq
на результат. Я не думаю, что у вас есть гарантия на заказ, в результате сл хотя
Aggregate-более сложная функция.
Рассмотрим сначала reduceLeft и reduceRight.
Пусть as-непустая последовательность as = Seq(a1, ... an)
элементов типа A и f: (A,A) => A
быть каким-то образом объединить два элемента типа A
в одну. Я отмечу это как двоичный оператор @
, a1 @ a2
, а не f(a1, a2)
. as.reduceLeft(@)
будет вычислить (((a1 @ a2) @ a3)... @ an)
. reduceRight
поставим скобки в другую сторону,(a1 @ (a2 @... @ an))))
. Если @
бывает ассоциативным, никто не заботится о скобках. Его можно вычислить как (a1 @... @ ap) @ (ap+1 @...@an)
(было бы parantheses внутри 2 больших parantheses тоже, но давайте не волнует). Тогда можно было бы сделать две части параллельно, в то время как вложенные скобки в reduceLeft или reduceRight заставляют полностью последовательное вычисление. Но параллельное вычисление возможно только тогда, когда @
известно, что ассоциативные и метод reduceLeft не знаю что.
тем не менее, может быть метод reduce
, вызывающий абонент которого будет отвечать за обеспечение ассоциативности операции. Тогда reduce
будет заказывать вызовы, как он считает нужным, возможно, делая их параллельно. Действительно, такой метод существует.
существует ограничение с различными методами сокращения, однако. Элементы Seq могут быть объединены только с результатом того же типа: @
должен быть (A,A) => A
. Но можно было бы иметь более общую проблему объединения их в B
. Один начинается со значения b
типа B
, и объедините его с каждым элементом последовательности. Оператор @
и (B,A) => B
, и один вычисляет (((b @ a1) @ a2) ... @ an)
. foldLeft
это. foldRight
делает то же самое, но начиная с an
. Там @
операция не имеет шансов быть ассоциативной. Когда пишешь b @ a1 @ a2
, это значит (b @ a1) @ a2
, as (a1 @ a2)
будет плохо набирается. Так метод использовать-foldleft и foldRight должны быть последовательными.
предположим однако, что каждый A
превращается в B
, давайте напишем это с !
, a!
типа B
. Предположим более того, что существует +
операция (B,B) => B
и @
такое, что b @ a
на самом деле b + a!
. Вместо того, чтобы комбинировать элементы с@, Можно сначала преобразовать все из них в B с !
, затем объедините их с +
. Это было бы as.map(!).reduceLeft(+)
. И если +
ассоциативно, то это можно сделать с помощью reduce, а не последовательно: as.карта (!).сводить.)+( Там может быть гипотетический метод.associativeFold(б !,+).
Aggregate очень близок к этому. Однако может оказаться, что существует более эффективный способ реализации b@a
чем b+a!
например, если тип B
is List[A]
, и B@A-это::B, то a!
будет a::Nil
и b1 + b2
будет b2 ::: b1
. a::b намного лучше, чем (a:: Nil)::: b. На пользу от ассоциативности, но все же используйте @
, один из первых расколов b + a1! + ... + an!
на (b + a1! + ap!) + (ap+1! + ..+ an!)
, затем вернитесь к использованию @
С (b @ a1 @ an) + (ap+1! @ @ an)
. Один все еще нуждается в ! на ap+1, потому что нужно начать с некоторого b. И + по-прежнему необходимо, появляясь между паратезами. Для этого as.associativeFold(!, +)
можно заменить на as.optimizedAssociativeFold(b, !, @, +)
.
на +
. +
ассоциативно, или эквивалентно, (B, +)
есть полугруппа. На практике большинство полугрупп, используемых в программировании, я тоже буду моноидами.e они содержат нейтральный элемент z
(for ноль) в B, так что для каждого b
, z + b
= b + z
= b
. В таком случае,!
операция, которая имеет смысл, вероятно, будет a! = z @ a
. Более того, поскольку z является нейтральным элементом b @ a1 ..@ an = (b + z) @ a1 @ an
что это b + (z + a1 @ an)
. Таким образом, всегда можно начать агрегацию с z. Если b
требуется вместо этого, вы делаете b + result
в конце. Со всеми этими гипотезами мы можем сделатьs.aggregate(z, @, +)
. Вот что aggregate
делает. @
- это
давайте посмотрим, если некоторые ascii искусство не помогает. Рассмотрим сигнатуру типа aggregate
:
def aggregate [B] (z: B)(seqop: (B, A) ⇒ B, combop: (B, B) ⇒ B): B
кроме того, обратите внимание, что A
относится к типу коллекции. Итак, допустим, у нас есть 4 элемента в этой коллекции, то aggregate
может работать так:
z A z A z A z A
\ / \ /seqop\ / \ /
B B B B
\ / combop \ /
B _ _ B
\ combop /
B
давайте посмотрим практический пример. Скажем, у меня есть GenSeq("This", "is", "an", "example")
и я хочу знать, сколько символов есть в нем. Я могу написать следующее:
обратите внимание на использование par
in ниже фрагмент кода. Вторая функция, передаваемая в aggregate, называется после вычисления отдельных последовательностей. Scala может сделать это только для наборов, которые могут быть распараллелены.
import scala.collection.GenSeq
val seq = GenSeq("This", "is", "an", "example")
val chars = seq.par.aggregate(0)(_ + _.length, _ + _)
Итак, сначала он вычислит это:
0 + "This".length // 4
0 + "is".length // 2
0 + "an".length // 2
0 + "example".length // 7
то, что он делает дальше, невозможно предсказать (существует более одного способа объединения результатов), но он может сделать это (как в ascii-искусстве выше):
4 + 2 // 6
2 + 7 // 9
в этот момент он завершает с
6 + 9 // 15
что дает конечный результат. Теперь это немного похоже по структуре на foldLeft
, но у него есть дополнительная функция (B, B) => B
, какой складки нет. Эта функция, однако, позволяет ему работать параллельно!
рассмотрим, например, что каждый из четырех начальных вычислений не зависит друг от друга и может выполняться параллельно. Следующие два (в результате 6 и 9) могут быть запущены после их вычислений на которых они зависят закончены, но эти двое могут и выполнить параллельно.
7 вычислений, распараллеленных, как указано выше, могут занять всего лишь 3 последовательных вычисления.
на самом деле, с такой небольшой коллекцией стоимость синхронизации вычислений будет достаточно большой, чтобы уничтожить любые выгоды. Кроме того, если вы сложите это, это займет всего 4 расчетов общей. Однако, как только ваши коллекции станут больше, вы начнете чтобы увидеть реальные выгоды.
рассмотрим, с другой стороны,foldLeft
. Поскольку он не имеет дополнительной функции, он не может распараллеливать любые вычисления:
(((0 + "This".length) + "is".length) + "an".length) + "example".length
каждая из внутренних скобок должна быть вычислена до того, как внешняя сможет продолжить.
подпись для коллекции с элементами типа:
def aggregate [B] (z: B)(seqop: (B, A) ⇒ B, combop: (B, B) ⇒ B): B
-
z
является объектом типа B, действующим как нейтральный элемент. Если вы хотите что-то подсчитать, вы можете использовать 0, если вы хотите создать список, начать с пустого списка и т. д. -
segop
это analoguous, чтобы функция, которую вы передаетеfold
методы. Он принимает два аргумента, первый из которых является тем же типом, что и нейтральный элемент, который вы передали, и представляет материал, который уже был агрегированный на предыдущей итерации, второй является следующим элементом вашей коллекции. Результат также должен быть по типуB
. -
combop
: является функцией, объединяющей два результата в одном.
в большинстве коллекций aggregate реализован в TraversableOnce
as:
def aggregate[B](z: B)(seqop: (B, A) => B, combop: (B, B) => B): B
= foldLeft(z)(seqop)
combop
игнорируется. Тем не менее, это имеет смысл для параллельных коллекций, потому чтоseqop
сначала будет применяться локально параллельно, а затем combop
is призван закончить агрегации.
Итак, для вашего примера вы можете попробовать сначала со складкой:
val seqOp =
(map:Map[String,Set[String]],tuple: (String,String)) =>
map + ( tuple._1 -> ( map.getOrElse( tuple._1, Set[String]() ) + tuple._2 ) )
list.foldLeft( Map[String,Set[String]]() )( seqOp )
// returns: Map(one -> Set(i, 1), two -> Set(2, ii), four -> Set(iv))
тогда вы должны найти способ сворачивания двух мультикапов:
val combOp = (map1: Map[String,Set[String]], map2: Map[String,Set[String]]) =>
(map1.keySet ++ map2.keySet).foldLeft( Map[String,Set[String]]() ) {
(result,k) =>
result + ( k -> ( map1.getOrElse(k,Set[String]() ) ++ map2.getOrElse(k,Set[String]() ) ) )
}
теперь вы можете использовать aggregate параллельно:
list.par.aggregate( Map[String,Set[String]]() )( seqOp, combOp )
//Returns: Map(one -> Set(i, 1), two -> Set(2, ii), four -> Set(iv))
применение метода " par " к списку, таким образом, используя параллельную коллекцию(scala.коллекция.параллельный.неизменный.ParSeq) списка, чтобы действительно воспользоваться преимуществами многоядерных процессоров. Без "par" не будет увеличение производительности, поскольку агрегат не выполняется в параллельной коллекции.
aggregate
как foldLeft
но может выполняться параллельно.
As missingfactor говорит линейная версия aggregate(z)(seqop, combop)
эквивалентно foldleft(z)(seqop)
. Однако это нецелесообразно в параллельном случае, где мы должны объединить не только на следующий элемент с предыдущим результатом (как в обычном фолд), но мы хотим разделить повторяемое в суб-итерируемые объекты, на которых мы называем совокупность и нужно объединить их снова. (В порядке слева направо, но не ассоциативно, как мы объединили последней части перед кулаком частей итерируемым.) Это повторное объединение в целом нетривиально, и поэтому нужен метод (S, S) => S
для достижения этой цели.
определение ParIterableLike
- это:
def aggregate[S](z: S)(seqop: (S, T) => S, combop: (S, S) => S): S = {
executeAndWaitResult(new Aggregate(z, seqop, combop, splitter))
}
, который фактически использует combop
.
Для справки:Aggregate
определено как:
protected[this] class Aggregate[S](z: S, seqop: (S, T) => S, combop: (S, S) => S, protected[this] val pit: IterableSplitter[T])
extends Accessor[S, Aggregate[S]] {
@volatile var result: S = null.asInstanceOf[S]
def leaf(prevr: Option[S]) = result = pit.foldLeft(z)(seqop)
protected[this] def newSubtask(p: IterableSplitter[T]) = new Aggregate(z, seqop, combop, p)
override def merge(that: Aggregate[S]) = result = combop(result, that.result)
}
и merge
здесь combop
применяется с двумя суб-результаты.
вот блог о том, как aggregate позволяет производительность на процессоре с несколькими ядрами с отметкой. http://markusjais.com/scalas-parallel-collections-and-the-aggregate-method/
вот видео на " Scala parallel collections "ток из"Scala Days 2011". http://days2011.scala-lang.org/node/138/272
описание на видео!--3-->
Параллельные Коллекции В Scala
Александар Prokopec
абстракции параллельного программирования становятся все более важными по мере роста числа ядер процессора. Высокоуровневая модель программирования позволяет программисту больше сосредоточиться на программе и меньше на деталях низкого уровня, таких как синхронизация и балансировка нагрузки. Параллельные коллекции Scala расширяют модель программирования платформы Scala collection framework, обеспечивая параллельные операции с наборами данных. В докладе будет описана архитектура структуры параллельной коллекции, разъяснение их реализации и проектных решений. Будут описаны конкретные реализации коллекции, такие как параллельные хэш-карты и параллельные хэш-попытки. Наконец, будет показано несколько примеров приложений, демонстрирующих модель программирования на практике.
определение aggregate
на TraversableOnce
источник:
def aggregate[B](z: B)(seqop: (B, A) => B, combop: (B, B) => B): B =
foldLeft(z)(seqop)
который ничем не отличается от простого foldLeft
. combop
кажется, нигде не используется. Я сам не понимаю, в чем цель этого метода.
просто чтобы прояснить объяснения тех, кто передо мной, в теории идея заключается в том, что aggregate должен работать так (я изменил имена параметров, чтобы сделать их более ясными):
Seq(1,2,3,4).aggragate(0)(
addToPrev = (prev,curr) => prev + curr,
combineSums = (sumA,sumB) => sumA + sumB)
следует логически перевести на
Seq(1,2,3,4)
.grouped(2) // split into groups of 2 members each
.map(prevAndCurrList => prevAndCurrList(0) + prevAndCurrList(1))
.foldLeft(0)(sumA,sumB => sumA + sumB)
поскольку агрегация и сопоставление являются отдельными, исходный список теоретически может быть разделен на разные группы разных размеров и работать параллельно или даже на разных машинах. На практике скала тока реализация не поддерживает эту функцию по умолчанию, но вы можете сделать это в свой собственный код.