Getting Map Key's Value Cleanly - scala

How can I idiomatically get a Map key's value if I know that it exists?
scala> val m = Map(1 -> "hi", 2 -> "world")
m: scala.collection.immutable.Map[Int,String] = Map(1 -> hi, 2 -> world)
scala> if (m.contains(1)) println(m.get(1) )
Some(hi)
Is there a more idiomatic alternative over m.get(1).get.get?
scala> if (m.contains(1)) println(m.get(1).get )
hi

scala Map has apply method:
scala> m.apply(1)
res1: String = hi
or with syntax sugar:
scala> m(1)
res0: String = hi
But more idiomatic ways is to iterate over Option:
scala> m.get(1) foreach println
hi

Related

Defining a Map[Int, String] with long strings as right value

I'm trying to use a concatenation of string litterals
as value in a Map[Int, String] definition:
scala> val m: Map[Int, String] = Map(1 -> "a" + "b")
but I get the following error from sbt console
<console>:7: error: type mismatch;
found : String
required: (Int, String)
val m: Map[Int, String] = Map(1 -> "a" + "b")
The reason I would want to do such a thing is because I want to define maps from an id to some code like so:
Map(1 -> s"""SELECT year, COUNT(*) FROM""" +
s""" (SELECT id, YEAR(pb_date) AS year FROM Publications) AS Res1""" +
s"""GROUP BY year;""")
without having to define a string for each of the code snippets present as Map right value.
Is there a way to achieve this?
You are just missing some parentheses:
scala> val m: Map[Int, String] = Map(1 -> ("a" + "b"))
m: Map[Int,String] = Map(1 -> ab)
The reason why you are getting that error specifically is because -> takes precedence over +, meaning that you actually get (1 -> "a") + b, as you can see below:
scala> 1 -> "a" + "b"
res4: String = (1,a)b

Why Scala REPL shows tuple type for Map expression?

Scala REPL gives the same type for both expressions - (tuple? -- strange!). Yet ("a" ->1) which is a Map I can add to map and ("a", 1)can not. Why Scala REPL shows tuple type type for Map expression?
scala> :t ("a" -> 1)
(String, Int)
scala> :t ("a",1)
(String, Int)
scala> val m = Map.empty[String, Int]
m: scala.collection.immutable.Map[String,Int] = Map()
scala> m + ("a",1)
<console>:9: error: type mismatch;
found : String("a")
required: (String, ?)
m + ("a",1)
^
scala> m + ("a" ->1)
res19: scala.collection.immutable.Map[String,Int] = Map(a -> 1)
Scala thinks a + (b,c) means you are trying to call the + method with two arguments, which is a real possibility since maps do have a multi-argument addition method so you can do things like
m + (("a" -> 1), ("b" -> 2))
the solution is simple: just add an extra set of parentheses so it's clear that (b,c) is in fact a tuple being passed as a single argument.
m + (("a", 1))
Actually, the reason for this is that Predef: http://www.scala-lang.org/api/current/index.html#scala.Predef$ (which is always in scope in Scala) contains an implicit conversion from Any to ArrowAssoc (the method implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A])
ArrowAssoc contains the method -> which converts it to a tuple.
So basically you are doing any2ArrowAssoc("a").->(1) which returns ("a",1).
From repl:
any2ArrowAssoc("a").->(1)
res1: (java.lang.String, Int) = (a,1)
Furthermore, you can work on immutable hashmaps like this:
val x = HashMap[Int,String](1 -> "One")
x: scala.collection.immutable.HashMap[Int,String] = Map((1,One))
val y = x ++ HashMap[Int,String](2 -> "Two")
y: scala.collection.immutable.Map[Int,String] = Map((1,One), (2,Two))
val z = x + (3 -> "Three")
z: scala.collection.immutable.HashMap[Int,String] = Map((1,One), (3,Three))

Idiomatic way to reduce a list of pairs to a map of keys and their aggregated count?

I'm trying to recreate Hadoop's word count map / reduce logic in a simple Scala program for learning
This is what I have so far
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)
This seems to work, but I'm sure there is a more idiomatic way to handle the reduce part (foldLeft)
I was thinking about perhaps a multimap, but I have a feeling Scala has a way to do this easily
Is there? e.g. a way to add to a map, and if the key exists, instead of replacing it, adding the value to the existing value. I'm sure I've seen this quesion somewhere, but couldn't find it and neither the answer.
I know groupBy is the way to do it probably in the real world, but I'm trying to implement it as close as possible to the original map/reduce logic in the link above.
You can use Scalaz's |+| operator because Maps are part of the Semigroup typeclass:
The |+| operator is the Monoid mappend function (a Monoid is any "thing" that can be "added" together. Many things can be added together like this: Strings, Ints, Maps, Lists, Options etc. An example:
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)
So in your case, rather then create a List[(String,Int)], create a List[Map[String,Int]], and then sum them:
val mapped = input.flatMap(_.split(" ").map(word => Map(word -> 1)))
mapped.suml
You could use a map that returns 0 as default value. Map offers withDefaultValue:
def withDefaultValue[B1 >: B](d: B1): Map[A, B1]
The same map with a given default value:
val emptyMap = Map[String,Int]().withDefaultValue(0)
mapped.foldLeft(emptyMap)((sofar,item) => {
sofar.updated(item._1, item._2 + sofar(item._1))
})
Correct me if I am wrong but how about this:
val w = words.groupBy(_.toString).map(x => (x._1,x._2.size)).toList
assuming words is the List of words:
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))
another version:
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)
Starting Scala 2.13, most collections are provided with the groupMapReduce method which can be seen as a close analog of Hadoop's map/reduce logic:
val words = List(words1, words2).flatMap(_.split(" "))
words.groupMapReduce(identity)(_ => 1)(_ + _)
// immutable.Map[String,Int] = HashMap(Goodbye -> 1, Hello -> 2, Bye -> 1, Hadoop -> 2, World -> 2)
This:
splits and merges words from the 2 input lists
groups elements by themselves (identity) (group part of groupMapReduce)
maps each grouped value occurrence to 1 (map part of groupMapReduce)
reduces values within a group of values (_ + _) by summing them (reduce part of groupMapReduce).
This is a one-pass version of what can be translated by:
words.groupBy(identity).mapValues(_.map(_ => 1).reduce(_ + _))

How to convert a String to a Seq[String] in a Map

I have a Map[String,String] and a third party function that requires a Map[String,Seq[String]]
Is there an easy way to convert this so I can pass the map to the function?
original.mapValues(Seq(_))
Note that mapValues returns a map view, so the function (Seq(_)) will be recomputed every time an element is accessed. To avoid this, just use normal map:
original.map{ case (k,v) => (k, Seq(v)) }
Usage:
scala> val original = Map("a" -> "b", "c" -> "d")
original: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(a -> b, c -> d)
scala> original.mapValues(Seq(_))
res1: scala.collection.immutable.Map[java.lang.String,Seq[java.lang.String]] = Map(a -> List(b), c -> List(d))
You could avoid some code duplication by using :-> from Scalaz.
If t is a Tuple2, f <-: t :-> g is equivalent to (f(t._1), g(t._2)).
scala> import scalaz._, Scalaz._
import scalaz._
import Scalaz._
scala> val m = Map(1 -> 'a, 2 -> 'b)
m: scala.collection.immutable.Map[Int,Symbol] = Map(1 -> 'a, 2 -> 'b)
scala> m.map(_ :-> Seq.singleton)
warning: there were 1 deprecation warnings; re-run with -deprecation for details
res15: scala.collection.immutable.Map[Int,Seq[Symbol]] = Map(1 -> List('a), 2 -> List('b))
scala> m.map(_ :-> (x => Seq(x)))
res16: scala.collection.immutable.Map[Int,Seq[Symbol]] = Map(1 -> List('a), 2 -> List('b))

Scala Map from tuple iterable

Constructing scala.collection.Map from other collections, I constantly find myself writing:
val map = Map(foo.map(x=>(x, f(x)))
However, this doesn't really work since Map.apply takes variable arguments only - so I have to write:
val map = Map(foo.map(x=>(x, f(x)) toSeq :_*)
to get what I want, but that seems painful. Is there a prettier way to construct a Map from an Iterable of tuples?
Use TraversableOnce.toMap which is defined if the elements of a Traversable/Iterable are of type Tuple2. (API)
val map = foo.map(x=>(x, f(x)).toMap
Alternatively you can use use collection.breakOut as the implicit CanBuildFrom argument to the map call; this will pick a result builder based on the expected type.
scala> val x: Map[Int, String] = (1 to 5).map(x => (x, "-" * x))(collection.breakOut)
x: Map[Int,String] = Map(5 -> -----, 1 -> -, 2 -> --, 3 -> ---, 4 -> ----)
It will perform better than the .toMap version, as it only iterates the collection once.
It's not so obvious, but this also works with a for-comprehension.
scala> val x: Map[Int, String] = (for (i <- (1 to 5)) yield (i, "-" * i))(collection.breakOut)
x: Map[Int,String] = Map(5 -> -----, 1 -> -, 2 -> --, 3 -> ---, 4 -> ----)
val map = foo zip (foo map f) toMap