我有N个字段的简单类。
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类的一个字段,asc - 一个排序方向。
所以,事先我不知道哪些字段(从0到N)和排序顺序进入我的函数来排序books
集合。它可能只是一个ID字段,也可能是特定顺序中所有类的现有字段。
如何实施?
答案 0 :(得分:4)
我会使用现有的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
答案 1 :(得分:1)
https://angular.io/docs/ts/latest/api/core/Injector-class.html提供了很好的答案,我已经接受了以下内容:
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)
}
}
}
接下来,我将类字段的名称映射到实际排序的方向
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)
}
}
答案 2 :(得分:0)
有可能。但据我所知,你将不得不使用反思。
此外,您必须稍微更改SortingField
类,因为scala编译器无法为每个字段找出正确的Ordering
类型类。
这是一个简化的例子。
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)
}
}
但是,对于按多个字段排序,您必须多次对序列进行排序或更好,然后才能正确组合各种排序。
所以这变得越来越复杂了#39;没有任何关于正确性和完整性的保证,但通过一点点测试,它不会失败:
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))
答案 3 :(得分:0)
案例类是产品,因此您可以使用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))