2013-03-16 64 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