Here's some code which looks reasonable enough to me:
val myMap: Map[Int, Int] = ((x: Int) => Map[Int, Int](1 -> x + 1, 2 -> x + 2))(4)
When I try to compile it, I get two errors like this:
Error:(20, 68) type mismatch;
found : Int(1)
required: String
val myMap: Map[Int, Int] = ((x: Int) => Map[Int, Int](1 -> x + 1, 2 -> x + 2))(4)
^
I understand that my compiler is trying to use the string addition implementation of +. But why is it doing that? How do I ask the compiler to use integer addition here?
(Changing Int to Integer doesn't help.)
Here is a simpler example that reproduces the error:
scala> def fail(x: Int) = 1 -> x + 1
<console>:10: error: type mismatch;
found : Int(1)
required: String
def fail(x: Int) = 1 -> x + 1
^
All of the operators in play are left-associative, so everything reads left-to-right. That is, the call chain looks like this:
1.->(x).+(1)
or using infix notation:
(1 -> x) + 1
1 -> x returns a tuple (1, x) and a tuple does not have a + method, so the compiler attempts to implicitly convert it to a String via toString, because String does have a + method, which eventually fails because Int is required.
Just use parentheses to group things appropriately:
scala> def foo(x: Int) = 1 -> (x + 1)
foo: (x: Int)(Int, Int)
'->' has the same precedence as '+', so 1 -> x + 1 parses as (1 -> x) + 1. Adding parentheses as 1 -> (x + 1) fixes this error.
The problem is that according to Scala's precedence rules, -> binds as tightly as +, because it starts with -.
It's fixed by adding parentheses:
val myMap: Map[Int, Int] = ((x: Int) => Map[Int, Int](1 -> (x + 1), 2 -> (x + 2)))(4)
Related
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
I am getting a taste of Scala through the artima "Programming in Scala" book.
While presenting the Map traits, the authors go to some lengths to describe the -> syntax as a method that can be applied to any type to get a tuple.
And indeed:
scala> (2->"two")
res1: (Int, String) = (2,two)
scala> (2,"two")
res2: (Int, String) = (2,two)
scala> (2->"two") == (2, "two")
res3: Boolean = true
But those are not equivalent:
scala> Map(1->"one") + (2->"two")
res4: scala.collection.immutable.Map[Int,String] = Map(1 -> one, 2 -> two)
scala> Map(1->"one") + (2, "two")
<console>:8: error: type mismatch;
found : Int(2)
required: (Int, ?)
Map(1->"one") + (2, "two")
Why is this so, since my first tests seem to show that both "pair" syntaxes build a tuple?
Regards.
They are exactly the same, thanks to this class in Predef (only partly reproduced here):
final class ArrowAssoc[A](val __leftOfArrow: A) extends AnyVal {
#inline def -> [B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y)
}
#inline implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] = new ArrowAssoc(x)
So now the question is when will (a,b) syntax be ambiguous where (a -> b) is not? And the answer is in function calls, especially when they're overloaded:
def f[A](a: A) = a.toString
def f[A,B](a: A, b: B) = a.hashCode + b.hashCode
f(1,2) // Int = 3
f(1 -> 2) // String = (1,2)
f((1, 2)) // String = (1,2)
Map + in particular gets confused because it's overloaded with a multiple-argument version, so you could
Map(1 -> 2) + (3 -> 4, 4 -> 5, 5 -> 6)
and it thus interprets
Map(1 -> 2) + (3, 4)
as trying to add both 3 to the map, and then 4 to the map. Which of course makes no sense, but it doesn't try the other interpretation.
With -> there is no such ambiguity.
However, you can't
Map(1 -> 2) + 3 -> 4
because + and - have the same precedence. Thus it is interpreted as
(Map(1 -> 2) + 3) -> 4
which again fails because you're trying to add 3 in place of a key-value pair.
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))
I have a List
val l : List[Map[String,Any]] = List(Map("a" -> 1, "b" -> 2.8), Map("a" -> 3, "c" -> 4), Map("c" -> 5, "d" -> "abc"))
and I used the following code to find the sum for the keys "a" (Int), "b" (Double) and "c" (Int). "d" is included as noise.
l.map(n => n.mapValues( v => if (v.isInstanceOf[Number]) {v match {
case x:Int => x.asInstanceOf[Int]
case x:Double => x.asInstanceOf[Double]
}} else 0)).foldLeft((0,0.0,0))((t, m) => (
t._1 + m.get("a").getOrElse(0),
t._2 + m.get("b").getOrElse(0.0),
t._3 + m.get("c").getOrElse(0)))
I expect the output would be (4, 2.8, 9) but instead I was trashed with
<console>:10: error: overloaded method value + with alternatives:
(x: Int)Int <and>
(x: Char)Int <and>
(x: Short)Int <and>
(x: Byte)Int
cannot be applied to (AnyVal)
I think the exception was trying to tell me that '+' doesn't work with AnyVal. How do I get this to work to get my the result that I want? Thanks
m.foldLeft(0)(_+_._2)
it's a very clear and simple solution.
reference: http://ktuman.blogspot.com/2009/10/how-to-simply-sum-values-in-map-in.html
You can use foldLeft function:
scala> val l : List[Map[String,Any]] = List(Map("a" -> 1, "b" -> 2.8), Map("a" -> 3, "c" -> 4), Map("c" -> 5, "d" -> "abc"))
l: List[Map[String,Any]] = List(Map(a -> 1, b -> 2.8), Map(a -> 3, c -> 4), Map(c -> 5, d -> abc))
scala> val (sa, sb, sc) = l.foldLeft((0: Int, 0: Double, 0: Int)){
| case ((a, b, c), m) => (
| a + m.get("a").collect{case i: Int => i}.getOrElse(0),
| b + m.get("b").collect{case i: Double => i}.getOrElse(0.),
| c + m.get("c").collect{case i: Int => i}.getOrElse(0)
| )
| }
sa: Int = 4
sb: Double = 2.8
sc: Int = 9
Updated using incrop's idea of collect instead of match.
First, you totally miss the point of pattern matching
{case i: Int => i
case d: Double => d
case _ => 0}
is the proper replacement of all your function inside mapValues. Yet this is not the problem, your writing, while complex, does the same thing.
Your function in mapValues returns Double (because some branches return Int and others return Double, and in this case, Int is promoted to Double. If it were not, it would return AnyVal).
So you get a List[Map[String, Double]]. At this point, you have lost the Ints.
When you do m.get("a"), this returns Option[Double]. Option[A] has method getOrElse(default: A) : A (actually, default: => X) but it makes no difference here).
If you call getOrElse(0.0) instead of getOrElse(0), you get a Double. Your code still fails, because your fold start with (Int, Double, Double), and you would return (Double, Double, Double). If you start your fold with (0.0, 0.0, 0.0), it works, but you have lost your Ints, you get (4.0, 2.8, 9.0)
Now, about the error message. You pass an Int to a method expecting a Double (getOrElse), the Int should normally be converted to Double, and it would be as if you called with getOrElse(0.0). Except that Option is covariant (declared trait Option[+A]). if X is an ancestor of A, an Option[A] is also an Option[X]. So an Option[Double] is also Option[AnyVal] and Option[Any]. The call getOrElse(0) works if the option is considered an Option[AnyVal], and the result is AnyVal (would work with Any too, but AnyVal is more precise and this is the one the compiler chooses). Because the expression compiles as is, there is no need to promote the 0 to 0.0. Thus m.get("a").getOrElse(0) is of type AnyVal, which cannot be added to t._1. This is what your error message says.
You have knowledge that "a" is associated with Int, "b" with double, but you don't pass this knowledge to the compiler.
A nifty one-liner:
l.map(_.filterKeys(_ != "d")).flatten groupBy(_._1) map { case (k,v) => v map { case (k2,v2: Number) => v2.doubleValue} sum }
res0: scala.collection.immutable.Iterable[Double] = List(9.0, 4.0, 2.8)
In general, if you don't know the keys, but just want to sum values you can do
val filtered = for {
map <- l
(k, v) <- map
if v.isInstanceOf[Number]
} yield k -> v.asInstanceOf[Number].doubleValue
val summed = filtered.groupBy(_._1) map { case (k, v) => k -> v.map(_._2).sum }
scala> l
res1: List[Map[String,Any]] = List(Map(a -> 1, b -> 2.8), Map(a -> 3, c -> 4), Map(c -> 5, d -> abc))
scala> filtered
res2: List[(String, Double)] = List((a,1.0), (b,2.8), (a,3.0), (c,4.0), (c,5.0))
scala> summed
res3: Map[String,Double] = Map(c -> 9.0, a -> 4.0, b -> 2.8)
Update
You can filter map by type you want, for example
scala> val intMap = for (x <- l) yield x collect { case (k, v: Int) => k -> v }
intMap: List[scala.collection.immutable.Map[String,Int]] = List(Map(a -> 1), Map(a -> 3, c -> 4), Map(c -> 5))
and then sum values (see linked question)
scala> intMap reduce { _ |+| _ }
res4: scala.collection.immutable.Map[String,Int] = Map(a -> 4, c -> 9)
Am I missing something or can you not just do:
map.values.sum
?
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