Scala: how to make a Hash(Trie)Map from a Map (via Anorm in Play)
Asked Answered
K

1

5

Having read this quote on HashTrieMaps on docs.scala-lang.org:

For instance, to find a given key in a map, one first takes the hash code of the key. Then, the lowest 5 bits of the hash code are used to select the first subtree, followed by the next 5 bits and so on. The selection stops once all elements stored in a node have hash codes that differ from each other in the bits that are selected up to this level.

I figured that be a great (read: fast!) collection to store my Map[String, Long] in.

In my Play Framework (using Scala) I have this piece of code using Anorm that loads in around 18k of elements. It takes a few seconds to load (no big deal, but any tips?). I'd like to have it 'in memory' for fast look ups for string to long translation.

val data = DB.withConnection { implicit c ⇒
  SQL( "SELECT stringType, longType FROM table ORDER BY stringType;" )
    .as( get[String]( "stringType" ) ~ get[Long]( "longType " )
    map { case ( s ~ l ) ⇒ s -> l }* ).toMap.withDefaultValue( -1L )
}

This code makes data of type class scala.collection.immutable.Map$WithDefault. I'd like this to be of type HashTrieMap (or HashMap, as I understand the linked quote all Scala HashMaps are of HashTrieMap?). Weirdly enough I found no way on how to convert it to a HashTrieMap. (I'm new to Scala, Play and Anorm.)

// Test for the repl (2.9.1.final). Map[String, Long]:
val data = Map( "Hello" -> 1L, "world" -> 2L ).withDefaultValue ( -1L )
data: scala.collection.immutable.Map[java.lang.String,Long] =
  Map(Hello -> 1, world -> 2)

// Google showed me this, but it is still a Map[String, Long].
val hm = scala.collection.immutable.HashMap( data.toArray: _* ).withDefaultValue( -1L )

// This generates an error.
val htm = scala.collection.immutable.HashTrieMap( data.toArray: _* ).withDefaultValue( -1L )

So my question is how to convert the MapWithDefault to HashTrieMap (or HashMap if that shares the implementation of HashTrieMap)?

Any feedback welcome.

Kilmarx answered 4/2, 2013 at 12:42 Comment(0)
E
9

As the documentation that you pointed to explains, immutable maps already are implemented under the hood as HashTrieMaps. You can easily verify this in the REPL:

scala> println( Map(1->"one", 2->"two", 3->"three", 4->"four", 5->"five").getClass )
class scala.collection.immutable.HashMap$HashTrieMap

So you have nothing special to do, your code already is using HashMap.HashTrieMap without you even realizing.

More precisely, the default implementation of immutable.Map is immutable.HashMap, which is further refined (extended) by immutable.HashMap.HashTrieMap. Note though that small immutable maps are not instances of immutable.HashMap.HashTrieMap, but are implemented as special cases (this is an optimization). There is a certain size threshold where they start being impelmented as immutable.HashMap.HashTrieMap. As an example, entering the following in the REPL:

val m0 = HashMap[Int,String]()
val m1 = m0 + (1 -> "one")
val m2 = m1 + (2 -> "two")
val m3 = m2 + (3 -> "three")
println(s"m0: ${m0.getClass.getSimpleName}, m1: ${m1.getClass.getSimpleName}, m2: ${m2.getClass.getSimpleName}, m3: ${m3.getClass.getSimpleName}")

will print this:

m0: EmptyHashMap$, m1: HashMap1, m2: HashTrieMap, m3: HashTrieMap

So here the empty map is an instance of EmptyHashMap$. Adding an element to that gives a HashMap1, and adding yet another element finally gives a HashTrieMap.


Finally, the use of withDefaultValue does not change anything, as withDefaultValue will just return an instance Map.WithDefault wich wraps the initial map (which will still be a HashMap.HashTrieMap).

Equator answered 4/2, 2013 at 13:49 Comment(2)
Has this (immutable maps already are implemented under the hood as HashTrieMaps) changed in the recent versions of scala? I get something else now with Scala 2.11.8 REPL for the first code snippet you have: class scala.collection.mutable.HashMapEberhart
Are you sure? Because I just tried it in a 2.11.8 REPL and get the exact same result. You might double-check if you haven't tried it with a mutable Map instead. Did you do something like import scala.collection.mutable._? if so when doing Map(1->"one", 2->"two", 3->"three", 4->"four", 5->"five") you're actually instantiating a mutable Map and printing its class indeed results in "class scala.collection.mutable.HashMap"Echopraxia

© 2022 - 2024 — McMap. All rights reserved.