간단한 케이스 클래스에 대한 Ordering을 정의하는 쉬운 관용적 방법


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

개인적으로 가장 좋아하는 방법은 명확하고 간결하며 정확하기 때문에 Tuples에 대해 제공된 암시 적 순서를 사용하는 것입니다.

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. Orderings에 대한 암시 적 s 의 존재는 튜플의 모든 요소 에 대해 암시 적 존재 를 제공하는 Tuple에서 TupleN[...]로 변환 할 수있게합니다 .Ordered[TupleN[...]]Ordering[TN]T1, ..., TNOrdering

튜플에 대한 암시 적 순서는 복합 정렬 키와 관련된 모든 정렬 시나리오를위한 것입니다.

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)별로 정렬하고 ID별로 정렬합니다. 다음과 같은 몇 가지 이점이 있습니다.

  • 정렬은 단일 위치에서 표시되는 코드 아티팩트로 정의됩니다. 여러 필드에 대해 복잡한 정렬이있는 경우 유용합니다.
  • 스칼라 라이브러리에 구현 된 대부분의 정렬 기능은의 인스턴스를 사용하여 작동 Ordering하므로 정렬을 제공하면 대부분의 경우 암시 적 변환이 직접 제거됩니다.

귀하의 모범은 훌륭합니다! 단일 라이너 및 기본 주문이 있습니다. 대단히 감사합니다.
ya_pulser 2013-10-13

7
답변에서 제안한 케이스 클래스 A는 스칼라 2.10에서 컴파일되지 않는 것 같습니다. 내가 뭔가를 놓치고 있습니까?
Doron Yaacoby 2014

3
@DoronYaacoby : 또한 오류가 발생 value compare is not a member of (String, Int)합니다.
bluenote10

1
@JCracknell 가져 오기 후에도 오류가 계속 발생합니다 (Scala 2.10.4). 컴파일 중에 오류가 발생하지만 IDE에서 플래그가 지정되지 않습니다. (흥미롭게도 REPL에서 제대로 작동합니다). 이 문제가있는 사람들에게는 이 SO 답변 의 솔루션 작동합니다 (위만큼 우아하지는 않지만). 버그 인 경우 신고 한 사람이 있습니까?
Jus12

2
FIX : Ordering의 범위가 끌어 들여지지 않고 암시 적으로 끌어 올 수는 있지만 Ordering을 직접 사용하기 만하면됩니다. def compare (that : A) = Ordering.Tuple2 [String, String] .compare (tuple (this ), tuple (that))
brendon

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

A가 변경 될 때마다 자동으로 업데이트된다는 이점이 있습니다. 그러나 A의 필드는 주문시 사용할 순서대로 배치되어야합니다.


멋지군요.하지만 어떻게 사용하는지 알 수 없습니다.<console>:12: error: not found: value unapply
zbstof

29

요약하면 다음과 같은 세 가지 방법이 있습니다.

  1. 일회성 정렬의 경우 @Shadowlands가 보여준 것처럼 .sortBy 메서드를 사용하십시오.
  2. @Keith가 말했듯이 정렬을 재사용하기 위해 Ordered 특성으로 케이스 클래스를 확장하십시오.
  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))

질문에 답하기 List ((2,1), (1,2))와 같은 마법을 할 수있는 Scala에 포함 된 표준 함수가 있습니까?

예를 들어 문자열, 최대 9 개의 튜플 등 의 사전 정의 된 순서 가 있습니다.

필드 이름이 사전에 알려지지 않았고 (적어도 매크로 마법없이) 케이스 클래스 필드에 액세스 할 수 없기 때문에 롤오프하기가 쉽지 않기 때문에 케이스 클래스에는 그런 것이 없습니다. 이름 / 제품 반복자 사용.


예를 들어 주셔서 대단히 감사합니다. 암묵적인 순서를 이해하려고 노력할 것입니다.
ya_pulser 2013-10-13

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 메서드는이를 수행하는 일반적인 방법 중 하나입니다. 예 : ( tag필드에 정렬 ) :

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 2013 년

를 사용하는 경우 sortBy예, 그 중 하나를 사용하거나 클래스에 적절한 함수를 추가 / 사용합니다 (예 : _.toString또는 사전에 의미있는 사용자 지정 메서드 또는 외부 함수).
Shadowlands

List((2,1),(1,2)).sorted케이스 클래스 객체 와 같은 마술을 할 수있는 Scala에 포함 된 표준 함수가 있습니까? 명명 된 튜플 (케이스 클래스 == 명명 된 튜플)과 간단한 튜플 사이에 큰 차이가 없습니다.
ya_pulser 2013-10-13

가장 가까운 방법은 컴패니언 객체의 unapply 메서드를 사용하여를 얻은 Option[TupleN]다음 해당 튜플에 제공된 순서를 사용하는 호출 get하는 것입니다. l.sortBy(A.unapply(_).get)foreach(println).
어둠의 땅

5

케이스 클래스 를 사용했기 때문에 다음 과 같이 Ordered로 확장 할 수 있습니다 .

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.