2012-12-13 9 views
11

che sto cercando di ricreare word count mappa di Hadoop/ridurre la logica in un programma semplice per imparare ScalaModo idiomatico per ridurre un elenco di coppie in una mappa di chiavi e il loro conteggio aggregato?

Questo è quello che ho finora

val words1 = "Hello World Bye World"  
val words2 = "Hello Hadoop Goodbye Hadoop" 

val input = List(words1,words2)   
val mapped = input.flatMap(line=>line.split(" ").map(word=>word->1)) 
    //> mapped : List[(String, Int)] = List((Hello,1), (World,1), (Bye,1), 
    //          (World,1), (Hello,1), (Hadoop,1), 
    //          (Goodbye,1), (Hadoop,1)) 

mapped.foldLeft(Map[String,Int]())((sofar,item)=>{ 
    if(sofar.contains(item._1)){ 
     sofar.updated(item._1, item._2 + sofar(item._1)) 
    }else{ 
     sofar + item 
    } 
})        
    //>Map(Goodbye -> 1, Hello -> 2, Bye -> 1, Hadoop -> 2, World -> 2) 

Questo sembra funzionare, ma sono sicuro che ci è un modo più idiomatico per gestire la parte ridurre (foldLeft)

stavo pensando forse un multimap, ma ho una sensazione Scala ha un modo per farlo facilmente

c'è? per esempio. un modo per aggiungere a una mappa e, se la chiave esiste, invece di sostituirla, aggiungendo il valore al valore esistente. Sono sicuro di aver visto questa domanda da qualche parte, ma non sono riuscita a trovarla e nemmeno la risposta.

Lo so groupBy è il modo per farlo probabilmente nel mondo reale, ma sto cercando di implementarlo il più vicino possibile alla mappa originale/ridurre la logica nel link sopra.

risposta

10

È possibile utilizzare Scalaz's|+| operatore perché Maps fanno parte della Semigroup typeclass:

L'operatore |+| è la funzione Monoid mappend (un Monoid è una "cosa" che può essere "aggiunta" insieme. Molte cose possono essere aggiunte insieme come: Stringhe, Ints, Ma ps, Liste, opzioni ecc Un esempio:

scala> import scalaz._ 
import scalaz._ 

scala> import Scalaz._ 
import Scalaz._ 

scala> val map1 = Map(1 -> 3 , 2 -> 4) 
map1: scala.collection.immutable.Map[Int,Int] = Map(1 -> 3, 2 -> 4) 

scala> val map2 = Map(1 -> 1, 3 -> 6) 
map2: scala.collection.immutable.Map[Int,Int] = Map(1 -> 1, 3 -> 6) 

scala> map1 |+| map2 
res2: scala.collection.immutable.Map[Int,Int] = Map(1 -> 4, 3 -> 6, 2 -> 4) 

Quindi nel tuo caso, piuttosto che creare un List[(String,Int)], creare un List[Map[String,Int]], e poi li Somma:

val mapped = input.flatMap(_.split(" ").map(word => Map(word -> 1))) 
mapped.suml 
+0

Nice, non ha ancora giocato con scalaz . Questo è un "Guava per Scala" in termini laici? –

+3

Una specie di suppongo.Scalaz è fondamentalmente una libreria che usa "pimps" (usando il pattern Pimp my Library) di default di Scala, con alcuni componenti di scelta che prendono le idee da Haskell e dalla teoria delle categorie. È progettato per consentire a Scala di essere utilizzato in uno stile molto più "classicamente funzionale" consentito dalle librerie predefinite. Quindi ottieni cose come monade di stato, funtori applicativi, astrazioni IO ecc. Cose che richiedono un po 'di tempo per capire, ma sono davvero utili. –

+0

@EranMedan Direi Haskell per Scala –

7

È possibile utilizzare una mappa che restituisce 0 come valore predefinito. Mappa offre withDefaultValue:

def withDefaultValue[B1 >: B](d: B1): Map[A, B1] 

la stessa mappa con un dato valore di default:

val emptyMap = Map[String,Int]().withDefaultValue(0) 
mapped.foldLeft(emptyMap)((sofar,item) => { 
    sofar.updated(item._1, item._2 + sofar(item._1)) 
}) 
+0

Questa grande opera. Speravo volentieri che ci fosse un qualche metodo magico nella forma 'sofar.addgregate (elemento, _ + _)' ed evitare tutte le notazioni '_1'' _2', ma immagino che sia abbastanza buono –

+1

Ricordo di aver letto un blog su monoidi, mappe e scalaz. Penso che sia possibile aggiungere alle mappe con | + | operatore ... – Jan

+2

A parte: un'alternativa a 'withDefaultValue' è invece di' sofar (item._1) 'usa' sofar.get (item._1) .getOrElse (0) ' –

3

un'altra versione:

val words1 = "Hello World Bye World"    
//> words1 : java.lang.String = Hello World Bye World 
val words2 = "Hello Hadoop Goodbye Hadoop"  
//> words2 : java.lang.String = Hello Hadoop Goodbye Hadoop 

val words = words1.split(" ") ++ words2.split(" ") 
//> words : Array[java.lang.String] = Array(Hello, World, Bye, World, Hello, Hadoop, Goodbye, Hadoop) 

words.map(m => (m, (0 /: words) 
    ((x, y) => if (y == m) x + 1 else x))). 
    toList.distinct.toMap 
//> res0: scala.collection.immutable.Map[java.lang.String,Int] = Map(Goodbye -> 1, Hello -> 2, Bye -> 1, Hadoop -> 2, World -> 2) 
3

mi corregga se sbaglio, ma come su questo:

val w = words.groupBy(_.toString).map(x => (x._1,x._2.size)).toList 

assumendo parole è la lista delle parole:

val words1 = "Hello World Bye World" 
val words2 = "Hello Hadoop Goodbye Hadoop" 
val words = words1.split(" ") ++ words2.split(" ") 
val w = words.groupBy(_.toString).map(x => (x._1,x._2.size)).toList 
//List((Goodbye,1), (Hello,2), (Bye,1), (Hadoop,2), (World,2)) 
+1

soluzione più pulita e doesn Richiedere ulteriori importazioni ... –

Problemi correlati