Пошаговое объяснение синтаксиса Scala, используемого в Википедии quicksort пример

Я пытаюсь понять Scala quicksort пример из Википедии. Как образец мог быть демонтирован шаг за шагом и что делает весь синтаксический включенный сахар, означают?

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

Так, как я могу собраться на данном этапе qsort, функция, которая не берет параметров и возвращает новый Function1 [Список [Интервал], Список [Интервал]], который реализует quicksort посредством использования сопоставления с образцом, управления списком и рекурсивных вызовов. Но я не могу вполне выяснить, куда центр прибывает из, и как точно синтаксис сопоставления с образцом работает в этом случае.

ОБНОВЛЕНИЕ:

Спасибо все для больших объяснений!

Я просто хотел совместно использовать другой пример quicksort реализации, которую я обнаружил в Scala Примером Martin Odersky. Хотя базирующийся вокруг массивов вместо списков и меньшего количества позерства с точки зрения различных функций 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 <)))
    }
}

20
задан Peter Mortensen 28 May 2013 в 19:10
поделиться

3 ответа

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 (меньше, 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))
15
ответ дан 29 November 2019 в 23:57
поделиться

Сводка в этом примере сопоставления с образцом является первым элементом списка:

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

Сопоставление с образцом основано на экстракторах и минусы не часть языка. Он использует инфиксный синтаксис. Вы также можете написать

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]){ ... }

Это класс case, поэтому экстрактор будет сгенерирован компилятором Scala. Как и в этом примере, класс A.

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

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

-> 1 2

На основе этого сопоставление машинных шаблонов поддерживается для списков, регулярных выражений и XML.

4
ответ дан 29 November 2019 в 23:57
поделиться
def qsort: List[Int] => List[Int] = {
  case Nil => Nil
  case pivot :: tail =>
    val (smaller, rest) = tail.partition(_ < pivot)
    qsort(smaller) ::: pivot :: qsort(rest)
}

давайте выделим несколько битов.

Именование

Операторы (такие как * или + ) являются допустимыми кандидатами для имен методов и классов в Scala (следовательно, у вас может быть класс с именем :: (или метод под названием :: , если на то пошло - и оба они действительно существуют). В Scala, похоже, есть перегрузка операторов, но на самом деле это не так: просто вы можете объявить метод с то же имя.

Сопоставление с шаблоном

target match {
  case p1 => 
  case p2 => 
}

Где p1 и p2 - шаблоны. Существует множество допустимых шаблонов (вы можете сопоставить строки, типы, конкретные экземпляры и т. д.). также может соответствовать так называемому экстрактору . Экстрактор в основном извлекает аргументы для вас в случае совпадения, поэтому:

target match {
  case MyExtractor(arg1, arg2, arg3) => //I can now use arg1, arg2 etc
}

В scala, если экстрактор (из которого case class является примером) существует под названием X , тогда шаблон X (a, b) эквивалентен a X b . case class :: имеет константу ructor берет 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)

Анонимные функции

Во-вторых, у нас есть функция «literal». tail - это экземпляр List , который имеет метод под названием partition , который принимает предикат и возвращает два списка; одна из записей, удовлетворяющих предикату, и одна из записей, которые не удовлетворяют.

val pred = (el: Int) => e < 2

Объявляет предикат функции, который принимает Int и возвращает истину , если значение int меньше 2. Существует сокращение для записи встроенных функций

tail.partition(_ < pivot) // _ is a placeholder for the parameter
tail.partition( (e: Int) => e < pivot )

Эти два выражения означают тоже самое.

Списки

Список Список - это запечатанный абстрактный класс только с двумя реализациями: Nil (пустой список) и :: (также называемый cons ), который представляет собой непустой список, состоящий из головы и хвоста (который также является списком). Теперь вы можете видеть, что совпадение с шаблоном соответствует тому, пуст список или нет. Список может быть создан путем добавления его в другие списки:

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] . Так что, очевидно, я мог бы сделать:

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!

Рекурсия

Когда вызов метода хвостовой рекурсивный , Scala оптимизирует это в шаблон итератора. В моем исходном ответе было msitake, потому что qsort выше не является хвостовым рекурсивным (хвостовой вызов является оператором cons)

20
ответ дан 29 November 2019 в 23:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: