Scala 按未知数量的字段排序
Scala sort by unknown number of fields
我有 N 个字段的简单 class。
case class Book(a: UUID... z: String)
和功能:
def sort(books:Seq[Book], fields:Seq[SortingFields]) = {...}
其中
case class SortingField(field: String, asc: Boolean)
where field - Book 的一个字段 class,asc - 排序方向。
所以,事先我不知道哪些字段(从 0 到 N)和排序顺序进入我的函数来对 books
集合进行排序。它可能只是一个 ID 字段,也可能是一个 class 中特定顺序的所有现有字段。
如何实现?
有可能。但据我所知,您将不得不使用反射。
此外,您必须稍微更改 SortingField
class,因为 scala 编译器无法找出正确的 Ordering
类型 class每个字段。
这是一个简化的例子。
import scala.reflect.ClassTag
/** You should be able to figure out the correct field ordering here. Use `reverse` to decide whether you want to sort ascending or descending. */
case class SortingField[T](field: String, ord: Ordering[T]) { type FieldType = T }
case class Book(a: Int, b: Long, c: String, z: String)
def sort[T](unsorted: Seq[T], fields: Seq[SortingField[_]])(implicit tag: ClassTag[T]): Seq[T] = {
val bookClazz = tag.runtimeClass
fields.foldLeft(unsorted) { case (sorted, currentField) =>
// keep in mind that scala generates a getter method for field 'a'
val field = bookClazz.getMethod(currentField.field)
sorted.sortBy[currentField.FieldType](
field.invoke(_).asInstanceOf[currentField.FieldType]
)(currentField.ord)
}
}
但是,要按多个字段排序,您必须对序列进行多次排序,或者更好的是正确组合各种顺序。
所以这变得有点 'sophisticated' 没有任何关于正确性和完整性的保证,但通过一些测试它不会失败:
def sort[T](unsorted: Seq[T], fields: Seq[SortingField[_]])(implicit tag: ClassTag[T]): Seq[T] = {
@inline def invokeGetter[A](field: Method, obj: T): A = field.invoke(obj).asInstanceOf[A]
@inline def orderingByField[A](field: Method)(implicit ord: Ordering[A]): Ordering[T] = {
Ordering.by[T, A](invokeGetter[A](field, _))
}
val bookClazz = tag.runtimeClass
if (fields.nonEmpty) {
val field = bookClazz.getMethod(fields.head.field)
implicit val composedOrdering: Ordering[T] = fields.tail.foldLeft {
orderingByField(field)(fields.head.ord)
} { case (ordering, currentField) =>
val field = bookClazz.getMethod(currentField.field)
val subOrdering: Ordering[T] = orderingByField(field)(currentField.ord)
new Ordering[T] {
def compare(x: T, y: T): Int = {
val upperLevelOrderingResult = ordering.compare(x, y)
if (upperLevelOrderingResult == 0) {
subOrdering.compare(x, y)
} else {
upperLevelOrderingResult
}
}
}
}
unsorted.sorted(composedOrdering)
} else {
unsorted
}
}
sort(
Seq[Book](
Book(1, 5L, "foo1", "bar1"),
Book(10, 50L, "foo10", "bar15"),
Book(2, 3L, "foo3", "bar3"),
Book(100, 52L, "foo4", "bar6"),
Book(100, 51L, "foo4", "bar6"),
Book(100, 51L, "foo3", "bar6"),
Book(11, 15L, "foo5", "bar7"),
Book(22, 45L, "foo6", "bar8")
),
Seq(
SortingField("a", implicitly[Ordering[Int]].reverse),
SortingField("b", implicitly[Ordering[Long]]),
SortingField("c", implicitly[Ordering[String]])
)
)
>> res0: Seq[Book] = List(Book(100,51,foo3,bar6), Book(100,51,foo4,bar6), Book(100,52,foo4,bar6), Book(22,45,foo6,bar8), Book(11,15,foo5,bar7), Book(10,50,foo10,bar15), Book(2,3,foo3,bar3), Book(1,5,foo1,bar1))
我会为此使用现有的 Ordering
特征,并使用从 Book
映射到字段的函数,即 Ordering.by[Book, String](_.author)
。然后你可以简单地用books.sorted(myOrdering)
排序。如果我在 Book
的伴生对象上定义辅助方法,获得这些顺序非常简单:
object Book {
def by[A: Ordering](fun: Book => A): Ordering[Book] = Ordering.by(fun)
}
case class Book(author: String, title: String, year: Int)
val xs = Seq(Book("Deleuze" /* and Guattari */, "A Thousand Plateaus", 1980),
Book("Deleuze", "Difference and Repetition", 1968),
Book("Derrida", "Of Grammatology", 1967))
xs.sorted(Book.by(_.title)) // A Thousand, Difference, Of Grammatology
xs.sorted(Book.by(_.year )) // Of Grammatology, Difference, A Thousand
然后要将多个字段的排序链接起来,您可以创建自定义排序,该排序通过字段进行,直到一个比较为非零。例如,我可以像这样将扩展方法 andThen
添加到 Ordering
:
implicit class OrderingAndThen[A](private val self: Ordering[A]) extends AnyVal {
def andThen(that: Ordering[A]): Ordering[A] = new Ordering[A] {
def compare(x: A, y: A): Int = {
val a = self.compare(x, y)
if (a != 0) a else that.compare(x, y)
}
}
}
所以我可以写:
val ayt = Book.by(_.author) andThen Book.by(_.year) andThen Book.by(_.title)
xs.sorted(ayt) // Difference, A Thousand, Of Grammatology
案例 类 是产品,因此您可以使用 instance.productIterator
遍历所有字段值。这为您提供了声明顺序的字段。您也可以通过它们的索引直接访问它们。据我所知,没有办法获取字段名称。这必须使用反射或宏来完成。 (也许像 Shapeless 这样的图书馆已经可以做到这一点)。
另一种方法是不定义按名称排序的字段,而是按函数定义的字段:
case class SortingField[T](field: Book => T, asc: Boolean)(implicit ordering: Ordering[T])
new SortingField(_.fieldName, true)
然后声明排序为:
def sort(books: Seq[Book], fields: Seq[SortingField[_]]) = {...}
并使用下面的比较方法实现合并排序:
def compare[T](b1: Book, b2: Book, field: SortingField[T]) =
field.ordering.compare(field.field(b1), field.field(b2))
有了 的精彩回答,我得出以下结论:
def by[A: Ordering](e: Book => A): Ordering[Book] = Ordering.by(e)
和
implicit class OrderingAndThen[A](private val self: Ordering[A]) extends AnyVal {
def andThen(that: Ordering[A]): Ordering[A] = new Ordering[A] {
def compare(x: A, y: A): Int = {
val a = self.compare(x, y)
if (a != 0) a else that.compare(x, y)
}
}
}
接下来我将 class 字段的名称映射到实际排序
def toOrdering(name: String, r: Boolean): Ordering[Book] = {
(name match {
case "id" => Book.by(_.id)
case "name" => Book.by(_.name)
}) |> (o => if (r) o.reverse else o)
}
使用正向管道运算符:
implicit class PipedObject[A](value: A) {
def |>[B](f: A => B): B = f(value)
}
最后我将所有排序与 reduce 函数结合起来:
val fields = Seq(SortedField("name", true), SortedField("id", false))
val order = fields.map(f => toOrdering(f.field, f.reverse)).reduce(combines(_,_))
coll.sorted(order)
哪里
val combine = (x: Ordering[Book], y: Ordering[Book]) => x andThen y
另一种方法是使用@tailrec:
def orderingSeq[T](os: Seq[Ordering[T]]): Ordering[T] = new Ordering[T] {
def compare(x: T, y: T): Int = {
@tailrec def compare0(rest: Seq[Ordering[T]], result: Int): Int = result match {
case 0 if rest.isEmpty => 0
case 0 => compare0(rest.tail, rest.head.compare(x, y))
case a => a
}
compare0(os, 0)
}
}
我有 N 个字段的简单 class。
case class Book(a: UUID... z: String)
和功能:
def sort(books:Seq[Book], fields:Seq[SortingFields]) = {...}
其中
case class SortingField(field: String, asc: Boolean)
where field - Book 的一个字段 class,asc - 排序方向。
所以,事先我不知道哪些字段(从 0 到 N)和排序顺序进入我的函数来对 books
集合进行排序。它可能只是一个 ID 字段,也可能是一个 class 中特定顺序的所有现有字段。
如何实现?
有可能。但据我所知,您将不得不使用反射。
此外,您必须稍微更改 SortingField
class,因为 scala 编译器无法找出正确的 Ordering
类型 class每个字段。
这是一个简化的例子。
import scala.reflect.ClassTag
/** You should be able to figure out the correct field ordering here. Use `reverse` to decide whether you want to sort ascending or descending. */
case class SortingField[T](field: String, ord: Ordering[T]) { type FieldType = T }
case class Book(a: Int, b: Long, c: String, z: String)
def sort[T](unsorted: Seq[T], fields: Seq[SortingField[_]])(implicit tag: ClassTag[T]): Seq[T] = {
val bookClazz = tag.runtimeClass
fields.foldLeft(unsorted) { case (sorted, currentField) =>
// keep in mind that scala generates a getter method for field 'a'
val field = bookClazz.getMethod(currentField.field)
sorted.sortBy[currentField.FieldType](
field.invoke(_).asInstanceOf[currentField.FieldType]
)(currentField.ord)
}
}
但是,要按多个字段排序,您必须对序列进行多次排序,或者更好的是正确组合各种顺序。
所以这变得有点 'sophisticated' 没有任何关于正确性和完整性的保证,但通过一些测试它不会失败:
def sort[T](unsorted: Seq[T], fields: Seq[SortingField[_]])(implicit tag: ClassTag[T]): Seq[T] = {
@inline def invokeGetter[A](field: Method, obj: T): A = field.invoke(obj).asInstanceOf[A]
@inline def orderingByField[A](field: Method)(implicit ord: Ordering[A]): Ordering[T] = {
Ordering.by[T, A](invokeGetter[A](field, _))
}
val bookClazz = tag.runtimeClass
if (fields.nonEmpty) {
val field = bookClazz.getMethod(fields.head.field)
implicit val composedOrdering: Ordering[T] = fields.tail.foldLeft {
orderingByField(field)(fields.head.ord)
} { case (ordering, currentField) =>
val field = bookClazz.getMethod(currentField.field)
val subOrdering: Ordering[T] = orderingByField(field)(currentField.ord)
new Ordering[T] {
def compare(x: T, y: T): Int = {
val upperLevelOrderingResult = ordering.compare(x, y)
if (upperLevelOrderingResult == 0) {
subOrdering.compare(x, y)
} else {
upperLevelOrderingResult
}
}
}
}
unsorted.sorted(composedOrdering)
} else {
unsorted
}
}
sort(
Seq[Book](
Book(1, 5L, "foo1", "bar1"),
Book(10, 50L, "foo10", "bar15"),
Book(2, 3L, "foo3", "bar3"),
Book(100, 52L, "foo4", "bar6"),
Book(100, 51L, "foo4", "bar6"),
Book(100, 51L, "foo3", "bar6"),
Book(11, 15L, "foo5", "bar7"),
Book(22, 45L, "foo6", "bar8")
),
Seq(
SortingField("a", implicitly[Ordering[Int]].reverse),
SortingField("b", implicitly[Ordering[Long]]),
SortingField("c", implicitly[Ordering[String]])
)
)
>> res0: Seq[Book] = List(Book(100,51,foo3,bar6), Book(100,51,foo4,bar6), Book(100,52,foo4,bar6), Book(22,45,foo6,bar8), Book(11,15,foo5,bar7), Book(10,50,foo10,bar15), Book(2,3,foo3,bar3), Book(1,5,foo1,bar1))
我会为此使用现有的 Ordering
特征,并使用从 Book
映射到字段的函数,即 Ordering.by[Book, String](_.author)
。然后你可以简单地用books.sorted(myOrdering)
排序。如果我在 Book
的伴生对象上定义辅助方法,获得这些顺序非常简单:
object Book {
def by[A: Ordering](fun: Book => A): Ordering[Book] = Ordering.by(fun)
}
case class Book(author: String, title: String, year: Int)
val xs = Seq(Book("Deleuze" /* and Guattari */, "A Thousand Plateaus", 1980),
Book("Deleuze", "Difference and Repetition", 1968),
Book("Derrida", "Of Grammatology", 1967))
xs.sorted(Book.by(_.title)) // A Thousand, Difference, Of Grammatology
xs.sorted(Book.by(_.year )) // Of Grammatology, Difference, A Thousand
然后要将多个字段的排序链接起来,您可以创建自定义排序,该排序通过字段进行,直到一个比较为非零。例如,我可以像这样将扩展方法 andThen
添加到 Ordering
:
implicit class OrderingAndThen[A](private val self: Ordering[A]) extends AnyVal {
def andThen(that: Ordering[A]): Ordering[A] = new Ordering[A] {
def compare(x: A, y: A): Int = {
val a = self.compare(x, y)
if (a != 0) a else that.compare(x, y)
}
}
}
所以我可以写:
val ayt = Book.by(_.author) andThen Book.by(_.year) andThen Book.by(_.title)
xs.sorted(ayt) // Difference, A Thousand, Of Grammatology
案例 类 是产品,因此您可以使用 instance.productIterator
遍历所有字段值。这为您提供了声明顺序的字段。您也可以通过它们的索引直接访问它们。据我所知,没有办法获取字段名称。这必须使用反射或宏来完成。 (也许像 Shapeless 这样的图书馆已经可以做到这一点)。
另一种方法是不定义按名称排序的字段,而是按函数定义的字段:
case class SortingField[T](field: Book => T, asc: Boolean)(implicit ordering: Ordering[T])
new SortingField(_.fieldName, true)
然后声明排序为:
def sort(books: Seq[Book], fields: Seq[SortingField[_]]) = {...}
并使用下面的比较方法实现合并排序:
def compare[T](b1: Book, b2: Book, field: SortingField[T]) =
field.ordering.compare(field.field(b1), field.field(b2))
有了
def by[A: Ordering](e: Book => A): Ordering[Book] = Ordering.by(e)
和
implicit class OrderingAndThen[A](private val self: Ordering[A]) extends AnyVal {
def andThen(that: Ordering[A]): Ordering[A] = new Ordering[A] {
def compare(x: A, y: A): Int = {
val a = self.compare(x, y)
if (a != 0) a else that.compare(x, y)
}
}
}
接下来我将 class 字段的名称映射到实际排序
def toOrdering(name: String, r: Boolean): Ordering[Book] = {
(name match {
case "id" => Book.by(_.id)
case "name" => Book.by(_.name)
}) |> (o => if (r) o.reverse else o)
}
使用正向管道运算符:
implicit class PipedObject[A](value: A) {
def |>[B](f: A => B): B = f(value)
}
最后我将所有排序与 reduce 函数结合起来:
val fields = Seq(SortedField("name", true), SortedField("id", false))
val order = fields.map(f => toOrdering(f.field, f.reverse)).reduce(combines(_,_))
coll.sorted(order)
哪里
val combine = (x: Ordering[Book], y: Ordering[Book]) => x andThen y
另一种方法是使用@tailrec:
def orderingSeq[T](os: Seq[Ordering[T]]): Ordering[T] = new Ordering[T] {
def compare(x: T, y: T): Int = {
@tailrec def compare0(rest: Seq[Ordering[T]], result: Int): Int = result match {
case 0 if rest.isEmpty => 0
case 0 => compare0(rest.tail, rest.head.compare(x, y))
case a => a
}
compare0(os, 0)
}
}