如何在Scala中对Python或运算符进行近似设置比较?

时间:2009-11-22 23:53:31

标签: python scala anonymous-function

在听到最新的Stack Overflow播客之后,Peter Norvig的紧凑型Python拼写检查器引起了我的兴趣,所以我决定在Scala中实现它,如果我能用功能Scala惯用语表达它,还要看看有多少行代码这需要。

这是整个问题。 (我们还不比较代码行。)

(两个注释:如果你愿意,可以在Scala解释器中运行它。如果你需要big.txt或整个项目的副本,那就是on GitHub。)

import scala.io.Source

val alphabet = "abcdefghijklmnopqrstuvwxyz"

def train(text:String) = {
  "[a-z]+".r.findAllIn(text).foldLeft(Map[String, Int]() withDefaultValue 1)
    {(a, b) => a(b) = a(b) + 1}
}

val NWORDS = train(Source.fromFile("big.txt").getLines.mkString.toLowerCase)

def known(words:Set[String]) =
  {Set.empty ++ (for(w <- words if NWORDS contains w) yield w)}

def edits1(word:String) = {
  Set.empty ++
  (for (i <- 0 until word.length) // Deletes
    yield (word take i) + (word drop (i + 1))) ++ 
  (for (i <- 0 until word.length - 1) // Transposes
    yield (word take i) + word(i + 1) + word(i) + (word drop (i + 2))) ++ 
  (for (i <- 0 until word.length; j <- alphabet) // Replaces
    yield (word take i) + j + (word drop (i+1))) ++ 
  (for (i <- 0 until word.length; j <- alphabet) // Inserts
    yield (word take i) + j + (word drop i))
}

def known_edits2(word:String) = {Set.empty ++ (for (e1 <- edits1(word);
  e2 <- edits1(e1) if NWORDS contains e2) yield e2)}

def correct(word:String) = {
  val options = Seq(() => known(Set(word)), () => known(edits1(word)),
    () => known_edits2(word), () => Set(word))
  val candidates = options.foldLeft(Set[String]())
    {(a, b) => if (a.isEmpty) b() else a}
  candidates.foldLeft("") {(a, b) => if (NWORDS(a) > NWORDS(b)) a else b}
}

具体来说,我想知道我能用correct函数做些什么更干净。在原始Python中,实现更清晰:

def correct(word):
    candidates = known([word]) or known(edits1(word)) or
      known_edits2(word) or [word]
    return max(candidates, key=NWORDS.get)

显然在Python中,空集将评估为布尔False,因此只会评估返回非空集的第一个候选项,从而节省对edits1和{known_edits2和{ {1}}。

我想出的唯一解决方案是你在这里看到的版本,其中调用匿名函数的Seq,直到一个返回非空Set,最后一个保证做。

经验丰富的Scala负责人,是否有更复杂的语法或更好的方法来实现这一目标?提前谢谢!

5 个答案:

答案 0 :(得分:6)

我不确定你为什么试图对known使用延迟评估而不是简单地使用流来表示oxbow_lakes。做他所做的更好的方式:

def correct(word: String) = {
  import Stream._

  val str = cons(known(Set(word)), 
            cons(known(edits1(word)),
            cons(known_edits2(word),
            cons(Set(word), empty))))

  str find { !_.isEmpty } match {
    case Some(candidates) =>
      candidates.foldLeft(Set[String]()) { (res, n) =>
        if (NWORDS(res) > NWORDS(n)) res else n
      }

    case None => Set()
  }
}

利用了Stream.cons已经懒惰的事实,因此我们不需要将所有内容包装在一个thunk中。

如果你真的心情愉快,我们可以为所有这些词汇添加一些语法糖:

implicit def streamSyntax[A](tail: =>Stream[A]) = new {
  def #::(hd: A) = Stream.cons(hd, tail)
}

现在,我们之前丑陋的str定义如下:

def correct(word: String) = {
  val str = known(Set(word)) #:: known(edits1(word)) #::
            known_edits2(word) #:: Set(word) #:: Stream.empty

  ...
}

答案 1 :(得分:4)

这会有用吗? _语法是部分应用的函数,并且使用(懒惰)Stream,我确保reduceLeft中的评估(我认为比foldLeft更合适这里只是按要求发生!

def correct(word:String) = {
  Stream(known(Set(word)) _, 
         known(edits1(word)) _, 
         known_edits2(word) _, 
         Set(word) _
         ).find( !_().isEmpty ) match {
    case Some(candidates) =>
      candidates.reduceLeft {(res, n) => if (NWORDS(res) > NWORDS(n)) res else n}
    case _ => "" //or some other value

}

我可能在这里犯了一些语法错误,但我认为Stream方法是有效的

答案 2 :(得分:3)

Scala 2.7-ready(包括隐式性能解决方案):

class Or[A](one: Set[A]) {
  def or(other: => Set[A]): Set[A] = if (one.isEmpty) other else one 
}

implicit def toOr[A](one: Set[A]) = new Or(one)

def correct(word: String) = {
  candidates = known(Set(word)) or known(edits1(word)) or known_edits2(word) or Set(word)
  candidates.foldLeft("") {(a, b) => if (NWORDS(a) > NWORDS(b)) a else b}
}

Scala 2.8-善良:

implicit def toOr[A](one: Set[A]) = new AnyRef { 
  def or(other: => Set[A]): Set[A] = if (one.isEmpty) other else one 
}

def correct(word: String) = {
  candidates = known(Set(word)) or known(edits1(word)) or known_edits2(word) or Set(word)
  candidates.max(Ordering.fromLessThan[String](NWORDS(_) < NWORDS(_)))
}

那就是说,我几乎赞成其他所有人。我没有考虑Stream

修改

似乎Ordering.fromLessThan可以导致两次必要的比较。以下是该行的替代版本:

  candidates.max(new Ordering[String] { def compare(x: String, y: String) = NWORDS(x) - NWORDS(y) })

答案 3 :(得分:2)

迭代器也是懒惰的(虽然不是很有用,因为你只能迭代它们一次。)所以,你可以这样做:

  def correct(word: String) = {
    val sets = List[String => Set[String]](
      x => known(Set(x)), x => known(edits1(x)), known_edits2
    ).elements.map(_(word))

    sets find { !_.isEmpty } match {
      case Some(candidates: Set[String]) => candidates.reduceLeft { (res, n) => if (NWORDS(res) > NWORDS(n)) res else n }
      case None => word
    }
  }

作为奖励,Iterator的find()方法不会强制评估下一个元素。

答案 4 :(得分:0)

我试图实现short Scala implementation of the spelling corrector。这是没有进口的15条线。 Python的最短替换或者是名称参数的简单调用:

def or[T](candidates : Seq[T], other : => Seq[T]) = if(candidates.isEmpty) other else candidates
def candidates(word: String) = or(known(List(word)), or(known(edits1(word)), known(edits2(word))))

在现实世界中,我会使用Daniel提出的隐式转换。