在scala中的不可变Map中存储文件的内容

时间:2012-04-15 20:26:41

标签: scala map iteration type-conversion

我正在尝试使用不可变映射在scala中实现一个简单的wordcount(这是故意的),我试图完成它的方式如下:

  1. 创建一个空的不可变地图
  2. 创建一个读取文件的扫描程序。
  3. 虽然scanner.hasNext()为true:

    • 检查地图是否包含单词,如果它不包含单词,则将计数初始化为零
    • 使用key = word和value = count + 1
    • 创建一个新条目
    • 更新地图
  4. 在迭代结束时,地图将填充所有值。

  5. 我的代码如下:

    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需要输入。

    有人可以指出为什么我会收到此异常,我该怎么做才能解决这个问题?

    谢谢

2 个答案:

答案 0 :(得分:7)

val wordMap = wordMap + (token,currentCount)

此行正在重新定义已定义的变量。如果您要执行此操作,则需要使用wordMap定义var,然后使用

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包装器)并行执行此操作(因为字数统计很平行)。 / p>

编辑:详细说明:

好的,首先看一下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(identity)`,我们将每个不同的单词 type 映射到每个单词 token

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

你有它。

答案 1 :(得分: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)
}