def merge(bigrams1: Map[String, mutable.SortedMap[String, Int]],
bigrams2: Map[String, mutable.SortedMap[String, Int]]): Map[String, mutable.SortedMap[String, Int]] = {
bigrams2 ++ bigrams1
.map(entry1 => entry1._1 -> (entry1._2 ++ bigrams2.getOrElse(entry1._1, mutable.SortedMap())
.map(entry2 => entry2._1 -> (entry2._2 + entry1._2.getOrElse(entry2._1, 0)))))
}
在编译时,出现以下错误:
Error:(64, 114) diverging implicit expansion for type scala.math.Ordering[T1]
starting with method Tuple9 in object Ordering
bigrams2 ++ bigrams1.map(entry1 => entry1._1 -> (entry1._2 ++ bigrams2.getOrElse(entry1._1, mutable.SortedMap()).map(entry2 => entry2._1 -> (entry2._2 + entry1._2.getOrElse(entry2._1, 0)))))
Error:(64, 114) not enough arguments for method apply: (implicit ord: scala.math.Ordering[A])scala.collection.mutable.SortedMap[A,B] in class SortedMapFactory.
Unspecified value parameter ord.
bigrams2 ++ bigrams1.map(entry1 => entry1._1 -> (entry1._2 ++ bigrams2.getOrElse(entry1._1, mutable.SortedMap()).map(entry2 => entry2._1 -> (entry2._2 + entry1._2.getOrElse(entry2._1, 0)))))
指定排序后的地图类型可以解决此问题:
def merge(bigrams1: Map[String, mutable.SortedMap[String, Int]],
bigrams2: Map[String, mutable.SortedMap[String, Int]]): Map[String, mutable.SortedMap[String, Int]] = {
bigrams2 ++ bigrams1
.map(entry1 => entry1._1 -> (entry1._2 ++ bigrams2.getOrElse(entry1._1, mutable.SortedMap[String, Int]())
.map(entry2 => entry2._1 -> (entry2._2 + entry1._2.getOrElse(entry2._1, 0)))))
}
为什么需要指定这些类型参数?为什么不能在隐式排序没有问题的情况下推断出它们?
完整代码:
import java.io.File
import scala.annotation.tailrec
import scala.collection.mutable
import scala.io.Source
import scala.util.matching.Regex
case class Bigrams(bigrams: Map[String, mutable.SortedMap[String, Int]]) {
def mergeIn(bigramsIn: Map[String, mutable.SortedMap[String, Int]]): Bigrams = {
Bigrams(Bigrams.merge(bigrams, bigramsIn))
}
def extractStatistics(path: String): Bigrams = {
val entry: File = new File(path)
if (entry.exists && entry.isDirectory) {
val bigramsFromDir: Map[String, mutable.SortedMap[String, Int]] = entry
.listFiles
.filter(file => file.isFile && file.getName.endsWith(".sgm"))
.map(Bigrams.getBigramsFrom)
.foldLeft(Map[String, mutable.SortedMap[String, Int]]())(Bigrams.merge)
val bigramsFromSubDirs: Bigrams = entry
.listFiles
.filter(entry => entry.isDirectory)
.map(entry => extractStatistics(entry.getAbsolutePath))
.foldLeft(Bigrams())(Bigrams.merge)
bigramsFromSubDirs.mergeIn(bigramsFromDir)
} else if (entry.exists && entry.isFile) {
Bigrams(Bigrams.getBigramsFrom(entry))
} else
throw new RuntimeException("Incorrect path")
}
def getFreqs(word: String): Option[mutable.SortedMap[String, Int]] = {
bigrams.get(word)
}
}
object Bigrams {
def fromPath(path: String): Bigrams = {
new Bigrams(Map[String, mutable.SortedMap[String, Int]]()).extractStatistics(path)
}
def apply(): Bigrams = {
new Bigrams(Map())
}
val BODY: Regex = "(?s).*<BODY>(.*)</BODY>(?s).*".r
// Return a list with the markup for each article
@tailrec
def readArticles(remainingLines: List[String], acc: List[String]): List[String] = {
if (remainingLines.size == 1) acc
else {
val nextLine = remainingLines.head
if (nextLine.startsWith("<REUTERS ")) readArticles(remainingLines.tail, nextLine +: acc)
else readArticles(remainingLines.tail, (acc.head + "\n" + nextLine) +: acc.tail)
}
}
def merge(bigrams1: Map[String, mutable.SortedMap[String, Int]],
bigrams2: Map[String, mutable.SortedMap[String, Int]]): Map[String, mutable.SortedMap[String, Int]] = {
bigrams2 ++ bigrams1
.map(entry1 => entry1._1 -> (entry1._2 ++ bigrams2.getOrElse(entry1._1, mutable.SortedMap[String, Int]())
.map(entry2 => entry2._1 -> (entry2._2 + entry1._2.getOrElse(entry2._1, 0)))))
}
def merge(bigrams1: Bigrams, bigrams2: Bigrams): Bigrams = {
new Bigrams(merge(bigrams1.bigrams, bigrams2.bigrams))
}
def getBigramsFrom(path: File): Map[String, mutable.SortedMap[String, Int]] = {
val file = Source.fromFile(path)
val fileLines: List[String] = file.getLines().toList
val articles: List[String] = Bigrams.readArticles(fileLines.tail, List())
val bodies: List[String] = articles.map(extractBody).filter(body => !body.isEmpty)
val sentenceTokens: List[List[String]] = bodies.flatMap(getSentenceTokens)
sentenceTokens.foldLeft(Map[String, mutable.SortedMap[String, Int]]())((acc, tokens) => addBigramsFrom(tokens, acc))
}
def getBigrams(tokens: List[String]): List[(String, String)] = {
tokens.indices.
map(i => {
if (i < tokens.size - 1) (tokens(i), tokens(i + 1))
else null
})
.filter(_ != null).toList
}
// Return the body of the markup of one article
def extractBody(article: String): String = {
try {
val body: String = article match {
case Bigrams.BODY(bodyGroup) => bodyGroup
}
body
}
catch {
case _: MatchError => ""
}
}
def getSentenceTokens(text: String): List[List[String]] = {
val separatedBySpace: List[String] = text
.replace('\n', ' ')
.replaceAll(" +", " ") // regex
.split(" ")
.map(token => if (token.endsWith(",")) token.init.toString else token)
.toList
val splitAt: List[Int] = separatedBySpace.indices
.filter(i => i > 0 && separatedBySpace(i - 1).endsWith(".") || i == 0)
.toList
groupBySentenceTokens(separatedBySpace, splitAt, List()).map(sentenceTokens => sentenceTokens.init :+ sentenceTokens.last.substring(0, sentenceTokens.last.length - 1))
}
@tailrec
def groupBySentenceTokens(tokens: List[String], splitAt: List[Int], sentences: List[List[String]]): List[List[String]] = {
if (splitAt.size <= 1) {
if (splitAt.size == 1) {
sentences :+ tokens.slice(splitAt.head, tokens.size)
} else {
sentences
}
}
else groupBySentenceTokens(tokens, splitAt.tail, sentences :+ tokens.slice(splitAt.head, splitAt.tail.head))
}
def addBigramsFrom(tokens: List[String], bigrams: Map[String, mutable.SortedMap[String, Int]]): Map[String, mutable.SortedMap[String, Int]] = {
var newBigrams = bigrams
val bigramsFromTokens: List[(String, String)] = Bigrams.getBigrams(tokens)
bigramsFromTokens.foreach(bigram => { // TODO: This code uses side effects to get the job done. Try to remove them.
val currentFreqs: mutable.SortedMap[String, Int] = newBigrams.get(bigram._1)
.map((map: mutable.SortedMap[String, Int]) => map)
.getOrElse(mutable.SortedMap())
val incrementedWordFreq = currentFreqs.get(bigram._2)
.map(freq => freq + 1)
.getOrElse(1)
val newFreqs = currentFreqs + (bigram._2 -> incrementedWordFreq)
newBigrams = newBigrams - bigram._1 + (bigram._1 -> newFreqs)
})
newBigrams
}
}
答案 0 :(得分:1)
问题是2.13中的Map#getOrElse方法(或2.12中的MapLike#getOrElse)具有签名
def getOrElse[V1 >: V](key: K, default: => V1): V1
https://github.com/scala/scala/blob/2.13.x/src/library/scala/collection/Map.scala#L132-L135
https://github.com/scala/scala/blob/2.12.x/src/library/scala/collection/MapLike.scala#L129-L132
即它期望default
的类型不一定与V
(或Map[K, +V]
)中的MapLike[K, +V, +This <: MapLike[K, V, This] with Map[K, V]]
相同,但可能是V
的超类型。在您的情况下,V
是mutable.SortedMap[String, Int]
,并且它的超类型太多(您可以自己查看继承层次结构)。 mutable.SortedMap()
可以是任何类型的mutable.SortedMap[A, B]
或它们的超级类型。
如果用固定的getOrElse
替换方法V
implicit class MapOps[K, V](m: Map[K, V]) {
def getOrElse1(key: K, default: => V): V = m.get(key) match {
case Some(v) => v
case None => default
}
}
或2.12
implicit class MapOps[K, V, +This <: MapLike[K, V, This] with Map[K, V]](m: MapLike[K, V, This]) {
def getOrElse1(key: K, default: => V): V = m.get(key) match {
case Some(v) => v
case None => default
}
}
然后在您的代码中
... bigrams2.getOrElse1(entry1._1, mutable.SortedMap())
所有类型都将被推断并编译。
因此,有时应该在编译器询问时明确指定类型。