Пример агрегатной функции 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 сначала будет применяться локально параллельно, а затем combopis призван закончить агрегации.

Итак, для вашего примера вы можете попробовать сначала со складкой:

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)

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