Идиоматический способ сокращения списка пар к карте ключей и их суммарному счету?

Я пытаюсь воссоздать Hadoop число слов map/уменьшить логику в простой программе Scala для обучения

Это то, что я до сих пор

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)

Это похоже на работу, но я уверен, что существует более идиоматический способ обработки сокращенной части (foldLeft)

Я думал о мультимапе, но у меня есть чувство, что Scala имеет способ сделать это легко

Есть ли? например способ добавить к карте, и если ключ существует, вместо его замены, добавив значение к существующему значению. Я уверен, что видел этот вопрос где-то, но не смог найти его и ни ответа.

Я знаю, что groupBy - это способ сделать это, вероятно, в реальном мире, но я пытаюсь реализовать его как можно ближе к исходной карте/сокращению логики в ссылке выше.

Ответ 1

Вы можете использовать оператор Scalaz |+|, потому что Maps являются частью Semigroup typeclass:

Оператор |+| - это функция Monoid mappend (моноид - это любая "вещь", которая может быть добавлена ​​вместе. Многие вещи могут быть добавлены вместе: строки, инты, карты, списки, параметры и т.д. Пример:

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)

Итак, в вашем случае, а затем создайте List[(String,Int)], создайте List[Map[String,Int]], а затем суммируйте их:

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

Ответ 2

Вы можете использовать карту, которая возвращает 0 в качестве значения по умолчанию. Карта предлагает с помощьюDefaultValue:

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

Та же карта с заданным значением по умолчанию:

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

Ответ 3

другая версия:

 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)

Ответ 4

Исправьте меня, если я ошибаюсь, но как насчет этого:

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

Предполагая, что слова - это список слов:

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