未指定类型参数时Scala隐式排序错误

时间:2019-07-22 11:18:51

标签: scala

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

1 个答案:

答案 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的超类型。在您的情况下,Vmutable.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())

所有类型都将被推断并编译。

因此,有时应该在编译器询问时明确指定类型。

相关问题