Cách dễ thành ngữ để xác định Thứ tự cho một lớp trường hợp đơn giản


110

Tôi có một danh sách các cá thể lớp trường hợp tỷ lệ đơn giản và tôi muốn in chúng theo thứ tự từ vựng, có thể dự đoán được bằng cách sử dụng list.sorted, nhưng nhận được "Không có Thứ tự ngầm định được xác định cho ...".

Có tồn tại một cách ngầm cung cấp thứ tự từ vựng cho các lớp trường hợp không?

Có cách thành ngữ đơn giản nào để trộn thứ tự từ vựng vào lớp chữ không?

scala> case class A(tag:String, load:Int)
scala> val l = List(A("words",50),A("article",2),A("lines",7))

scala> l.sorted.foreach(println)
<console>:11: error: No implicit Ordering defined for A.
          l.sorted.foreach(println)
            ^

Tôi không hài lòng với một 'hack':

scala> l.map(_.toString).sorted.foreach(println)
A(article,2)
A(lines,7)
A(words,50)

8
Tôi vừa viết một bài blog với một vài giải pháp chung ở đây .
Travis Brown

Câu trả lời:


152

Phương pháp yêu thích của cá nhân tôi là sử dụng thứ tự ngầm định được cung cấp cho Tuples, vì nó rõ ràng, ngắn gọn và chính xác:

case class A(tag: String, load: Int) extends Ordered[A] {
  // Required as of Scala 2.11 for reasons unknown - the companion to Ordered
  // should already be in implicit scope
  import scala.math.Ordered.orderingToOrdered

  def compare(that: A): Int = (this.tag, this.load) compare (that.tag, that.load)
}

Này hoạt động bởi vì bạn đồng hành đểOrdered định nghĩa một chuyển đổi ngầm từ Ordering[T]để Ordered[T]mà là trong phạm vi cho bất kỳ lớp thực hiện Ordered. Sự tồn tại của các hàm ẩn Orderingcho Tuples cho phép chuyển đổi từ TupleN[...]thành với Ordered[TupleN[...]]điều kiện là Ordering[TN]tồn tại một cách ngầm định cho tất cả các phần tử T1, ..., TNcủa bộ tuple, điều này luôn đúng như vậy vì không có ý nghĩa gì khi sắp xếp theo kiểu dữ liệu không có Ordering.

Thứ tự ngầm cho Tuples là lựa chọn của bạn cho bất kỳ tình huống sắp xếp nào liên quan đến khóa sắp xếp tổng hợp:

as.sortBy(a => (a.tag, a.load))

Vì câu trả lời này đã được chứng minh là phổ biến, tôi muốn mở rộng nó, lưu ý rằng một giải pháp tương tự như sau trong một số trường hợp có thể được coi là cấp doanh nghiệp ™:

case class Employee(id: Int, firstName: String, lastName: String)

object Employee {
  // Note that because `Ordering[A]` is not contravariant, the declaration
  // must be type-parametrized in the event that you want the implicit
  // ordering to apply to subclasses of `Employee`.
  implicit def orderingByName[A <: Employee]: Ordering[A] =
    Ordering.by(e => (e.lastName, e.firstName))

  val orderingById: Ordering[Employee] = Ordering.by(e => e.id)
}

Đã cho es: SeqLike[Employee], es.sorted()sẽ sắp xếp theo tên và es.sorted(Employee.orderingById)sẽ sắp xếp theo id. Điều này có một số lợi ích:

  • Các loại được xác định ở một vị trí duy nhất dưới dạng các tạo tác mã hiển thị. Điều này rất hữu ích nếu bạn có những phân loại phức tạp trên nhiều lĩnh vực.
  • Hầu hết chức năng sắp xếp được triển khai trong thư viện scala hoạt động bằng cách sử dụng các phiên bản của Ordering, do đó, việc cung cấp một thứ tự trực tiếp loại bỏ chuyển đổi ngầm trong hầu hết các trường hợp.

Ví dụ của bạn là tuyệt vời! Một lớp lót và tôi có thứ tự mặc định. Cảm ơn rât nhiều.
ya_pulser

7
Loại trường hợp A được đề xuất trong câu trả lời dường như không được biên dịch theo tỷ lệ 2.10. Tui bỏ lỡ điều gì vậy?
Doron Yaacoby

3
@DoronYaacoby: Tôi cũng gặp lỗi value compare is not a member of (String, Int).
bluenote 10

1
@JCracknell Lỗi vẫn còn đó ngay cả sau khi nhập (Scala 2.10.4). Lỗi xảy ra trong khi biên dịch, nhưng không được gắn cờ trong IDE. (thú vị là nó hoạt động bình thường trong REPL). Đối với những người gặp vấn đề này, giải pháp tại câu trả lời SO này hoạt động (mặc dù không thanh lịch như ở trên). Nếu nó là một lỗi, đã có ai báo cáo nó?
Jus 12

2
Khắc phục: Phạm vi của Đặt hàng không được kéo vào, bạn có thể ngầm kéo nó vào nhưng đủ dễ dàng chỉ cần sử dụng Trực tiếp Đặt hàng: def so sánh (that: A) = Or Order.Tuple2 [String, String] .compare (tuple (this ), tuple (đó))
brendon

46
object A {
  implicit val ord = Ordering.by(unapply)
}

Điều này có lợi ích là nó được cập nhật tự động bất cứ khi nào A thay đổi. Tuy nhiên, các trường của A cần được đặt theo thứ tự mà thứ tự sẽ sử dụng chúng.


Ngoại hình, thú vị nhưng tôi không thể tìm ra cách để sử dụng này, tôi nhận được:<console>:12: error: not found: value unapply
zbstof

29

Tóm lại, có ba cách để làm điều này:

  1. Để phân loại một lần, hãy sử dụng phương pháp .sortBy, như @Shadowlands đã cho thấy
  2. Đối với việc tái sử dụng sắp xếp mở rộng lớp trường hợp với đặc điểm Có thứ tự, như @Keith đã nói.
  3. Xác định một thứ tự tùy chỉnh. Lợi ích của giải pháp này là bạn có thể sử dụng lại các orderings và có nhiều cách để sắp xếp các trường hợp của cùng một lớp:

    case class A(tag:String, load:Int)
    
    object A {
      val lexicographicalOrdering = Ordering.by { foo: A => 
        foo.tag 
      }
    
      val loadOrdering = Ordering.by { foo: A => 
        foo.load 
      }
    }
    
    implicit val ord = A.lexicographicalOrdering 
    val l = List(A("words",1), A("article",2), A("lines",3)).sorted
    // List(A(article,2), A(lines,3), A(words,1))
    
    // now in some other scope
    implicit val ord = A.loadOrdering
    val l = List(A("words",1), A("article",2), A("lines",3)).sorted
    // List(A(words,1), A(article,2), A(lines,3))

Trả lời câu hỏi của bạn Có bất kỳ chức năng tiêu chuẩn nào được đưa vào Scala có thể thực hiện phép thuật như Danh sách ((2,1), (1,2)) không.

Có một tập hợp các orderings được xác định trước , ví dụ như đối với String, các bộ giá trị lên đến 9 arity, v.v.

Không có điều gì như vậy tồn tại đối với các lớp trường hợp, vì nó không phải là điều dễ dàng để triển khai, vì các tên trường không được biết đến là tiên nghiệm (ít nhất là không có ma thuật macro) và bạn không thể truy cập các trường của lớp trường hợp theo cách khác với đặt tên / sử dụng trình lặp sản phẩm.


Cảm ơn bạn rất nhiều về các ví dụ. Tôi sẽ cố gắng hiểu thứ tự ngầm.
ya_pulser

8

Các unapplyphương pháp của đối tượng bạn đồng hành cung cấp một sự chuyển đổi từ lớp trường hợp của bạn đến một Option[Tuple], nơi Tuplelà tuple tương ứng với danh sách đối số đầu tiên của lớp hợp cụ thể. Nói cách khác:

case class Person(name : String, age : Int, email : String)

def sortPeople(people : List[Person]) = 
    people.sortBy(Person.unapply)

6

Phương thức sortBy sẽ là một cách điển hình để thực hiện việc này, ví dụ: (sắp xếp trên tagtrường):

scala> l.sortBy(_.tag)foreach(println)
A(article,2)
A(lines,7)
A(words,50)

Phải làm gì trong trường hợp 3+ trường trong lớp trường hợp? l.sortBy( e => e._tag + " " + e._load + " " + ... )?
ya_pulser

Nếu sử dụng sortBy, thì có, hoặc là có, hoặc thêm / sử dụng một hàm phù hợp vào / trên lớp (ví dụ: _.toStringhoặc phương pháp tùy chỉnh có ý nghĩa về mặt từ vựng hoặc hàm bên ngoài của riêng bạn).
Shadowlands

Có bất kỳ chức năng tiêu chuẩn nào được bao gồm trong Scala có thể thực hiện phép thuật như List((2,1),(1,2)).sortedcác đối tượng lớp trường hợp không? Tôi thấy không có sự khác biệt lớn giữa các bộ giá trị được đặt tên (lớp trường hợp == được đặt tên là tuple) và các bộ giá trị đơn giản.
ya_pulser

Cách gần nhất tôi có thể làm theo những dòng đó là sử dụng phương thức không được áp dụng của đối tượng đồng hành để lấy một Option[TupleN], sau đó gọi getrằng : l.sortBy(A.unapply(_).get)foreach(println), sử dụng thứ tự được cung cấp trên bộ tuple tương ứng, nhưng đây chỉ đơn giản là một ví dụ rõ ràng về ý tưởng chung mà tôi đưa ra ở trên .
Shadowlands

5

Vì bạn đã sử dụng một lớp trường hợp, bạn có thể mở rộng với Order như vậy:

case class A(tag:String, load:Int) extends Ordered[A] { 
  def compare( a:A ) = tag.compareTo(a.tag) 
}

val ls = List( A("words",50), A("article",2), A("lines",7) )

ls.sorted
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.