Himpunan unik di Scala

Katakanlah saya memiliki sebuah List dari angka seperti berikut

val list = List(1, 2, 3, 4, 2, 3, 4)

Jika saya ingin membuatnya tidak berisi angka berulang, saya bisa panggil method distinct dari List tersebut.

scala> val list = List(1, 2, 3, 4, 2, 3, 4)
list: List[Int] = List(1, 2, 3, 4, 2, 3, 4)

scala> list.distinct
res1: List[Int] = List(1, 2, 3, 4)

Nah bagaimana jika isi List adalah objek dari kelas buatan sendiri?

Pertama, mari kita coba gunakan case class. Katakanlah saya butuh menyimpan dua buah nilai yaitu posisi: Int dan asamAmino: String yang menyatakan sebuah mutasi gen seperti berikut.

case class M1(val posisi: Int, val asamAmino: String)

Mari kita uji apakah distinct bisa bekerja sesuai harapan.

scala> case class M1(val posisi: Int, val asamAmino: String)
defined class M1

scala> val list = List(M1(1, "a"), M1(2, "b"), M1(1, "c"), M1(3, "b"), M1(1, "a"))
list: List[M1] = List(M1(1,a), M1(2,b), M1(1,c), M1(3,b), M1(1,a))

scala> list.distinct
res2: List[M1] = List(M1(1,a), M1(2,b), M1(1,c), M1(3,b))

Terlihat masih oke.. Bagaimana kalau pakai class biasa?

scala> class M2(val posisi: Int, val asamAmino: String) {
     | override def toString() = "%d%s".format(posisi, asamAmino)
     | }
defined class M2

scala> val list = List(new M2(1, "a"), new M2(2, "b"), new M2(1, "c"), new M2(3, "b"), new M2(1, "a"))
list: List[M2] = List(1a, 2b, 1c, 3b, 1a)

scala> list.distinct
res3: List[M2] = List(1a, 2b, 1c, 3b, 1a)

Wah tidak bekerja sesuai harapan ternyata 🙁 Mutasi 1a masih berjumlah ganda. Mungkin karena si Scala atau Java tidak tahu cara mengecek dengan benar apakah dua buah objek M2 itu bernilai sama atau tidak. Kalau yang dibandingkan hanyalah alamat memori, tentu saja akan berbeda semua.

Setelah ngubek2 internet, akhirnya saya mendapatkan sebuah petunjuk di StackOverflow.com. Pada saat pengecekan kesamaan nilai, Scala menggunakan dua buah method untuk mencari tahu apakah dua buah objek bernilai sama atau tidak. Dua method tersebut adalah hashCode dan equals. Nilai hashCode akan dibandingkan terlebih dahulu dan jika sama, maka method equals akan dipanggil agar si objek membandingkan sendiri dengan objek lain apakah mereka bernilai sama atau tidak.

Berbekal dari informasi tersebut, kita bisa ubah si kelas M2 menjadi seperti berikut.

class M3(val posisi: Int, val asamAmino: String) {
  override def toString() = "%d%s".format(posisi, asamAmino)

  override def hashCode = posisi

  override def equals(other: Any) =
    if (other.isInstanceOf[M3]) {
      val m = other.asInstanceOf[M3]
      m.posisi == posisi && m.asamAmino == asamAmino
    } else false
}

Untuk hashCode, agar sederhana saya samakan saja nilainya dengan nilai posisi. Pada saat pemangilan equals, nilai posisi dan asamAmino akan dibandingkan untuk memutuskan apakah dua buah mutasi bernilai sama atau tidak.

Mari kita coba lagi..

scala> val list = List(new M3(1, "a"), new M3(2, "b"), new M3(1, "c"), new M3(3, "b"), new M3(1, "a"))
list: List[M3] = List(1a, 2b, 1c, 3b, 1a)

scala> list.distinct
res0: List[M3] = List(1a, 2b, 1c, 3b)

Hore berhasil!

Mengapa saya ngga memakai case class saja sehingga tidak perlu repot2 mengimplementasikan hashCode dan equals? Karena dalam kasus yang saya hadapi, kelas mutasi ini berada dalam wilayah bahasa Java! Yap, saya sedang mencampur2 kode Java dan Scala.

Mari kita ubah si kelas mutasi menjadi bahasa Java lalu digunakan dari Scala.

public class M4 {
  private int posisi;
  private String asamAmino;

  public M4(int posisi, String asamAmino) {
    this.posisi = posisi;
    this.asamAmino = asamAmino;
  }

  public int getPosisi() {
    return posisi;
  }

  public String getAsamAmino() {
    return asamAmino;
  }

  @Override
  public int hashCode() {
    return posisi;
  }

  @Override
  public boolean equals(Object other) {
    if (other instanceof M4) {
      M4 m = (M4)other;
      return posisi == m.getPosisi() && asamAmino.equals(m.getAsamAmino());
    }
    return false;
  }

  @Override
  public String toString() {
    return posisi + asamAmino;
  }
}

Wah kodenya jadi bengkak ya? 😀 Silakan lihat kode lengkap dapat dilihat di https://gist.github.com/fajran/5718827

Leave a Reply