【问题标题】:Storing the contents of a file in an immutable Map in scala将文件的内容存储在scala中的不可变映射中
【发布时间】:2012-04-27 06:56:09
【问题描述】:

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

  1. 创建一个空的不可变映射
  2. 创建一个读取文件的扫描器。
  3. 当scanner.hasNext() 为真时:

    • 检查Map是否包含该单词,如果不包含该单词,则将计数初始化为零
    • 使用 key=word 和 value=count+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)
}

想法是 wordMap 将在迭代结束时拥有所有 wordCounts... 每当我尝试运行这个 sn-p 时,都会出现以下异常

递归值wordMap需要类型。

谁能指出我为什么会遇到这个异常以及我可以做些什么来补救它?

谢谢

【问题讨论】:

    标签: scala map iteration type-conversion


    【解决方案1】:

    您有几个错误:您定义了两次wordMapval 是声明一个值)。此外,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)
    }
    

    【讨论】:

      【解决方案2】:
      val wordMap = wordMap + (token,currentCount)
      

      这一行正在重新定义一个已经定义的变量。如果你想这样做,你需要用var定义wordMap,然后使用

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

      你有它。

      【讨论】:

      • 你的另一个相当成熟的sn-p。我仍在尝试了解 scala 的基础知识,但很高兴了解 scala 的一些功能。感谢您的提示。
      • 我实际上正在查看您的 sn-p,它确实像一个魅力一样计算字数,但我无法理解您在那里所拥有的一些微妙之处。比如我们为什么需要flatMap? groupBy(identity).mapValues(.size) 做什么? toStream 是如何工作的? io.source.fromFile("textFile.txt").getLines 是否同时返回所有行的集合? .groupBy(.1).mapValues(.map(._2).sum) 是如何工作的?简而言之,一些句法烟火让我有点困惑。了解其中的细节会很棒。
      • @sc_ray,我会为你分解一下。给我几分钟。
      • @sc_ray,已添加详细说明。
      • 太棒了!插入 REPL 中的语句并阅读您的解释确实产生了巨大的差异。非常感谢!
      猜你喜欢
      • 1970-01-01
      • 2012-02-21
      • 2012-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-09
      • 2011-03-26
      • 1970-01-01
      相关资源
      最近更新 更多