1

I am creating a Map which has an Array inside it. I need to keep adding values to that Array. How do I do that?

var values: Map[String, Array[Float]] = Map()

I tried several ways such as:

myobject.values.getOrElse("key1", Array()).++(Array(float1))

Few other ways to but nothing updates the array inside the Map.

1
  • How you would like to update the array? Change the value at the position or append / prepend it? Commented Mar 19, 2020 at 19:31

4 Answers 4

6

There is a problem with this code:

values.getOrElse("key1", Array()).++(Array(float1))

This does not update the Map in values, it just creates a new Array and then throws it away.

You need to replace the original Map with a new, updated Map, like this:

values = values.updated("key1", values.getOrElse("key1", Array.empty[Float]) :+ float1)

To understand this you need to be clear on the distinction between mutable variables and mutable data.

var is used to create a mutable variable which means that the variable can be assigned a new value, e.g.

var name = "John"
name = "Peter" // Would not work if name was a val

By contrast mutable data is held in objects whose contents can be changed

val a = Array(1,2,3)
a(0) = 12 // Works even though a is a val not a var

In your example values is a mutable variable but the Map is immutable so it can't be changed. You have to create a new, immutable, Map and assign it to the mutable var.

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

8 Comments

Replace the whole map with the original map? Wouldn't that be VERY costly! I mean at any given point there could be over 50 keys in the Map & each key might have more than 200 values. I thought I was using 'var' not 'val' so I am using 'mutable' objects no?
Immutable data-structures reuse whatever possible and are relatively cheap.
There is some cost but it is not as bad as you think because the data structures are optimised for this sort of operation. But if you need the best performance you need to use scala.collection.mutable.Map which does allow elements to be updated. (As I explained values in a var are not necessarily mutable)
Here is nice visualisation of some scala collections: stanch.github.io/reftree/talks/Immutability.html I envy you. Discovering this is real joy :)
I am working on a streaming app which will process millions of rows per minute so this Map will get rebuilt constantly. This solution is a bit scary but seems like you guys are experts so I will give it a shot. Thanks.
|
4

From what I can see (according to ++), you would like to append Array, with one more element. But Array fixed length structure, so instead I'd recommend to use Vector. Because, I suppose, you are using immutable Map you need update it as well.

So the final solution might look like:

var values: Map[String, Vector[Float]] = Map()
val key = "key1"
val value = 1.0
values = values + (key -> (values.getOrElse(key, Vector.empty[Float]) :+ value))

Hope this helps!

9 Comments

@LuisMiguelMejíaSuárez thanks for suggested issue, I changed it to Vector
This looks promising... but I just want to understand...This will NOT 'rebuild' the entire 'values' vector, correct? I simply want to add an element to the Vector. Since the 'values' is 'mutable' object I am assuming this code will not rebuild it. Please confirm. Thanks.
Is there no way to simply get the 'reference' of the Vector & add element to the vector using that reference? This is how I would do it in Java. Sorry.. bit new to Scala. Thanks.
@DilTeam Is there no way to simply get the 'reference' - as I mentioned, because of using immutable structures you won't be able to just get and modify internal Vector reference, because :+ will create new one, which need to be changed in outer map
Note that this doesn't compile, that Nil is not compatible with Vector[Float] and value needs to be Float not Double
|
4

Immutable vs Mutable Collections

You need to choose what type of collection you will use immutable or mutable one. Both are great and works totally differently. I guess you are familiar with mutable one (from other languages), but immutable are default in scala and probably you are using it in your code (because it doesn't need any imports). Immutable Map cannot be changed... you can only create new one with updated values (Tim's and Ivan's answers covers that).

There are few ways to solve your problem and all are good depending on use case.

See implementation below (m1 to m6):

//just for convenience 
type T = String
type E = Long

import scala.collection._

//immutable map with immutable seq (default).
var m1 = immutable.Map.empty[T,List[E]] 

//mutable map with immutable seq. This is great for most use-cases.
val m2 = mutable.Map.empty[T,List[E]]

//mutable concurrent map with immutable seq.
//should be fast and threadsafe (if you know how to deal with it) 
val m3 = collection.concurrent.TrieMap.empty[T,List[E]] 

//mutable map with mutable seq.
//should be fast but could be unsafe. This is default in most imperative languages (PHP/JS/JAVA and more).
//Probably this is what You have tried to do
val m4 = mutable.Map.empty[T,mutable.ArrayBuffer[E]] 

//immutable map with mutable seq.
//still could be unsafe 
val m5 = immutable.Map.empty[T,mutable.ArrayBuffer[E]] 
//immutable map with mutable seq v2 (used in next snipped)
var m6 = immutable.Map.empty[T,mutable.ArrayBuffer[E]]

//Oh... and NEVER DO THAT, this is wrong
//I mean... don't keep mutable Map in `var`
//var mX = mutable.Map.empty[T,...]

Other answers show immutable.Map with immutable.Seq and this is preferred way (or default at least). It costs something but for most apps it is perfectly ok. Here You have nice source of info about immutable data structures: https://stanch.github.io/reftree/talks/Immutability.html.

Each variant has it's own Pros and Cons. Each deals with updates differently, and it makes this question much harder than it looks at the first glance.

Solutions

val k = "The Ultimate Answer"
val v = 42f

//immutable map with immutable seq (default).
m1 = m1.updated(k, v :: m1.getOrElse(k, Nil))

//mutable map with immutable seq.
m2.update(k, v :: m2.getOrElse(k, Nil))

//mutable concurrent map with immutable seq.
//m3 is bit harder to do in scala 2.12... sorry :)

//mutable map with mutable seq.
m4.getOrElseUpdate(k, mutable.ArrayBuffer.empty[Float]) += v

//immutable map with mutable seq.
m5 = m5.updated(k, {
  val col = m5.getOrElse(k, c.mutable.ArrayBuffer.empty[E])
  col += v
  col
})

//or another implementation of immutable map with mutable seq.
m6.get(k) match {
  case None => m6 = m6.updated(k, c.mutable.ArrayBuffer(v))
  case Some(col) => col += v
}

check scalafiddle with this implementations. https://scalafiddle.io/sf/WFBB24j/3. This is great tool (ps: you can always save CTRL+S your changes and share link to write question about your snippet).

Oh... and if You care about concurrency (m3 case) then write another question. Such topic deserve to be in separate thread :)

(im)mutable api VS (im)mutable Collections

You can have mutable collection and still use immutable api that will copy orginal seq. For example Array is mutable:

val example = Array(1,2,3) 
example(0) = 33 //edit in place
println(example.mkString(", ")) //33, 2, 3

But some functions on it (e.g. ++) will create new sequence... not change existing one:

val example2 = example ++ Array(42, 41) //++ is immutable operator
println(example.mkString(", ")) //33, 2, 3  //example stays unchanged
println(example2.mkString(", ")) //33, 2, 3, 42, 41 //but new sequence is created

There is method updateWith that is mutable and will exist only in mutable sequences. There is also updatedWith and it exists in both immutable AND mutable collections and if you are not careful enough you will use wrong one (yea ... 1 letter more).

This means you need to be careful which functions you are using, immutable or mutable one. Most of the time you can distinct them by result type. If something returns collection then it will be probably some kind of copy of original seq. It result is unit then it is mutable for sure.

3 Comments

Thanks for the explanation. I have immutable Map with mutable array (as given in my original question.) Your four examples don't cover that, right? Both Tim's & Ivan's solutions worked for me. I am sticking to Tim's because that allows me to continue using Array.
Neither Tim's or Ivan answers is immutable.Map[mutable.Seq] to be honest. Both are immutable.Map[immutable.Seq] in core because they use only immutable api.
@DilTeam see my updated answer. If it is helpful upvote please (I put too much work into it :)
2

You can use Scala 2.13's transform function to transform your map anyway you want.

val values = Map("key" -> Array(1f, 2f, 3f), "key2" -> Array(4f,5f,6f))

values.transform {
  case ("key", v) => v ++ Array(6f)
  case (_,v) => v
}

Result:

Map(key -> Array(1.0, 2.0, 3.0, 6.0), key2 -> Array(4.0, 5.0, 6.0))

Note that appending to arrays takes linear time so you might want to consider a more efficient data structure such as Vector or Queue or even a List (if you can afford to prepend rather than append).

Update:

However, if it is only one key you want to update, it is probably better to use updatedWith:

values.updatedWith("key")(_.map(_ ++ Array(6f)))

which will give the same result. The nice thing about the above code is that if the key does not exist, it will not change the map at all without throwing any error.

1 Comment

One more thing. In update you use updateWith in text and updatedWith in snipped ... those are different functions :). I think it could confuse.

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.