Przykład funkcji agregującej Scala

Szukałem i nie mogę znaleźć przykładu lub dyskusji aggregate funkcji w Scali, które mogę zrozumieć. Wydaje się dość potężny.

Czy można użyć tej funkcji do zmniejszenia wartości krotek w celu utworzenia kolekcji typu multimap? Na przykład:

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

Po zastosowaniu agregatu:

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

Możesz też podać przykładowe parametry z, segop, i combop? Nie wiem, do czego służą te parametry.

Author: Nathaniel Ford, 2011-08-03

7 answers

Funkcja agregująca tego nie robi (poza tym, że jest to funkcja bardzo ogólna i może być do tego używana). Chcesz groupBy. Przynajmniej blisko. Gdy zaczynasz od Seq[(String, String)] i grupujesz, biorąc pierwszy element w krotce (czyli (String, String) => String), zwróci on Map[String, Seq[(String, String)]). Następnie należy odrzucić pierwszy parametr w wartościach Seq[String, String)].

Więc

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

Tutaj masz Map[String, Seq[(String, String)]. Jeśli chcesz Seq zamiast Map, wywołaj toSeq na wynik. Nie wiem. myślę, że masz gwarancję na zamówienie w wynikowym Seq chociaż


Agregat jest trudniejszą funkcją.

Rozważmy najpierw redukcję i redukcję. Niech będzie niepustym ciągiem as = Seq(a1, ... an) elementów typu A, oraz f: (A,A) => A niech będzie sposobem połączenia dwóch elementów typu A W jeden. Odnotuję to jako operator binarny@, a1 @ a2 zamiast f(a1, a2). as.reduceLeft(@) obliczy (((a1 @ a2) @ a3)... @ an). reduceRight będzie umieszczać nawiasy w drugą stronę, (a1 @ (a2 @... @ an)))). If @ happens to be asocjacyjne, nie dbają o nawiasy. Można by to obliczyć jako (a1 @... @ ap) @ (ap+1 @...@an) (W 2 dużych parantezach też byłyby parantezy, ale nie dbajmy o to). Następnie można wykonać dwie części równolegle, podczas gdy zagnieżdżone bracketing w reduceLeft lub reduceRight wymusza w pełni sekwencyjne obliczenia. Jednak obliczenia równoległe są możliwe tylko wtedy, gdy @ jest znany jako asocjacyjny, a metoda reduceLeft nie może tego wiedzieć.

Mimo to, może być metoda reduce, którego rozmówca byłby odpowiedzialny za zapewnienie, że operacja jest asocjacyjna. Następnie reduce uporządkowałby połączenia według własnego uznania, ewentualnie wykonując je równolegle. Rzeczywiście, istnieje taka metoda.

Istnieje jednak ograniczenie z różnymi metodami redukcji. Elementy Seq mogą być łączone tylko w wyniku tego samego typu: @ musi być (A,A) => A. Ale można mieć bardziej ogólny problem z połączeniem ich w B. Zaczyna się od wartości b typu B, i połączyć go z każdym elementem sekwencji. Operatorem @ jest (B,A) => B, a jeden oblicza (((b @ a1) @ a2) ... @ an). foldLeft robi to. foldRight robi to samo, ale zaczynając od an. Tam operacja @ nie ma szans na asocjację. Kiedy ktoś pisze b @ a1 @ a2, musi to oznaczać (b @ a1) @ a2, ponieważ (a1 @ a2) byłoby źle napisane. Więc foldLeft i foldRight muszą być sekwencyjne.

Załóżmy jednak, że każda A może być zamieniona na B, napiszmy ją z !, a! jest typu B. Załóżmy ponadto, że istnieje + operacja (B,B) => B i że @ jest taka, że b @ a jest w rzeczywistości b + a!. Zamiast łączyć elementy z @, można najpierw przekształcić je wszystkie do B za pomocą !, a następnie połączyć je z +. To by było as.map(!).reduceLeft(+). A jeśli + jest asocjacyjny, to można to zrobić za pomocą reduce, a nie sekwencyjnie: as. Mapa (!).zmniejsz (+). Może istnieć hipotetyczna metoda as.Asocjacja gwiazdowa (b, !, +).

/ Align = "left" / Może to być jednak, że istnieje bardziej efektywny sposób implementacji b@a niż b+a!, na przykład, jeśli Typ B to List[A], A b@a to a::b, to a! będzie a::Nil, a b1 + b2 będzie b2 ::: b1. a:: B jest o wiele lepsze niż (a::Nil):: b. aby korzystać z asocjacji, ale nadal używać @, najpierw należy podzielić b + a1! + ... + an! na (b + a1! + ap!) + (ap+1! + ..+ an!), a następnie wrócić do używania @ z (b @ a1 @ an) + (ap+1! @ @ an). Jeden nadal potrzebuje ! na ap+1, bo trzeba zaczynać od jakiegoś b. i jeszcze + jest potrzebne, pojawiające się między parantezami. Na zrób to, as.associativeFold(!, +) można zmienić na as.optimizedAssociativeFold(b, !, @, +).

Powrót do +. + jest asocjacyjny lub równoważny, (B, +) jest półgrupą. W praktyce większość półgrup używanych w programowaniu również jest monoidami, tzn. zawierają element neutralny z (dla zero) w B, tak że dla każdego b, z + b = b + z = b. W takim przypadku operacja !, która ma sens, prawdopodobnie będzie be a! = z @ a. Co więcej, ponieważ z jest elementem neutralnym b @ a1 ..@ an = (b + z) @ a1 @ an, który jest b + (z + a1 @ an). Tak jest zawsze można rozpocząć agregację za pomocą z. jeśli {[27] } jest poszukiwana zamiast tego, robisz b + result na końcu. Z tymi wszystkimi hipotezami, możemy zrobić s.aggregate(z, @, +). To właśnie robi aggregate. @ jest argumentem seqop (zastosowanym w sekwencji z @ a1 @ a2 @ ap), i + jest combop (stosowane do już częściowo połączonych wyników, jak w (z + a1@...@ap) + (z + ap+1@...@an)).

Podsumowując, as.aggregate(z)(seqop, combop) oblicza to samo co as.foldLeft(z)( seqop) pod warunkiem, że

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

Implementacja agregująca może używać asocjacji combop do grupowania obliczeń według własnych upodobań(nie zamieniając jednak elementów, + nie musi być przemienna,::: nie jest). Może prowadzić je równolegle.

Ostatecznie rozwiązanie początkowego problemu za pomocą {83]} pozostawiono jako ćwiczenie dla czytelnika. Podpowiedź: zaimplementuj używając foldLeft, a następnie znajdź z i combo, które spełnią powyższe warunki.
 59
Author: Didier Dupont,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-18 19:46:48

Zobaczmy, czy jakaś Sztuka ascii nie pomoże. Przyjrzyjmy się sygnaturze typu aggregate:

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

Należy również zauważyć, że A odnosi się do typu zbioru. Tak więc, powiedzmy, że mamy 4 elementy w tej kolekcji, wtedy aggregate może działać tak:

z   A   z   A   z   A   z   A
 \ /     \ /seqop\ /     \ /    
  B       B       B       B
    \   /  combop   \   /
      B _           _ B
         \ combop  /
              B
Zobaczmy na to praktyczny przykład. Powiedzmy, że mam GenSeq("This", "is", "an", "example") i chcę wiedzieć, ile znaków w nim jest. Mogę napisać:

Zwróć uwagę na użycie par W poniższym fragmencie kodu. Drugi funkcja przekazywana do agregatu jest to, co nazywa się po obliczeniu poszczególnych sekwencji. Scala jest w stanie to zrobić tylko dla zestawów, które mogą być równoległe.

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

Więc najpierw obliczyłoby to:

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

Tego, co zrobi dalej, nie można przewidzieć (istnieje więcej niż jeden sposób łączenia wyników), ale może to zrobić (jak w powyższym artu ascii): {]}

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

W którym to momencie kończy się

6 + 9 // 15

Co daje ostateczny wynik. Teraz, to jest trochę podobna w strukturze do foldLeft, ale posiada dodatkową funkcję (B, B) => B, której fold nie posiada. Ta funkcja umożliwia jednak pracę równolegle!

Weźmy na przykład, że każde z czterech obliczeń początkowe obliczenia są niezależne od siebie i mogą być wykonywane równolegle. Kolejne dwa (w wyniku 6 i 9) mogą być uruchomione po zakończeniu obliczeń, od których zależą, ale te dwa mogą również działać równolegle.

The 7 obliczeń, równoległych jak wyżej, może zająć tak mało, jak w tym samym czasie 3 szeregowe obliczenia.

Właściwie, przy tak małym zbiorze koszt synchronizacji obliczeń byłby wystarczająco duży, aby wymazać wszelkie zyski. Co więcej, jeśli to złożysz, zajmie to tylko 4 / align = "left" / Gdy jednak Twoje Kolekcje staną się większe, zaczniesz dostrzegać prawdziwe zyski.

Zastanów się, z drugiej strony, foldLeft. Ponieważ nie posiada dodatkowej funkcji, to nie można porównywać żadnych obliczeń:

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

Każdy z nawiasów wewnętrznych musi być obliczony, zanim zewnętrzny będzie mógł kontynuować.

 91
Author: Daniel C. Sobral,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-20 04:56:32

Sygnatura zbioru z elementami typu A wynosi:

def aggregate [B] (z: B)(seqop: (B, A) ⇒ B, combop: (B, B) ⇒ B): B 
  • {[5] } jest obiektem typu B działającym jako element neutralny. Jeśli chcesz coś policzyć, możesz użyć 0, jeśli chcesz zbudować listę, zacznij od pustej listy, itp.
  • {[6] } jest analogiczna do funkcji przekazywanej do metod fold. Wymaga dwóch argumentów, pierwszy jest tego samego typu co element neutralny, który przekazałeś i reprezentuje rzeczy, które zostały już zagregowane na poprzednim iteracja, druga to kolejny element Twojej kolekcji. Wynik musi być również typu B.
  • combop: jest funkcją łączącą dwa wyniki w jednym.

W większości zbiorów agregat zaimplementowany jest w TraversableOnce jako:

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

Zatem combop jest ignorowany. Jednak ma to sens dla zbiorów równoległych , ponieważ seqop będzie najpierw stosowane lokalnie równolegle, a następnie wywołane zostanie combop, aby zakończyć agregację.

Więc dla Twojego przykładu, ty można spróbować z krotnie pierwszy:

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

Potem trzeba znaleźć sposób na zawalenie dwóch multimapów:

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

Teraz możesz używać agregatu równolegle:

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

Zastosowanie metody " par " do listy, a tym samym użycie zbioru równoległego (scala.kolekcja.równolegle.niezmienny.ParSeq) z listy, aby naprawdę wykorzystać procesory wielordzeniowe. Bez " par " nie będzie żadnego zwiększenia wydajności, ponieważ agregat nie jest wykonywany na kolekcji równoległej.

 10
Author: paradigmatic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-29 03:13:33

aggregate jest jak foldLeft, ale może być wykonywany równolegle.

Jak mówi missingfactor , liniowa wersja aggregate(z)(seqop, combop) jest równoważna foldleft(z)(seqop). Jest to jednak niepraktyczne w przypadku równoległym, gdzie musielibyśmy połączyć nie tylko następny element z poprzednim wynikiem (jak w normalnym foldzie), ale chcemy podzielić iterable na sub-iterable, które nazywamy agregatem i musimy połączyć je ponownie. (W porządku od lewej do prawej, ale nie asocjacyjnym, ponieważ moglibyśmy połączyć ostatnie części przed częściami pięści iterowalnych.) To ponowne łączenie się w ogólnie nietrywialne, a zatem potrzeba metody (S, S) => S, aby to osiągnąć.

Definicja w ParIterableLike to:

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

Który rzeczywiście używa combop.

Dla odniesienia, Aggregate jest zdefiniowane jako:

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

Ważną częścią jest merge gdzie combop jest stosowany z dwoma pod-wynikami.

 9
Author: Debilski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:25

Oto blog o tym, jak agregować włączyć wydajność na procesorze wielordzeniowym z bench mark. http://markusjais.com/scalas-parallel-collections-and-the-aggregate-method/

Oto wideo na temat rozmowy "Scala parallel collections" z "dni Scala 2011". http://days2011.scala-lang.org/node/138/272

Opis na filmie

Scala Parallel Collections

Aleksandar Prokopec

Abstrakcje programowania równoległego stają się coraz większe znaczenie wraz ze wzrostem liczby rdzeni procesora. Model programowania wysokiego poziomu umożliwia programiście skupienie się bardziej na programie, a mniej na niskopoziomowych szczegółach, takich jak synchronizacja i równoważenie obciążenia. Scala parallel collections rozszerza model programowania Scala collection framework, zapewniając równoległe operacje na zbiorach danych. Wykład będzie opisywał architekturę równoległych RAM kolekcji, wyjaśniając ich wdrożenie i decyzje projektowe. Beton zostaną opisane implementacje kolekcji, takie jak parallel hash maps i parallel hash tries. Na koniec zostanie pokazanych kilka przykładowych aplikacji, demonstrujących model programowania w praktyce.

 3
Author: Win Myo Htet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-29 02:07:21

Definicja aggregate w TraversableOnce źródle to:

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

Który nie różni się niczym od prostego foldLeft. combop nie wydaje się być nigdzie używany. Sam jestem zdezorientowany, jaki jest cel tej metody.

 1
Author: missingfaktor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-03 18:39:21

Dla wyjaśnienia tych przede mną, w teorii chodzi o to, że agregat powinien działać w ten sposób, (zmieniłem nazwy parametrów, aby były jaśniejsze):

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

Należy logicznie przetłumaczyć na

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)

Ponieważ agregacja i mapowanie są oddzielne, oryginalna lista może teoretycznie być podzielona na różne grupy o różnych rozmiarach i działać równolegle lub nawet na różnych maszynach. W praktyce scala obecna implementacja nie obsługuje ta funkcja domyślnie, ale możesz to zrobić w swoim własnym kodzie.

 1
Author: Micheal Kris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-20 21:53:03