🔀
REPLで体感する Scala 2のOrdered入門 ― 自作クラスを「<」とsortedで並べ替える
— 「< や sorted が自作クラスでも使えるって本当?」を確かめる
0. 前提:なぜ Ordered が要るの?
- List などの並べ替えには「順序づけ」が必要
- 既存型(Int, String)は最初から順序が定義されている
- 自分のクラスでも < <= > >= や sorted を使いたい → そのための仕組みが Ordered[T]
Ordered[T] を ミックスインして compare(that: T): Int を 1 つ実装すれば、
比較演算子一式と min/max/sorted などが自動で使えるようになります。
1. REPL 準備
scala
以降は コピペして Enter で進められます。出力は例です(res0 等の番号は環境で変わります)。
2. Rational(有理数)を Ordered にする
2-1. まずは素のクラス
scala> class Rational(n: Int, d: Int) {
| require(d != 0, "denominator must be non-zero")
| private val g = BigInt(n).gcd(BigInt(d)).toInt
| val numer: Int = n / g
| val denom: Int = d / g
| override def toString = s"$numer/$denom"
| }
class Rational
2-2. まだ比較できない確認
scala> val half = new Rational(1, 2)
val half: Rational = 1/2
scala> val third = new Rational(1, 3)
val third: Rational = 1/3
scala> half < third
^
error: value < is not a member of Rational
2-3. Ordered[Rational] をミックスイン
scala> class OrdRational(n: Int, d: Int) extends Rational(n , d) with Ordered[OrdRational] {
| // a/b ? c/d を a*d と c*b で比較 (クロス乗算)
| def compare(that: OrdRational): Int =
| (this.numer * that.denom) - (that.numer * this.denom)
| }
class OrdRational
2-4. 比較演算子が全部使える!
scala> val a = new OrdRational(1, 2) // 1/2
val a: OrdRational = 1/2
scala> val b = new OrdRational(1, 3) // 1/3
val b: OrdRational = 1/3
scala> a < b
val res1: Boolean = false
scala> a > b
val res2: Boolean = true
scala> a <= b
val res3: Boolean = false
scala> a >= b
val res4: Boolean = true
scala> List(a, b, new OrdRational(3, 4)).sorted
val res5: List[OrdRational] = List(1/3, 1/2, 3/4)
ポイント
- 実装したのは compare だけ。演算子は Ordered が面倒を見てくれます。
- sorted が動く=全順序(total order)が提供できている、ということ。
3. equals / hashCode も整える(Set/Mapで効く)
Ordered は 順序を与えるだけで 等値は関知しません。
Set/Map のキーにするなら、equals と hashCode は約束どおりに。
scala> class KeyRational(n: Int, d: Int) extends OrdRational(n, d) {
| override def equals(obj: Any): Boolean = obj match {
| case that: KeyRational =>
| this.numer == that.numer && this.denom == that.denom
| case _ => false
| }
| override def hashCode(): Int = 41 * (41 + numer) + denom
| }
class KeyRational
確認:
scala> val s = scala.collection.mutable.Set[KeyRational]()
val s: scala.collection.mutable.Set[KeyRational] = HashSet()
scala> s += new KeyRational(1, 2); s += new KeyRational(2, 4); s
val res6: scala.collection.mutable.Set[KeyRational] = HashSet(1/2)
1/2 と 2/4 は既約形が同じ → 1 要素にまとまる(equals/hashCode 整備の効果)。
4. もう一つ:自然順ではない独自順序(長さ順など)
Ordered をミックスインする代わりに、別に Ordering[T] を用意して sorted に渡す方法もあります(比較だけ差し替えたい時に便利)。
scala> val byLen: Ordering[Vec2] =
| Ordering.by(v => v.x*v.x + v.y*v.y) // 長さの2乗で比較
val byLen: Ordering[Vec2] = scala.math.Ordering$$anon$5@6ff08304
scala> val vs = List(Vec2(3, 4), Vec2(1, 1), Vec2(0, 2))
val vs: List[Vec2] = List(Vec2(3.0,4.0), Vec2(1.0,1.0), Vec2(0.0,2.0))
scala> vs.sorted(byLen)
val res7: List[Vec2] = List(Vec2(1.0,1.0), Vec2(0.0,2.0), Vec2(3.0,4.0))
5. まとめ(今日使える実用レシピ)
- 自作クラスを並べ替えたい → class X extends Ordered[X] { def compare(that:X):Int = ... }
- Set/Map で重複排除もさせたい → equals / hashCode を 比較基準と一致させる
- 用途ごとに順序を切替 → Ordering[T] を用意して sorted(ordering) を使う
Discussion