Reputation: 260
I have come across a most troubling bug in my code below. The buttons Map mutates even though I pass it as an immutable Map. The keys remain the same, and the map points to two immutable Ints, yet below you can see the map clearly has different values during the run. I am absolutely stumped and have no idea what is happening.
def makeTrace(trace : List[(String)], buttons : Map[String, (Int,Int)],
outputScreen : ScreenRegion, hashMap : Map[Array[Byte], String])
: (List[(String,String)], Map[Array[Byte], String]) = {
println(buttons.toString)
//clearing the device
val clear = buttons.getOrElse("clear", throw new Exception("Clear Not Found"))
//clear.circle(3000)
val thisButton = new ScreenLocation(clear._1, clear._2)
click(thisButton)
//updates the map and returns a list of (transition, state)
trace.foldLeft((Nil : List[(String,String)], hashMap))( (list, trace) => {
println(buttons.toString)
val transition : String = trace
val location = buttons.getOrElse(transition, throw new Exception("whatever"))
val button = new ScreenLocation(location._1, location._2)
button.circle(500)
button.label(transition, 500)
click(button)
//reading and hashing
pause(500)
val capturedImage : BufferedImage = outputScreen.capture()
val outputStream : ByteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(capturedImage, "png", outputStream)
val byte : Array[Byte] = outputStream.toByteArray();
//end hash
//if our state exists for the hash
if (hashMap.contains(byte)){ list match {
case (accumulator, map) => ((transition , hashMap.getOrElse(byte, throw new Exception("Our map broke if"))):: accumulator, map)
}
//if we need to update the map
}else list match {
case (accumulator, map) => {
//adding a new state based on the maps size
val newMap : Map[Array[Byte], String] = map + ((byte , "State" + map.size.toString))
val imageFile : File = new File("State" + map.size.toString + ".png");
ImageIO.write(capturedImage, "png", imageFile);
((transition, newMap.getOrElse(byte, throw new Exception("Our map broke else"))) :: accumulator, newMap)
}
}
})
}
Right before I call this function I initialize the map to an immutable map that points to immutable objects.
val buttons = makeImmutable(MutButtons)
val traceAndMap = TraceFinder.makeTrace(("clear" ::"five"::"five"::"minus"::"five"::"equals":: Nil), buttons, outputScreen, Map.empty)
Where makeImmutable is
def makeImmutable(buttons : Map[String, (Int,Int)]) : Map[String, (Int,Int)] = {
buttons.mapValues(button => button match {
case (x, y) =>
val newX = x
val newY = y
(newX,newY)
})
}
Here is the output, you can see the state change for clear, minus, and five
Map(equals -> (959,425), clear -> (842,313), minus -> (920,409), five -> (842,377))
Map(equals -> (959,425), clear -> (842,313), minus -> (920,409), five -> (842,377))
Map(equals -> (959,425), clear -> (959,345), minus -> (920,409), five -> (842,377))
Map(equals -> (959,425), clear -> (842,313), minus -> (920,409), five -> (842,377))
Map(equals -> (959,425), clear -> (842,313), minus -> (920,409), five -> (881,377))
Map(equals -> (959,425), clear -> (842,313), minus -> (920,409), five -> (842,377))
Map(equals -> (959,425), clear -> (842,313), minus -> (920,441), five -> (842,377))
Map(equals -> (959,425), clear -> (842,313), minus -> (920,409), five -> (881,377))
Map(equals -> (959,425), clear -> (842,313), minus -> (920,409), five -> (842,377))
Map(equals -> (959,425), clear -> (842,313), minus -> (920,409), five -> (842,377))
Upvotes: 2
Views: 284
Reputation: 167871
First, try sprinkling println(map.getClass)
around your code. Make sure it really is the map you think it is. It should have immutable
in its package name, e.g.:
scala> println(Map(1->1, 2->2, 3->3, 4->4).getClass)
class scala.collection.immutable.Map$Map4
Second, make sure you really are printing out the exact same map you think you are; using the reference identity hash code is a good way to do that: println(System.identityHashCode(map))
.
Chances are extraordinarily good that one of these things will not give you the expected result. Then you just have to figure out where the problem leaked in. Without a full runnable example it's hard to offer better advice without inordinate amounts of code-staring.
Upvotes: 2
Reputation: 3855
I suspect you have an import for scala.collection.Map in scope of the makeImmutable function, which allows you to pass MutButtons (presumably a mutable Map) to the makeImmutable function. The mapValues method on Map returns a view of the underlying map, not an immutable copy. For example:
import scala.collection.Map
import scala.collection.mutable.{Map => MutableMap}
def doubleValues(m: Map[Int, Int]) = m mapValues { _ * 2 }
val m = MutableMap(1 -> 1, 2 -> 2)
val n = doubleValues(m)
println("m = " + m)
println("n = " + n)
m += (3 -> 3)
println("n = " + n)
Running this program produces this output:
m = Map(2 -> 2, 1 -> 1)
n = Map(2 -> 4, 1 -> 2)
n = Map(2 -> 4, 1 -> 2, 3 -> 6)
To return a truly immutable map from makeImmutable, call .toMap after mapping over the values.
Upvotes: 0