วิธีสำนวนง่ายๆในการกำหนดการสั่งซื้อสำหรับคลาสเคสง่ายๆ


110

ฉันมีรายการอินสแตนซ์คลาสเคสสกาล่าอย่างง่ายและต้องการพิมพ์ตามลำดับที่คาดเดาได้และเป็นศัพท์โดยใช้list.sortedแต่ได้รับ "ไม่มีการกำหนดลำดับโดยนัยสำหรับ ... "

มีนัยที่ให้การเรียงลำดับศัพท์สำหรับคลาสเคสหรือไม่?

มีวิธีสำนวนง่ายๆในการผสมการเรียงลำดับศัพท์ในชั้นเรียนหรือไม่?

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)
            ^

ฉันไม่พอใจกับ 'แฮ็ค':

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

8
ผมเคยเขียนขึ้นเพียงบล็อกโพสต์กับคู่ของการแก้ปัญหาทั่วไปที่นี่
Travis Brown

คำตอบ:


152

วิธีที่ฉันชอบคือการใช้คำสั่งโดยนัยที่ให้ไว้สำหรับทูเปิลเนื่องจากชัดเจนกระชับและถูกต้อง:

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)
}

นี้ทำงานได้เพราะสหายOrderedกำหนดแปลงนัยจากOrdering[T]การที่อยู่ในขอบเขตการดำเนินการใดOrdered[T] ๆ ในชั้นเรียน Orderedการดำรงอยู่ของปริยายOrderingสำหรับTuples ช่วยให้การแปลงจากTupleN[...]การOrdered[TupleN[...]]ให้โดยนัยOrdering[TN]ที่มีอยู่สำหรับทุกองค์ประกอบT1, ..., TNของ tuple Orderingซึ่งควรจะเป็นกรณีนี้เพราะมันทำให้รู้สึกไม่ไปจัดเรียงบนชนิดของข้อมูลที่ไม่มี

การเรียงลำดับโดยปริยายสำหรับทูเปิลคือสิ่งที่คุณต้องทำสำหรับสถานการณ์การเรียงลำดับใด ๆ ที่เกี่ยวข้องกับคีย์การเรียงลำดับแบบผสม:

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

เนื่องจากคำตอบนี้ได้รับการพิสูจน์แล้วว่าเป็นที่นิยมฉันจึงขอขยายความโดยสังเกตว่าโซลูชันที่มีลักษณะดังต่อไปนี้อาจถือได้ว่าเป็นระดับองค์กร™:

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)
}

ป.ร. ให้ไว้es: SeqLike[Employee], es.sorted()จะเรียงตามชื่อและes.sorted(Employee.orderingById)จะจัดเรียงตามรหัส สิ่งนี้มีประโยชน์บางประการ:

  • ประเภทถูกกำหนดไว้ในตำแหน่งเดียวเป็นส่วนรหัสที่มองเห็นได้ สิ่งนี้มีประโยชน์หากคุณมีประเภทที่ซับซ้อนในหลายช่อง
  • ฟังก์ชันการเรียงลำดับส่วนใหญ่ที่ใช้ในไลบรารีสกาล่าจะทำงานโดยใช้อินสแตนซ์Orderingดังนั้นการจัดเรียงลำดับโดยตรงจะช่วยลดการแปลงโดยนัยในกรณีส่วนใหญ่

ตัวอย่างของคุณวิเศษมาก! ซับเดี่ยวและฉันมีคำสั่งเริ่มต้น ขอบคุณมาก.
ya_pulser

7
กรณีคลาส A ที่แนะนำในคำตอบดูเหมือนจะไม่รวบรวมภายใต้สกาล่า 2.10 ฉันพลาดอะไรไปรึเปล่า?
Doron Yaacoby

3
@DoronYaacoby: value compare is not a member of (String, Int)ฉันยังได้รับข้อผิดพลาด
bluenote10

1
@JCracknell ข้อผิดพลาดยังคงมีอยู่แม้หลังจากการนำเข้า (Scala 2.10.4) เกิดข้อผิดพลาดระหว่างคอมไพล์ แต่ไม่ได้รับการตั้งค่าสถานะใน IDE (น่าสนใจมันทำงานได้อย่างถูกต้องใน REPL) สำหรับผู้ที่มีปัญหานี้วิธีแก้ปัญหาที่SO answer ใช้งานได้ (แม้ว่าจะไม่สวยหรูเท่าด้านบน) ถ้าเป็นบั๊กมีใครรายงานไหม
12

2
การแก้ไข: ขอบเขตของการสั่งซื้อไม่ได้ถูกดึงเข้ามาคุณสามารถดึงเข้ามาโดยปริยายได้ แต่ง่ายพอที่จะใช้การสั่งซื้อโดยตรง: def เปรียบเทียบ (that: A) = Ordering.Tuple2 [String, String] .compare (tuple (นี่ ), tuple (นั้น))
เบรนดอน

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

สิ่งนี้มีประโยชน์ที่จะอัปเดตโดยอัตโนมัติเมื่อใดก็ตามที่ A เปลี่ยนแปลง แต่ฟิลด์ของ A จะต้องวางตามลำดับที่การสั่งซื้อจะใช้


ดูดี แต่ฉันไม่สามารถหาวิธีใช้สิ่งนี้ได้ฉันได้รับ:<console>:12: error: not found: value unapply
zbst ของ

29

สรุปได้สามวิธีในการดำเนินการนี้:

  1. สำหรับการเรียงลำดับครั้งเดียวใช้วิธี. shortBy ตามที่ @Shadowlands ได้แสดงให้เห็น
  2. สำหรับการใช้การเรียงลำดับชั้นกรณีขยายที่มีลักษณะสั่งซ้ำตามที่ @Keith กล่าว
  3. กำหนดลำดับที่กำหนดเอง ข้อดีของโซลูชันนี้คือคุณสามารถใช้คำสั่งซื้อซ้ำและมีหลายวิธีในการจัดเรียงอินสแตนซ์ของคลาสเดียวกัน:

    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))
    

ตอบคำถามของคุณมีฟังก์ชันมาตรฐานใดบ้างที่รวมอยู่ใน Scala ที่สามารถใช้เวทมนตร์ได้เช่น List ((2,1), (1,2))

มีชุดของคำสั่งที่กำหนดไว้ล่วงหน้าเช่นสำหรับ String สิ่งที่มีค่าสูงสุดถึง 9 arity เป็นต้น

ไม่มีสิ่งนี้สำหรับคลาสเคสเนื่องจากไม่ใช่เรื่องง่ายที่จะปิดเนื่องจากชื่อฟิลด์ไม่เป็นที่รู้จัก (อย่างน้อยก็ไม่มีเวทย์มนตร์มาโคร) และคุณไม่สามารถเข้าถึงฟิลด์คลาสเคสด้วยวิธีอื่นที่ไม่ใช่โดย ชื่อ / การใช้ตัววนซ้ำผลิตภัณฑ์


ขอบคุณมากสำหรับตัวอย่าง ฉันจะพยายามเข้าใจการสั่งซื้อโดยปริยาย
ya_pulser

8

unapplyวิธีการของวัตถุสหายให้การแปลงจากชั้นกรณีของคุณไปยังOption[Tuple]ที่Tupleเป็นอันดับที่สอดคล้องกับรายการอาร์กิวเมนต์แรกของการเรียนกรณี กล่าวอีกนัยหนึ่ง:

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

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

6

เมธอด sortBy จะเป็นวิธีทั่วไปวิธีหนึ่งในการทำเช่นนี้ (sort on tagfield):

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

จะทำอย่างไรในกรณีที่มีฟิลด์ 3+ ในคลาสเคส? l.sortBy( e => e._tag + " " + e._load + " " + ... )เหรอ?
ya_pulser

ถ้าใช้sortByก็ใช่หรือเพิ่ม / ใช้ฟังก์ชันที่เหมาะสมกับ / ในคลาส (เช่น_.toStringหรือวิธีกำหนดเองที่มีนัยสำคัญทางศัพท์ของคุณเองหรือฟังก์ชันภายนอก)
Shadowlands

มีฟังก์ชั่นมาตรฐานใด ๆ ที่รวมอยู่ใน Scala ที่สามารถใช้เวทมนตร์ได้เหมือนกับList((2,1),(1,2)).sortedวัตถุคลาสเคส ฉันไม่เห็นความแตกต่างอย่างมากระหว่างสิ่งที่มีชื่อ (case class == name tuple) และสิ่งที่เรียบง่าย
ya_pulser

สิ่งที่ใกล้ที่สุดที่ฉันสามารถทำได้ในบรรทัดเหล่านั้นคือการใช้วิธี unapply ของวัตถุที่แสดงร่วมกันเพื่อรับOption[TupleN]แล้วเรียกgetสิ่งนั้น: l.sortBy(A.unapply(_).get)foreach(println)ซึ่งใช้คำสั่งที่ให้ไว้ในทูเปิลที่เกี่ยวข้อง แต่นี่เป็นเพียงตัวอย่างที่ชัดเจนของแนวคิดทั่วไปที่ฉันให้ไว้ข้างต้น .
Shadowlands

5

เนื่องจากคุณใช้คลาสเคสคุณสามารถขยายด้วยคำสั่งเช่นนี้:

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.