2013-03-16 32 views
2

我在下面的代码中遇到了一个最令人不安的错误。即使我将它作为不可变的Map传递,按钮Map也会发生变异。按键保持不变,并且地图指向两个不可变的Ints,但在下方,您可以看到地图在运行过程中显然具有不同的值。我绝对难住,不知道发生了什么。为什么我的不可变对象突变Scala

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) 
    }   
    } 
}) 

}

之前,我调用这个函数初始化我的地图指向不可变对象的不可变映射。

val buttons = makeImmutable(MutButtons) 
    val traceAndMap = TraceFinder.makeTrace(("clear" ::"five"::"five"::"minus"::"five"::"equals":: Nil), buttons, outputScreen, Map.empty) 

哪里makeImmutable是

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) 
    }) 
} 

这里是输出,你可以看到清晰的状态变化,减,五

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)) 
+0

要么地图必须变异,要么地图上的* objects *必须是变异的。没有明智的选择。找出哪些 - 慢慢删除所有不必要的代码,使问题依然存在。 (好吧,我想这可能是一个不同的地图打印在某处..) – 2013-03-16 21:02:12

+0

我无法弄清格式,但我会在上面插入它。我在调用函数之前立即做的是用不可变的对象创建一个不可变的映射。我将发布上面的代码。 – dakillakan 2013-03-16 21:05:54

回答

2

首先,尝试在你的代码周围喷洒println(map.getClass)。确保它确实是你认为它的地图。它应该有immutable在其包名,例如:

scala> println(Map(1->1, 2->2, 3->3, 4->4).getClass) 
class scala.collection.immutable.Map$Map4 

第二,确保你真的是打印出你以为你是完全相同的地图;使用参考身份哈希码是一个很好的方法来做到这一点:println(System.identityHashCode(map))

机会非常好,其中一件事情不会给你预期的结果。那么你只需要弄清楚问题出在哪里。如果没有完整的可运行示例,如果没有过多的代码凝视,很难提供更好的建议。

+0

这是很棒的建议,谢谢!我想出了我的问题,还有一些关于scala的东西。看起来,当你使用mapValues函数时,scala将围绕用于映射的函数而不是值!我将我的地图转换成了一个列表,然后再次回到地图中,这似乎解决了它! 编辑:我不够酷,以upvote你,对不起 – dakillakan 2013-03-16 22:26:15

+0

很高兴你解决它。另一种严格映射值的方法是这样的:'m map {case(k,v)=>(k,f(v))}'。这立即构建一个新的地图。 – mpilquist 2013-03-16 22:41:19

+0

这比我的代码更加简洁,谢谢! – dakillakan 2013-03-16 22:42:43

0

我怀疑你有斯卡拉进口.collection.Map在makeImmutable函数的作用域中,它允许您将MutButtons(推测是一个可变的Map)传递给makeImmutable函数。 Map上的mapValues方法返回底层映射的视图,而不是不可变的副本。例如:

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) 

运行此程序产生以下输出:

m = Map(2 -> 2, 1 -> 1) 
n = Map(2 -> 4, 1 -> 2) 
n = Map(2 -> 4, 1 -> 2, 3 -> 6) 

要从makeImmutable返回一个真正不可变的映射,在所述映射的值后调用.toMap。

+0

谢谢你的帮助,但恐怕不是这样。我从不声明可变映射,并且我的makeImmutable函数可以证明我自己并不疯狂。我添加了.toMap只是为了确定,我仍然有突变的问题。 – dakillakan 2013-03-16 21:42:42

+0

啊,无赖。我曾经以这种方式被mapValues咬过,所以我认为这可能是原因。 – mpilquist 2013-03-16 21:51:59

相关问题