2012-04-15 49 views
3

存儲文件的內容的不可變映射我試圖實現使用的不可變映射(這是故意的),我試圖完成其計算方法如下方式Scala中的一個簡單的單詞計數:斯卡拉

  1. 創建一個空的不可變的映射
  2. 創建一個掃描器,通過讀取文件。
  3. 雖然scanner.hasNext()是正確的:

    • 檢查地圖中包含單詞,如果它不包含單詞,初始化計數爲零
    • 創建新條目密鑰=字和值=計數+ 1
    • 更新地圖
  4. 在迭代結束時,地圖被填充有所有的值。

我的代碼如下:

val wordMap = Map.empty[String,Int] 
val input = new java.util.scanner(new java.io.File("textfile.txt")) 
while(input.hasNext()){ 
    val token = input.next() 
    val currentCount = wordMap.getOrElse(token,0) + 1 
    val wordMap = wordMap + (token,currentCount) 
} 

IDES的是,wordMap將所有的wordCounts在迭代... 結束每當我嘗試運行這個片段中,我得到以下異常

遞歸值wordMap需要的類型。

有人可以指出爲什麼我得到這個異常,我能做些什麼來糾正它?

感謝

回答

7
val wordMap = wordMap + (token,currentCount) 

這條線被重新定義一個已定義的變量。如果要做到這一點,你需要定義wordMapvar,然後只用

wordMap = wordMap + (token,currentCount) 

雖然這個怎麼樣,而不是?:

io.Source.fromFile("textfile.txt")   // read from the file 
    .getLines.flatMap{ line =>     // for each line 
    line.split("\\s+")      // split the line into tokens 
     .groupBy(identity).mapValues(_.size) // count each token in the line 
    }           // this produces an iterator of token counts 
    .toStream         // make a Stream so we can groupBy 
    .groupBy(_._1).mapValues(_.map(_._2).sum) // combine all the per-line counts 
    .toList 

注意,每行預聚合使用嘗試並減少所需的內存。一次計算整個文件可能太大。使用Scala的並行集合或Hadoop(使用Scrunch或Scoobi等很酷的Scala Hadoop包裝器之一),如果你的文件真的非常龐大,我會建議使用並行處理(因爲字數並不重要)。

EDIT:詳細說明:

好的,在flatMap的內部部分的第一外觀。我們採取一個字符串,除了拆分它空白:

val line = "a b c b" 
val tokens = line.split("\\s+") // Array(a, b, c, a, b) 

現在identity is a function that just returns its argument, so if we GROUPBY(身份)',我們映射每個不同的字,每個字令牌

val grouped = tokens.groupBy(identity) // Map(c -> Array(c), a -> Array(a), b -> Array(b, b)) 

最後,我們要計算每種類型的令牌數量:

val counts = grouped.mapValues(_.size) // Map(c -> 1, a -> 1, b -> 2) 

由於我們映射了對於文件中的所有行,我們以每行的標記計數結束。

那麼flatMap是做什麼的?那麼,它會在每一行上運行令牌計數功能,然後將所有結果合併爲一個大集合。

假設文件是​​:

a b c b 
b c d d d 
e f c 

然後我們得到:

val countsByLine = 
    io.Source.fromFile("textfile.txt")   // read from the file 
    .getLines.flatMap{ line =>     // for each line 
     line.split("\\s+")      // split the line into tokens 
     .groupBy(identity).mapValues(_.size) // count each token in the line 
    }           // this produces an iterator of token counts 
println(countsByLine.toList) // List((c,1), (a,1), (b,2), (c,1), (d,3), (b,1), (c,1), (e,1), (f,1)) 

所以現在我們需要每一行的計數合併成一個大集計數。 countsByLine變量是Iterator,所以它沒有groupBy方法。相反,我們可以將其轉換爲Stream,這基本上是一個懶惰列表。我們希望懶惰,因爲我們不希望在開始之前將整個文件讀入內存。然後,groupBy將所有同一詞類型的計數分組在一起。

val groupedCounts = countsByLine.toStream.groupBy(_._1) 
println(groupedCounts.mapValues(_.toList)) // Map(e -> List((e,1)), f -> List((f,1)), a -> List((a,1)), b -> List((b,2), (b,1)), c -> List((c,1), (c,1), (c,1)), d -> List((d,3))) 

最後,我們可以通過從每個元組(計數)抓住了第二個項目,總結歸納起來,從每行的每個字類型的計數:

val totalCounts = groupedCounts.mapValues(_.map(_._2).sum) 
println(totalCounts.toList) 
List((e,1), (f,1), (a,1), (b,3), (c,3), (d,3)) 

有你有它。

+0

從你的另一個相當成熟的片段。我仍然試圖通過scala的基礎知識,但瞭解scala的一些功能是很好的。感謝您的提示。 – 2012-04-16 00:20:53

+0

我其實正在瀏覽你的代碼片段,它的名字算得上是一種魅力,但我無法理解你在那裏的一些微妙之處。像我們爲什麼需要flatMap? groupBy(identity).mapValues(_。size)是做什麼的? toStream如何工作? io.source.fromFile(「textFile.txt」)。getLines同時返回所有行的集合嗎? .groupBy(_。1).mapValues(_。map(_._ 2).sum)如何工作?總之一些語法上的煙火讓我有些迷惑。理解這些基本內容是很棒的。 – 2012-04-16 01:25:51

+0

@sc_ray,我會爲你分解一下。給我幾分鐘。 – dhg 2012-04-16 01:27:19

3

你有幾個錯誤:你定義wordMap兩次(val是聲明的值)。此外,Map是不變的,所以你要麼必須聲明它作爲一個var或使用可變地圖(我認爲前)。

嘗試這種情況:

var wordMap = Map.empty[String,Int] withDefaultValue 0 
val input = new java.util.Scanner(new java.io.File("textfile.txt")) 
while(input.hasNext()){ 
    val token = input.next() 
    wordMap += token -> (wordMap(token) + 1) 
}