I don't understand this with Scala hasmaps:
How do I create a value or update one if it does not exist?
I am tryng to count the number of characters in a list of Strings.
I've tried this code but it doesn't work :
def times(chars: List[Char]): List[(Char, Int)] = {
val map = new HashMap[Char, Int]()
chars.foreach(
(c : Char) => {
map.update(c, map.get(c) + 1)
})
}
I understand the returning type isn't correct.
But is my foreach loop wrong?
Is there a prettier way to write it?
I think this will answer your question:
scala> "abaccdba".groupBy(identity).mapValues(_.length)
res3: scala.collection.immutable.Map[Char,Int] = Map(b -> 2, d -> 1, a -> 3, c -> 2)
Oh, and btw HashMap has a method getOrElseUpdate as to your original question
If someone wonder how to use GetOrElseUpdate and find this post here is the exemple I found :
val map = Map('a' -> 1, 'b' -> 2) //> map :
scala.collection.immutable.Map[Char,Int] = Map(a -> 1, b -> 2)
val newval = map.getOrElse('b', 0) + 1 //> newval : Int = 3
val updated = map + ('b' -> (newval)) //> updated :
scala.collection.immutable.Map[Char,Int] = Map(a -> 1, b -> 3)
Related
I have a list in Scala that I want to group by a key and sum up the values of each key:
val l = List(("abc",1),("abc",2),("cbe",5),("cab",1))
I tried this code:
l.groupBy(identity).mapValues(_.sum)
But got the following error:
error: type mismatch;
found : scala.collection.immutable.Map[(String, Int),Int]
required: Seq[(String, Int)]
It might have been already answered in stackoverflow, but you group and then sum the values of list.
scala> val l = List(("abc",1),("abc",2),("cbe",5),("cab",1))
.groupBy(_._1)
.map { case (k, v) => k -> v.map { _._2}.sum}
l: scala.collection.immutable.Map[String,Int] = HashMap(cbe -> 5, abc -> 3, cab -> 1)
Given List:
val list = List(("abc",1),("abc",2),("cbe",5),("cab",1))
Using,
list.groupBy(_._1).mapValues(_.map(_._2).sum)
In Scala REPL:
scala> list.groupBy(_._1).mapValues(_.map(_._2).sum)
res13: scala.collection.immutable.Map[String,Int] = Map(cab -> 1, abc -> 3, cbe -> 5)
Can also be done with groupMapReduce
scala> val l = List(("abc",1),("abc",2),("cbe",5),("cab",1))
.groupMapReduce(_._1)(_._2)(_ + _)
val l: scala.collection.immutable.Map[String,Int] = Map(abc -> 3, cab -> 1, cbe -> 5)
My understanding of Scala immutable Map is that duplicate keys are not allowed. However if flatMap on List then duplicate keys within the Map are generated :
val l = List(1,2) //> l : List[Int] = List(1, 2)
l.flatMap(m => Map("a" -> m)) //> res0: List[(String, Int)] = List((a,1), (a,2))
But this does not occur if creating a map and populating using + operator :
val m = Map("a" -> 1) //> m : scala.collection.immutable.Map[String,Int] = Map(a -> 1)
val update = m +("a" -> 1) //> update : scala.collection.immutable.Map[String,Int] = Map(a -> 1)
Why does flatMap allow the creation of duplicate keys ?
List.flatMap takes a function that returns a GenTraversableOnce. Map is a GenTraversableOnce of tuples.
You can think of your initial code as equivalent to this:
List(1, 2).flatMap(m => List("a" -> m))
You never actually work with a Map here, just "list-like" things, so there is no notion of key unicity - there isn't even a notion of key.
How can I create a Map in Scala which does not only take a single parameter as key, but rather two or three.
val map = //..?
map("abc", 1) = 1
println(map("abc", 2)) // => null
println(map("abc", 1)) // => 1
I tried using tuples as a key, but then I have to assign values like this
map(("abc", 1)) = 1
Can I somehow get rid of the inner parentheses?
You could also use
map += ("abc", 1) -> 1
If the map key represents something (e.g. user info) and if you want to add clarity to your code (especially if you have 3 elements in the key), I would go with a case class as the key. Case classes have equals and hashcode implemented so you can safely use them as keys in a map. The code would be more verbose though:
case class MapKey(s: String, i: Int, d: Double)
val map = Map[MapKey, X](MapKey("a", 1, 1.1) -> "value1", MapKey("b", 2, 2.2) -> "value2")
val map2 = map + (MapKey("c", 3, 3.3) -> "value3")
//or for mutable map
map(MapKey("d", 4, 4.4)) = "value4"
//or
map += MapKey("e", 5, 5.5) -> "value5"
You can add your own enhancement to Map that will do the trick:
import collection.mutable.Map
implicit class EnhancedMap[A,B,C](m: Map[(A,B),C]) {
def update(a: A, b: B, c: C) { m((a,b)) = c }
}
then
val map = Map(("abc", 1) -> 0)
map("abc", 1) = 1
works just fine.
You can use -> syntax for tuples:
map("abc" -> 1) = 1
I got compiling errors using Luigi Plinge's approach. The following approach works for me, which is simpler.
scala> var b = Map[(Int, Int), Int]()
b: scala.collection.mutable.Map[(Int, Int),Int] = Map()
scala> b = b + ((1,1)->2)
b: scala.collection.mutable.Map[(Int, Int),Int] = Map((1,1) -> 2)
scala> b
res15: scala.collection.mutable.Map[(Int, Int),Int] = Map((1,1) -> 2)
scala> b = b + ((1,2)->2)
b: scala.collection.mutable.Map[(Int, Int),Int] = Map((1,1) -> 2, (1,2) -> 2)
scala> b(1,1)
res16: Int = 2
scala> b(1,2)
res17: Int = 2
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
?
Inside a function of mine I construct a result set by filling a new mutable HashMap with data (if there is a better way - I'd appreciate comments). Then I'd like to return the result set as an immutable HashMap. How to derive an immutable from a mutable?
Discussion about returning immutable.Map vs. immutable.HashMap notwithstanding, what about simply using the toMap method:
scala> val m = collection.mutable.HashMap(1 -> 2, 3 -> 4)
m: scala.collection.mutable.HashMap[Int,Int] = Map(3 -> 4, 1 -> 2)
scala> m.toMap
res22: scala.collection.immutable.Map[Int,Int] = Map(3 -> 4, 1 -> 2)
As of 2.9, this uses the method toMap in TraversableOnce, which is implemented as follows:
def toMap[T, U](implicit ev: A <:< (T, U)): immutable.Map[T, U] = {
val b = immutable.Map.newBuilder[T, U]
for (x <- self)
b += x
b.result
}
scala> val m = collection.mutable.HashMap(1->2,3->4)
m: scala.collection.mutable.HashMap[Int,Int] = Map(3 -> 4, 1 -> 2)
scala> collection.immutable.HashMap() ++ m
res1: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 3 -> 4)
or
scala> collection.immutable.HashMap(m.toSeq:_*)
res2: scala.collection.immutable.HashMap[Int,Int] = Map(1 -> 2, 3 -> 4)
If you have a map : logMap: Map[String, String]
just need to do : logMap.toMap()