在Scala中排序-以猫为例

哈Ha!我将我在《媒介:在Scala中的排序-猫店的例子》一文的俄文翻译带上法庭本文面向那些了解Scala语言语法并熟悉标准库基本工具的读者。


尽管Java和Scala都将JVM用作运行时平台,但由于对功能编程概念和丰富的标准库进行了重大修改,Scala作为一种更具表现力的语言而广为人知。在本文中,我将考虑这种表现力的示例,尝试想象一小部分代码库和相应需求会随着时间的推移而发展。


问题的初步陈述


想象一下,有一家猫店可供我们使用(因为是Scala生态系统中最受欢迎的动物)。由于获取有关可出售猫的信息的特殊性,在某些情况下,该信息不是来自数据库,因此必须在将HTTP响应发送给客户端或以其他任何方式处理之前手动进行分类。当然,主题区域的主要对象是Cat一开始只有三个原始类型的字段。目标是开发一种用于对cat集合进行排序的API,该API可以满足以下要求:


  • 可以为每个字段确定排序顺序。
  • 可能没有为任何字段定义排序顺序
  • 对于排序顺序不确定的字段,排序必须稳定
  • 排序时每个字段都有一个预定义的优先级(即按字段排序age总是比按字段排序优先级更高name

第一次迭代


case class Cat(age: Int,
               name: String,
               available: Boolean)

由于问题的陈述相对简单,并且没有有关项目其他部分的信息,因此最好使初始解决方案简单。幸运的是,它scala.Ordering已经定义了一种方便的方法Tuple3,您可以使用该方法Ordering为维3的元组创建实例,可以轻松地将此类的任何对象简化为Cat


, Tuple3 , . , — Tuple1, Tuple2, Tuple3, Ordering, . 9 (3 3 ), .


, API , " " . , 3 : (), ( ) "" ( ). (ADT):


sealed trait SortOrder

object SortOrder {

  case object Keep extends SortOrder

  case object Asc extends SortOrder

  case object Desc extends SortOrder
}

Ordering. SortOrder , , , :


import common.OrderingUtil
import iteration1.SortOrder.{Asc, Desc, Keep}

object syntax {

  implicit class OrderSyntax(val order: SortOrder) extends AnyVal {

    def apply[A](ordering: Ordering[A]): Ordering[A] =
      order match {
        case Keep => OrderingUtil.identity
        case Asc => ordering
        case Desc => ordering.reverse
      }
  }
}

OrderingUtil.identity — , A, . : Ordering.by(_ => 0).


, , Ordering[Cat]. CatOrdering:


import iteration1.syntax._

object CatOrdering {

  def of(idOrder: SortOrder,
         nameOrder: SortOrder,
         availableOrder: SortOrder): Ordering[Cat] =
    Ordering
      .Tuple3(idOrder(Ordering.Int), nameOrder(Ordering.String), availableOrder(Ordering.Boolean))
      .on[Cat](cat => (cat.age, cat.name, cat.available))
}

(Ordering[Cat]) CatOrdering.of:


CatOrdering.of(SortOrder.Asc, SortOrder.Keep, SortOrder.Desc)

, ScalaTest ScalaCheck property-based . , . .



Cat .


case class Cat(age: Int,
               name: String,
               available: Boolean,
               owner: Option[String])

, — ( null!), , , , . , 4 :


  1. , ()
  2. ,
  3. ,
  4. , ( )

(1 4) scala.Ordering.Option, 2 . SortOrder , :


sealed trait SortOrder

object SortOrder {

  case class Asc(emptyFirst: Boolean) extends SortOrder

  case class Desc(emptyFirst: Boolean) extends SortOrder

  case object Keep extends SortOrder

  object Asc {
    def emptyFirst: Asc = Asc(emptyFirst = true)

    def emptyLast: Asc = Asc(emptyFirst = false)
  }

  object Desc {
    def emptyFirst: Desc = Desc(emptyFirst = true)

    def emptyLast: Desc = Desc(emptyFirst = false)
  }
}

SortOrder. optional Ordering[Option[A]] A, , SortOrder. , , Ordering[Option[A]] apply, Ordering[Option[A]], . , A apply Option. <:<, StackOverflow ( Dotty Not ).


import common.OrderingUtil
import iteration2.sort_order.SortOrder._

object syntax {

  private object OptionOrdering {

    def apply[A](rootOrdering: Ordering[A],
                 emptyFirst: Boolean): Ordering[Option[A]] =
      if (emptyFirst)
        OptionOrdering.emptyFirst(rootOrdering)
      else
        OptionOrdering.emptyLast(rootOrdering)

    def emptyFirst[A](rootOrdering: Ordering[A]): Ordering[Option[A]] =
      (x: Option[A], y: Option[A]) => (x, y) match {
        case (None, None) => 0
        case (None, _) => -1
        case (_, None) => 1
        case (Some(a), Some(b)) => rootOrdering.compare(a, b)
      }

    def emptyLast[A](rootOrdering: Ordering[A]): Ordering[Option[A]] =
      (x: Option[A], y: Option[A]) => (x, y) match {
        case (None, None) => 0
        case (None, _) => 1
        case (_, None) => -1
        case (Some(a), Some(b)) => rootOrdering.compare(a, b)
      }
  }

  implicit class OrderSyntax(val order: SortOrder) extends AnyVal {

    def optional[A](ordering: Ordering[A]): Ordering[Option[A]] =
      order match {
        case Keep => OrderingUtil.identity
        case Asc(emptyFirst) => OptionOrdering(ordering, emptyFirst)
        case Desc(emptyFirst) => OptionOrdering(ordering.reverse, emptyFirst)
      }

    def apply[A](ordering: Ordering[A]): Ordering[A] =
      order match {
        case Keep => OrderingUtil.identity
        case Asc(_) => ordering
        case Desc(_) => ordering.reverse
      }
  }
}

import iteration2.sort_order.SortOrder
import iteration2.sort_order.syntax._

import scala.Ordering.{Boolean => BooleanO, Int => IntO, String => StringO}

object CatOrdering {

  def toOrdering(idOrder: SortOrder,
                 nameOrder: SortOrder,
                 availableOrder: SortOrder,
                 ownerOrder: SortOrder): Ordering[Cat] = {
    Ordering
      .Tuple4(idOrder(IntO), nameOrder(StringO), availableOrder(BooleanO), ownerOrder.optional(StringO))
      .on[Cat](cat => (cat.age, cat.name, cat.available, cat.owner))
  }
}

Ordering[Cat] . , .


CatOrdering.toOrdering(
  SortOrder.Asc.emptyFirst, 
  SortOrder.Asc.emptyFirst, 
  SortOrder.Asc.emptyFirst,
  SortOrder.Asc.emptyFirst
)

, Option, . .



. , :


  1. .
  2. - , SortOrder.Keep.
  3. Cat 9. Tuple10 .

- . , Cat 10 . , , . , , SortOrder . .


, , . Cat. ( №1) ( №2), ( №3). (SortOrder ):


import java.time.LocalDate

case class Cat(age: Int,
               name: String,
               available: Boolean,
               owner: Option[String],
               breed: String,
               furColor: String,
               eyeColor: String,
               registrationId: String,
               lastHealthCheck: Option[LocalDate],
               urgentSell: Boolean)

import java.time.LocalDate

import iteration3.sort_order.SortOrder
import iteration3.sort_order.syntax._

import scala.Ordering._

sealed trait CatField {
  def toOrdering(sortOrder: SortOrder): Ordering[Cat]
}

object CatField {

  case object Age extends CatField {
    override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
      sortOrder(Ordering.Int).on(_.age)
  }

  case object Name extends CatField {
    override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
      sortOrder(Ordering.String).on(_.name)
  }

  case object Available extends CatField {
    override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
      sortOrder(Ordering.Boolean).on(_.available)
  }

  case object Owner extends CatField {
    override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
      sortOrder.optional(Ordering.String).on(_.owner)
  }

  case object Breed extends CatField {
    override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
      sortOrder(Ordering.String).on(_.breed)
  }

  case object FurColor extends CatField {
    override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
      sortOrder(Ordering.String).on(_.furColor)
  }

  case object EyeColor extends CatField {
    override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
      sortOrder(Ordering.String).on(_.eyeColor)
  }

  case object RegistrationId extends CatField {
    override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
      sortOrder(Ordering.String).on(_.registrationId)
  }

  case object LastHealthCheck extends CatField {
    override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
      sortOrder.optional(Ordering.by[LocalDate, Long](_.toEpochDay)).on(_.lastHealthCheck)
  }

  case object UrgentSell extends CatField {
    override def toOrdering(sortOrder: SortOrder): Ordering[Cat] =
      sortOrder(Ordering.Boolean).on(_.urgentSell)
  }
}

import common.OrderingUtil
import iteration3.sort_order.SortOrder

object CatOrdering {

  def byFields(fields: Seq[(CatField, SortOrder)]): Ordering[Cat] =
    if (fields.isEmpty) OrderingUtil.identity[Cat]
    else {
      val (head, headOrder) = fields.head
      val (res, _) = fields.tail.foldLeft[(Ordering[Cat], Set[CatField])]((head.toOrdering(headOrder), Set())) {
        case (acc@(_, presentFields), (field, _)) if presentFields.contains(field) =>
          acc

        case ((ordering, presentFields), (field, order)) =>
          (ordering.orElse(field.toOrdering(order)), presentFields + field)
      }
      res
    }
}

orElse, , . thenComparing Comparator Java. Ordering, Comparator . , orElse Scala, orElseBy.


, , byFields , . , OrderingUtil.identity, . " ", foldLeft, .


SortOrder.Keep, , , . , , . HTTP , .


. , , , . ( ) , , . , , , , , .


.



尽管最终的实现在其他编程语言中可能看起来相似,但是在我看来,Scala在类型安全性和代码可理解性与可读性之间提供了很好的折衷方案。我希望该出版物有助于更好地了解如何在Scala中对集合进行排序,或者提出一些可以在与无关的代码中应用的想法Ordering


所有示例代码在此github存储库中都可用


All Articles