Exemple de la fonction d'agrégation Scala

j'ai cherché et je ne peux pas trouver un exemple ou une discussion de la fonction aggregate dans Scala que je peux comprendre. Il semble assez puissant.

cette fonction peut-elle être utilisée pour réduire les valeurs des tuples pour faire une collection de type multimap? Par exemple:

val list = Seq(("one", "i"), ("two", "2"), ("two", "ii"), ("one", "1"), ("four", "iv"))

après application de l'agrégat:

Seq(("one" -> Seq("i","1")), ("two" -> Seq("2", "ii")), ("four" -> Seq("iv"))

aussi, pouvez-vous donner un exemple de paramètres z , segop , et combop ? Je suis dans le flou sur ce que ces paramètres ne.

34
demandé sur Nathaniel Ford 2011-08-03 18:47:30

7 réponses

la fonction agrégée ne fait pas cela (sauf qu'il s'agit d'une fonction très générale, et elle pourrait être utilisée pour le faire). Vous voulez groupBy . Près de au moins. Comme vous commencez avec un Seq[(String, String)] , et vous groupez en prenant le premier article dans le tuple (qui est (String, String) => String) , il retournerait un Map[String, Seq[(String, String)] ). Vous devez ensuite rejeter le premier paramètre dans les valeurs Seq[String, String].

Donc

list.groupBy(_._1).mapValues(_.map(_._2))

là vous obtenez un Map[String, Seq[(String, String)] . Si vous voulez un Seq au lieu de Map , appelez toSeq sur le résultat. Je ne pense pas que vous ayez une garantie sur l'ordre dans les Seq résultants bien que


L'agrégat est une fonction plus difficile.

Considérons d'abord reduceLeft et reduceRight. Soit une séquence non vide as = Seq(a1, ... an) d'éléments de type A, et f: (A,A) => A soit une façon de combiner deux éléments de type A en un seul. Je vais le noter comme un opérateur binaire @ , a1 @ a2 plutôt que f(a1, a2) . as.reduceLeft(@) calculera (((a1 @ a2) @ a3)... @ an) . reduceRight mettra les parenthèses de l'autre côté, (a1 @ (a2 @... @ an)))) . Si @ se trouve être associatif, on ne se soucie pas des parenthèses. On pourrait le calculer comme (a1 @... @ ap) @ (ap+1 @...@an) (il y aurait des paranthèses à l'intérieur des 2 grandes paranthèses aussi, mais ne nous en soucions pas). Ensuite, on peut faire les deux parties en parallèle, tandis que le bracketing imbriqué dans la force réduceleft ou reduceRight un calcul entièrement séquentiel. Mais le calcul parallèle n'est possible que lorsque @ est connu pour être associatif, et la méthode reduceLeft ne peut pas le savoir.

pourtant , il pourrait y avoir la méthode reduce , dont l'appelant serait responsable de s'assurer que l'opération est associative. Puis reduce ordonnerait les appels comme il le juge bon, peut-être en les faisant en parallèle. En effet, il y en a un méthode.

il y a cependant une limitation avec les différentes méthodes de réduction. Les éléments des Seq ne peuvent être combinés qu'à un résultat du même type: @ doit être (A,A) => A . Mais on pourrait avoir le problème plus général de les combiner dans un B . On commence par une valeur b de type B , et on la combine avec tous les éléments de la séquence. L'opérateur @ est (B,A) => B , et on calcule (((b @ a1) @ a2) ... @ an) . foldLeft fait ça. foldRight fait la même chose mais en commençant par an . Là, l'opération @ n'a aucune chance d'être associative. Quand on écrit b @ a1 @ a2 , cela doit signifier (b @ a1) @ a2 , car (a1 @ a2) serait mal dactylographié. Donc foldLeft et foldRight doivent être séquentiels.

supposons cependant que chaque A puisse être transformé en un B , écrivons-le avec ! , a! est de type B . Supposons en outre qu'il y ait une + opération (B,B) => B , et que @ soit tel que b @ a soit en fait b + a! . Plutôt que de combiner des éléments avec @, on pourrait d'abord les transformer tous en B avec ! , puis les combiner avec + . Ce serait as.map(!).reduceLeft(+) . Et si + est associatif, alors cela peut être fait avec reduce, et non pas sequential: as.carte(!).réduire.)+( Il pourrait y avoir une méthode hypothétique comme.associativeFold (b, !, +).

L'agrégat

est très proche de cela. Il se peut cependant qu'il existe un moyen plus efficace de mettre en œuvre b@a que b+a! Par exemple, si le type B est List[A] , et b@a::b, puis a! sera a::Nil , et b1 + b2 sera b2 ::: b1 . a::b est meilleur que celui de (a::Néant:::b. Pour bénéficier de l'associativité, mais toujours utiliser @ , une des premières divisions b + a1! + ... + an! , dans (b + a1! + ap!) + (ap+1! + ..+ an!) , puis revenir à l'utilisation de @ avec (b @ a1 @ an) + (ap+1! @ @ an) . On a besoin encore de la ! sur ap+1, parce qu'il faut commencer par quelque B. Et le + est encore nécessaire aussi, apparaissant entre les paranthèses. Pour ce faire, as.associativeFold(!, +) pourrait être remplacé par as.optimizedAssociativeFold(b, !, @, +) .

retour à + . + est associatif, ou l'équivalent, (B, +) est un semi-groupe. En pratique, la plupart des semigroupes utilisés dans la programmation se trouvent être des monoïdes aussi, I. e contenir un élément neutre z (pour zéro ) en B, de sorte que pour chaque b , z + b = b + z = b . Dans ce cas, l'opération ! qui a du sens est probablement a! = z @ a . De plus, as z est un élément neutre b @ a1 ..@ an = (b + z) @ a1 @ an qui est b + (z + a1 @ an) . Il est donc toujours possible de commencer l'agrégation avec z. Si b est demandé à la place, vous faites b + result à la fin. Avec toutes ces hypothèses, on peut faire un s.aggregate(z, @, +) . C'est ce que fait aggregate . @ est l'argument seqop (appliqué dans une séquence 15191200920 z @ a1 @ a2 @ ap ), et + est combop (appliqué à déjà partiellement combiné résultats, comme dans (z + a1@...@ap) + (z + ap+1@...@an) ).

pour résumer, as.aggregate(z)(seqop, combop) calcule la même chose que as.foldLeft(z)( seqop) pourvu que

  • (B, combop, z) est un monoid
  • seqop(b,a) = combop(b, seqop(z,a))

globale de mise en œuvre peuvent utiliser l'associativité de l'combop de groupe les calculs qu'elle aime (pas la permutation des éléments toutefois, + n'a pas à être commutative, ::: n'est pas). Il peut s'exécuter en parallèle.

enfin, résoudre le problème initial en utilisant aggregate est laissé au lecteur comme exercice. Un conseil: implémenter en utilisant foldLeft , puis trouver z et combo qui satisfera aux conditions énoncées ci-dessus.

59
répondu Didier Dupont 2017-07-18 19:46:48

voyons si l'art ascii n'aide pas. Considérons le type de signature de aggregate :

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

aussi, notez que A se réfère au type de la collection. Donc, disons que nous avons 4 éléments dans cette collection, alors aggregate pourrait fonctionner comme ceci:

z   A   z   A   z   A   z   A
 \ /     \ /seqop\ /     \ /    
  B       B       B       B
    \   /  combop   \   /
      B _           _ B
         \ combop  /
              B

voyons un exemple pratique de cela. Disons que j'ai un GenSeq("This", "is", "an", "example") , et je veux savoir combien il y a de caractères dedans. Je peux écrire le

noter l'utilisation de par dans l'extrait de code ci-dessous. La seconde fonction transmise à aggregate est ce qui est appelé après les séquences individuelles sont calculées. Scala est seulement capable de faire cela pour les ensembles peuvent être parallélisées.

import scala.collection.GenSeq
val seq = GenSeq("This", "is", "an", "example")
val chars = seq.par.aggregate(0)(_ + _.length, _ + _)

donc, d'abord il calculerait ceci:

0 + "This".length     // 4
0 + "is".length       // 2
0 + "an".length       // 2
0 + "example".length  // 7

ce qu'il fait ensuite ne peut pas être prédit (il ya plus d'une façon de combiner les résultats), mais il pourrait faites ceci (comme dans l'art ascii ci-dessus):

4 + 2 // 6
2 + 7 // 9

où il se termine par

6 + 9 // 15

qui donne le résultat final. Maintenant, c'est un peu similaire dans la structure à foldLeft , mais il a une fonction supplémentaire (B, B) => B , que fold n'a pas. Cette fonction lui permet toutefois de travailler en parallèle!

considèrent, par exemple, que chacun des quatre calculs les calculs initiaux sont indépendants les uns des autres et peuvent être effectués en parallèle. Les deux suivants (résultant en 6 et 9) peuvent être commencés une fois que leurs calculs sur lesquels ils dépendent sont finis, mais ces deux peuvent aussi exécuter en parallèle.

les 7 calculs, mis en parallèle comme ci-dessus, pouvaient prendre aussi peu que le même temps 3 calculs en série.

en fait, avec une si petite collection Le coût de synchronisation des calculs serait assez grand pour effacer tout gain. En outre, si vous avez plié ceci, il ne faudrait 4 calculs total. Une fois que vos collections sont plus grandes, cependant, vous commencez à voir des gains réels.

Considèrent, en revanche, foldLeft . Parce qu'il n'a pas la fonction supplémentaire, il ne peut pas paralléliser n'importe quel calcul:

(((0 + "This".length) + "is".length) + "an".length) + "example".length

chacune des parenthèses intérieures doit être calculée avant la parenthèse extérieure pouvez procéder.

91
répondu Daniel C. Sobral 2017-11-20 04:56:32

la signature pour une collection avec des éléments de type A est:

def aggregate [B] (z: B)(seqop: (B, A) ⇒ B, combop: (B, B) ⇒ B): B 
  • z est un objet de type B agissant comme un élément neutre. Si vous voulez compter quelque chose, vous pouvez utiliser 0, si vous voulez construire une liste, commencer avec une liste vide, etc.
  • segop est analogue à la fonction que vous passez aux méthodes fold . Elle prend deux arguments, la première est du même type que l'élément neutre vous passé et représentent la substance qui a déjà été agrégé sur itération précédente, le second est l'élément suivant de votre collection. Le résultat doit également être de type B .
  • combop est une fonction combinant deux résultats en un.

dans la plupart des collections, aggregate est mis en œuvre dans TraversableOnce comme:

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

ainsi combop est ignoré. Cependant, il est logique pour collections parallèles , parce que seqop sera d'abord appliqué localement en parallèle, puis combop est appelé pour terminer l'agrégation.

Donc, pour votre exemple, vous pouvez essayer avec un pli d'abord:

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))

, Alors vous devez trouver un moyen de s'effondrer deux multimaps:

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]() ) ) ) 
       } 

Maintenant, vous pouvez utiliser aggregate en parallèle:

list.par.aggregate( Map[String,Set[String]]() )( seqOp, combOp )
//Returns: Map(one -> Set(i, 1), two -> Set(2, ii), four -> Set(iv))

Application de la méthode "égalité" pour liste, utilisant ainsi la collection parallèle (scala.collection.parallèle.immuable.ParSeq) de la liste pour vraiment profiter des processeurs multi core. Sans "par", il n'y aura pas de gain de performance puisque l'agrégat n'est pas fait sur la collecte parallèle.

10
répondu paradigmatic 2011-11-29 03:13:33

aggregate est comme foldLeft mais peut être exécuté en parallèle.

Comme missingfactor dit , la version linéaire de aggregate(z)(seqop, combop) est équivalent à foldleft(z)(seqop) . Cela n'est toutefois pas possible dans le cas parallèle, où nous aurions besoin de combiner non seulement l'élément suivant avec le résultat précédent (comme dans un pli normal), mais nous voulons diviser le itérable en sous-itérables sur lesquels nous appelons agrégat et avons besoin de combiner à nouveau ceux-ci. (De gauche à droite, mais pas associatif que nous pourrions avoir combiné les dernières pièces avant que le poing parties de l'itératif.) Cette re-combinaison en général non-trivial, et donc, on a besoin d'une méthode (S, S) => S pour accomplir cela.

la définition de ParIterableLike est la suivante:

def aggregate[S](z: S)(seqop: (S, T) => S, combop: (S, S) => S): S = {
  executeAndWaitResult(new Aggregate(z, seqop, combop, splitter))
}

qui utilise en effet combop .

pour référence, Aggregate est défini comme:

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)
}

La partie importante est mergecombop est appliquée avec deux sous-résultats.

9
répondu Debilski 2017-05-23 10:31:25

voici le blog sur la façon dont aggregate activer la performance sur le multi core processeur avec bench mark. http://markusjais.com/scalas-parallel-collections-and-the-aggregate-method/

Voici une vidéo sur la "Scala parallèle des collections" parler de "Scala Jours 2011". http://days2011.scala-lang.org/node/138/272

la description sur la vidéo

Collections Parallèles De Scala

Aleksandar Prokopec

les abstractions de programmation en parallèle prennent de plus en plus d'importance au fur et à mesure que le nombre de cœurs des processeurs augmente. Une programmation de haut niveau permet au programmeur de se concentrer davantage sur le programme, et moins sur les détails de bas niveau comme la synchronisation et l'équilibrage de charge. Les collections parallèles de Scala étendent le modèle de programmation du cadre de collecte de Scala, en fournissant des opérations parallèles sur les ensembles de données. La présentation décrira architecture du cadre de collecte parallèle, expliquant leurs décisions de mise en œuvre et de conception. Des applications concrètes de collecte telles que des cartes de hachage parallèles et des essais de hachage parallèles seront décrites. Enfin, plusieurs exemples d'applications seront présentés, démontrant le modèle de programmation dans la pratique.

3
répondu Win Myo Htet 2011-11-29 02:07:21

la définition de aggregate dans TraversableOnce source est:

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

qui n'est pas différent d'un simple foldLeft . combop ne semble être utilisé nulle part. Je suis moi-même confus quant à ce que le but de cette méthode est.

1
répondu missingfaktor 2011-08-03 18:39:21

juste pour clarifier les explications de ceux qui sont devant moi, en théorie l'idée est que total devrait fonctionner comme ça, (j'ai changé les noms des paramètres pour les rendre plus claire):

Seq(1,2,3,4).aggragate(0)(
     addToPrev = (prev,curr) => prev + curr, 
     combineSums = (sumA,sumB) => sumA + sumB)

devrait logiquement se traduire par

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)

parce que l'agrégation et la cartographie sont séparées, la liste originale pourrait théoriquement être divisée en différents groupes de tailles différentes et fonctionner en parallèle ou même sur des machines différentes. Dans l'implémentation actuelle de la pratique scala ne supporte pas cette fonctionnalité par défaut mais vous pouvez le faire dans votre propre code.

1
répondu Micheal Kris 2016-05-20 21:53:03