0

I am trying to implement a nested map in scala in play framework. what I am trying to achieve is a map which is of type Map[String, Map[Long,Int]]. I am not able to assign the value of the parent map which is itself a map. The application is compiling properly but the value is not getting updated. Can you please suggest what I am doing wrong and how to assign a map value to another map?

import java.util.concurrent.ConcurrentHashMap
import scala.collection._
import scala.collection.convert.decorateAsScala._
import scala.collection.JavaConversions.mapAsScalaMap

case class Events(eventType: String, timeStamp: Long)

object Events {

  var eventMap = new ConcurrentHashMap[String, ConcurrentHashMap[Long,Int]]()

  def save(events: Events) = {
    var eventsKey: String = ""
    var count: Int = 1
    var timeKey: Long = 0L
    var newEntry: Boolean = false
    eventsKey = events.eventType
    var countMap = new ConcurrentHashMap[Long,Int]()
    countMap.clear()

    if (eventMap.containsKey(eventsKey)) {
      countMap = eventMap.get(eventsKey) // this is not working
      timeKey = events.timeStamp
      if (countMap.containsKey(timeKey)) { 
        count = countMap(timeKey) + 1
        countMap.put(timeKey, count)
        eventMap.put(eventsKey, countMap)
      } // End of counter check
      else {
        newEntry = true
      }
    } // End of if event key check
    else {
      newEntry = true
    } // End of else event key check

    if (newEntry) {
      countMap.putIfAbsent(timeKey, 1)
      eventMap.putIfAbsent(eventsKey, countMap)
    }
  }
}
2
  • One bug I notice with the code above, when the eventsKey is missing, you do not set the timeKey from the passed-in events instance, causing the new entry to have the default timeKey value you specify of 0L. Commented Nov 1, 2015 at 3:52
  • Can you provide a example of what are you calling (with parameters)? Ex: save("aaaabc", 123323) Commented Nov 1, 2015 at 4:45

1 Answer 1

2

The problem is happening because you are only setting temporary timeKey inside the if, and you still need this value outside of the if. This will make your program to always insert the first timeStamp of a given eventType as 0 :

Events.save(Events("aa", 222))     // {aa={0=1}}

Events.save(Events("aa", 333))     // {aa={0=1, 333=1}}

Events.save(Events("aa", 333))     // {aa={0=1, 333=2}}

Events.save(Events("bb", 444))     // {aa={0=1, 333=2}, bb={0=1}}

To fix it, just move the code out of the if:

...     
var newEntry: Boolean = false
eventsKey = events.eventType
timeKey = events.timeStamp // <--- This fixes it
var countMap = new ConcurrentHashMap[Long,Int]()
countMap.clear()

if (eventMap.containsKey(eventsKey)) {
  countMap = eventMap.get(eventsKey) //this is not working

  if (countMap.containsKey(timeKey)) {
    count = countMap(timeKey) + 1
...

Now when you run, you get:

Events.save(Events("aa", 222))     // {aa={222=1}}

Events.save(Events("aa", 333))     // {aa={333=1, 222=1}}

Events.save(Events("aa", 333))     // {aa={333=2, 222=1}}

Events.save(Events("bb", 444))     // {aa={333=2, 222=1}, bb={444=1}}

Extra

I know you didn't asked about it, but I thought it would be nice to have a more idiomatic scala code:

case class Event(eventType: String, timeStamp: Long)

object Event {
  val eventMap = new ConcurrentHashMap[String, ConcurrentHashMap[Long,Int]]()

  def save(event: Event) = {
    val Event(eventKey, timeKey) = event

    val countMap = eventMap.getOrElse(eventKey, new ConcurrentHashMap[Long,Int]())

    val count = countMap.getOrElse(timeKey, 0) + 1

    countMap.put(timeKey, count)
    eventMap.put(eventKey, countMap)
  }
}

PS: I renamed Events to Event

PS2: Notice how the bug you stumbled upon is more difficult to happen when you are programming in a more idiomatic way. :)

Sign up to request clarification or add additional context in comments.

5 Comments

Hello @Onilton, Thank you very much for the clarification, the code is working perfectly now. I have 1 more question it may be silly but as i am new to scala the question is: val is immutable and in the example above, the map you have defined is of type val how is it adding/updating keys and values if it is of type val shouldn't be it of type var? and thank you once again for your "extra" code which is way better than i was using .
That's because Java/Scala vars are reference variables. If you come from C/C++ background, think they are like pointers. If not, think variable are like names or aliases you put on objects.
eventMap is the name. You are creating a new object new ConcurrentHashMap and assigning the name eventMap. When using var, you can change your mind and attach this name to another object/instance (of the same type) for example, another new ConcurrentHashMap. This is the only thing val do: forbids that, the name can only be used to that specific object. But you can still mutate, call methods and change the object properties.
The best article (that explains this concept in the easiest way) I could find is for python radar.oreilly.com/2014/10/… . What a shame there isn't such kind of article for scala/java, but if you know a little bit of python it won't be a problem. val is like final for variables in Java, so there's also the excelent article for final in wikipedia en.wikipedia.org/wiki/Final_(Java)#Final_variables
Hello @Onilton thank you very much for the clarification.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.