维基百科快速排序示例中使用的Scala语法的逐步说明

时间:2010-02-26 13:28:14

标签: scala

我想了解维基百科的Scala quicksort example。如何逐步拆解样品,所涉及的所有语法糖是什么意思?

def qsort: List[Int] => List[Int] = {
  case Nil => Nil
  case pivot :: tail =>
    val (smaller, rest) = tail.partition(_ < pivot)
    qsort(smaller) ::: pivot :: qsort(rest)
}

尽可能在此阶段收集 qsort 是一个不带参数的函数,并返回一个实现quicksort的新Function1 [List [Int],List [Int]]通过使用模式匹配,列表操作和递归调用。但我无法弄清楚枢轴的来源,以及模式匹配语法在这种情况下的确切运作方式。

更新

感谢大家的精彩解释!

我只想分享另一个我在Scala by Example Martin Odersky {{3}}中发现的快速实施实例。虽然基于数组而不是列表而不是基于varios Scala特性的炫耀,我个人觉得它比它的维基百科对应的更少复杂,并且更加清晰和底层算法的点表达式:

def sort(xs: Array[Int]): Array[Int] = {
    if (xs.length <= 1) xs
    else {
        val pivot = xs(xs.length / 2)
        Array.concat(
            sort(xs filter (pivot >)),
            xs filter (pivot ==),
            sort(xs filter (pivot <)))
    }
}

3 个答案:

答案 0 :(得分:20)

def qsort: List[Int] => List[Int] = {
  case Nil => Nil
  case pivot :: tail =>
    val (smaller, rest) = tail.partition(_ < pivot)
    qsort(smaller) ::: pivot :: qsort(rest)
}

让我们分开几点。

命名

运算符(例如*+)是Scala中方法和类名的有效候选者(因此您可以拥有一个名为::的类(或称为{{1的方法)对于那个问题 - 实际上两者都存在。)Scala似乎有运算符重载但实际上它没有:它只是你可以声明一个具有相同名称的方法。

模式匹配

::

其中target match { case p1 => case p2 => } p1是模式。有许多有效的模式(您可以匹配字符串,类型,特定实例等)。您还可以匹配称为提取器的内容。在匹配的情况下,提取器基本上为您提取参数,所以:

p2

在scala中,如果存在一个名为target match { case MyExtractor(arg1, arg2, arg3) => //I can now use arg1, arg2 etc } 的提取器(以案例类为例),则模式X等同于X(a, b)。案例类a X b有一个带有2个参数的构造函数,并将它们组合在一起得到:

::

是等同的。此匹配表示“如果我的列表是case x :: xs => case ::(x, xs) => 的实例,则将值::提取到head,将x提取到tail”。模式匹配也用于变量声明。例如,如果xs是模式,则该有效:

p

这就是我们可以声明变量的原因:

val p = expression

匿名函数

其次我们有一个“文字”功能。 val x :: xs = List(1, 2, 3) val (a, b) = xs.partition(_ % 2 == 0 ) //returns a Tuple2 which is a pattern (t1, t2) tail的一个实例,它有一个名为List的方法,它接受一个谓词并返回两个列表;其中一个条目满足谓词,其中一个条目没有。

partition

声明一个函数谓词,它接受val pred = (el: Int) => e < 2 并返回true iff int值小于2.有一个写内联函数的简写

Int

这两个表达方式意思相同。

解释

tail.partition(_ < pivot) // _ is a placeholder for the parameter tail.partition( (e: Int) => e < pivot ) 是一个密封的抽象类,只有两个实现,List(空列表)和Nil(也称为 cons < / em>),这是一个由头和尾组成的非空列表(也是一个列表)。您现在可以看到模式匹配与列表是否为空匹配。可以通过创建到其他列表来创建::

List

以上几行只是方法调用(val l = 1 :: 2 :: Nil val m = List(1, 2, 3) ::: List(4, 5, 6) 是scala中的有效方法名称)。这些和普通方法调用之间的唯一区别是,如果方法以冒号::结尾并且使用空格调用,则目标和参数的顺序相反:

:

功能类型

a :: b === b.::(a)

上一行将引用val f: A => B 键入为f并返回A的函数,这样我就可以这样做:

B

因此,您可以看到val a = new A val b: B = f(a) 声明了一个名为def qsort: List[Int] => List[Int]的方法,该方法返回函数,其中包含qsort并返回List[Int]。所以我显然可以做到:

List[Int]

递归

当方法调用 tail recursive 时,Scala会将其优化为迭代器模式。我的原始答案中有一个msitake,因为上面的val l = List(2, 4, 1) val m = qsort.apply(l) //apply is to Function what run is to Runnable val n = qsort(l) //syntactic sugar - you don't have to define apply explicitly! 不是尾递归的(尾调用是cons运算符)

答案 1 :(得分:15)

def qsort: List[Int] => List[Int] = { 
  case Nil => Nil 
  case pivot :: tail => 
    val (smaller, rest) = tail.partition(_ < pivot) 
    qsort(smaller) ::: pivot :: qsort(rest) 
}

让我们改写一下。首先,将函数文字替换为Function1

的实例
def qsort: List[Int] => List[Int] = new Function1[List[Int], List[Int]] {
  def apply(input: List[Int]): List[Int] = input match {
    case Nil => Nil 
    case pivot :: tail => 
      val (smaller, rest) = tail.partition(_ < pivot) 
      qsort(smaller) ::: pivot :: qsort(rest) 
  }
}

接下来,我将使用等效的if / else语句替换模式匹配。请注意,它们是等效的,不一样。模式匹配的字节码更加优化。例如,第二个if和下面抛出的异常不存在,因为编译知道第二个匹配将始终发生,如果第一个匹配失败。

def qsort: List[Int] => List[Int] = new Function1[List[Int], List[Int]] {
  def apply(input: List[Int]): List[Int] = if (input == Nil) {
    Nil
  } else if (input.isInstanceOf[::[_]] && 
             scala.collection.immutable.::.unapply(input.asInstanceOf[::[Int]]) != None) {
    val unapplyResult = scala.collection.immutable.::.unapply(input.asInstanceOf[::[Int]]).get
    val pivot = unapplyResult._1
    val tail = unapplyResult._2
    val (smaller, rest) = tail.partition(_ < pivot) 
    qsort(smaller) ::: pivot :: qsort(rest) 
  } else {
    throw new scala.MatchError(input)
  }
}

实际上,val (smaller, rest)也是模式匹配,所以让我们分解它:

def qsort: List[Int] => List[Int] = new Function1[List[Int], List[Int]] {
  def apply(input: List[Int]): List[Int] = if (input == Nil) {
    Nil
  } else if (input.isInstanceOf[::[_]] && 
             scala.collection.immutable.::.unapply(input.asInstanceOf[::[Int]]) != None) {
    val unapplyResult0 = scala.collection.immutable.::.unapply(input.asInstanceOf[::[Int]]).get
    val pivot = unapplyResult0._1
    val tail = unapplyResult0._2
    val tmp0 = tail.partition(_ < pivot)
    if (Tuple2.unapply(tmp0) == None)
      throw new scala.MatchError(tmp0)
    val unapplyResult1 = Tuple2.unapply(tmp0).get
    val smaller = unapplyResult1._1
    val rest = unapplyResult1._2
    qsort(smaller) ::: pivot :: qsort(rest) 
  } else {
    throw new scala.MatchError(input)
  }
}

显然,这是非常不受欢迎的。更糟糕的是,有一些函数调用不止一次,这在原始函数中不会发生。不幸的是,要解决这个问题,需要对代码进行一些结构性更改。

这里还有一些语法糖。有一个匿名函数被传递给分区,并且有用于调用函数的语法糖。重写那些产生以下结果:

def qsort: List[Int] => List[Int] = new Function1[List[Int], List[Int]] {
  def apply(input: List[Int]): List[Int] = if (input == Nil) {
    Nil
  } else if (input.isInstanceOf[::[_]] &&
             scala.collection.immutable.::.unapply(input.asInstanceOf[::[Int]]) != None) {
    val unapplyResult0 = scala.collection.immutable.::.unapply(input.asInstanceOf[::[Int]]).get
    val pivot = unapplyResult0._1
    val tail = unapplyResult0._2
    val func0 = new Function1[Int, Boolean] {
      def apply(input: Int): Boolean = input < pivot
    }
    val tmp0 = tail.partition(func0)
    if (Tuple2.unapply(tmp0) == None)
      throw new scala.MatchError(tmp0)
    val unapplyResult1 = Tuple2.unapply(tmp0).get
    val smaller = unapplyResult1._1
    val rest = unapplyResult1._2
    qsort.apply(smaller) ::: pivot :: qsort.apply(rest) 
  } else {
    throw new scala.MatchError(input)
  }
}

有一次,关于每种语法糖的广泛解释及其如何运作正由其他人完成。 :-)我希望这能补充他们的答案。最后一点,以下两行是等效的:

    qsort(smaller) ::: pivot :: qsort(rest) 
    qsort(rest).::(pivot).:::(qsort(smaller))

答案 2 :(得分:4)

此模式匹配示例中的数据透视表是列表的第一个元素:

scala> List(1,2,3) match {
     |     case x :: xs => println(x)
     |     case _ => println("empty")
     | }
1

模式匹配基于extractors,缺点不是语言的一部分。它使用中缀语法。你也可以写

scala> List(1,2,3) match {
     |     case ::(x,xs) => println(x)
     |     case _ => println("empty")
     | }
1

也是。所以有type ::看起来像cons运算符。此类型定义了它的提取方式:

final case class ::[B](private var hd: B, private[scala] var tl: List[B]){ ... }

这是一个案例类,因此提取器将由Scala编译器生成。就像在这个例子中的类A。

case class A(x : Int, y : Int)

A(1,2) match { case x A y => printf("%s %s", x, y)}

-> 1 2

基于这种机制模式,Lists,Regexp和XML支持匹配。